Fast forward existing prototype work

This commit is contained in:
Dave Syer
2013-04-24 10:02:07 +01:00
parent 80b151e2b3
commit fb6b224470
294 changed files with 23494 additions and 0 deletions

View File

@@ -0,0 +1,49 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap;
import java.io.PrintStream;
/**
* Writes the 'Spring Bootstrap' banner.
*
* @author Phillip Webb
*/
abstract class Banner {
private static final String[] BANNER = {
" . ____ _ ____ _ _ __ _ ",
" /\\\\ / ___'_ __ _ _(_)_ __ __ _| __ ) ___ ___| |_ ___| |_ _ _ __ _ _ __\\ \\ \\ ",
"( ( )\\___ | '_ | '_| | '_ \\/ _` | _ \\/ _ \\/ _ | __/ __| __| '_/ _` | '_ \\\\ \\ \\ ",
" \\\\/ ___)| |_)| | | | | || (_| | |_)| (_)| (_)| |_\\__ | |_| || (_| | |_) |} } }",
" ' |____| .__|_| |_|_| |_\\__, |____/\\___/\\___/\\__|___/\\__|_| \\__,_| .__// / / ",
" =========|_|==============|___/====================================|_|==/_/_/ " };
/**
* Write the banner to the specified print stream.
* @param printStream the output print stream
*/
public static void write(PrintStream printStream) {
System.out.println();
for (String line : BANNER) {
printStream.println(line);
}
System.out.println();
System.out.println();
}
}

View File

@@ -0,0 +1,175 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap;
import java.util.HashSet;
import java.util.Set;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.annotation.AnnotatedBeanDefinitionReader;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.filter.AbstractTypeHierarchyTraversingFilter;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.util.Assert;
/**
* Loads bean definitions from underlying sources, including XML and JavaConfig. Acts as a
* simple facade over {@link AnnotatedBeanDefinitionReader},
* {@link XmlBeanDefinitionReader} and {@link ClassPathBeanDefinitionScanner}. See
* {@link SpringApplication} for the types of sources that are supported.
*
* @author Phillip Webb
* @see #setBeanNameGenerator(BeanNameGenerator)
*/
class BeanDefinitionLoader {
private static final ResourceLoader DEFAULT_RESOURCE_LOADER = new DefaultResourceLoader();
private Object[] sources;
private AnnotatedBeanDefinitionReader annotatedReader;
private XmlBeanDefinitionReader xmlReader;
private ClassPathBeanDefinitionScanner scanner;
private ResourceLoader resourceLoader;
/**
* Create a new {@link BeanDefinitionLoader} that will load beans into the specified
* {@link BeanDefinitionRegistry}.
* @param registry the bean definition registry that will contain the loaded beans
* @param sources the bean sources
*/
public BeanDefinitionLoader(BeanDefinitionRegistry registry, Object... sources) {
Assert.notNull(registry, "Registry must not be null");
Assert.notEmpty(sources, "Sources must not be empty");
this.sources = sources;
this.annotatedReader = new AnnotatedBeanDefinitionReader(registry);
this.xmlReader = new XmlBeanDefinitionReader(registry);
this.scanner = new ClassPathBeanDefinitionScanner(registry);
this.scanner.addExcludeFilter(new ClassExcludeFilter(sources));
}
/**
* Set the bean name generator to be used by the underlying readers and scanner.
* @param beanNameGenerator the bean name generator
*/
public void setBeanNameGenerator(BeanNameGenerator beanNameGenerator) {
this.annotatedReader.setBeanNameGenerator(beanNameGenerator);
this.xmlReader.setBeanNameGenerator(beanNameGenerator);
this.scanner.setBeanNameGenerator(beanNameGenerator);
}
/**
* Set the resource loader to be used by the underlying readers and scanner.
* @param resourceLoader the resource loader
*/
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
this.xmlReader.setResourceLoader(resourceLoader);
this.scanner.setResourceLoader(resourceLoader);
}
/**
* Set the environment to be used by the underlying readers and scanner.
* @param environment
*/
public void setEnvironment(ConfigurableEnvironment environment) {
this.annotatedReader.setEnvironment(environment);
this.xmlReader.setEnvironment(environment);
this.scanner.setEnvironment(environment);
}
/**
* Load the sources into the reader.
* @return the number of loaded beans
*/
public int load() {
int count = 0;
for (Object source : this.sources) {
count += load(source);
}
return count;
}
private int load(Object source) {
Assert.notNull(source, "Source must not be null");
if (source instanceof Class<?>) {
this.annotatedReader.register((Class<?>) source);
return 1;
}
if (source instanceof Resource) {
return this.xmlReader.loadBeanDefinitions((Resource) source);
}
if (source instanceof Package) {
// FIXME register the scanned package for data to pick up
return this.scanner.scan(((Package) source).getName());
}
if (source instanceof CharSequence) {
try {
return load(Class.forName(source.toString()));
} catch (ClassNotFoundException e) {
}
Resource loadedResource = (this.resourceLoader != null ? this.resourceLoader
: DEFAULT_RESOURCE_LOADER).getResource(source.toString());
if (loadedResource != null && loadedResource.exists()) {
return load(loadedResource);
}
Package packageResource = Package.getPackage(source.toString());
if (packageResource != null) {
return load(packageResource);
}
}
throw new IllegalArgumentException("Invalid source '" + source + "'");
}
/**
* Simple {@link TypeFilter} used to ensure that specified {@link Class} sources are
* not accidentally re-added during scanning.
*/
private static class ClassExcludeFilter extends AbstractTypeHierarchyTraversingFilter {
private Set<String> classNames = new HashSet<String>();
public ClassExcludeFilter(Object... sources) {
super(false, false);
for (Object source : sources) {
if (source instanceof Class<?>) {
this.classNames.add(((Class<?>) source).getName());
}
}
}
@Override
protected boolean matchClassName(String className) {
return this.classNames.contains(className);
}
}
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
/**
* Interface used to indicate that a bean should <em>run</em> when it is contained within
* a {@link SpringApplication}. Multiple {@link CommandLineRunner} beans can be defined
* within the same application context and can be ordered using the {@link Ordered}
* interface or {@link Order @Order} annotation.
*
* @author Dave Syer
*/
public interface CommandLineRunner {
/**
* Callback used to run the bean.
* @param args incoming main method arguments
*/
void run(String... args);
}

View File

@@ -0,0 +1,266 @@
/*
* Copyright 2010-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import org.springframework.bootstrap.config.YamlProcessor.ArrayDocumentMatcher;
import org.springframework.bootstrap.config.YamlProcessor.DocumentMatcher;
import org.springframework.bootstrap.config.YamlPropertiesFactoryBean;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.Ordered;
import org.springframework.core.env.CommandLinePropertySource;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.util.StringUtils;
/**
* {@link ApplicationContextInitializer} that configures the context environment by
* loading properties from well known file locations. By default properties will be loaded
* from 'application.properties' and/or 'application.yaml' files in the following
* locations:
* <ul>
* <li>classpath:</li>
* <li>file:./</li>
* <li>classpath:config/</li>
* <li>file:./config/:</li>
* </ul>
* <p>
* Alternative locations and names can be specified using
* {@link #setSearchLocations(String[])} and {@link #setName(String)}.
*
* <p>
* Additional files will also be loaded based on active profiles. For example if a 'web'
* profile is active 'application-web.properties' and 'application-web.yaml' will be
* considered.
*
* <p>
* The 'spring.config.name' property can be used to specify an alternative name to load or
* alternatively the 'spring.config.location' property can be used to specify an exact
* resource location.
*
* @author Dave Syer
* @author Phillip Webb
*/
public class ConfigFileApplicationContextInitializer implements
ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {
private static final Loader[] LOADERS = { new PropertiesLoader(), new YamlLoader() };
private static final String LOCATION_VARIABLE = "${spring.config.location}";
private String[] searchLocations = new String[] { "classpath:", "file:./",
"classpath:config/", "file:./config/" };
private String name = "${spring.config.name:application}";
private int order = Integer.MIN_VALUE;
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
List<String> candidates = getCandidateLocations(applicationContext);
// Initial load allows profiles to be activated
for (String candidate : candidates) {
load(applicationContext, candidate, null);
}
// Second load for specific profiles
for (String profile : applicationContext.getEnvironment().getActiveProfiles()) {
for (String candidate : candidates) {
load(applicationContext, candidate, profile);
}
}
}
private List<String> getCandidateLocations(
ConfigurableApplicationContext applicationContext) {
List<String> candidates = new ArrayList<String>();
for (String searchLocation : this.searchLocations) {
for (Loader loader : LOADERS) {
for (String extension : loader.getExtensions()) {
String location = searchLocation + this.name + extension;
candidates.add(location);
}
}
}
candidates.add(LOCATION_VARIABLE);
return candidates;
}
private void load(ConfigurableApplicationContext applicationContext, String location,
String profile) {
location = applicationContext.getEnvironment().resolvePlaceholders(location);
String suffix = "." + StringUtils.getFilenameExtension(location);
if (StringUtils.hasLength(profile)) {
location = location.replace(suffix, "-" + profile + suffix);
}
for (Loader loader : LOADERS) {
if (loader.getExtensions().contains(suffix.toLowerCase())) {
Resource resource = applicationContext.getResource(location);
if (resource != null && resource.exists()) {
loader.load(resource, applicationContext);
}
return;
}
}
}
public void setOrder(int order) {
this.order = order;
}
@Override
public int getOrder() {
return this.order;
}
/**
* Sets the name of the file that should be loaded (excluding any file extension).
*/
public void setName(String name) {
this.name = name;
}
/**
* Set the search locations that will be considered.
*/
public void setSearchLocations(String[] searchLocations) {
this.searchLocations = searchLocations;
}
/**
* Strategy interface used to load a {@link PropertySource}.
*/
private static interface Loader {
/**
* @return The supported extensions (including '.' and in lowercase)
*/
public Set<String> getExtensions();
/**
* Load the resource into the destination application context.
*/
void load(Resource resource, ConfigurableApplicationContext applicationContext);
}
/**
* Strategy to load '.properties' files.
*/
private static class PropertiesLoader implements Loader {
@Override
public Set<String> getExtensions() {
return Collections.singleton(".properties");
}
@Override
public void load(Resource resource,
ConfigurableApplicationContext applicationContext) {
try {
Properties properties = loadProperties(resource, applicationContext);
MutablePropertySources propertySources = applicationContext
.getEnvironment().getPropertySources();
if (propertySources
.contains(CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME)) {
propertySources.addAfter(
CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME,
new PropertiesPropertySource(resource.getDescription(),
properties));
} else {
propertySources.addFirst(new PropertiesPropertySource(resource
.getDescription(), properties));
}
} catch (IOException e) {
throw new IllegalStateException("Could not load properties file from "
+ resource, e);
}
}
protected Properties loadProperties(Resource resource,
ConfigurableApplicationContext applicationContext) throws IOException {
return PropertiesLoaderUtils.loadProperties(resource);
}
}
/**
* Strategy to load '.yml' files.
*/
private static class YamlLoader extends PropertiesLoader {
@Override
public Set<String> getExtensions() {
return Collections.singleton(".yml");
}
@Override
protected Properties loadProperties(final Resource resource,
final ConfigurableApplicationContext applicationContext)
throws IOException {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
List<DocumentMatcher> matchers = new ArrayList<DocumentMatcher>();
matchers.add(new DocumentMatcher() {
@Override
public boolean matches(Properties properties) {
String[] profiles = applicationContext.getEnvironment()
.getActiveProfiles();
if (profiles.length > 0) {
return new ArrayDocumentMatcher("spring.profiles", profiles)
.matches(properties);
} else {
return properties.getProperty("spring.profiles", "NONE")
.contains("default");
}
}
});
matchers.add(new DocumentMatcher() {
@Override
public boolean matches(Properties properties) {
if (!properties.containsKey("spring.profiles")) {
Set<String> profiles = StringUtils
.commaDelimitedListToSet(properties.getProperty(
"spring.profiles.active", ""));
for (String profile : profiles) {
// allow document with no profile to set the active one
applicationContext.getEnvironment().addActiveProfile(profile);
}
// matches default profile
return true;
} else {
return false;
}
}
});
factory.setMatchDefault(false);
factory.setDocumentMatchers(matchers);
factory.setResources(new Resource[] { resource });
return factory.getObject();
}
}
}

View File

@@ -0,0 +1,115 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.springframework.beans.BeanUtils;
import org.springframework.context.ApplicationContextException;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/**
* {@link ApplicationContextInitializer} that delegates to other initializers that are
* specified under a {@literal context.initializer.classes} environment property.
*
* @author Dave Syer
* @author Phillip Webb
*/
public class EnvironmentDelegateApplicationContextInitializer implements
ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {
// NOTE: Similar to org.springframework.web.context.ContextLoader
private static final String PROPERTY_NAME = "context.initializer.classes";
private int order = 0;
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
ConfigurableEnvironment env = applicationContext.getEnvironment();
List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> initializerClasses = determineContextInitializerClasses(env);
if (initializerClasses.size() == 0) {
// no ApplicationContextInitializers have been declared -> nothing to do
return;
}
Class<?> contextClass = applicationContext.getClass();
ArrayList<ApplicationContextInitializer<ConfigurableApplicationContext>> initializerInstances = new ArrayList<ApplicationContextInitializer<ConfigurableApplicationContext>>();
for (Class<ApplicationContextInitializer<ConfigurableApplicationContext>> initializerClass : initializerClasses) {
Class<?> initializerContextClass = GenericTypeResolver.resolveTypeArgument(
initializerClass, ApplicationContextInitializer.class);
Assert.isAssignable(
initializerContextClass,
contextClass,
String.format(
"Could not add context initializer [%s] as its generic parameter [%s] "
+ "is not assignable from the type of application context used by this "
+ "context loader [%s]: ",
initializerClass.getName(),
initializerContextClass.getName(), contextClass.getName()));
initializerInstances.add(BeanUtils.instantiateClass(initializerClass));
}
Collections.sort(initializerInstances, new AnnotationAwareOrderComparator());
for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : initializerInstances) {
initializer.initialize(applicationContext);
}
}
@SuppressWarnings("unchecked")
private List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> determineContextInitializerClasses(
ConfigurableEnvironment env) {
String classNames = env.getProperty(PROPERTY_NAME);
List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> classes = new ArrayList<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>>();
if (StringUtils.hasLength(classNames)) {
for (String className : StringUtils.tokenizeToStringArray(classNames, ",")) {
try {
Class<?> clazz = ClassUtils.forName(className,
ClassUtils.getDefaultClassLoader());
Assert.isAssignable(ApplicationContextInitializer.class, clazz,
"class [" + className
+ "] must implement ApplicationContextInitializer");
classes.add((Class<ApplicationContextInitializer<ConfigurableApplicationContext>>) clazz);
} catch (ClassNotFoundException ex) {
throw new ApplicationContextException(
"Failed to load context initializer class [" + className
+ "]", ex);
}
}
}
return classes;
}
public void setOrder(int order) {
this.order = order;
}
@Override
public int getOrder() {
return this.order;
}
}

View File

@@ -0,0 +1,507 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.bootstrap.context.embedded.AnnotationConfigEmbeddedWebApplicationContext;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotatedBeanDefinitionReader;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.AnnotationConfigUtils;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.env.CommandLinePropertySource;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.SimpleCommandLinePropertySource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.core.type.StandardAnnotationMetadata;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.web.context.ConfigurableWebApplicationContext;
/**
* Classes that can be used to bootstrap and launch a Spring application from a Java main
* method. By default class will perform the following steps to bootstrap your
* application:
*
* <ul>
* <li>Create an appropriate {@link ApplicationContext} instance (
* {@link AnnotationConfigApplicationContext} or
* {@link AnnotationConfigEmbeddedWebApplicationContext} depending on your classpath)</li>
*
* <li>Register a {@link CommandLinePropertySource} to expose command line arguments as
* Spring properties</li>
*
* <li>Refresh the application context, loading all singleton beans</li>
*
* <li>Trigger any {@link CommandLineRunner} beans</li>
* </ul>
*
* In most circumstances the static {@link #run(Object, String[])} method can be called
* directly from your {@literal main} method to bootstrap your application:
*
* <pre class="code">
* &#064;Configuration
* &#064;EnableAutoConfiguration
* public class MyApplication {
*
* // ... Bean definitions
*
* public static void main(String[] args) throws Exception {
* SpringApplication.run(MyApplication.class, args);
* }
* </pre>
*
* <p>
* For more advanced configuration a {@link SpringApplication} instance can be created and
* customized before being run:
*
* <pre class="code">
* public static void main(String[] args) throws Exception {
* SpringApplication app = new SpringApplication(MyApplication.class);
* // ... customize app settings here
* app.run(args)
* }
* </pre>
*
* {@link SpringApplication}s can read beans from a variety of different sources. It is
* generally recommended that a single {@code @Configuration} class is used to bootstrap
* your application, however, any of the following sources can also be used:
*
* <p>
* <ul>
* <li>{@link Class} - A Java class to be loaded by {@link AnnotatedBeanDefinitionReader}</li>
*
* <li>{@link Resource} - A XML resource to be loaded by {@link XmlBeanDefinitionReader}</li>
*
* <li>{@link Package} - A Java package to be scanned by
* {@link ClassPathBeanDefinitionScanner}</li>
*
* <li>{@link CharSequence} - A class name, resource handle or package name to loaded as
* appropriate. If the {@link CharSequence} cannot be resolved to class and does not
* resolve to a {@link Resource} that exists it will be considered a {@link Package}.</li>
* </ul>
*
* @author Phillip Webb
* @author Dave Syer
* @see #run(Object, String[])
* @see #run(Object[], String[])
* @see #SpringApplication(Object...)
*/
public class SpringApplication {
private static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
+ "annotation.AnnotationConfigApplicationContext";
private static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework.bootstrap."
+ "context.embedded.AnnotationConfigEmbeddedWebApplicationContext";
private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext" };
private Object[] sources;
private boolean showBanner = true;
private boolean addCommandLineProperties = true;
private ResourceLoader resourceLoader;
private BeanNameGenerator beanNameGenerator;
private ConfigurableEnvironment environment;
private ApplicationContext applicationContext;
private Class<? extends ApplicationContext> applicationContextClass;
private boolean webEnvironment;
private List<ApplicationContextInitializer<?>> initializers;
/**
* Crate a new {@link SpringApplication} instance. The application context will load
* beans from the specified sources (see {@link SpringApplication class-level}
* documentation for details. The instance can be customized before calling
* {@link #run(String...)}.
* @param sources the bean sources
* @see #run(Object, String[])
* @see #SpringApplication(ResourceLoader, Object...)
*/
public SpringApplication(Object... sources) {
Assert.notEmpty(sources, "Sources must not be empty");
this.sources = sources;
initialize();
}
/**
* Crate a new {@link SpringApplication} instance. The application context will load
* beans from the specified sources (see {@link SpringApplication class-level}
* documentation for details. The instance can be customized before calling
* {@link #run(String...)}.
* @param resourceLoader the resource loader to use
* @param sources the bean sources
* @see #run(Object, String[])
* @see #SpringApplication(ResourceLoader, Object...)
*/
public SpringApplication(ResourceLoader resourceLoader, Object... sources) {
Assert.notEmpty(sources, "Sources must not be empty");
this.resourceLoader = resourceLoader;
this.sources = sources;
initialize();
}
protected void initialize() {
this.webEnvironment = deduceWebEnvironment();
this.initializers = new ArrayList<ApplicationContextInitializer<?>>();
@SuppressWarnings("rawtypes")
Collection<ApplicationContextInitializer> factories = SpringFactoriesLoader
.loadFactories(ApplicationContextInitializer.class,
SpringApplication.class.getClassLoader());
for (ApplicationContextInitializer<?> initializer : factories) {
this.initializers.add(initializer);
}
}
private boolean deduceWebEnvironment() {
for (String className : WEB_ENVIRONMENT_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return false;
}
}
return true;
}
/**
* Run the Spring application, creating and refreshing a new
* {@link ApplicationContext}.
* @param args the application arguments (usually passed from a Java main method)
* @return a running {@link ApplicationContext}
*/
public ApplicationContext run(String... args) {
if (this.showBanner) {
printBanner();
}
ApplicationContext context = createApplicationContext();
postProcessApplicationContext(context);
addPropertySources(context, args);
if (context instanceof ConfigurableApplicationContext) {
applyInitializers((ConfigurableApplicationContext) context);
}
load(context, this.sources);
refresh(context);
runCommandLineRunners(context, args);
return context;
}
/**
* Print a simple banner message to the console. Subclasses can override this method
* to provide additional or alternative banners.
* @see #setShowBanner(boolean)
*/
protected void printBanner() {
Banner.write(System.out);
}
/**
* Apply any {@link ApplicationContextInitializer}s to the context before it is
* refreshed.
* @param context the configured ApplicationContext (not refreshed yet)
* @see ConfigurableApplicationContext#refresh()
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
protected void applyInitializers(ConfigurableApplicationContext context) {
for (ApplicationContextInitializer initializer : this.initializers) {
Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(
initializer.getClass(), ApplicationContextInitializer.class);
Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
initializer.initialize(context);
}
}
/**
* Strategy method used to create the {@link ApplicationContext}. By default this
* method will respect any explicitly set application context or application context
* class before falling back to a suitable default.
* @return the application context (not yet refreshed)
* @see #setApplicationContext(ApplicationContext)
* @see #setApplicationContextClass(Class)
*/
protected ApplicationContext createApplicationContext() {
if (this.applicationContext != null) {
return this.applicationContext;
}
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
contextClass = Class
.forName(this.webEnvironment ? DEFAULT_WEB_CONTEXT_CLASS
: DEFAULT_CONTEXT_CLASS);
} catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Unable create a default ApplicationContext, "
+ "please specify an ApplicationContextClass", ex);
}
}
return (ApplicationContext) BeanUtils.instantiate(contextClass);
}
/**
* Apply any relevant post processing the {@link ApplicationContext}. Subclasses can
* apply additional processing as required.
* @param context the application context
*/
protected void postProcessApplicationContext(ApplicationContext context) {
if (this.webEnvironment) {
if (context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext configurableContext = (ConfigurableWebApplicationContext) context;
if (this.beanNameGenerator != null) {
configurableContext.getBeanFactory().registerSingleton(
AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR,
this.beanNameGenerator);
}
}
}
if (context instanceof AbstractApplicationContext) {
((AbstractApplicationContext) context).setEnvironment(this.environment);
}
if (context instanceof GenericApplicationContext) {
((GenericApplicationContext) context).setResourceLoader(this.resourceLoader);
}
}
/**
* Add any {@link PropertySource}s to the application context environment.
* @param context the application context
* @param args run arguments
*/
protected void addPropertySources(ApplicationContext context, String[] args) {
Environment environment = context.getEnvironment();
if (environment instanceof ConfigurableEnvironment) {
ConfigurableEnvironment configurable = (ConfigurableEnvironment) environment;
if (this.addCommandLineProperties) {
CommandLinePropertySource<?> propertySource = new SimpleCommandLinePropertySource(
args);
configurable.getPropertySources().addFirst(propertySource);
}
}
}
/**
* Load beans into the application context.
* @param context the context to load beans into
*/
protected void load(ApplicationContext context, Object[] sources) {
Assert.isInstanceOf(BeanDefinitionRegistry.class, context);
BeanDefinitionLoader loader = createBeanDefinitionLoader(
(BeanDefinitionRegistry) context, sources);
if (this.beanNameGenerator != null) {
loader.setBeanNameGenerator(this.beanNameGenerator);
}
if (this.resourceLoader != null) {
loader.setResourceLoader(this.resourceLoader);
}
if (this.environment != null) {
loader.setEnvironment(this.environment);
}
loader.load();
}
/**
* Factory method used to create the {@link BeanDefinitionLoader}.
*/
protected BeanDefinitionLoader createBeanDefinitionLoader(
BeanDefinitionRegistry registry, Object[] sources) {
return new BeanDefinitionLoader(registry, sources);
}
private void runCommandLineRunners(ApplicationContext context, String... args) {
List<CommandLineRunner> runners = new ArrayList<CommandLineRunner>(context
.getBeansOfType(CommandLineRunner.class).values());
AnnotationAwareOrderComparator.sort(runners);
for (CommandLineRunner runner : runners) {
runner.run(args);
}
}
/**
* Refresh the underlying {@link ApplicationContext}.
* @param applicationContext the application context to refresh
*/
protected void refresh(ApplicationContext applicationContext) {
Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
((AbstractApplicationContext) applicationContext).refresh();
}
/**
* Sets if this application is running within a web environment. If not specified will
* attempt to deduce the environment based on the classpath.
*/
public void setWebEnvironment(boolean webEnvironment) {
this.webEnvironment = webEnvironment;
}
/**
* Sets if the Spring banner should be displayed when the application runs. Defaults
* to {@code true}.
* @see #printBanner()
*/
public void setShowBanner(boolean showBanner) {
this.showBanner = showBanner;
}
/**
* Sets if a {@link CommandLinePropertySource} should be added to the application
* context in order to expose arguments. Defaults to {@code true}.
*/
public void setAddCommandLineProperties(boolean addCommandLineProperties) {
this.addCommandLineProperties = addCommandLineProperties;
}
/**
* Sets the bean name generator that should be used when generating bean names.
*/
public void setBeanNameGenerator(BeanNameGenerator beanNameGenerator) {
this.beanNameGenerator = beanNameGenerator;
}
/**
* Sets the underlying environment that should be used when loading.
*/
public void setEnvironment(ConfigurableEnvironment environment) {
this.environment = environment;
}
/**
* Sets the {@link ResourceLoader} that should be used when loading resources.
*/
public void setResourceLoader(ResourceLoader resourceLoader) {
Assert.notNull(resourceLoader, "ResourceLoader must not be null");
this.resourceLoader = resourceLoader;
}
/**
* Sets the type of Spring {@link ApplicationContext} that will be created. If not
* specified defaults to {@link AnnotationConfigEmbeddedWebApplicationContext} for web
* based applications or {@link AnnotationConfigApplicationContext} for non web based
* applications.
* @param applicationContextClass the context class to set
* @see #setApplicationContext(ApplicationContext)
*/
public void setApplicationContextClass(
Class<? extends ApplicationContext> applicationContextClass) {
this.applicationContextClass = applicationContextClass;
}
/**
* Sets a Spring {@link ApplicationContext} that will be used for the application. If
* not specified an {@link AnnotationConfigEmbeddedWebApplicationContext} will be
* created for web based applications or an {@link AnnotationConfigApplicationContext}
* for non web based applications.
* @param applicationContext the spring application context.
* @see #setApplicationContextClass(Class)
*/
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
/**
* @param initializers the initializers to set
*/
public void setInitializers(
Collection<? extends ApplicationContextInitializer<?>> initializers) {
this.initializers = new ArrayList<ApplicationContextInitializer<?>>(initializers);
}
/**
* @param initializers
*/
public void addInitializers(ApplicationContextInitializer<?>... initializers) {
this.initializers.addAll(Arrays.asList(initializers));
}
/**
* @return the initializers
*/
public List<ApplicationContextInitializer<?>> getInitializers() {
return this.initializers;
}
/**
* Static helper that can be used to run a {@link SpringApplication} from the
* specified source using default settings.
* @param source the source to load
* @param args the application arguments (usually passed from a Java main method)
* @return the running {@link ApplicationContext}
*/
public static ApplicationContext run(Object source, String... args) {
return run(new Object[] { source }, args);
}
/**
* Static helper that can be used to run a {@link SpringApplication} from the
* specified sources using default settings.
* @param sources the sources to load
* @param args the application arguments (usually passed from a Java main method)
* @return the running {@link ApplicationContext}
*/
public static ApplicationContext run(Object[] sources, String[] args) {
return new SpringApplication(sources).run(args);
}
/**
* Static helper that can be used to run a {@link SpringApplication} from the
* specified {@code @Component} sources. Any class not annotated or meta-annotated
* with {@code @Component} will be ignored.
* @param classes the source classes to consider loading
* @param args the application arguments (usually passed from a Java main method)
* @return the running {@link ApplicationContext}
*/
public static ApplicationContext runComponents(Class<?>[] classes, String... args) {
List<Class<?>> componentClasses = new ArrayList<Class<?>>();
for (Class<?> candidate : classes) {
StandardAnnotationMetadata metadata = new StandardAnnotationMetadata(
candidate);
if (metadata.isAnnotated(Component.class.getName())) {
componentClasses.add(candidate);
}
}
return new SpringApplication(componentClasses.toArray()).run(args);
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.autoconfigure;
import org.springframework.bootstrap.context.annotation.ConditionalOnMissingBean;
import org.springframework.bootstrap.context.annotation.EnableAutoConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
/**
* {@link EnableAutoConfiguration Auto-configuration} for
* {@link PropertySourcesPlaceholderConfigurer}.
*
* @author Phillip Webb
*/
@Configuration
@ConditionalOnMissingBean(PropertySourcesPlaceholderConfigurer.class)
public class PropertyPlaceholderAutoConfiguration {
@Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer(
ApplicationContext context) {
return new PropertySourcesPlaceholderConfigurer();
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.autoconfigure.data;
import org.springframework.bootstrap.context.annotation.ConditionalOnClass;
import org.springframework.bootstrap.context.annotation.ConditionalOnMissingBean;
import org.springframework.bootstrap.context.annotation.EnableAutoConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean;
/**
* {@link EnableAutoConfiguration Auto-configuration} for Spring Data's JPA Repositories.
*
* @author Phillip Webb
* @see EnableJpaRepositories
*/
@Configuration
@ConditionalOnClass(JpaRepository.class)
@ConditionalOnMissingBean(JpaRepositoryFactoryBean.class)
@Import(JpaRepositoriesAutoConfigureRegistrar.class)
public class JpaRepositoriesAutoConfiguration {
}

View File

@@ -0,0 +1,122 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.autoconfigure.data;
import java.util.Collection;
import java.util.List;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.bootstrap.context.annotation.AutoConfigurationUtils;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.StandardAnnotationMetadata;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.data.jpa.repository.config.JpaRepositoryConfigExtension;
import org.springframework.data.repository.config.AnnotationRepositoryConfigurationSource;
import org.springframework.data.repository.config.RepositoryBeanDefinitionBuilder;
import org.springframework.data.repository.config.RepositoryBeanNameGenerator;
import org.springframework.data.repository.config.RepositoryConfiguration;
import org.springframework.data.repository.config.RepositoryConfigurationExtension;
import org.springframework.util.Assert;
/**
* {@link ImportBeanDefinitionRegistrar} used to auto-configure Spring Data JPA
* Repositories.
*
* @author Phillip Webb
*/
class JpaRepositoriesAutoConfigureRegistrar implements ImportBeanDefinitionRegistrar,
BeanFactoryAware, BeanClassLoaderAware {
private BeanFactory beanFactory;
private ClassLoader beanClassLoader;
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
final BeanDefinitionRegistry registry) {
final ResourceLoader resourceLoader = new DefaultResourceLoader();
final AnnotationRepositoryConfigurationSource configurationSource = getConfigurationSource();
final RepositoryConfigurationExtension extension = new JpaRepositoryConfigExtension();
extension.registerBeansForRoot(registry, configurationSource);
final RepositoryBeanNameGenerator generator = new RepositoryBeanNameGenerator();
generator.setBeanClassLoader(this.beanClassLoader);
Collection<RepositoryConfiguration<AnnotationRepositoryConfigurationSource>> repositoryConfigurations = extension
.getRepositoryConfigurations(configurationSource, resourceLoader);
for (final RepositoryConfiguration<AnnotationRepositoryConfigurationSource> repositoryConfiguration : repositoryConfigurations) {
RepositoryBeanDefinitionBuilder builder = new RepositoryBeanDefinitionBuilder(
repositoryConfiguration, extension);
BeanDefinitionBuilder definitionBuilder = builder.build(registry,
resourceLoader);
extension.postProcess(definitionBuilder, configurationSource);
String beanName = generator.generateBeanName(
definitionBuilder.getBeanDefinition(), registry);
registry.registerBeanDefinition(beanName,
definitionBuilder.getBeanDefinition());
}
}
private AnnotationRepositoryConfigurationSource getConfigurationSource() {
StandardAnnotationMetadata metadata = new StandardAnnotationMetadata(
EnableJpaRepositoriesConfiguration.class, true);
AnnotationRepositoryConfigurationSource configurationSource = new AnnotationRepositoryConfigurationSource(
metadata, EnableJpaRepositories.class) {
@Override
public java.lang.Iterable<String> getBasePackages() {
return JpaRepositoriesAutoConfigureRegistrar.this.getBasePackages();
};
};
return configurationSource;
}
protected Iterable<String> getBasePackages() {
List<String> basePackages = AutoConfigurationUtils
.getBasePackages(this.beanFactory);
Assert.notEmpty(
basePackages,
"Unable to find JPA repository base packages, please define "
+ "a @ComponentScan annotation or disable JpaRepositoriesAutoConfigure");
return basePackages;
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.beanClassLoader = classLoader;
}
@EnableJpaRepositories
private static class EnableJpaRepositoriesConfiguration {
}
}

View File

@@ -0,0 +1,87 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.autoconfigure.jdbc;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import javax.sql.DataSource;
import org.springframework.bootstrap.context.annotation.ConditionalOnMissingBean;
import org.springframework.bootstrap.context.annotation.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.AsyncUtils;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.util.ClassUtils;
/**
* {@link EnableAutoConfiguration Auto-configuration} for embedded databases.
*
* @author Phillip Webb
*/
@Configuration
@Conditional(EmbeddedDatabaseAutoConfiguration.EmbeddedDatabaseCondition.class)
@ConditionalOnMissingBean(DataSource.class)
public class EmbeddedDatabaseAutoConfiguration {
private static final Map<EmbeddedDatabaseType, String> EMBEDDED_DATABASE_TYPE_CLASSES;
static {
EMBEDDED_DATABASE_TYPE_CLASSES = new LinkedHashMap<EmbeddedDatabaseType, String>();
EMBEDDED_DATABASE_TYPE_CLASSES.put(EmbeddedDatabaseType.HSQL,
"org.hsqldb.Database");
}
private ExecutorService executorService;
@Bean
public DataSource dataSource() {
EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder()
.setType(getEmbeddedDatabaseType());
return AsyncUtils.submitVia(this.executorService, builder).build();
}
public static EmbeddedDatabaseType getEmbeddedDatabaseType() {
for (Map.Entry<EmbeddedDatabaseType, String> entry : EMBEDDED_DATABASE_TYPE_CLASSES
.entrySet()) {
if (ClassUtils.isPresent(entry.getValue(),
EmbeddedDatabaseAutoConfiguration.class.getClassLoader())) {
return entry.getKey();
}
}
return null;
}
static class EmbeddedDatabaseCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
if (!ClassUtils.isPresent(
"org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType",
context.getClassLoader())) {
return false;
}
return getEmbeddedDatabaseType() != null;
}
}
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.autoconfigure.orm.jpa;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.bootstrap.autoconfigure.jdbc.EmbeddedDatabaseAutoConfiguration;
import org.springframework.bootstrap.context.annotation.ConditionalOnClass;
import org.springframework.bootstrap.context.annotation.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.annotation.EnableTransactionManagement;
/**
* {@link EnableAutoConfiguration Auto-configuration} for Hibernate JPA.
*
* @author Phillip Webb
*/
@Configuration
@ConditionalOnClass(name = "org.hibernate.ejb.HibernateEntityManager")
@EnableTransactionManagement
public class HibernateJpaAutoConfiguration extends JpaAutoConfiguration {
private static final Map<EmbeddedDatabaseType, String> EMBEDDED_DATABASE_DIALECTS;
static {
EMBEDDED_DATABASE_DIALECTS = new LinkedHashMap<EmbeddedDatabaseType, String>();
EMBEDDED_DATABASE_DIALECTS.put(EmbeddedDatabaseType.HSQL,
"org.hibernate.dialect.HSQLDialect");
}
@Bean
@Override
public JpaVendorAdapter jpaVendorAdapter() {
return new HibernateJpaVendorAdapter();
}
@Override
protected void configure(
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean) {
Map<String, Object> properties = entityManagerFactoryBean.getJpaPropertyMap();
if (isAutoConfiguredDataSource()) {
properties.put("hibernate.hbm2ddl.auto", "create-drop");
String dialect = EMBEDDED_DATABASE_DIALECTS
.get(EmbeddedDatabaseAutoConfiguration.getEmbeddedDatabaseType());
if (dialect != null) {
properties.put("hibernate.dialect", dialect);
}
}
properties.put("hibernate.cache.provider_class",
"org.hibernate.cache.HashtableCacheProvider");
}
}

View File

@@ -0,0 +1,114 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.autoconfigure.orm.jpa;
import java.util.List;
import javax.persistence.EntityManager;
import javax.sql.DataSource;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.bootstrap.autoconfigure.jdbc.EmbeddedDatabaseAutoConfiguration;
import org.springframework.bootstrap.context.annotation.AutoConfigurationUtils;
import org.springframework.bootstrap.context.annotation.ConditionalOnBean;
import org.springframework.bootstrap.context.annotation.ConditionalOnClass;
import org.springframework.bootstrap.context.annotation.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.util.Assert;
/**
* Base {@link EnableAutoConfiguration Auto-configuration} for JPA.
*
* @author Phillip Webb
*/
@ConditionalOnClass({ LocalContainerEntityManagerFactoryBean.class,
EnableTransactionManagement.class, EntityManager.class })
@ConditionalOnBean(DataSource.class)
public abstract class JpaAutoConfiguration implements BeanFactoryAware {
private ConfigurableListableBeanFactory beanFactory;
@Bean
public PlatformTransactionManager txManager() {
return new JpaTransactionManager(entityManagerFactory().getObject());
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
entityManagerFactoryBean.setJpaVendorAdapter(jpaVendorAdapter());
entityManagerFactoryBean.setDataSource(getDataSource());
entityManagerFactoryBean.setPackagesToScan(getPackagesToScan());
configure(entityManagerFactoryBean);
return entityManagerFactoryBean;
}
/**
* Determines if the {@code dataSource} being used by Spring was created from
* {@link EmbeddedDatabaseAutoConfiguration}.
* @return true if the data source was auto-configured.
*/
protected boolean isAutoConfiguredDataSource() {
try {
BeanDefinition beanDefinition = this.beanFactory
.getBeanDefinition("dataSource");
return EmbeddedDatabaseAutoConfiguration.class.getName().equals(
beanDefinition.getFactoryBeanName());
} catch (NoSuchBeanDefinitionException e) {
return false;
}
}
@Bean
public abstract JpaVendorAdapter jpaVendorAdapter();
protected DataSource getDataSource() {
try {
return this.beanFactory.getBean("dataSource", DataSource.class);
} catch (RuntimeException e) {
return this.beanFactory.getBean(DataSource.class);
}
}
protected String[] getPackagesToScan() {
List<String> basePackages = AutoConfigurationUtils
.getBasePackages(this.beanFactory);
Assert.notEmpty(basePackages,
"Unable to find JPA packages to scan, please define "
+ "a @ComponentScan annotation or disable JpaAutoConfiguration");
return basePackages.toArray(new String[basePackages.size()]);
}
protected void configure(
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean) {
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
}
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.autoconfigure.web;
import javax.servlet.Servlet;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.Loader;
import org.springframework.bootstrap.context.annotation.ConditionalOnClass;
import org.springframework.bootstrap.context.annotation.ConditionalOnMissingBean;
import org.springframework.bootstrap.context.annotation.EnableAutoConfiguration;
import org.springframework.bootstrap.context.embedded.EmbeddedServletContainerFactory;
import org.springframework.bootstrap.context.embedded.jetty.JettyEmbeddedServletContainerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* {@link EnableAutoConfiguration Auto-configuration} for
* {@link JettyEmbeddedServletContainerFactory}.
*
* @author Phillip Webb
*/
@Configuration
@ConditionalOnClass({ Servlet.class, Server.class, Loader.class })
@ConditionalOnMissingBean(EmbeddedServletContainerFactory.class)
public class EmbeddedJettyAutoConfiguration {
@Bean
public JettyEmbeddedServletContainerFactory jettyEmbeddedServletContainerFactory() {
return new JettyEmbeddedServletContainerFactory();
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.autoconfigure.web;
import javax.servlet.Servlet;
import org.apache.catalina.startup.Tomcat;
import org.springframework.bootstrap.context.annotation.ConditionalOnClass;
import org.springframework.bootstrap.context.annotation.ConditionalOnMissingBean;
import org.springframework.bootstrap.context.annotation.EnableAutoConfiguration;
import org.springframework.bootstrap.context.embedded.EmbeddedServletContainerFactory;
import org.springframework.bootstrap.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* {@link EnableAutoConfiguration Auto-configuration} for
* {@link TomcatEmbeddedServletContainerFactory}.
*
* @author Phillip Webb
*/
@Configuration
@ConditionalOnClass({ Servlet.class, Tomcat.class })
@ConditionalOnMissingBean(EmbeddedServletContainerFactory.class)
public class EmbeddedTomcatAutoConfiguration {
@Bean
public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
return new TomcatEmbeddedServletContainerFactory();
}
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.autoconfigure.web;
import javax.servlet.Servlet;
import org.springframework.bootstrap.autoconfigure.web.WebMvcAutoConfiguration.WebMvcConfiguration;
import org.springframework.bootstrap.context.annotation.ConditionalOnClass;
import org.springframework.bootstrap.context.annotation.ConditionalOnMissingBean;
import org.springframework.bootstrap.context.annotation.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.HandlerAdapter;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
/**
* {@link EnableAutoConfiguration Auto-configuration} for {@link EnableWebMvc Web MVC}.
*
* @author Phillip Webb
*/
@Configuration
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class })
@ConditionalOnMissingBean({ HandlerAdapter.class, HandlerMapping.class })
@Import(WebMvcConfiguration.class)
public class WebMvcAutoConfiguration {
/**
* Nested configuration used because {@code @EnableWebMvc} will add HandlerAdapter and
* HandlerMapping, causing the condition to fail and the additional DispatcherServlet
* bean never to be registered if it were declared directly.
*/
@EnableWebMvc
public static class WebMvcConfiguration extends WebMvcConfigurerAdapter {
@Bean
public DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}
@Override
public void configureDefaultServletHandling(
DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
}

View File

@@ -0,0 +1,98 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.bind;
import java.beans.IntrospectionException;
import java.util.HashMap;
import java.util.Map;
import org.yaml.snakeyaml.constructor.Constructor;
import org.yaml.snakeyaml.introspector.Property;
import org.yaml.snakeyaml.introspector.PropertyUtils;
import org.yaml.snakeyaml.nodes.NodeId;
/**
* Extended version of snakeyaml's Constructor class to facilitate mapping custom YAML
* keys to Javabean property names.
*
* @author Luke Taylor
*/
public class CustomPropertyConstructor extends Constructor {
private final Map<Class<?>, Map<String, Property>> properties = new HashMap<Class<?>, Map<String, Property>>();
private final PropertyUtils propertyUtils = new PropertyUtils();
public CustomPropertyConstructor(Class<?> theRoot) {
super(theRoot);
this.yamlClassConstructors.put(NodeId.mapping,
new CustomPropertyConstructMapping());
}
public CustomPropertyConstructor(Class<?> theRoot,
Map<Class<?>, Map<String, String>> propertyAliases) {
this(theRoot);
for (Class<?> key : propertyAliases.keySet()) {
Map<String, String> map = propertyAliases.get(key);
if (map != null) {
for (String alias : map.keySet()) {
addPropertyAlias(alias, key, map.get(alias));
}
}
}
}
/**
* Adds an alias for a Javabean property name on a particular type. The values of YAML
* keys with the alias name will be mapped to the Javabean property.
*/
protected final void addPropertyAlias(String alias, Class<?> type, String name) {
Map<String, Property> typeMap = this.properties.get(type);
if (typeMap == null) {
typeMap = new HashMap<String, Property>();
this.properties.put(type, typeMap);
}
try {
typeMap.put(alias, this.propertyUtils.getProperty(type, name));
} catch (IntrospectionException e) {
throw new RuntimeException(e);
}
}
class CustomPropertyConstructMapping extends ConstructMapping {
@Override
protected Property getProperty(Class<?> type, String name)
throws IntrospectionException {
Property p = lookupProperty(type, name);
return p != null ? p : super.getProperty(type, name);
}
private Property lookupProperty(Class<?> type, String name) {
Map<String, Property> m = CustomPropertyConstructor.this.properties.get(type);
if (m != null) {
return m.get(name);
}
return null;
}
}
}

View File

@@ -0,0 +1,246 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.bind;
import java.util.Locale;
import java.util.Properties;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.core.env.PropertySources;
import org.springframework.util.Assert;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.DataBinder;
import org.springframework.validation.ObjectError;
import org.springframework.validation.Validator;
/**
* Validate some {@link Properties} by binding them to an object of a specified type and
* then optionally running a {@link Validator} over it.
*
* @author Dave Syer
*/
public class PropertiesConfigurationFactory<T> implements FactoryBean<T>,
MessageSourceAware, InitializingBean {
private static final Log logger = LogFactory
.getLog(PropertiesConfigurationFactory.class);
private boolean ignoreUnknownFields = true;
private boolean ignoreInvalidFields = false;
private boolean exceptionIfInvalid;
private Properties properties;
private PropertySources propertySources;
private T configuration;
private Validator validator;
private MessageSource messageSource;
private boolean initialized = false;
private String targetName;
/**
* @param target the target object to bind too
* @see #PropertiesConfigurationFactory(Class)
*/
public PropertiesConfigurationFactory(T target) {
Assert.notNull(target);
this.configuration = target;
}
/**
* Create a new factory for an object of the given type.
*
* @see #PropertiesConfigurationFactory(Class)
*/
@SuppressWarnings("unchecked")
public PropertiesConfigurationFactory(Class<?> type) {
Assert.notNull(type);
this.configuration = (T) BeanUtils.instantiate(type);
}
/**
* Set whether to ignore unknown fields, that is, whether to ignore bind parameters
* that do not have corresponding fields in the target object.
* <p>
* Default is "true". Turn this off to enforce that all bind parameters must have a
* matching field in the target object.
* <p>
*/
public void setIgnoreUnknownFields(boolean ignoreUnknownFields) {
this.ignoreUnknownFields = ignoreUnknownFields;
}
/**
* Set whether to ignore invalid fields, that is, whether to ignore bind parameters
* that have corresponding fields in the target object which are not accessible (for
* example because of null values in the nested path).
* <p>
* Default is "false". Turn this on to ignore bind parameters for nested objects in
* non-existing parts of the target object graph.
* <p>
*/
public void setIgnoreInvalidFields(boolean ignoreInvalidFields) {
this.ignoreInvalidFields = ignoreInvalidFields;
}
/**
* @param targetName
*/
public void setTargetName(String targetName) {
this.targetName = targetName;
}
/**
* @param messageSource the messageSource to set
*/
@Override
public void setMessageSource(MessageSource messageSource) {
this.messageSource = messageSource;
}
/**
* @param properties the properties to set
*/
public void setProperties(Properties properties) {
this.properties = properties;
}
/**
* @param propertySources the propertySources to set
*/
public void setPropertySources(PropertySources propertySources) {
this.propertySources = propertySources;
}
/**
* @param validator the validator to set
*/
public void setValidator(Validator validator) {
this.validator = validator;
}
public void setExceptionIfInvalid(boolean exceptionIfInvalid) {
this.exceptionIfInvalid = exceptionIfInvalid;
}
@Override
public void afterPropertiesSet() throws Exception {
Assert.state(this.properties != null || this.propertySources != null,
"Properties or propertySources should not be null");
try {
if (this.properties != null) {
logger.trace("Properties:\n" + this.properties);
} else {
logger.trace("Property Sources: " + this.propertySources);
}
this.initialized = true;
RelaxedDataBinder dataBinder;
if (this.targetName != null) {
dataBinder = new RelaxedDataBinder(this.configuration, this.targetName);
} else {
dataBinder = new RelaxedDataBinder(this.configuration);
}
if (this.validator != null) {
dataBinder.setValidator(this.validator);
}
dataBinder.setIgnoreInvalidFields(this.ignoreInvalidFields);
dataBinder.setIgnoreUnknownFields(this.ignoreUnknownFields);
customizeBinder(dataBinder);
PropertyValues pvs;
if (this.properties != null) {
pvs = new MutablePropertyValues(this.properties);
} else {
pvs = new PropertySourcesPropertyValues(this.propertySources);
}
dataBinder.bind(pvs);
if (this.validator != null) {
dataBinder.validate();
BindingResult errors = dataBinder.getBindingResult();
if (errors.hasErrors()) {
logger.error("Properties configuration failed validation");
for (ObjectError error : errors.getAllErrors()) {
logger.error(this.messageSource != null ? this.messageSource
.getMessage(error, Locale.getDefault())
+ " ("
+ error
+ ")" : error);
}
if (this.exceptionIfInvalid) {
BindException summary = new BindException(errors);
throw summary;
}
}
}
} catch (BindException e) {
if (this.exceptionIfInvalid) {
throw e;
}
logger.error(
"Failed to load Properties validation bean. Your Properties may be invalid.",
e);
}
}
/**
* @param dataBinder the data binder that will be used to bind and validate
*/
protected void customizeBinder(DataBinder dataBinder) {
}
@Override
public Class<?> getObjectType() {
if (this.configuration == null) {
return Object.class;
}
return this.configuration.getClass();
}
@Override
public boolean isSingleton() {
return true;
}
@Override
public T getObject() throws Exception {
if (!this.initialized) {
afterPropertiesSet();
}
return this.configuration;
}
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.bind;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.bootstrap.context.annotation.ConfigurationProperties;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.env.PropertySources;
import org.springframework.validation.Validator;
/**
* @author Dave Syer
*/
public class PropertySourcesBindingPostProcessor implements BeanPostProcessor {
private PropertySources propertySources;
private Validator validator;
/**
* @param propertySources
*/
public void setPropertySources(PropertySources propertySources) {
this.propertySources = propertySources;
}
/**
* @param validator the validator to set
*/
public void setValidator(Validator validator) {
this.validator = validator;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
ConfigurationProperties annotation = AnnotationUtils.findAnnotation(
bean.getClass(), ConfigurationProperties.class);
if (annotation != null) {
PropertiesConfigurationFactory<Object> factory = new PropertiesConfigurationFactory<Object>(
bean);
factory.setPropertySources(this.propertySources);
factory.setValidator(this.validator);
factory.setIgnoreInvalidFields(annotation.ignoreInvalidFields());
factory.setIgnoreUnknownFields(annotation.ignoreUnknownFields());
String targetName = "".equals(annotation.value()) ? ("".equals(annotation
.name()) ? null : annotation.name()) : annotation.value();
factory.setTargetName(targetName);
try {
bean = factory.getObject();
} catch (BeansException e) {
throw e;
} catch (Exception e) {
throw new BeanCreationException(beanName, "Could not bind", e);
}
}
return bean;
}
}

View File

@@ -0,0 +1,119 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.bind;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.PropertyValues;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.PropertySources;
import org.springframework.core.env.PropertySourcesPropertyResolver;
import org.springframework.validation.DataBinder;
/**
* A {@link PropertyValues} implementation backed by a {@link PropertySources}, bridging
* the two abstractions and allowing (for instance) a regular {@link DataBinder} to be
* used with the latter.
*
* @author Dave Syer
*/
public class PropertySourcesPropertyValues implements PropertyValues {
private Map<String, PropertyValue> propertyValues = new ConcurrentHashMap<String, PropertyValue>();
private PropertySources propertySources;
/**
* Create a new PropertyValues from the given PropertySources
*
* @param propertySources a PropertySources instance
*/
public PropertySourcesPropertyValues(PropertySources propertySources) {
this.propertySources = propertySources;
// TODO: maybe lazy initialization?
PropertySourcesPropertyResolver resolver = new PropertySourcesPropertyResolver(
propertySources);
for (PropertySource<?> source : propertySources) {
if (source instanceof EnumerablePropertySource) {
EnumerablePropertySource<?> enumerable = (EnumerablePropertySource<?>) source;
if (enumerable.getPropertyNames().length > 0) {
for (String propertyName : enumerable.getPropertyNames()) {
Object value = resolver.getProperty(propertyName);
this.propertyValues.put(propertyName, new PropertyValue(
propertyName, value));
}
}
}
}
}
@Override
public PropertyValue[] getPropertyValues() {
Collection<PropertyValue> values = this.propertyValues.values();
return values.toArray(new PropertyValue[values.size()]);
}
@Override
public PropertyValue getPropertyValue(String propertyName) {
PropertyValue propertyValue = this.propertyValues.get(propertyName);
if (propertyValue != null) {
return propertyValue;
}
for (PropertySource<?> source : this.propertySources) {
Object value = source.getProperty(propertyName);
if (value != null) {
propertyValue = new PropertyValue(propertyName, value);
this.propertyValues.put(propertyName, propertyValue);
return propertyValue;
}
}
return null;
}
@Override
public PropertyValues changesSince(PropertyValues old) {
MutablePropertyValues changes = new MutablePropertyValues();
// for each property value in the new set
for (PropertyValue newPv : getPropertyValues()) {
// if there wasn't an old one, add it
PropertyValue pvOld = old.getPropertyValue(newPv.getName());
if (pvOld == null) {
changes.addPropertyValue(newPv);
} else if (!pvOld.equals(newPv)) {
// it's changed
changes.addPropertyValue(newPv);
}
}
return changes;
}
@Override
public boolean contains(String propertyName) {
return getPropertyValue(propertyName) != null;
}
@Override
public boolean isEmpty() {
return this.propertyValues.isEmpty();
}
}

View File

@@ -0,0 +1,226 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.bind;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.InvalidPropertyException;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValue;
import org.springframework.util.StringUtils;
import org.springframework.validation.DataBinder;
/**
* Binder implementation that allows caller to bind to maps and also allows property names
* to match a bit loosely (if underscores or dashes are removed and replaced with camel
* case for example).
*
* @author Dave Syer
*/
public class RelaxedDataBinder extends DataBinder {
private String namePrefix;
/**
* @param target the target into which properties are bound
*/
public RelaxedDataBinder(Object target) {
super(target);
}
/**
* @param target the target into which properties are bound
* @param namePrefix An optional prefix to be used when reading properties
*/
public RelaxedDataBinder(Object target, String namePrefix) {
super(target, (StringUtils.hasLength(namePrefix) ? namePrefix
: DEFAULT_OBJECT_NAME));
this.namePrefix = (StringUtils.hasLength(namePrefix) ? namePrefix + "." : null);
}
@Override
protected void doBind(MutablePropertyValues propertyValues) {
propertyValues = modifyProperties(propertyValues, getTarget());
super.doBind(propertyValues);
}
/**
* Modify the property values so that period separated property paths are valid for
* map keys. Also creates new maps for properties of map type that are null (assuming
* all maps are potentially nested). The standard bracket <code>[...]</code>
* dereferencing is also accepted.
*
* @param propertyValues the property values
* @param target the target object
*/
private MutablePropertyValues modifyProperties(MutablePropertyValues propertyValues,
Object target) {
BeanWrapper targetWrapper = new BeanWrapperImpl(target);
targetWrapper.setAutoGrowNestedPaths(true);
propertyValues = getProperyValuesForNamePrefix(propertyValues);
List<PropertyValue> list = propertyValues.getPropertyValueList();
for (int i = 0; i < list.size(); i++) {
modifyProperty(propertyValues, targetWrapper, list.get(i), i);
}
return propertyValues;
}
private MutablePropertyValues getProperyValuesForNamePrefix(
MutablePropertyValues propertyValues) {
if (this.namePrefix == null) {
return propertyValues;
}
MutablePropertyValues rtn = new MutablePropertyValues();
for (PropertyValue pv : propertyValues.getPropertyValues()) {
String name = pv.getName();
if (name.startsWith(this.namePrefix)) {
name = name.substring(this.namePrefix.length());
rtn.add(name, pv.getValue());
}
}
return rtn;
}
private void modifyProperty(MutablePropertyValues propertyValues, BeanWrapper target,
PropertyValue propertyValue, int index) {
String name = propertyValue.getName();
StringBuilder builder = new StringBuilder();
Class<?> type = target.getWrappedClass();
for (String key : StringUtils.delimitedListToStringArray(name, ".")) {
if (builder.length() != 0) {
builder.append(".");
}
String oldKey = key;
key = getActualPropertyName(target, builder.toString(), oldKey);
builder.append(key);
String base = builder.toString();
if (!oldKey.equals(key)) {
propertyValues.setPropertyValueAt(
new PropertyValue(base, propertyValue.getValue()), index);
}
type = target.getPropertyType(base);
// Any nested properties that are maps, are assumed to be simple nested
// maps of maps...
if (type != null && Map.class.isAssignableFrom(type)) {
String suffix = name.substring(base.length());
Map<String, Object> nested = new LinkedHashMap<String, Object>();
if (target.getPropertyValue(base) != null) {
@SuppressWarnings("unchecked")
Map<String, Object> existing = (Map<String, Object>) target
.getPropertyValue(base);
nested = existing;
} else {
target.setPropertyValue(base, nested);
}
Map<String, Object> value = nested;
nested = new LinkedHashMap<String, Object>();
String[] tree = StringUtils.delimitedListToStringArray(suffix, ".");
for (int j = 1; j < tree.length - 1; j++) {
if (!value.containsKey(tree[j])) {
value.put(tree[j], nested);
}
value = nested;
nested = new LinkedHashMap<String, Object>();
}
String refName = base + suffix.replaceAll("\\.([a-zA-Z0-9]*)", "[$1]");
propertyValues.setPropertyValueAt(new PropertyValue(refName,
propertyValue.getValue()), index);
break;
}
}
}
private String getActualPropertyName(BeanWrapper target, String prefix, String name) {
for (Variation variation : Variation.values()) {
for (Manipulation manipulation : Manipulation.values()) {
// Apply all manipulations before attempting variations
String candidate = variation.apply(manipulation.apply(name));
try {
if (target.getPropertyType(prefix + candidate) != null) {
return candidate;
}
} catch (InvalidPropertyException ex) {
}
}
}
return name;
}
static enum Variation {
NONE {
@Override
public String apply(String value) {
return value;
}
},
UPPERCASE {
@Override
public String apply(String value) {
return value.toUpperCase();
}
},
LOWERCASE {
@Override
public String apply(String value) {
return value.toLowerCase();
}
};
public abstract String apply(String value);
}
static enum Manipulation {
NONE {
@Override
public String apply(String value) {
return value;
}
},
UNDERSCORE {
@Override
public String apply(String value) {
return value.replace("-", "_");
}
},
CAMELCASE {
@Override
public String apply(String value) {
StringBuilder builder = new StringBuilder();
for (String field : UNDERSCORE.apply(value).split("_")) {
builder.append(builder.length() == 0 ? field : StringUtils
.capitalize(field));
}
return builder.toString();
}
};
public abstract String apply(String value);
}
}

View File

@@ -0,0 +1,191 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.bind;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.core.io.Resource;
import org.springframework.util.Assert;
import org.springframework.util.StreamUtils;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.validation.Validator;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.Constructor;
import org.yaml.snakeyaml.error.YAMLException;
/**
* Validate some YAML by binding it to an object of a specified type and then optionally
* running a {@link Validator} over it.
*
* @author Luke Taylor
* @author Dave Syer
*/
public class YamlConfigurationFactory<T> implements FactoryBean<T>, MessageSourceAware,
InitializingBean {
private static final Log logger = LogFactory.getLog(YamlConfigurationFactory.class);
private Class<?> type;
private boolean exceptionIfInvalid;
private String yaml;
private Resource resource;
private T configuration;
private Validator validator;
private MessageSource messageSource;
private Map<Class<?>, Map<String, String>> propertyAliases = Collections.emptyMap();
/**
* Sets a validation constructor which will be applied to the YAML doc to see whether
* it matches the expected Javabean.
*/
public YamlConfigurationFactory(Class<?> type) {
Assert.notNull(type);
this.type = type;
}
/**
* @param messageSource the messageSource to set
*/
@Override
public void setMessageSource(MessageSource messageSource) {
this.messageSource = messageSource;
}
/**
* @param propertyAliases the propertyAliases to set
*/
public void setPropertyAliases(Map<Class<?>, Map<String, String>> propertyAliases) {
this.propertyAliases = new HashMap<Class<?>, Map<String, String>>(propertyAliases);
}
/**
* @param yaml the yaml to set
*/
public void setYaml(String yaml) {
this.yaml = yaml;
}
/**
* @param resource the resource to set
*/
public void setResource(Resource resource) {
this.resource = resource;
}
/**
* @param validator the validator to set
*/
public void setValidator(Validator validator) {
this.validator = validator;
}
public void setExceptionIfInvalid(boolean exceptionIfInvalid) {
this.exceptionIfInvalid = exceptionIfInvalid;
}
@Override
@SuppressWarnings("unchecked")
public void afterPropertiesSet() throws Exception {
if (this.yaml == null) {
Assert.state(this.resource != null, "Resource should not be null");
this.yaml = StreamUtils.copyToString(this.resource.getInputStream(),
Charset.defaultCharset());
}
Assert.state(
this.yaml != null,
"Yaml document should not be null: either set it directly or set the resource to load it from");
try {
logger.trace("Yaml document is\n" + this.yaml);
Constructor constructor = new CustomPropertyConstructor(this.type,
this.propertyAliases);
this.configuration = (T) (new Yaml(constructor)).load(this.yaml);
if (this.validator != null) {
BindingResult errors = new BeanPropertyBindingResult(this.configuration,
"configuration");
this.validator.validate(this.configuration, errors);
if (errors.hasErrors()) {
logger.error("YAML configuration failed validation");
for (ObjectError error : errors.getAllErrors()) {
logger.error(this.messageSource != null ? this.messageSource
.getMessage(error, Locale.getDefault())
+ " ("
+ error
+ ")" : error);
}
if (this.exceptionIfInvalid) {
BindException summary = new BindException(errors);
throw summary;
}
}
}
} catch (YAMLException e) {
if (this.exceptionIfInvalid) {
throw e;
}
logger.error(
"Failed to load YAML validation bean. Your YAML file may be invalid.",
e);
}
}
@Override
public Class<?> getObjectType() {
if (this.configuration == null) {
return Object.class;
}
return this.configuration.getClass();
}
@Override
public boolean isSingleton() {
return true;
}
@Override
public T getObject() throws Exception {
if (this.configuration == null) {
afterPropertiesSet();
}
return this.configuration;
}
}

View File

@@ -0,0 +1,127 @@
/*
* Copyright 2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.config;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import org.springframework.beans.factory.FactoryBean;
/**
* Factory for Map that reads from a YAML source. YAML is a nice human-readable format for
* configuration, and it has some useful hierarchical properties. It's more or less a
* superset of JSON, so it has a lot of similar features. If multiple resources are
* provided the later ones will override entries in the earlier ones hierarchically - that
* is all entries with the same nested key of type Map at any depth are merged. For
* example:
*
* <pre>
* foo:
* bar:
* one: two
* three: four
*
* <pre>
*
* plus (later in the list)
*
* <pre>
* foo:
* bar:
* one: 2
* five: six
*
* <pre>
*
* results in an effecive input of
*
* <pre>
* foo:
* bar:
* one: 2
* three: four
* five: six
*
* <pre>
*
* Note that the value of "foo" in the first document is not simply replaced with the value in the second, but its nested values are merged.
*
* @author Dave Syer
* @since 3.2
*
*/
public class YamlMapFactoryBean extends YamlProcessor implements
FactoryBean<Map<String, Object>> {
private boolean singleton = true;
private Map<String, Object> instance;
@Override
public Map<String, Object> getObject() {
if (!this.singleton || this.instance == null) {
final Map<String, Object> result = new LinkedHashMap<String, Object>();
MatchCallback callback = new MatchCallback() {
@Override
public void process(Properties properties, Map<String, Object> map) {
merge(result, map);
}
};
process(callback);
this.instance = result;
}
return this.instance;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private void merge(Map<String, Object> output, Map<String, Object> map) {
for (Entry<String, Object> entry : map.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
Object existing = output.get(key);
if (value instanceof Map && existing instanceof Map) {
Map<String, Object> result = new LinkedHashMap<String, Object>(
(Map) existing);
merge(result, (Map) value);
output.put(key, result);
} else {
output.put(key, value);
}
}
}
@Override
public Class<?> getObjectType() {
return Map.class;
}
/**
* Set if a singleton should be created, or a new object on each request otherwise.
* Default is <code>true</code> (a singleton).
*/
public void setSingleton(boolean singleton) {
this.singleton = singleton;
}
@Override
public boolean isSingleton() {
return this.singleton;
}
}

View File

@@ -0,0 +1,317 @@
/*
* Copyright 2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.config;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.io.Resource;
import org.springframework.util.StringUtils;
import org.yaml.snakeyaml.Yaml;
/**
* Base class for Yaml factories.
*
* @author Dave Syer
* @since 3.2
*/
public class YamlProcessor {
public interface MatchCallback {
void process(Properties properties, Map<String, Object> map);
}
public interface DocumentMatcher {
boolean matches(Properties properties);
}
private static final Log logger = LogFactory.getLog(YamlProcessor.class);
public static enum ResolutionMethod {
OVERRIDE, OVERRIDE_AND_IGNORE, FIRST_FOUND
}
private ResolutionMethod resolutionMethod = ResolutionMethod.OVERRIDE;
private Resource[] resources = new Resource[0];
private List<DocumentMatcher> documentMatchers = Collections.emptyList();
private boolean matchDefault = true;
/**
* A map of document matchers allowing callers to selectively use only some of the
* documents in a YAML resource. In YAML documents are separated by
* <code>---<code> lines, and each document is converted to properties before the match is made. E.g.
*
* <pre>
* environment: dev
* url: http://dev.bar.com
* name: Developer Setup
* ---
* environment: prod
* url:http://foo.bar.com
* name: My Cool App
* </pre>
*
* when mapped with <code>documentMatchers = YamlProcessor.mapMatcher({"environment": "prod"})</code>
* would end up as
*
* <pre>
* environment=prod
* url=http://foo.bar.com
* name=My Cool App
* url=http://dev.bar.com
* </pre>
*
* @param matchers a map of keys to value patterns (regular expressions)
*/
public void setDocumentMatchers(List<? extends DocumentMatcher> matchers) {
this.documentMatchers = Collections.unmodifiableList(matchers);
}
/**
* Flag indicating that a document that contains none of the keys in the
* {@link #setDocumentMatchers(List) document matchers} will nevertheless match.
*
* @param matchDefault the flag to set (default true)
*/
public void setMatchDefault(boolean matchDefault) {
this.matchDefault = matchDefault;
}
/**
* Method to use for resolving resources. Each resource will be converted to a Map, so
* this property is used to decide which map entries to keep in the final output from
* this factory. Possible values:
* <ul>
* <li><code>OVERRIDE</code> for replacing values from earlier in the list</li>
* <li><code>FIRST_FOUND</code> if you want to take the first resource in the list
* that exists and use just that.</li>
* </ul>
*
*
* @param resolutionMethod the resolution method to set. Defaults to OVERRIDE.
*/
public void setResolutionMethod(ResolutionMethod resolutionMethod) {
this.resolutionMethod = resolutionMethod;
}
/**
* @param resources the resources to set
*/
public void setResources(Resource[] resources) {
this.resources = resources;
}
/**
* Provides an opportunity for subclasses to process the Yaml parsed from the supplied
* resources. Each resource is parsed in turn and the documents inside checked against
* the {@link #setDocumentMatchers(List) matchers}. If a document matches it is passed
* into the callback, along with its representation as Properties. Depending on the
* {@link #setResolutionMethod(ResolutionMethod)} not all of the documents will be
* parsed.
*
* @param callback a callback to delegate to once matching documents are found
*/
protected void process(MatchCallback callback) {
Yaml yaml = new Yaml();
boolean found = false;
for (Resource resource : this.resources) {
try {
logger.info("Loading from YAML: " + resource);
int count = 0;
for (Object object : yaml.loadAll(resource.getInputStream())) {
if (this.resolutionMethod != ResolutionMethod.FIRST_FOUND || !found) {
@SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>) object;
if (map != null) {
found = process(map, callback);
if (found) {
count++;
}
}
}
}
logger.info("Loaded " + count + " document" + (count > 1 ? "s" : "")
+ " from YAML resource: " + resource);
if (this.resolutionMethod == ResolutionMethod.FIRST_FOUND && found) {
// No need to load any more resources
break;
}
} catch (IOException e) {
if (this.resolutionMethod == ResolutionMethod.FIRST_FOUND
|| this.resolutionMethod == ResolutionMethod.OVERRIDE_AND_IGNORE) {
if (logger.isWarnEnabled()) {
logger.warn("Could not load map from " + resource + ": "
+ e.getMessage());
}
} else {
throw new IllegalStateException(e);
}
}
}
}
private boolean process(Map<String, Object> map, MatchCallback callback) {
Properties properties = new Properties();
assignProperties(properties, map, null);
if (this.documentMatchers.isEmpty()) {
logger.debug("Merging document (no matchers set)" + map);
callback.process(properties, map);
} else {
boolean valueFound = false;
for (DocumentMatcher matcher : this.documentMatchers) {
if (matcher.matches(properties)) {
callback.process(properties, map);
valueFound = true;
// No need to check for more matches
break;
}
}
if (!valueFound && this.matchDefault) {
logger.debug("Matched document with default matcher: " + map);
callback.process(properties, map);
} else if (!valueFound) {
logger.debug("Unmatched document");
return false;
}
}
return true;
}
private void assignProperties(Properties properties, Map<String, Object> input,
String path) {
for (Entry<String, Object> entry : input.entrySet()) {
String key = entry.getKey();
if (StringUtils.hasText(path)) {
if (key.startsWith("[")) {
key = path + key;
} else {
key = path + "." + key;
}
}
Object value = entry.getValue();
if (value instanceof String) {
properties.put(key, value);
} else if (value instanceof Map) {
// Need a compound key
@SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>) value;
assignProperties(properties, map, key);
} else if (value instanceof Collection) {
// Need a compound key
@SuppressWarnings("unchecked")
Collection<Object> collection = (Collection<Object>) value;
properties.put(key,
StringUtils.collectionToCommaDelimitedString(collection));
int count = 0;
for (Object object : collection) {
assignProperties(properties,
Collections.singletonMap("[" + (count++) + "]", object), key);
}
} else {
properties.put(key, value == null ? "" : value);
}
}
}
/**
* Matches a document containing a given key and where the value of that key matches
* one of the given values (interpreted as a regex).
*
* @author Dave Syer
*
*/
public static class SimpleDocumentMatcher implements DocumentMatcher {
private String key;
private String[] patterns;
public SimpleDocumentMatcher(final String key, final String... patterns) {
this.key = key;
this.patterns = patterns;
}
@Override
public boolean matches(Properties properties) {
if (!properties.containsKey(this.key)) {
return false;
}
String value = properties.getProperty(this.key);
for (String pattern : this.patterns) {
if (value == null || value.matches(pattern)) {
return true;
}
}
return false;
}
}
/**
* Matches a document containing a given key and where the value of that key is an
* array containing one of the given values, or where one of the values matches one of
* the given values (interpreted as regexes).
*
* @author Dave Syer
*
*/
public static class ArrayDocumentMatcher implements DocumentMatcher {
private String key;
private String[] patterns;
public ArrayDocumentMatcher(final String key, final String... patterns) {
this.key = key;
this.patterns = patterns;
}
@Override
public boolean matches(Properties properties) {
if (!properties.containsKey(this.key)) {
return false;
}
Set<String> values = StringUtils.commaDelimitedListToSet(properties
.getProperty(this.key));
for (String pattern : this.patterns) {
for (String value : values) {
if (value.matches(pattern)) {
return true;
}
}
}
return false;
}
}
}

View File

@@ -0,0 +1,111 @@
/*
* Copyright 2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.config;
import java.util.Map;
import java.util.Properties;
import org.springframework.beans.factory.FactoryBean;
/**
* Factory for Java Properties that reads from a YAML source. YAML is a nice
* human-readable format for configuration, and it has some useful hierarchical
* properties. It's more or less a superset of JSON, so it has a lot of similar features.
* The Properties created by this factory have nested paths for hierarchical objects, so
* for instance this YAML
*
* <pre>
* environments:
* dev:
* url: http://dev.bar.com
* name: Developer Setup
* prod:
* url: http://foo.bar.com
* name: My Cool App
* </pre>
*
* is transformed into these Properties:
*
* <pre>
* environments.dev.url=http://dev.bar.com
* environments.dev.name=Developer Setup
* environments.prod.url=http://foo.bar.com
* environments.prod.name=My Cool App
* </pre>
*
* Lists are represented as comma-separated values (useful for simple String values) and
* also as property keys with <code>[]</code> dereferencers, for example this YAML:
*
* <pre>
* servers:
* - dev.bar.com
* - foo.bar.com
* </pre>
*
* becomes java Properties like this:
*
* <pre>
* servers=dev.bar.com,foo.bar.com
* servers[0]=dev.bar.com
* servers[1]=foo.bar.com
* </pre>
*
* @author Dave Syer
* @since 3.2
*
*/
public class YamlPropertiesFactoryBean extends YamlProcessor implements
FactoryBean<Properties> {
private boolean singleton = true;
private Properties instance;
@Override
public Properties getObject() {
if (!this.singleton || this.instance == null) {
final Properties result = new Properties();
MatchCallback callback = new MatchCallback() {
@Override
public void process(Properties properties, Map<String, Object> map) {
result.putAll(properties);
}
};
process(callback);
this.instance = result;
}
return this.instance;
}
@Override
public Class<?> getObjectType() {
return Properties.class;
}
/**
* Set if a singleton should be created, or a new object on each request otherwise.
* Default is <code>true</code> (a singleton).
*/
public void setSingleton(boolean singleton) {
this.singleton = singleton;
}
@Override
public boolean isSingleton() {
return this.singleton;
}
}

View File

@@ -0,0 +1,93 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.context.annotation;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.MultiValueMap;
/**
* Base for {@link OnBeanCondition} and {@link OnMissingBeanCondition}.
*
* @author Phillip Webb
*/
abstract class AbstractOnBeanCondition implements Condition {
protected abstract Class<?> annotationClass();
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes(
annotationClass().getName(), true);
List<String> beanClasses = collect(attributes, "value");
List<String> beanNames = collect(attributes, "value");
Assert.isTrue(beanClasses.size() > 0 || beanNames.size() > 0,
"@" + ClassUtils.getShortName(annotationClass())
+ " annotations must specify at least one bean");
List<String> beanClassesFound = new ArrayList<String>();
List<String> beanNamesFound = new ArrayList<String>();
for (String beanClass : beanClasses) {
try {
// eagerInit set to false to prevent early instantiation (some
// factory beans will not be able to determine their object type at this
// stage, so those are not eligible for matching this condition)
String[] beans = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
context.getBeanFactory(),
ClassUtils.forName(beanClass, context.getClassLoader()), false,
false);
if (beans.length != 0) {
beanClassesFound.add(beanClass);
}
} catch (ClassNotFoundException ex) {
}
}
for (String beanName : beanNames) {
if (context.getBeanFactory().containsBeanDefinition(beanName)) {
beanNamesFound.add(beanName);
}
}
return evaluate(beanClassesFound, beanNamesFound);
}
protected boolean evaluate(List<String> beanClassesFound, List<String> beanNamesFound) {
return !beanClassesFound.isEmpty() || !beanNamesFound.isEmpty();
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private List<String> collect(MultiValueMap<String, Object> attributes, String key) {
List<String> collected = new ArrayList<String>();
List<String[]> valueList = (List) attributes.get(key);
for (String[] valueArray : valueList) {
for (String value : valueArray) {
collected.add(value);
}
}
return collected;
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.context.annotation;
import java.util.Collections;
import java.util.List;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
/**
* Convenience class for storing base packages during component scan, for reference later
* (e.g. by JPA entity scanner).
*
* @author Phil Webb
* @author Dave Syer
*
*/
public abstract class AutoConfigurationUtils {
private static String BASE_PACKAGES_BEAN = AutoConfigurationUtils.class.getName()
+ ".basePackages";
@SuppressWarnings("unchecked")
public static List<String> getBasePackages(BeanFactory beanFactory) {
try {
return beanFactory.getBean(BASE_PACKAGES_BEAN, List.class);
} catch (NoSuchBeanDefinitionException e) {
return Collections.emptyList();
}
}
public static void storeBasePackages(ConfigurableListableBeanFactory beanFactory,
List<String> basePackages) {
beanFactory.registerSingleton(BASE_PACKAGES_BEAN,
Collections.unmodifiableList(basePackages));
}
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.context.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Conditional;
/**
* {@link Conditional} that only matches when the specified bean classes and/or names are
* already contained in the {@link BeanFactory}.
*
* @author Phillip Webb
*/
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnBean {
/**
* The class type of bean that should be checked. The condition matches when any of
* the classes specified is contained in the {@link ApplicationContext}.
* @return the class types of beans to check
*/
Class<?>[] value() default {};
/**
* The names of beans to check. The condition matches when any of the bean names
* specified is contained in the {@link ApplicationContext}.
* @return the name of beans to check
*/
String[] name() default {};
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.context.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Conditional;
/**
* {@link Conditional} that only matches when the specified classes are on the classpath.
*
* @author Phillip Webb
*/
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {
/**
* The classes that must be present. Since this annotation parsed by loading class
* bytecode it is safe to specify classes here that may ultimately not be on the
* classpath.
* @return the classes that must be present
*/
public Class<?>[] value() default {};
/**
* The classes names that must be present. When possible {@link #value()} should be
* used in preference to this property.
* @return the class names that must be present.
*/
public String[] name() default {};
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.context.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Conditional;
/**
* Configuration annotation for a conditional element that depends on teh value of a SpEL
* expression.
*
* @author Dave Syer
*
*/
@Conditional(ExpressionCondition.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
public @interface ConditionalOnExpression {
String value() default "true";
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.context.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Conditional;
/**
* {@link Conditional} that only matches when the specified bean classes and/or names are
* not already contained in the {@link BeanFactory}.
*
* @author Phillip Webb
*/
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnMissingBeanCondition.class)
public @interface ConditionalOnMissingBean {
/**
* The class type of bean that should be checked. The condition matches when each
* class specified is missing in the {@link ApplicationContext}.
* @return the class types of beans to check
*/
Class<?>[] value() default {};
/**
* The names of beans to check. The condition matches when each bean name specified is
* missing in the {@link ApplicationContext}.
* @return the name of beans to check
*/
String[] name() default {};
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.context.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.bootstrap.bind.PropertySourcesBindingPostProcessor;
/**
* Annotation for externalized configuration. Add this to a class definition if you want
* to bind and validate some external Properties (e.g. from a .properties file).
*
* @see PropertySourcesBindingPostProcessor
*
* @author Dave Syer
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ConfigurationProperties {
/**
* The (optional) name of the object to be bound. Properties to bind can have a name
* prefix to select the properties that are valid to this object. Synonym for
* {@link #name()}.
*
* @return the name prefix of the properties to bind
*/
String value() default "";
/**
* The (optional) name of the object to be bound. Properties to bind can have a name
* prefix to select the properties that are valid to this object. Synonym for
* {@link #value()}.
*
* @return the name prefix of the properties to bind
*/
String name() default "";
/**
* Flag to indicate that when binding to this object invalid fields should be ignored.
* Invalid means invalid according to the binder that is used, and usually this means
* fields of the wrong type (or that cannot be coerced into the correct type).
*
* @return the flag value (default false)
*/
boolean ignoreInvalidFields() default false;
/**
* Flag to indicate that when binding to this object unknown fields should be ignored.
* An unknown field could be a sign of a mistake in the Properties.
*
* @return the flag value (default true)
*/
boolean ignoreUnknownFields() default true;
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.context.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.bootstrap.context.embedded.EmbeddedServletContainerFactory;
import org.springframework.bootstrap.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.io.support.SpringFactoriesLoader;
/**
* Enable auto-configuration of the Spring Application Context, attempting to guess and
* configure beans that you are likely to need.
*
* Auto-configuration classes are usually applied based on your classpath and what beans
* you have defined. For example, If you have {@code tomat-embedded.jar} on your classpath
* you are likely to want a {@link TomcatEmbeddedServletContainerFactory} (unless you have
* defined your own {@link EmbeddedServletContainerFactory} bean).
*
* <p>
* Auto-configuration tries to be as intelligent as possible and will back-away as you
* define more of your own configuration. You can always manually {@link #exclude()} any
* configuration that you never want to apply. Auto-configuration is always applied after
* user-defined beans have been registered.
*
* <p>
* Auto-configuration classes are regular Spring {@link Configuration} beans. They are
* located using the {@link SpringFactoriesLoader} mechanism (keyed against this class).
* Generally auto-configuration beans are {@link Conditional @Conditional} beans (most
* often using {@link ConditionalOnClass @ConditionalOnClass} and
* {@link ConditionalOnMissingBean @ConditionalOnMissingBean} annotations).
*
* @author Phillip Webb
* @see ConditionalOnBean
* @see ConditionalOnMissingBean
* @see ConditionalOnClass
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
/**
* Exclude a specific auto-configuration class such that it will never be applied.
*/
Class<?>[] exclude() default {};
}

View File

@@ -0,0 +1,170 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.context.annotation;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.DeferredImportSelector;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.StandardAnnotationMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/**
* {@link DeferredImportSelector} to handle {@link EnableAutoConfiguration
* auto-configuration}.
*
* @author Phillip Webb
* @see EnableAutoConfiguration
*/
@Order(Ordered.LOWEST_PRECEDENCE)
class EnableAutoConfigurationImportSelector implements DeferredImportSelector,
BeanClassLoaderAware, BeanFactoryAware {
private final Log logger = LogFactory.getLog(getClass());
private ClassLoader beanClassLoader;
private BeanFactory beanFactory;
private MetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory();
@Override
public String[] selectImports(AnnotationMetadata metadata) {
storeComponentScanBasePackages();
AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata
.getAnnotationAttributes(EnableAutoConfiguration.class.getName(), true));
List<String> factories = new ArrayList<String>(
SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class,
this.beanClassLoader));
factories.removeAll(Arrays.asList(attributes.getStringArray("exclude")));
return factories.toArray(new String[factories.size()]);
}
private void storeComponentScanBasePackages() {
if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
storeComponentScanBasePackages((ConfigurableListableBeanFactory) this.beanFactory);
} else {
if (this.logger.isWarnEnabled()) {
this.logger
.warn("Unable to read @ComponentScan annotations for auto-configure");
}
}
}
private void storeComponentScanBasePackages(
ConfigurableListableBeanFactory beanFactory) {
List<String> basePackages = new ArrayList<String>();
for (String beanName : beanFactory.getBeanDefinitionNames()) {
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
String[] basePackagesAttribute = (String[]) beanDefinition
.getAttribute("componentScanBasePackages");
if (basePackagesAttribute != null) {
basePackages.addAll(Arrays.asList(basePackagesAttribute));
}
AnnotationMetadata metadata = getMetadata(beanDefinition);
basePackages.addAll(getBasePackages(metadata));
}
AutoConfigurationUtils.storeBasePackages(beanFactory, basePackages);
}
private AnnotationMetadata getMetadata(BeanDefinition beanDefinition) {
if (beanDefinition instanceof AbstractBeanDefinition
&& ((AbstractBeanDefinition) beanDefinition).hasBeanClass()) {
Class<?> beanClass = ((AbstractBeanDefinition) beanDefinition).getBeanClass();
if (Enhancer.isEnhanced(beanClass)) {
beanClass = beanClass.getSuperclass();
}
return new StandardAnnotationMetadata(beanClass, true);
}
String className = beanDefinition.getBeanClassName();
if (className != null) {
try {
MetadataReader metadataReader = this.metadataReaderFactory
.getMetadataReader(className);
return metadataReader.getAnnotationMetadata();
} catch (IOException ex) {
if (this.logger.isDebugEnabled()) {
this.logger.debug(
"Could not find class file for introspecting @ComponentScan classes: "
+ className, ex);
}
}
}
return null;
}
private List<String> getBasePackages(AnnotationMetadata metadata) {
AnnotationAttributes attributes = AnnotationAttributes
.fromMap((metadata == null ? null : metadata.getAnnotationAttributes(
ComponentScan.class.getName(), true)));
if (attributes != null) {
List<String> basePackages = new ArrayList<String>();
addAllHavingText(basePackages, attributes.getStringArray("value"));
addAllHavingText(basePackages, attributes.getStringArray("basePackages"));
for (String packageClass : attributes.getStringArray("basePackageClasses")) {
basePackages.add(ClassUtils.getPackageName(packageClass));
}
if (basePackages.isEmpty()) {
basePackages.add(ClassUtils.getPackageName(metadata.getClassName()));
}
return basePackages;
}
return Collections.emptyList();
}
private void addAllHavingText(List<String> list, String[] strings) {
for (String s : strings) {
if (StringUtils.hasText(s)) {
list.add(s);
}
}
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.beanClassLoader = classLoader;
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.context.annotation;
import org.springframework.beans.factory.config.BeanExpressionContext;
import org.springframework.beans.factory.config.BeanExpressionResolver;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
/**
* A Condition that evaluates a SpEL expression.
*
* @author Dave Syer
*
*/
public class ExpressionCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String value = (String) metadata.getAnnotationAttributes(
ConditionalOnExpression.class.getName()).get("value");
if (!value.startsWith("#{")) {
// For convenience allow user to provide bare expression with no #{} wrapper
value = "#{" + value + "}";
}
// Explicitly allow environment placeholders inside the expression
value = context.getEnvironment().resolvePlaceholders(value);
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
BeanExpressionResolver resolver = beanFactory.getBeanExpressionResolver();
BeanExpressionContext expressionContext = (beanFactory != null) ? new BeanExpressionContext(
beanFactory, null) : null;
return (Boolean) resolver.evaluate(value, expressionContext);
}
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.context.annotation;
import org.springframework.context.annotation.Condition;
/**
* {@link Condition} that checks that specific beans are present.
*
* @author Phillip Webb
* @see ConditionalOnBean
*/
class OnBeanCondition extends AbstractOnBeanCondition {
@Override
protected Class<?> annotationClass() {
return ConditionalOnBean.class;
}
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.context.annotation;
import java.util.ArrayList;
import java.util.List;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.MultiValueMap;
/**
* {@link Condition} that checks for the specific classes.
*
* @author Phillip Webb
* @see ConditionalOnClass
*/
class OnClassCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes(
ConditionalOnClass.class.getName(), true);
if (attributes != null) {
List<String> classNames = new ArrayList<String>();
collectClassNames(classNames, attributes.get("value"));
collectClassNames(classNames, attributes.get("name"));
Assert.isTrue(classNames.size() > 0,
"@ConditionalOnClass annotations must specify at least one class value");
for (String className : classNames) {
if (!ClassUtils.isPresent(className, context.getClassLoader())) {
return false;
}
}
}
return true;
}
private void collectClassNames(List<String> classNames, List<Object> values) {
for (Object value : values) {
for (Object valueItem : (Object[]) value) {
classNames.add(valueItem instanceof Class<?> ? ((Class<?>) valueItem)
.getName() : valueItem.toString());
}
}
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.context.annotation;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
/**
* {@link Condition} that checks that specific beans are missing.
*
* @author Phillip Webb
* @see ConditionalOnMissingBean
*/
class OnMissingBeanCondition extends AbstractOnBeanCondition {
@Override
protected Class<?> annotationClass() {
return ConditionalOnMissingBean.class;
}
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return !super.matches(context, metadata);
}
}

View File

@@ -0,0 +1,305 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.context.embedded;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.Assert;
import org.springframework.web.ServletContextInitializer;
/**
* Abstract base class for {@link EmbeddedServletContainerFactory} implementations.
*
* @author Phillip Webb
* @author Dave Syer
* @since 4.0
*/
public abstract class AbstractEmbeddedServletContainerFactory implements
EmbeddedServletContainerFactory {
private final Log logger = LogFactory.getLog(getClass());
private String contextPath = "";
private boolean registerDefaultServlet = true;
private boolean registerJspServlet = true;
private String jspServletClassName = "org.apache.jasper.servlet.JspServlet";
private int port = 8080;
private List<ServletContextInitializer> initializers = new ArrayList<ServletContextInitializer>();
private File documentRoot;
private Set<ErrorPage> errorPages = new LinkedHashSet<ErrorPage>();
/**
* Create a new {@link AbstractEmbeddedServletContainerFactory} instance.
*/
public AbstractEmbeddedServletContainerFactory() {
}
/**
* Create a new {@link AbstractEmbeddedServletContainerFactory} instance with the
* specified port.
* @param port the port number for the embedded servlet container
*/
public AbstractEmbeddedServletContainerFactory(int port) {
setPort(port);
}
/**
* Create a new {@link AbstractEmbeddedServletContainerFactory} instance with the
* specified context path and port.
* @param contextPath the context path for the embedded servlet container
* @param port the port number for the embedded servlet container
*/
public AbstractEmbeddedServletContainerFactory(String contextPath, int port) {
setContextPath(contextPath);
setPort(port);
}
/**
* Sets the context path for the embedded servlet container. The context should start
* with a "/" character but not end with a "/" character. The default context path can
* be specified using an empty string.
* @param contextPath the contextPath to set
* @see #getContextPath
*/
public void setContextPath(String contextPath) {
Assert.notNull(contextPath, "ContextPath must not be null");
if (contextPath.length() > 0) {
if ("/".equals(contextPath)) {
throw new IllegalArgumentException(
"Root ContextPath must be specified using an empty string");
}
if (!contextPath.startsWith("/") || contextPath.endsWith("/")) {
throw new IllegalArgumentException(
"ContextPath must start with '/ and not end with '/'");
}
}
this.contextPath = contextPath;
}
/**
* Returns the context path for the embedded servlet container. The path will start
* with "/" and not end with "/". The root context is represented by an empty string.
*/
public String getContextPath() {
return this.contextPath;
}
/**
* Sets the port that the embedded servlet container should listen on. If not
* specified port '8080' will be used.
* @param port the port to set
*/
public void setPort(int port) {
if (port < 1 || port > 65535) {
throw new IllegalArgumentException("Port must be between 1 and 65535");
}
this.port = port;
}
/**
* Returns the port that the embedded servlet container should listen on.
*/
public int getPort() {
return this.port;
}
/**
* Sets {@link ServletContextInitializer} that should be applied in addition to
* {@link #getEmbdeddedServletContainer(ServletContextInitializer...)} parameters.
* This method will replace any previously set or added initializers.
* @param initializers the initializers to set
* @see #addInitializers
* @see #getInitializers
*/
public void setInitializers(List<? extends ServletContextInitializer> initializers) {
Assert.notNull(initializers, "Initializers must not be null");
this.initializers = new ArrayList<ServletContextInitializer>(initializers);
}
/**
* Add {@link ServletContextInitializer}s to those that should be applied in addition
* to {@link #getEmbdeddedServletContainer(ServletContextInitializer...)} parameters.
* @param initializers the initializers to add
* @see #setInitializers
* @see #getInitializers
*/
public void addInitializers(ServletContextInitializer... initializers) {
Assert.notNull(initializers, "Initializers must not be null");
this.initializers.addAll(Arrays.asList(initializers));
}
/**
* Returns a mutable list of {@link ServletContextInitializer} that should be applied
* in addition to {@link #getEmbdeddedServletContainer(ServletContextInitializer...)}
* parameters.
* @return the initializers
*/
public List<ServletContextInitializer> getInitializers() {
return this.initializers;
}
/**
* Sets the document root folder which will be used by the web context to serve static
* files.
* @param documentRoot the document root or {@code null} if not required
*/
public void setDocumentRoot(File documentRoot) {
this.documentRoot = documentRoot;
}
/**
* Returns the document root which will be used by the web context to serve static
* files.
*/
public File getDocumentRoot() {
return this.documentRoot;
}
/**
* Sets the error pages that will be used when handling exceptions.
* @param errorPages the error pages
*/
public void setErrorPages(Set<ErrorPage> errorPages) {
Assert.notNull(errorPages, "ErrorPages must not be null");
this.errorPages = new LinkedHashSet<ErrorPage>(errorPages);
}
/**
* Adds error pages that will be used when handling exceptions.
* @param errorPages the error pages
*/
public void addErrorPages(ErrorPage... errorPages) {
Assert.notNull(this.initializers, "ErrorPages must not be null");
this.errorPages.addAll(Arrays.asList(errorPages));
}
/**
* Returns a mutable set of {@link ErrorPage}s that will be used when handling
* exceptions.
*/
public Set<ErrorPage> getErrorPages() {
return this.errorPages;
}
/**
* Set if the DefaultServlet should be registered. Defaults to {@code true} so that
* files from the {@link #setDocumentRoot(File) document root} will be served.
* @param registerDefaultServlet if the default servlet should be registered
*/
public void setRegisterDefaultServlet(boolean registerDefaultServlet) {
this.registerDefaultServlet = registerDefaultServlet;
}
/**
* Flag to indicate that the JSP servlet should be registered if available on the
* classpath.
*
* @return true if the JSP servlet is to be registered
*/
protected boolean getRegisterJspServlet() {
return this.registerJspServlet;
}
/**
* Set if the JspServlet should be registered if it is on the classpath. Defaults to
* {@code true} so that files from the {@link #setDocumentRoot(File) document root}
* will be served.
* @param registerJspServlet if the JSP servlet should be registered
*/
public void setRegisterJspServlet(boolean registerJspServlet) {
this.registerJspServlet = registerJspServlet;
}
/**
* Flag to indicate that the default servlet should be registered.
*
* @return true if the default servlet is to be registered
*/
protected boolean getRegisterDefaultServlet() {
return this.registerDefaultServlet;
}
/**
* The class name for the jsp servlet if used. If
* {@link #setRegisterJspServlet(boolean) <code>registerJspServlet</code>} is true
* <b>and</b> this class is on the classpath then it will be registered. Since both
* Tomcat and Jetty use Jasper for their JSP implementation the default is
* <code>org.apache.jasper.servlet.JspServlet</code>.
*
* @param jspServletClassName the class name for the JSP servlet if used
*/
public void setJspServletClassName(String jspServletClassName) {
this.jspServletClassName = jspServletClassName;
}
/**
* @return the JS{ servlet class name
*/
protected String getJspServletClassName() {
return this.jspServletClassName;
}
/**
* Utility method that can be used by subclasses wishing to combine the specified
* {@link ServletContextInitializer} parameters with those defined in this instance.
* @param initializers the initializers to merge
* @return a complete set of merged initializers (with the specified parameters
* appearing first)
*/
protected final ServletContextInitializer[] mergeInitializers(
ServletContextInitializer... initializers) {
List<ServletContextInitializer> mergedInitializers = new ArrayList<ServletContextInitializer>();
mergedInitializers.addAll(Arrays.asList(initializers));
mergedInitializers.addAll(getInitializers());
return mergedInitializers
.toArray(new ServletContextInitializer[mergedInitializers.size()]);
}
/**
* Returns the absolute document root when it points to a valid folder, logging a
* warning and returning {@code null} otherwise.
*/
protected final File getValidDocumentRoot() {
File[] roots = new File[] { getDocumentRoot(), new File("src/main/webapp"),
new File("public") };
for (File root : roots) {
if (root != null && root.exists() && root.isDirectory()) {
return root.getAbsoluteFile();
}
}
if (this.logger.isWarnEnabled()) {
this.logger.warn("None of the document roots " + roots
+ " point to a directory and will be ignored.");
}
return null;
}
}

View File

@@ -0,0 +1,175 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.context.embedded;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.context.annotation.AnnotatedBeanDefinitionReader;
import org.springframework.context.annotation.AnnotationConfigUtils;
import org.springframework.context.annotation.AnnotationScopeMetadataResolver;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ScopeMetadataResolver;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.util.Assert;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
/**
* {@link EmbeddedWebApplicationContext} that accepts annotated classes as input - in
* particular {@link org.springframework.context.annotation.Configuration
* <code>@Configuration</code>} -annotated classes, but also plain
* {@link org.springframework.stereotype.Component <code>@Component</code>} classes and
* JSR-330 compliant classes using {@code javax.inject} annotations. Allows for
* registering classes one by one (specifying class names as config location) as well as
* for classpath scanning (specifying base packages as config location).
*
* <p>
* Note: In case of multiple {@code @Configuration} classes, later {@code @Bean}
* definitions will override ones defined in earlier loaded files. This can be leveraged
* to deliberately override certain bean definitions via an extra Configuration class.
*
* @author Phillip Webb
* @since 4.0
* @see #register(Class...)
* @see #scan(String...)
* @see EmbeddedWebApplicationContext
* @see AnnotationConfigWebApplicationContext
*/
public class AnnotationConfigEmbeddedWebApplicationContext extends
EmbeddedWebApplicationContext {
private final AnnotatedBeanDefinitionReader reader;
private final ClassPathBeanDefinitionScanner scanner;
/**
* Create a new {@link AnnotationConfigEmbeddedWebApplicationContext} that needs to be
* populated through {@link #register} calls and then manually {@linkplain #refresh
* refreshed}.
*/
public AnnotationConfigEmbeddedWebApplicationContext() {
this.reader = new AnnotatedBeanDefinitionReader(this);
this.scanner = new ClassPathBeanDefinitionScanner(this);
}
/**
* Create a new {@link AnnotationConfigEmbeddedWebApplicationContext}, deriving bean
* definitions from the given annotated classes and automatically refreshing the
* context.
* @param annotatedClasses one or more annotated classes, e.g. {@link Configuration
* <code>@Configuration</code>} classes
*/
public AnnotationConfigEmbeddedWebApplicationContext(Class<?>... annotatedClasses) {
this();
register(annotatedClasses);
refresh();
}
/**
* Create a new {@link AnnotationConfigEmbeddedWebApplicationContext}, scanning for
* bean definitions in the given packages and automatically refreshing the context.
* @param basePackages the packages to check for annotated classes
*/
public AnnotationConfigEmbeddedWebApplicationContext(String... basePackages) {
this();
scan(basePackages);
refresh();
}
/**
* {@inheritDoc}
* <p>
* Delegates given environment to underlying {@link AnnotatedBeanDefinitionReader} and
* {@link ClassPathBeanDefinitionScanner} members.
*/
@Override
public void setEnvironment(ConfigurableEnvironment environment) {
super.setEnvironment(environment);
this.reader.setEnvironment(environment);
this.scanner.setEnvironment(environment);
}
/**
* Provide a custom {@link BeanNameGenerator} for use with
* {@link AnnotatedBeanDefinitionReader} and/or {@link ClassPathBeanDefinitionScanner}
* , if any.
* <p>
* Default is
* {@link org.springframework.context.annotation.AnnotationBeanNameGenerator}.
* <p>
* Any call to this method must occur prior to calls to {@link #register(Class...)}
* and/or {@link #scan(String...)}.
* @see AnnotatedBeanDefinitionReader#setBeanNameGenerator
* @see ClassPathBeanDefinitionScanner#setBeanNameGenerator
*/
public void setBeanNameGenerator(BeanNameGenerator beanNameGenerator) {
this.reader.setBeanNameGenerator(beanNameGenerator);
this.scanner.setBeanNameGenerator(beanNameGenerator);
this.getBeanFactory().registerSingleton(
AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR,
beanNameGenerator);
}
/**
* Set the {@link ScopeMetadataResolver} to use for detected bean classes.
* <p>
* The default is an {@link AnnotationScopeMetadataResolver}.
* <p>
* Any call to this method must occur prior to calls to {@link #register(Class...)}
* and/or {@link #scan(String...)}.
*/
public void setScopeMetadataResolver(ScopeMetadataResolver scopeMetadataResolver) {
this.reader.setScopeMetadataResolver(scopeMetadataResolver);
this.scanner.setScopeMetadataResolver(scopeMetadataResolver);
}
/**
* Register one or more annotated classes to be processed. Note that
* {@link #refresh()} must be called in order for the context to fully process the new
* class.
* <p>
* Calls to {@link #register} are idempotent; adding the same annotated class more
* than once has no additional effect.
* @param annotatedClasses one or more annotated classes, e.g. {@link Configuration
* <code>@Configuration</code>} classes
* @see #scan(String...)
* @see #refresh()
*/
public void register(Class<?>... annotatedClasses) {
Assert.notEmpty(annotatedClasses,
"At least one annotated class must be specified");
this.reader.register(annotatedClasses);
}
/**
* Perform a scan within the specified base packages. Note that {@link #refresh()}
* must be called in order for the context to fully process the new class.
* @param basePackages the packages to check for annotated classes
* @see #register(Class...)
* @see #refresh()
*/
public void scan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
this.scanner.scan(basePackages);
}
@Override
protected void prepareRefresh() {
this.scanner.clearCache();
super.prepareRefresh();
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.context.embedded;
/**
* Simple interface that represents a fully configure embedded servlet container (for
* example Tomcat or Jetty). Allows the container to be {@link #stop() stopped}.
*
* <p>
* Instances of this class are usually obtained via a
* {@link EmbeddedServletContainerFactory}.
*
* @author Phillip Webb
* @since 4.0
* @see EmbeddedServletContainerFactory
*/
public interface EmbeddedServletContainer {
/**
* Stops the embedded servlet container. Calling this method on an already stopped
* container has no effect.
* @throws EmbeddedServletContainerException of the container cannot be stopped
*/
void stop() throws EmbeddedServletContainerException;
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.context.embedded;
/**
* Exceptions thrown by an embedded servlet container.
*
* @author Phillip Webb
*/
@SuppressWarnings("serial")
public class EmbeddedServletContainerException extends RuntimeException {
public EmbeddedServletContainerException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.context.embedded;
import org.springframework.bootstrap.context.embedded.jetty.JettyEmbeddedServletContainerFactory;
import org.springframework.bootstrap.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
import org.springframework.web.ServletContextInitializer;
/**
* Factory interface that can be used to create {@link EmbeddedServletContainer}s.
* Implementations are encouraged to extends
* {@link AbstractEmbeddedServletContainerFactory} when possible.
*
* @author Phillip Webb
* @since 4.0
* @see EmbeddedServletContainer
* @see AbstractEmbeddedServletContainerFactory
* @see JettyEmbeddedServletContainerFactory
* @see TomcatEmbeddedServletContainerFactory
*/
public interface EmbeddedServletContainerFactory {
/**
* Gets a new fully configured and started {@link EmbeddedServletContainer}
* instance,blocking until the point that clients can connect.
* @param initializers {@link ServletContextInitializer}s that should be applied as
* the container starts
* @return a fully configured and started {@link EmbeddedServletContainer}
* @see EmbeddedServletContainer#stop()
*/
EmbeddedServletContainer getEmbdeddedServletContainer(
ServletContextInitializer... initializers);
}

View File

@@ -0,0 +1,320 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.context.embedded;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import javax.servlet.Filter;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextException;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.io.Resource;
import org.springframework.web.ServletContextInitializer;
import org.springframework.web.context.ContextLoader;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.ServletContextAware;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.GenericWebApplicationContext;
import org.springframework.web.context.support.ServletContextAwareProcessor;
import org.springframework.web.context.support.ServletContextResource;
import org.springframework.web.context.support.WebApplicationContextServletContextAwareProcessor;
import org.springframework.web.context.support.WebApplicationContextUtils;
/**
* A {@link WebApplicationContext} that can be used to bootstrap itself from a contained
* {@link EmbeddedServletContainerFactory} bean.
*
* <p>
* This context will create, initialize and run an {@link EmbeddedServletContainer} by
* searching for a single {@link EmbeddedServletContainerFactory} bean within the
* {@link ApplicationContext} itself. The {@link EmbeddedServletContainerFactory} is free
* to use standard Spring concepts (such as dependency injection, lifecycle callbacks and
* property placeholder variables).
*
* <p>
* In addition, any {@link Servlet} or {@link Filter} beans defined in the context will be
* automatically registered with the embedded Servlet container. In the case of a single
* Servlet bean, the '/*' mapping will be used. If multiple Servlet beans are found then
* the lowercase bean name will be used as a mapping prefix. Filter beans will be mapped
* to all URLs ('/*').
*
* <p>
* For more advanced configuration, the context can instead define beans that implement
* the {@link ServletContextInitializer} interface (most often
* {@link ServletRegistrationBean}s and/or {@link FilterRegistrationBean}s). To prevent
* double registration, the use of {@link ServletContextInitializer} beans will disable
* automatic Servlet and Filter bean registration.
*
* <p>
* Although this context can be used directly, most developers should consider using the
* {@link AnnotationConfigEmbeddedWebApplicationContext} or
* {@link XmlEmbeddedWebApplicationContext} variants.
*
* @author Phillip Webb
* @since 4.0
* @see AnnotationConfigEmbeddedWebApplicationContext
* @see XmlEmbeddedWebApplicationContext
* @see EmbeddedServletContainerFactory
*/
public class EmbeddedWebApplicationContext extends GenericWebApplicationContext {
private EmbeddedServletContainer embeddedServletContainer;
private ServletConfig servletConfig;
private String namespace;
/**
* Register ServletContextAwareProcessor.
* @see ServletContextAwareProcessor
*/
@Override
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
beanFactory
.addBeanPostProcessor(new WebApplicationContextServletContextAwareProcessor(
this));
beanFactory.ignoreDependencyInterface(ServletContextAware.class);
}
@Override
protected void onRefresh() {
super.onRefresh();
registerShutdownHook();
createAndStartEmbeddedServletContainer();
}
@Override
protected void doClose() {
super.doClose();
stopAndReleaseEmbeddedServletContainer();
}
private synchronized void createAndStartEmbeddedServletContainer() {
if (this.embeddedServletContainer == null) {
EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
this.embeddedServletContainer = containerFactory
.getEmbdeddedServletContainer(getServletContextInitializers());
WebApplicationContextUtils.registerWebApplicationScopes(getBeanFactory(),
getServletContext());
WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(),
getServletContext());
initPropertySources();
}
}
/**
* Returns the {@link EmbeddedServletContainerFactory} that should be used to create
* the embedded servlet container. By default this method searches for a suitable bean
* in the context itself.
* @return a {@link EmbeddedServletContainerFactory} (never {@code null})
*/
protected EmbeddedServletContainerFactory getEmbeddedServletContainerFactory() {
try {
return getBeanFactory().getBean(EmbeddedServletContainerFactory.class);
} catch (NoUniqueBeanDefinitionException ex) {
throw new ApplicationContextException(
"Unable to start EmbeddedWebApplicationContext due to multiple "
+ "EmbeddedServletContainerFactory beans.", ex);
} catch (NoSuchBeanDefinitionException ex) {
throw new ApplicationContextException(
"Unable to start EmbeddedWebApplicationContext due to missing "
+ "EmbeddedServletContainerFactory bean.", ex);
}
}
/**
* Returns all {@link ServletContextInitializer}s that should be applied.
*/
private ServletContextInitializer[] getServletContextInitializers() {
List<ServletContextInitializer> initializers = new ArrayList<ServletContextInitializer>();
initializers.add(getSelfInitializer());
initializers.addAll(getServletContextInitializerBeans());
return initializers.toArray(new ServletContextInitializer[initializers.size()]);
}
/**
* Returns the {@link ServletContextInitializer} that will be used to complete the
* setup of this {@link WebApplicationContext}.
* @see #prepareEmbeddedWebApplicationContext(ServletContext)
*/
private ServletContextInitializer getSelfInitializer() {
return new ServletContextInitializer() {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
prepareEmbeddedWebApplicationContext(servletContext);
}
};
}
/**
* Returns {@link ServletContextInitializer}s that should be used with the embedded
* Servlet context. By default this method will first attempt to find
* {@link ServletContextInitializer} beans, if none are found it will instead search
* for {@link Servlet} and {@link Filter} beans.
*/
protected Collection<ServletContextInitializer> getServletContextInitializerBeans() {
Set<ServletContextInitializer> initializers = new LinkedHashSet<ServletContextInitializer>();
for (Entry<String, ServletContextInitializer> initializerBean : getOrderedBeansOfType(ServletContextInitializer.class)) {
initializers.add(initializerBean.getValue());
}
if (initializers.isEmpty()) {
SortedSet<Entry<String, Servlet>> servletBeans = getOrderedBeansOfType(Servlet.class);
for (Entry<String, Servlet> servletBean : servletBeans) {
String url = (servletBeans.size() == 1 ? "/" : "/"
+ servletBean.getKey().toLowerCase() + "/*");
ServletRegistrationBean registration = new ServletRegistrationBean(
servletBean.getValue(), url);
registration.setName(servletBean.getKey());
initializers.add(registration);
}
for (Entry<String, Filter> filterBean : getOrderedBeansOfType(Filter.class)) {
FilterRegistrationBean registration = new FilterRegistrationBean(
filterBean.getValue());
registration.setName(filterBean.getKey());
initializers.add(registration);
}
}
return initializers;
}
/**
* Prepare the {@link WebApplicationContext} with the given fully loaded
* {@link ServletContext}. This method is usually called from
* {@link ServletContextInitializer#onStartup(ServletContext)} and is similar to the
* functionality usually provided by a {@link ContextLoaderListener}.
* @param servletContext the operational servlet context
*/
protected void prepareEmbeddedWebApplicationContext(ServletContext servletContext) {
if (servletContext
.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
throw new IllegalStateException(
"Cannot initialize context because there is already a root application context present - "
+ "check whether you have multiple ServletContextInitializer!");
}
Log logger = LogFactory.getLog(ContextLoader.class);
servletContext.log("Initializing Spring embedded WebApplicationContext");
try {
servletContext.setAttribute(
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this);
if (logger.isDebugEnabled()) {
logger.debug("Published root WebApplicationContext as ServletContext attribute with name ["
+ WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
+ "]");
}
setServletContext(servletContext);
if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - getStartupDate();
logger.info("Root WebApplicationContext: initialization completed in "
+ elapsedTime + " ms");
}
} catch (RuntimeException ex) {
logger.error("Context initialization failed", ex);
servletContext.setAttribute(
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw ex;
} catch (Error err) {
logger.error("Context initialization failed", err);
servletContext.setAttribute(
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
throw err;
}
}
private <T> SortedSet<Entry<String, T>> getOrderedBeansOfType(Class<T> type) {
SortedSet<Entry<String, T>> beans = new TreeSet<Entry<String, T>>(
new Comparator<Entry<String, T>>() {
@Override
public int compare(Entry<String, T> o1, Entry<String, T> o2) {
return AnnotationAwareOrderComparator.INSTANCE.compare(
o1.getValue(), o2.getValue());
}
});
beans.addAll(getBeanFactory().getBeansOfType(type, true, true).entrySet());
return beans;
}
private synchronized void stopAndReleaseEmbeddedServletContainer() {
if (this.embeddedServletContainer != null) {
try {
this.embeddedServletContainer.stop();
this.embeddedServletContainer = null;
} catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
}
@Override
protected Resource getResourceByPath(String path) {
if (getServletContext() == null) {
return new ClassPathContextResource(path, getClassLoader());
}
return new ServletContextResource(getServletContext(), path);
}
@Override
public void setNamespace(String namespace) {
this.namespace = namespace;
}
@Override
public String getNamespace() {
return this.namespace;
}
@Override
public void setServletConfig(ServletConfig servletConfig) {
this.servletConfig = servletConfig;
}
@Override
public ServletConfig getServletConfig() {
return this.servletConfig;
}
/**
* Returns the {@link EmbeddedServletContainer} that was created by the context or
* {@code null} if the container has not yet been created.
*/
public EmbeddedServletContainer getEmbeddedServletContainer() {
return this.embeddedServletContainer;
}
}

View File

@@ -0,0 +1,126 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.context.embedded;
import org.springframework.util.ObjectUtils;
/**
* Simple container-independent abstraction for servlet error pages. Roughly equivalent to
* the {@literal &lt;error-page&gt;} element traditionally found in web.xml.
*
* @author Dave Syer
* @since 4.0
*/
public class ErrorPage {
private String path;
private Class<? extends Throwable> exception = null;
private int status = 0;
public ErrorPage(String path) {
super();
this.path = path;
}
public ErrorPage(int status, String path) {
super();
this.status = status;
this.path = path;
}
public ErrorPage(Class<? extends Throwable> exception, String path) {
super();
this.exception = exception;
this.path = path;
}
/**
* The path to render (usually implemented as a forward), starting with "/". A custom
* controller or servlet path can be used, or if the container supports it, a template
* path (e.g. "/error.jsp").
*
* @return the path that will be rendered for this error
*/
public String getPath() {
return this.path;
}
/**
* @return the exception type (or null for a page that matches by status)
*/
public Class<? extends Throwable> getException() {
return this.exception;
}
/**
* The HTTP status value that this error page matches.
*
* @return the status value (or 0 for a page that matches any status)
*/
public int getStatus() {
return this.status;
}
/**
* The exception type name.
*
* @return the exception type name (or null if there is none)
*/
public String getExceptionName() {
return this.exception == null ? null : this.exception.getName();
}
/**
* @return is this error page a global one (matches all unmatched status and exception
* types)?
*/
public boolean isGlobal() {
return this.status == 0 && this.exception == null;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ObjectUtils.nullSafeHashCode(getExceptionName());
result = prime * result + ObjectUtils.nullSafeHashCode(this.path);
result = prime * result + this.status;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
ErrorPage other = (ErrorPage) obj;
boolean rtn = true;
rtn &= ObjectUtils.nullSafeEquals(getExceptionName(), other.getExceptionName());
rtn &= ObjectUtils.nullSafeEquals(this.path, other.path);
rtn &= this.status == other.status;
return rtn;
}
}

View File

@@ -0,0 +1,255 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.context.embedded;
import java.util.Arrays;
import java.util.Collection;
import java.util.EnumSet;
import java.util.LinkedHashSet;
import java.util.Set;
import javax.servlet.DispatcherType;
import javax.servlet.Filter;
import javax.servlet.FilterRegistration;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import org.springframework.util.Assert;
import org.springframework.web.ServletContextInitializer;
/**
* A {@link ServletContextInitializer} to register {@link Filter}s in a Servlet 3.0+
* container. Similar to the {@link ServletContext#addFilter(String, Filter) registration}
* features provided by {@link ServletContext} but with a Spring Bean friendly design.
*
* <p>
* The {@link #setFilter(Filter) Filter} must be specified before calling
* {@link #onStartup(ServletContext)}. Registrations can be associated with
* {@link #setUrlPatterns URL patterns} and/or servlets (either by
* {@link #setServletNames name} or via a {@link #setServletRegistrationBeans
* ServletRegistrationBean}s. When no URL pattern or servlets are specified the filter
* will be associated to '/*'. The filter name will be deduced if not specified.
*
* @author Phillip Webb
* @since 4.0
* @see ServletContextInitializer
* @see ServletContext#addFilter(String, Filter)
*/
public class FilterRegistrationBean extends RegistrationBean {
static final EnumSet<DispatcherType> ASYNC_DISPATCHER_TYPES = EnumSet.of(
DispatcherType.FORWARD, DispatcherType.INCLUDE, DispatcherType.REQUEST,
DispatcherType.ASYNC);
static final EnumSet<DispatcherType> NON_ASYNC_DISPATCHER_TYPES = EnumSet.of(
DispatcherType.FORWARD, DispatcherType.INCLUDE, DispatcherType.REQUEST);
private static final String[] DEFAULT_URL_MAPPINGS = { "/*" };
private Filter filter;
private Set<ServletRegistrationBean> servletRegistrationBeans = new LinkedHashSet<ServletRegistrationBean>();
private Set<String> servletNames = new LinkedHashSet<String>();
private Set<String> urlPatterns = new LinkedHashSet<String>();
private EnumSet<DispatcherType> dispatcherTypes;
private boolean matchAfter = false;
/**
* Create a new {@link FilterRegistrationBean} instance.
*/
public FilterRegistrationBean() {
}
/**
* Create a new {@link FilterRegistrationBean} instance to be registered with the
* specified {@link ServletRegistrationBean}s.
* @param filter the filter to register
* @param servletRegistrationBeans associate {@link ServletRegistrationBean}s
*/
public FilterRegistrationBean(Filter filter,
ServletRegistrationBean... servletRegistrationBeans) {
setFilter(filter);
addServletRegistrationBeans(servletRegistrationBeans);
}
/**
* Set the filter to be registered.
*/
public void setFilter(Filter filter) {
Assert.notNull(filter, "Filter must not be null");
this.filter = filter;
}
/**
* Set {@link ServletRegistrationBean}s that the filter will be registered against.
* @param servletRegistrationBeans the Servlet registration beans
*/
public void setServletRegistrationBeans(
Collection<? extends ServletRegistrationBean> servletRegistrationBeans) {
Assert.notNull(servletRegistrationBeans,
"ServletRegistrationBeans must not be null");
this.servletRegistrationBeans = new LinkedHashSet<ServletRegistrationBean>(
servletRegistrationBeans);
}
/**
* Return a mutable collection of the {@link ServletRegistrationBean} that the filter
* will be registered against. {@link ServletRegistrationBean}s.
* @return the Servlet registration beans
* @see #setServletNames
* @see #setUrlPatterns
*/
public Collection<ServletRegistrationBean> getServletRegistrationBeans() {
return this.servletRegistrationBeans;
}
/**
* Add {@link ServletRegistrationBean}s for the filter.
* @param servletRegistrationBeans the servlet registration beans to add
* @see #setServletRegistrationBeans
*/
public void addServletRegistrationBeans(
ServletRegistrationBean... servletRegistrationBeans) {
Assert.notNull(servletRegistrationBeans,
"ServletRegistrationBeans must not be null");
for (ServletRegistrationBean servletRegistrationBean : servletRegistrationBeans) {
this.servletRegistrationBeans.add(servletRegistrationBean);
}
}
/**
* Set servlet names that the filter will be registered against. This will replace any
* previously specified servlet names.
* @param servletNames the servlet names
* @see #setServletRegistrationBeans
* @see #setUrlPatterns
*/
public void setServletNames(Collection<String> servletNames) {
Assert.notNull(servletNames, "ServletNames must not be null");
this.servletNames = new LinkedHashSet<String>(servletNames);
}
/**
* Return a mutable collection of servlet names that the filter will be registered
* against.
* @return the servlet names
*/
public Collection<String> getServletNames() {
return this.servletNames;
}
/**
* Add servlet names for the filter.
* @param servletNames the servlet names to add
*/
public void addServletNames(String... servletNames) {
Assert.notNull(servletNames, "ServletNames must not be null");
this.servletNames.addAll(Arrays.asList(servletNames));
}
/**
* Set the URL patterns that the filter will be registered against. This will replace
* any previously specified URL patterns.
* @param urlPatterns the URL patterns
* @see #setServletRegistrationBeans
* @see #setServletNames
*/
public void setUrlPatterns(Collection<String> urlPatterns) {
Assert.notNull(urlPatterns, "UrlPatterns must not be null");
this.urlPatterns = new LinkedHashSet<String>(urlPatterns);
}
/**
* Return a mutable collection of URL patterns that the filter will be registered
* against.
* @return the URL patterns
*/
public Collection<String> getUrlPatterns() {
return this.urlPatterns;
}
/**
* Add URL patterns that the filter will be registered against.
* @param urlPatterns the URL patterns
*/
public void addUrlPatterns(String... urlPatterns) {
Assert.notNull(urlPatterns, "UrlPatterns must not be null");
for (String urlPattern : urlPatterns) {
this.urlPatterns.add(urlPattern);
}
}
/**
* Set if the filter mappings should be matched after any declared filter mappings of
* the ServletContext. Defaults to {@code false} indicating the filters are supposed
* to be matched before any declared filter mappings of the ServletContext.
*/
public void setMatchAfter(boolean matchAfter) {
this.matchAfter = matchAfter;
}
/**
* Return if filter mappings should be matched after any declared Filter mappings of
* the ServletContext.
*/
public boolean isMatchAfter() {
return this.matchAfter;
}
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
Assert.notNull(this.filter, "Filter must not be null");
configure(servletContext.addFilter(getOrDeduceName(this.filter), this.filter));
}
/**
* Configure registration settings. Subclasses can override this method to perform
* additional configuration if required.
*/
protected void configure(FilterRegistration.Dynamic registration) {
super.configure(registration);
EnumSet<DispatcherType> dispatcherTypes = this.dispatcherTypes;
if (dispatcherTypes == null) {
dispatcherTypes = (isAsyncSupported() ? ASYNC_DISPATCHER_TYPES
: NON_ASYNC_DISPATCHER_TYPES);
}
Set<String> servletNames = new LinkedHashSet<String>();
for (ServletRegistrationBean servletRegistrationBean : this.servletRegistrationBeans) {
servletNames.add(servletRegistrationBean.getServletName());
}
servletNames.addAll(this.servletNames);
if (servletNames.isEmpty() && this.urlPatterns.isEmpty()) {
registration.addMappingForUrlPatterns(dispatcherTypes, this.matchAfter,
DEFAULT_URL_MAPPINGS);
} else {
if (servletNames.size() > 0) {
registration.addMappingForServletNames(dispatcherTypes, this.matchAfter,
servletNames.toArray(new String[servletNames.size()]));
}
if (this.urlPatterns.size() > 0) {
registration.addMappingForUrlPatterns(dispatcherTypes, this.matchAfter,
this.urlPatterns.toArray(new String[this.urlPatterns.size()]));
}
}
}
}

View File

@@ -0,0 +1,115 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.context.embedded;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.servlet.Registration;
import org.springframework.core.Conventions;
import org.springframework.util.Assert;
import org.springframework.web.ServletContextInitializer;
/**
* Base class for {@link ServletRegistrationBean servlet} and
* {@link FilterRegistrationBean filter} registration beans.
*
* @author Phillip Webb
* @since 4.0
* @see ServletRegistrationBean
* @see FilterRegistrationBean
*/
public abstract class RegistrationBean implements ServletContextInitializer {
private String name;
private boolean asyncSupported = true;
private Map<String, String> initParameters = new LinkedHashMap<String, String>();
/**
* Set the name of this registration. If not specified the bean name will be used.
*/
public void setName(String name) {
Assert.hasLength(name, "Name must not be empty");
this.name = name;
}
/**
* Sets if asynchronous operations are support for this registration. If not specified
* defaults to {@code true}.
*/
public void setAsyncSupported(boolean asyncSupported) {
this.asyncSupported = asyncSupported;
}
/**
* Returns if asynchronous operations are support for this registration.
*/
public boolean isAsyncSupported() {
return asyncSupported;
}
/**
* Set init-parameters for this registration. Calling this method will replace any
* existing init-parameters.
* @see #getInitParameters
* @see #addInitParameter
*/
public void setInitParameters(Map<String, String> initParameters) {
Assert.notNull(initParameters, "InitParameters must not be null");
this.initParameters = new LinkedHashMap<String, String>(initParameters);
}
/**
* Returns a mutable Map of the registration init-parameters.
*/
public Map<String, String> getInitParameters() {
return this.initParameters;
}
/**
* Add a single init-parameter, replacing any existing parameter with the same name.
* @param name the init-parameter name
* @param value the init-parameter value
*/
public void addInitParameter(String name, String value) {
Assert.notNull(name, "Name must not be null");
this.initParameters.put(name, value);
}
/**
* Deduces the name for this registration. Will return user specified name or fallback
* to convention based naming.
* @param value the object used for convention based names
*/
protected final String getOrDeduceName(Object value) {
return (this.name != null ? this.name : Conventions.getVariableName(value));
}
/**
* Configure registration base settings.
*/
protected void configure(Registration.Dynamic registration) {
registration.setAsyncSupported(this.asyncSupported);
if (this.initParameters.size() > 0) {
registration.setInitParameters(this.initParameters);
}
}
}

View File

@@ -0,0 +1,182 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.context.embedded;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Set;
import javax.servlet.Filter;
import javax.servlet.Servlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;
import org.springframework.util.Assert;
import org.springframework.web.ServletContextInitializer;
/**
* A {@link ServletContextInitializer} to register {@link Servlet}s in a Servlet 3.0+
* container. Similar to the {@link ServletContext#addServlet(String, Servlet)
* registration} features provided by {@link ServletContext} but with a Spring Bean
* friendly design.
*
* <p>
* The {@link #setServlet(Servlet) servlet} must be specified before calling
* {@link #onStartup}. URL mapping can be configured used {@link #setUrlMappings} or
* omitted when mapping to '/*'. The servlet name will be deduced if not specified.
*
* @author Phillip Webb
* @since 4.0
* @see ServletContextInitializer
* @see ServletContext#addServlet(String, Servlet)
*/
public class ServletRegistrationBean extends RegistrationBean {
private static final String[] DEFAULT_MAPPINGS = { "/*" };
private Servlet servlet;
private Set<String> urlMappings = new LinkedHashSet<String>();
private int loadOnStartup = 1;
private Set<Filter> filters = new LinkedHashSet<Filter>();
/**
* Create a new {@link ServletRegistrationBean} instance.
*/
public ServletRegistrationBean() {
}
/**
* Create a new {@link ServletRegistrationBean} instance with the specified
* {@link Servlet} and URL mapping.
* @param servlet the servlet being mapped
* @param urlMappings the URLs being mapped
*/
public ServletRegistrationBean(Servlet servlet, String... urlMappings) {
setServlet(servlet);
addUrlMappings(urlMappings);
}
/**
* Sets the servlet to be registered.
*/
public void setServlet(Servlet servlet) {
Assert.notNull(servlet, "Servlet must not be null");
this.servlet = servlet;
}
/**
* Set the URL mappings for the servlet. If not specified the mapping will default to
* '/'. This will replace any previously specified mappings.
* @param urlMappings the mappings to set
* @see #addUrlMappings(String...)
*/
public void setUrlMappings(Collection<String> urlMappings) {
Assert.notNull(urlMappings, "UrlMappings must not be null");
this.urlMappings = new LinkedHashSet<String>(urlMappings);
}
/**
* Return a mutable collection of the URL mappings for the servlet.
* @return the urlMappings
*/
public Collection<String> getUrlMappings() {
return this.urlMappings;
}
/**
* Add URL mappings for the servlet.
* @param urlMappings the mappings to add
* @see #setUrlMappings(Collection)
*/
public void addUrlMappings(String... urlMappings) {
Assert.notNull(urlMappings, "UrlMappings must not be null");
this.urlMappings.addAll(Arrays.asList(urlMappings));
}
/**
* Sets the <code>loadOnStartup</code> priority. See
* {@link ServletRegistration.Dynamic#setLoadOnStartup} for details.
*/
public void setLoadOnStartup(int loadOnStartup) {
this.loadOnStartup = loadOnStartup;
}
/**
* Sets any Filters that should be registered to this servlet. Any previously
* specified Filters will be replaced.
* @param filters the Filters to set
*/
public void setFilters(Collection<? extends Filter> filters) {
Assert.notNull(filters, "Filters must not be null");
this.filters = new LinkedHashSet<Filter>(filters);
}
/**
* Returns a mutable collection of the Filters being registered with this servlet.
*/
public Collection<Filter> getFilters() {
return this.filters;
}
/**
* Add Filters that will be registered with this servlet.
* @param filters the Filters to add
*/
public void addFilters(Filter... filters) {
Assert.notNull(filters, "Filters must not be null");
this.filters.addAll(Arrays.asList(filters));
}
/**
* Returns the servlet name that will be registered.
*/
public String getServletName() {
return getOrDeduceName(this.servlet);
}
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
Assert.notNull(this.servlet, "Servlet must not be null");
configure(servletContext.addServlet(getServletName(), this.servlet));
for (Filter filter : this.filters) {
FilterRegistrationBean filterRegistration = new FilterRegistrationBean(
filter, this);
filterRegistration.setAsyncSupported(isAsyncSupported());
filterRegistration.onStartup(servletContext);
}
}
/**
* Configure registration settings. Subclasses can override this method to perform
* additional configuration if required.
*/
protected void configure(ServletRegistration.Dynamic registration) {
super.configure(registration);
String[] urlMapping = this.urlMappings
.toArray(new String[this.urlMappings.size()]);
if (urlMapping.length == 0) {
urlMapping = DEFAULT_MAPPINGS;
}
registration.addMapping(urlMapping);
registration.setLoadOnStartup(this.loadOnStartup);
}
}

View File

@@ -0,0 +1,133 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.context.embedded;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.web.context.support.XmlWebApplicationContext;
/**
* {@link EmbeddedWebApplicationContext} which takes its configuration from XML documents,
* understood by an {@link org.springframework.beans.factory.xml.XmlBeanDefinitionReader}.
*
* <p>
* Note: In case of multiple config locations, later bean definitions will override ones
* defined in earlier loaded files. This can be leveraged to deliberately override certain
* bean definitions via an extra XML file.
*
* @author Phillip Webb
* @see #setNamespace
* @see #setConfigLocations
* @see EmbeddedWebApplicationContext
* @see XmlWebApplicationContext
*/
public class XmlEmbeddedWebApplicationContext extends EmbeddedWebApplicationContext {
private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
/**
* Create a new {@link XmlEmbeddedWebApplicationContext} that needs to be
* {@linkplain #load loaded} and then manually {@link #refresh refreshed}.
*/
public XmlEmbeddedWebApplicationContext() {
reader.setEnvironment(this.getEnvironment());
}
/**
* Create a new {@link XmlEmbeddedWebApplicationContext}, loading bean definitions
* from the given resources and automatically refreshing the context.
* @param resources the resources to load from
*/
public XmlEmbeddedWebApplicationContext(Resource... resources) {
load(resources);
refresh();
}
/**
* Create a new {@link XmlEmbeddedWebApplicationContext}, loading bean definitions
* from the given resource locations and automatically refreshing the context.
* @param resourceLocations the resources to load from
*/
public XmlEmbeddedWebApplicationContext(String... resourceLocations) {
load(resourceLocations);
refresh();
}
/**
* Create a new {@link XmlEmbeddedWebApplicationContext}, loading bean definitions
* from the given resource locations and automatically refreshing the context.
* @param relativeClass class whose package will be used as a prefix when loading each
* specified resource name
* @param resourceNames relatively-qualified names of resources to load
*/
public XmlEmbeddedWebApplicationContext(Class<?> relativeClass,
String... resourceNames) {
load(relativeClass, resourceNames);
refresh();
}
/**
* Set whether to use XML validation. Default is {@code true}.
*/
public void setValidating(boolean validating) {
this.reader.setValidating(validating);
}
/**
* {@inheritDoc}
* <p>
* Delegates the given environment to underlying {@link XmlBeanDefinitionReader}.
* Should be called before any call to {@link #load}.
*/
@Override
public void setEnvironment(ConfigurableEnvironment environment) {
super.setEnvironment(environment);
this.reader.setEnvironment(this.getEnvironment());
}
/**
* Load bean definitions from the given XML resources.
* @param resources one or more resources to load from
*/
public void load(Resource... resources) {
this.reader.loadBeanDefinitions(resources);
}
/**
* Load bean definitions from the given XML resources.
* @param resourceLocations one or more resource locations to load from
*/
public void load(String... resourceLocations) {
this.reader.loadBeanDefinitions(resourceLocations);
}
/**
* Load bean definitions from the given XML resources.
* @param relativeClass class whose package will be used as a prefix when loading each
* specified resource name
* @param resourceNames relatively-qualified names of resources to load
*/
public void load(Class<?> relativeClass, String... resourceNames) {
Resource[] resources = new Resource[resourceNames.length];
for (int i = 0; i < resourceNames.length; i++) {
resources[i] = new ClassPathResource(resourceNames[i], relativeClass);
}
this.load(resources);
}
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.context.embedded.jetty;
import org.eclipse.jetty.server.Server;
import org.springframework.bootstrap.context.embedded.EmbeddedServletContainer;
import org.springframework.bootstrap.context.embedded.EmbeddedServletContainerException;
import org.springframework.util.Assert;
/**
* {@link EmbeddedServletContainer} that can be used to control an embedded Jetty server.
* Usually this class should be created using the
* {@link JettyEmbeddedServletContainerFactory} and not directly.
*
* @author Phillip Webb
* @since 4.0
* @see JettyEmbeddedServletContainerFactory
*/
public class JettyEmbeddedServletContainer implements EmbeddedServletContainer {
private final Server server;
/**
* Create a new {@link JettyEmbeddedServletContainer} instance.
* @param server the underlying Jetty server
*/
public JettyEmbeddedServletContainer(Server server) {
Assert.notNull(server, "Jetty Server must not be null");
this.server = server;
start();
}
private synchronized void start() {
try {
this.server.start();
} catch (Exception ex) {
throw new EmbeddedServletContainerException(
"Unable to start embedded Jetty servlet container", ex);
}
}
@Override
public synchronized void stop() {
try {
this.server.stop();
} catch (Exception ex) {
throw new EmbeddedServletContainerException(
"Unable to stop embedded Jetty servlet container", ex);
}
}
/**
* Returns access to the underlying Jetty Server.
*/
public Server getServer() {
return this.server;
}
}

View File

@@ -0,0 +1,273 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.context.embedded.jetty;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ErrorHandler;
import org.eclipse.jetty.servlet.ErrorPageErrorHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.servlet.ServletMapping;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.webapp.AbstractConfiguration;
import org.eclipse.jetty.webapp.Configuration;
import org.eclipse.jetty.webapp.WebAppContext;
import org.springframework.bootstrap.context.embedded.AbstractEmbeddedServletContainerFactory;
import org.springframework.bootstrap.context.embedded.EmbeddedServletContainerFactory;
import org.springframework.bootstrap.context.embedded.ErrorPage;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.ServletContextInitializer;
/**
* {@link EmbeddedServletContainerFactory} that can be used to create
* {@link JettyEmbeddedServletContainer}s. Can be initialized using Spring's
* {@link ServletContextInitializer}s or Jetty {@link Configuration}s.
*
* <p>
* Unless explicitly configured otherwise this factory will created containers that listen
* for HTTP requests on port 8080.
*
* @author Phillip Webb
* @author Dave Syer
* @since 4.0
* @see #setPort(int)
* @see #setConfigurations(Collection)
* @see JettyEmbeddedServletContainer
*/
public class JettyEmbeddedServletContainerFactory extends
AbstractEmbeddedServletContainerFactory implements ResourceLoaderAware {
private List<Configuration> configurations = new ArrayList<Configuration>();
private ResourceLoader resourceLoader;
/**
* Create a new {@link JettyEmbeddedServletContainerFactory} instance.
*/
public JettyEmbeddedServletContainerFactory() {
super();
}
/**
* Create a new {@link JettyEmbeddedServletContainerFactory} that listens for requests
* using the specified port.
* @param port the port to listen on
*/
public JettyEmbeddedServletContainerFactory(int port) {
super(port);
}
/**
* Create a new {@link JettyEmbeddedServletContainerFactory} with the specified
* context path and port.
* @param contextPath root the context path
* @param port the port to listen on
*/
public JettyEmbeddedServletContainerFactory(String contextPath, int port) {
super(contextPath, port);
}
@Override
public JettyEmbeddedServletContainer getEmbdeddedServletContainer(
ServletContextInitializer... initializers) {
Server server = new Server(getPort());
WebAppContext context = new WebAppContext();
if (this.resourceLoader != null) {
context.setClassLoader(this.resourceLoader.getClassLoader());
}
String contextPath = getContextPath();
context.setContextPath(StringUtils.hasLength(contextPath) ? contextPath : "/");
configureDocumentRoot(context);
if (getRegisterDefaultServlet()) {
addDefaultServlet(context);
}
if (getRegisterJspServlet()
&& ClassUtils.isPresent(getJspServletClassName(), getClass()
.getClassLoader())) {
addJspServlet(context);
}
ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
Configuration[] configurations = getWebAppContextConfigurations(context,
initializersToUse);
context.setConfigurations(configurations);
postProcessWebAppContext(context);
server.setHandler(context);
return getJettyEmbeddedServletContainer(server);
}
private void configureDocumentRoot(WebAppContext handler) {
File root = getValidDocumentRoot();
if (root != null) {
try {
handler.setBaseResource(Resource.newResource(root));
} catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
}
private void addDefaultServlet(WebAppContext context) {
ServletHolder holder = new ServletHolder();
holder.setName("default");
holder.setClassName("org.eclipse.jetty.servlet.DefaultServlet");
holder.setInitParameter("dirAllowed", "false");
holder.setInitOrder(1);
context.getServletHandler().addServletWithMapping(holder, "/");
context.getServletHandler().getServletMapping("/").setDefault(true);
}
private void addJspServlet(WebAppContext context) {
ServletHolder holder = new ServletHolder();
holder.setName("jsp");
holder.setClassName(getJspServletClassName());
holder.setInitParameter("fork", "false");
holder.setInitOrder(3);
context.getServletHandler().addServlet(holder);
ServletMapping mapping = new ServletMapping();
mapping.setServletName("jsp");
mapping.setPathSpecs(new String[] { "*.jsp", "*.jspx" });
context.getServletHandler().addServletMapping(mapping);
}
/**
* Return the Jetty {@link Configuration}s that should be applied to the server.
* @param webAppContext the Jetty {@link WebAppContext}
* @param initializers the {@link ServletContextInitializer}s to apply
* @return configurations to apply
*/
protected Configuration[] getWebAppContextConfigurations(WebAppContext webAppContext,
ServletContextInitializer... initializers) {
List<Configuration> configurations = new ArrayList<Configuration>();
configurations.add(getServletContextInitializerConfiguration(webAppContext,
initializers));
configurations.addAll(getConfigurations());
configurations.add(getErrorPageConfiguration());
return configurations.toArray(new Configuration[configurations.size()]);
}
/**
* Create a configuration object that adds error handlers
*
* @return a configuration object for adding error pages
*/
private Configuration getErrorPageConfiguration() {
return new AbstractConfiguration() {
@Override
public void configure(WebAppContext context) throws Exception {
ErrorHandler errorHandler = context.getErrorHandler();
if (errorHandler instanceof ErrorPageErrorHandler) {
ErrorPageErrorHandler handler = (ErrorPageErrorHandler) errorHandler;
for (ErrorPage errorPage : getErrorPages()) {
if (errorPage.isGlobal()) {
handler.addErrorPage(ErrorPageErrorHandler.GLOBAL_ERROR_PAGE,
errorPage.getPath());
} else {
if (errorPage.getExceptionName() != null) {
handler.addErrorPage(errorPage.getExceptionName(),
errorPage.getPath());
} else {
handler.addErrorPage(errorPage.getStatus(),
errorPage.getPath());
}
}
}
}
}
};
}
/**
* Return a Jetty {@link Configuration} that will invoke the specified
* {@link ServletContextInitializer}s. By default this method will return a
* {@link ServletContextInitializerConfiguration}.
* @param webAppContext the Jetty {@link WebAppContext}
* @param initializers the {@link ServletContextInitializer}s to apply
* @return the {@link Configuration} instance
*/
protected Configuration getServletContextInitializerConfiguration(
WebAppContext webAppContext, ServletContextInitializer... initializers) {
return new ServletContextInitializerConfiguration(webAppContext, initializers);
}
/**
* Post process the Jetty {@link WebAppContext} before it used with the Jetty Server.
* Subclasses can override this method to apply additional processing to the
* {@link WebAppContext}.
* @param webAppContext the Jetty {@link WebAppContext}
*/
protected void postProcessWebAppContext(WebAppContext webAppContext) {
}
/**
* Factory method called to create the {@link JettyEmbeddedServletContainer}.
* Subclasses can override this method to return a different
* {@link JettyEmbeddedServletContainer} or apply additional processing to the Jetty
* server.
* @param server the Jetty server.
* @return a new {@link JettyEmbeddedServletContainer} instance
*/
protected JettyEmbeddedServletContainer getJettyEmbeddedServletContainer(Server server) {
return new JettyEmbeddedServletContainer(server);
}
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
/**
* Sets Jetty {@link Configuration}s that will be applied to the {@link WebAppContext}
* before the server is created. Calling this method will replace any existing
* configurations.
* @param configurations the Jetty configurations to apply
*/
public void setConfigurations(Collection<? extends Configuration> configurations) {
Assert.notNull(configurations, "Configurations must not be null");
this.configurations = new ArrayList<Configuration>(configurations);
}
/**
* Returns a mutable collection of Jetty {@link Configuration}s that will be applied
* to the {@link WebAppContext} before the server is created.
* @return the Jetty {@link Configuration}s
*/
public Collection<Configuration> getConfigurations() {
return this.configurations;
}
/**
* Add {@link Configuration}s that will be applied to the {@link WebAppContext} before
* the server is create.
* @param configurations the configurations to add
*/
public void addConfigurations(Configuration... configurations) {
Assert.notNull(configurations, "Configurations must not be null");
this.configurations.addAll(Arrays.asList(configurations));
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.context.embedded.jetty;
import javax.servlet.ServletContext;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.eclipse.jetty.webapp.AbstractConfiguration;
import org.eclipse.jetty.webapp.Configuration;
import org.eclipse.jetty.webapp.WebAppContext;
import org.springframework.util.Assert;
import org.springframework.web.ServletContextInitializer;
/**
* Jetty {@link Configuration} that calls {@link ServletContextInitializer}s.
*
* @author Phillip Webb
* @since 4.0
*/
public class ServletContextInitializerConfiguration extends AbstractConfiguration {
private ContextHandler contextHandler;
private ServletContextInitializer[] initializers;
/**
* Create a new {@link ServletContextInitializerConfiguration}.
* @param contextHandler the Jetty ContextHandler
* @param initializers the initializers that should be invoked
*/
public ServletContextInitializerConfiguration(ContextHandler contextHandler,
ServletContextInitializer... initializers) {
Assert.notNull(contextHandler, "Jetty ContextHandler must not be null");
Assert.notNull(initializers, "Initializers must not be null");
this.contextHandler = contextHandler;
this.initializers = initializers;
}
@Override
public void configure(WebAppContext context) throws Exception {
context.addBean(new InitializerListener(), true);
}
private class InitializerListener extends AbstractLifeCycle {
@Override
protected void doStart() throws Exception {
ServletContext servletContext = contextHandler.getServletContext();
for (ServletContextInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}
}
}

View File

@@ -0,0 +1,21 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Support for Jetty {@link org.springframework.bootstrap.context.embedded.EmbeddedServletContainer EmbeddedServletContainers}.
*/
package org.springframework.bootstrap.context.embedded.jetty;

View File

@@ -0,0 +1,23 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Support for embedded servlet containers.
* @see org.springframework.bootstrap.context.embedded.EmbeddedServletContainerFactory
* @see org.springframework.bootstrap.context.embedded.EmbeddedWebApplicationContext
*/
package org.springframework.bootstrap.context.embedded;

View File

@@ -0,0 +1,63 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.context.embedded.tomcat;
import javax.servlet.ServletException;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleEvent;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.core.StandardContext;
import org.springframework.util.Assert;
import org.springframework.web.ServletContextInitializer;
/**
* Tomcat {@link LifecycleListener} that calls {@link ServletContextInitializer}s.
*
* @author Phillip Webb
* @since 4.0
*/
public class ServletContextInitializerLifecycleListener implements LifecycleListener {
private ServletContextInitializer[] initializers;
/**
* Create a new {@link ServletContextInitializerLifecycleListener} instance with the
* specified initializers.
* @param initializers the initializers to call
*/
public ServletContextInitializerLifecycleListener(
ServletContextInitializer... initializers) {
this.initializers = initializers;
}
@Override
public void lifecycleEvent(LifecycleEvent event) {
if (Lifecycle.CONFIGURE_START_EVENT.equals(event.getType())) {
Assert.isInstanceOf(StandardContext.class, event.getSource());
StandardContext standardContext = (StandardContext) event.getSource();
for (ServletContextInitializer initializer : this.initializers) {
try {
initializer.onStartup(standardContext.getServletContext());
} catch (ServletException ex) {
throw new IllegalStateException(ex);
}
}
}
}
}

View File

@@ -0,0 +1,88 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.context.embedded.tomcat;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.startup.Tomcat;
import org.springframework.bootstrap.context.embedded.EmbeddedServletContainer;
import org.springframework.bootstrap.context.embedded.EmbeddedServletContainerException;
import org.springframework.util.Assert;
/**
* {@link EmbeddedServletContainer} that can be used to control an embedded Tomcat server.
* Usually this class should be created using the
* {@link TomcatEmbeddedServletContainerFactory} and not directly.
*
* @author Phillip Webb
* @since 4.0
* @see TomcatEmbeddedServletContainerFactory
*/
public class TomcatEmbeddedServletContainer implements EmbeddedServletContainer {
private final Tomcat tomcat;
/**
* Create a new {@link TomcatEmbeddedServletContainer} instance.
* @param tomcat the underlying Tomcat server
*/
public TomcatEmbeddedServletContainer(Tomcat tomcat) {
Assert.notNull(tomcat, "Tomcat Server must not be null");
this.tomcat = tomcat;
start();
}
private synchronized void start() throws EmbeddedServletContainerException {
try {
this.tomcat.start();
// Unlike Jetty, all Tomcat threads are daemon threads. We create a
// blocking non-daemon to stop immediate shutdown
Thread awaitThread = new Thread() {
@Override
public void run() {
tomcat.getServer().await();
};
};
awaitThread.setDaemon(false);
awaitThread.start();
} catch (Exception ex) {
throw new EmbeddedServletContainerException(
"Unable to start embdedded Tomcat", ex);
}
}
@Override
public synchronized void stop() throws EmbeddedServletContainerException {
try {
try {
this.tomcat.stop();
} catch (LifecycleException e) {
}
this.tomcat.destroy();
} catch (Exception ex) {
throw new EmbeddedServletContainerException(
"Unable to stop embdedded Tomcat", ex);
}
}
/**
* Returns access to the underlying Tomcat server.
*/
public Tomcat getTomcat() {
return this.tomcat;
}
}

View File

@@ -0,0 +1,360 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.context.embedded.tomcat;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import org.apache.catalina.Context;
import org.apache.catalina.Host;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.Server;
import org.apache.catalina.Valve;
import org.apache.catalina.Wrapper;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardEngine;
import org.apache.catalina.core.StandardHost;
import org.apache.catalina.core.StandardService;
import org.apache.catalina.startup.Tomcat;
import org.apache.catalina.startup.Tomcat.FixContextListener;
import org.springframework.bootstrap.context.embedded.AbstractEmbeddedServletContainerFactory;
import org.springframework.bootstrap.context.embedded.EmbeddedServletContainer;
import org.springframework.bootstrap.context.embedded.EmbeddedServletContainerException;
import org.springframework.bootstrap.context.embedded.EmbeddedServletContainerFactory;
import org.springframework.bootstrap.context.embedded.ErrorPage;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.web.ServletContextInitializer;
/**
* {@link EmbeddedServletContainerFactory} that can be used to create
* {@link TomcatEmbeddedServletContainer}s. Can be initialized using Spring's
* {@link ServletContextInitializer}s or Tomcat {@link LifecycleListener}s.
*
* <p>
* Unless explicitly configured otherwise this factory will created containers that listen
* for HTTP requests on port 8080.
*
* @author Phillip Webb
* @author Dave Syer
* @since 4.0
* @see #setPort(int)
* @see #setContextLifecycleListeners(Collection)
* @see TomcatEmbeddedServletContainer
*/
public class TomcatEmbeddedServletContainerFactory extends
AbstractEmbeddedServletContainerFactory implements ResourceLoaderAware {
private File baseDirectory;
private List<Valve> contextValves = new ArrayList<Valve>();
private List<LifecycleListener> contextLifecycleListeners = new ArrayList<LifecycleListener>();
private ResourceLoader resourceLoader;
private Connector connector;
private Tomcat tomcat = new Tomcat();
/**
* Create a new {@link TomcatEmbeddedServletContainerFactory} instance.
*/
public TomcatEmbeddedServletContainerFactory() {
super();
}
/**
* Create a new {@link TomcatEmbeddedServletContainerFactory} that listens for
* requests using the specified port.
* @param port the port to listen on
*/
public TomcatEmbeddedServletContainerFactory(int port) {
super(port);
}
/**
* Create a new {@link TomcatEmbeddedServletContainerFactory} with the specified
* context path and port.
* @param contextPath root the context path
* @param port the port to listen on
*/
public TomcatEmbeddedServletContainerFactory(String contextPath, int port) {
super(contextPath, port);
}
@Override
public EmbeddedServletContainer getEmbdeddedServletContainer(
ServletContextInitializer... initializers) {
File baseDir = (this.baseDirectory != null ? this.baseDirectory
: createTempDir("tomcat"));
this.tomcat.setBaseDir(baseDir.getAbsolutePath());
if (this.connector != null) {
this.connector.setPort(getPort());
this.tomcat.getService().addConnector(this.connector);
this.tomcat.setConnector(this.connector);
} else {
Connector connector = new Connector(
"org.apache.coyote.http11.Http11NioProtocol");
connector.setPort(getPort());
this.tomcat.getService().addConnector(connector);
this.tomcat.setConnector(connector);
}
this.tomcat.getHost().setAutoDeploy(false);
this.tomcat.getEngine().setBackgroundProcessorDelay(-1);
prepareContext(this.tomcat.getHost(), initializers);
return getTomcatEmbeddedServletContainer(this.tomcat);
}
protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
File docBase = getValidDocumentRoot();
docBase = (docBase != null ? docBase : createTempDir("tomcat-docbase"));
Context context = new StandardContext();
context.setName(getContextPath());
context.setPath(getContextPath());
context.setDocBase(docBase.getAbsolutePath());
context.addLifecycleListener(new FixContextListener());
if (this.resourceLoader != null) {
context.setParentClassLoader(this.resourceLoader.getClassLoader());
}
if (getRegisterDefaultServlet()) {
addDefaultServlet(context);
}
if (getRegisterJspServlet()
&& ClassUtils.isPresent(getJspServletClassName(), getClass()
.getClassLoader())) {
addJspServlet(context);
}
ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
configureContext(context, initializersToUse);
host.addChild(context);
postProcessContext(context);
}
private void addDefaultServlet(Context context) {
Wrapper defaultServlet = context.createWrapper();
defaultServlet.setName("default");
defaultServlet.setServletClass("org.apache.catalina.servlets.DefaultServlet");
defaultServlet.addInitParameter("debug", "0");
defaultServlet.addInitParameter("listings", "false");
defaultServlet.setLoadOnStartup(1);
// Otherwise the default location of a Spring DispatcherServlet cannot be set
defaultServlet.setOverridable(true);
context.addChild(defaultServlet);
context.addServletMapping("/", "default");
}
private void addJspServlet(Context context) {
Wrapper jspServlet = context.createWrapper();
jspServlet.setName("jsp");
jspServlet.setServletClass(getJspServletClassName());
jspServlet.addInitParameter("fork", "false");
jspServlet.setLoadOnStartup(3);
context.addChild(jspServlet);
context.addServletMapping("*.jsp", "jsp");
context.addServletMapping("*.jspx", "jsp");
}
/**
* Configure the Tomcat {@link Context}.
* @param context the Tomcat context
* @param initializers initializers to apply
*/
protected void configureContext(Context context,
ServletContextInitializer[] initializers) {
context.addLifecycleListener(new ServletContextInitializerLifecycleListener(
initializers));
for (LifecycleListener lifecycleListener : this.contextLifecycleListeners) {
context.addLifecycleListener(lifecycleListener);
}
for (Valve valve : this.contextValves) {
context.getPipeline().addValve(valve);
}
for (ErrorPage errorPage : getErrorPages()) {
org.apache.catalina.deploy.ErrorPage tomcatPage = new org.apache.catalina.deploy.ErrorPage();
tomcatPage.setLocation(errorPage.getPath());
tomcatPage.setExceptionType(errorPage.getExceptionName());
tomcatPage.setErrorCode(errorPage.getStatus());
context.addErrorPage(tomcatPage);
}
}
/**
* Post process the Tomcat {@link Context} before it used with the Tomcat Server.
* Subclasses can override this method to apply additional processing to the
* {@link Context}.
* @param context the Tomcat {@link Context}
*/
protected void postProcessContext(Context context) {
}
/**
* Factory method called to create the {@link TomcatEmbeddedServletContainer}.
* Subclasses can override this method to return a different
* {@link TomcatEmbeddedServletContainer} or apply additional processing to the Tomcat
* server.
* @param tomcat the Tomcat server.
* @return a new {@link TomcatEmbeddedServletContainer} instance
*/
protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(
Tomcat tomcat) {
return new TomcatEmbeddedServletContainer(tomcat);
}
private File createTempDir(String prefix) {
try {
File tempFolder = File.createTempFile(prefix + ".", "." + getPort());
tempFolder.delete();
tempFolder.mkdir();
tempFolder.deleteOnExit();
return tempFolder;
} catch (IOException ex) {
throw new EmbeddedServletContainerException(
"Unable to create Tomcat tempdir", ex);
}
}
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
/**
* Set the Tomcat base directory. If not specified a temporary directory will be used.
* @param baseDirectory the tomcat base directory
*/
public void setBaseDirectory(File baseDirectory) {
this.baseDirectory = baseDirectory;
}
/**
* The tomcat connector to use (e.g. if you want to change the protocol handler).
*
* @param connector the connector to set
*/
public void setConnector(Connector connector) {
this.connector = connector;
}
/**
* Set {@link Valve}s that should be applied to the Tomcat {@link Context}. Calling
* this method will replace any existing listeners.
* @param contextValves the valves to set
*/
public void setContextValves(Collection<? extends Valve> contextValves) {
Assert.notNull(contextValves, "Valves must not be null");
this.contextValves = new ArrayList<Valve>(contextValves);
}
/**
* Returns a mutable collection of the {@link Valve}s that will be applied to the
* Tomcat {@link Context}.
* @return the contextValves the valves that will be applied
*/
public Collection<Valve> getValves() {
return this.contextValves;
}
/**
* Add {@link Valve}s that should be applied to the Tomcat {@link Context}.
* @param contextValves the valves to add
*/
public void addContextValves(Valve... contextValves) {
Assert.notNull(contextValves, "Valves must not be null");
this.contextValves.addAll(Arrays.asList(contextValves));
}
/**
* Set {@link LifecycleListener}s that should be applied to the Tomcat {@link Context}
* . Calling this method will replace any existing listeners.
* @param contextLifecycleListeners the listeners to set
*/
public void setContextLifecycleListeners(
Collection<? extends LifecycleListener> contextLifecycleListeners) {
Assert.notNull(contextLifecycleListeners,
"ContextLifecycleListeners must not be null");
this.contextLifecycleListeners = new ArrayList<LifecycleListener>(
contextLifecycleListeners);
}
/**
* Returns a mutable collection of the {@link LifecycleListener}s that will be applied
* to the Tomcat {@link Context} .
* @return the contextLifecycleListeners the listeners that will be applied
*/
public Collection<LifecycleListener> getContextLifecycleListeners() {
return this.contextLifecycleListeners;
}
/**
* Add {@link LifecycleListener}s that should be applied to the Tomcat {@link Context}
* .
* @param contextLifecycleListeners the listeners to add
*/
public void addContextLifecycleListeners(
LifecycleListener... contextLifecycleListeners) {
Assert.notNull(contextLifecycleListeners,
"ContextLifecycleListeners must not be null");
this.contextLifecycleListeners.addAll(Arrays.asList(contextLifecycleListeners));
}
public TomcatEmbeddedServletContainerFactory getChildContextFactory(final String name) {
final Server server = this.tomcat.getServer();
return new TomcatEmbeddedServletContainerFactory() {
@Override
public EmbeddedServletContainer getEmbdeddedServletContainer(
ServletContextInitializer... initializers) {
StandardService service = new StandardService();
service.setName(name);
Connector connector = new Connector("HTTP/1.1");
connector.setPort(getPort());
service.addConnector(connector);
StandardEngine engine = new StandardEngine();
engine.setName(name);
engine.setDefaultHost("localhost");
service.setContainer(engine);
server.addService(service);
StandardHost host = new StandardHost();
host.setName("localhost");
engine.addChild(host);
prepareContext(host, initializers);
return new EmbeddedServletContainer() {
@Override
public void stop() throws EmbeddedServletContainerException {
// noop
}
};
}
};
}
}

View File

@@ -0,0 +1,21 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Support for Tomcat {@link org.springframework.bootstrap.context.embedded.EmbeddedServletContainer EmbeddedServletContainers}.
*/
package org.springframework.bootstrap.context.embedded.tomcat;

View File

@@ -0,0 +1,53 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.logging;
import java.io.FileNotFoundException;
import java.util.logging.LogManager;
import java.util.logging.Logger;
import org.springframework.util.ResourceUtils;
import org.springframework.util.SystemPropertyUtils;
/**
* Logging initializer for {@link Logger java.util.logging}.
*
* @author Dave Syer
*
*/
public abstract class JavaLoggerConfigurer {
/**
* Configure the logging system from the specified location (a properties file).
*
* @param location the location to use to configure logging
*/
public static void initLogging(String location) throws FileNotFoundException {
String resolvedLocation = SystemPropertyUtils.resolvePlaceholders(location);
try {
LogManager.getLogManager().readConfiguration(
ResourceUtils.getURL(resolvedLocation).openStream());
} catch (FileNotFoundException e) {
throw e;
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new IllegalArgumentException("Could not initialize logging from "
+ location, e);
}
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.logging;
import java.io.FileNotFoundException;
import java.net.URL;
import org.slf4j.impl.StaticLoggerBinder;
import org.springframework.util.ResourceUtils;
import org.springframework.util.SystemPropertyUtils;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.util.ContextInitializer;
import ch.qos.logback.core.joran.spi.JoranException;
/**
* Logging initializer for <a href="http://logback.qos.ch">logback</a>.
*
* @author Dave Syer
*
*/
public abstract class LogbackConfigurer {
/**
* Configure the logback system from the specified location.
*
* @param location the location to use to configure logback
*/
public static void initLogging(String location) throws FileNotFoundException {
String resolvedLocation = SystemPropertyUtils.resolvePlaceholders(location);
URL url = ResourceUtils.getURL(resolvedLocation);
LoggerContext context = (LoggerContext) StaticLoggerBinder.getSingleton()
.getLoggerFactory();
context.stop();
try {
new ContextInitializer(context).configureByResource(url);
} catch (JoranException e) {
throw new IllegalArgumentException("Could not initialize logging from "
+ location, e);
}
}
}

View File

@@ -0,0 +1,181 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.logging;
import java.lang.management.ManagementFactory;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.Ordered;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.ClassUtils;
import org.springframework.util.Log4jConfigurer;
/**
* <p>
* An {@link ApplicationContextInitializer} that configures a logging framework depending
* on what it finds on the classpath and in the {@link Environment}. If the environment
* contains a property <code>logging.config</code> then that will be used to initialize
* the logging system, otherwise a default location is used. The classpath is probed for
* log4j and logback and if those are present they will be reconfigured, otherwise vanilla
* <code>java.util.logging</code> will be used.
* </p>
*
* <p>
* The default config locations are <code>classpath:log4j.properties</code> or
* <code>classpath:log4j.xml</code> for log4j; <code>classpath:logback.xml</code> for
* logback; and <code>classpath:logging.properties</code> for
* <code>java.util.logging</code>. If the correct one of those files is not found then
* some sensible defaults are adopted from files of the same name but in the package
* containing {@link LoggingInitializer}.
* </p>
*
* <p>
* Some system properties may be set as side effects, and these can be useful if the
* logging configuration supports placeholders (i.e. log4j or logback):
* <ul>
* <li><code>LOG_FILE</code> is set to the value of <code>logging.file</code> if found in
* the environment</li>
* <li><code>LOG_PATH</code> is set to the value of <code>logging.path</code> if found in
* the environment</li>
* <li><code>PID</code> is set to the value of the current process ID if it can be
* determined</li>
* </ul>
* @author Dave Syer
*
*/
public class LoggingInitializer implements
ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {
private int order = Integer.MIN_VALUE + 1;
/**
* Initialize the logging system according to preferences expressed through the
* {@link Environment} and the classpath.
*/
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
String configLocation = getDefaultConfigLocation();
ConfigurableEnvironment environment = applicationContext.getEnvironment();
if (environment.containsProperty("logging.config")) {
configLocation = environment.getProperty("logging.config");
}
if (environment.containsProperty("logging.file")) {
String location = environment.getProperty("logging.file");
System.setProperty("LOG_FILE", location);
}
if (environment.containsProperty("logging.path")) {
String location = environment.getProperty("logging.path");
System.setProperty("LOG_PATH", location);
}
if (!environment.containsProperty("PID")) {
System.setProperty("PID", getPid());
}
initLogging(applicationContext, configLocation);
}
private void initLogging(ResourceLoader resourceLoader, String configLocation) {
try {
if (isLog4jPresent()) {
Log4jConfigurer.initLogging(configLocation);
} else {
if (isLogbackPresent()) {
LogbackConfigurer.initLogging(configLocation);
} else {
JavaLoggerConfigurer.initLogging(configLocation);
}
}
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new IllegalStateException("Cannot initialize logging from "
+ configLocation, e);
}
}
private boolean isLogbackPresent() {
return ClassUtils.isPresent("ch.qos.logback.core.Appender",
ClassUtils.getDefaultClassLoader());
}
private boolean isLog4jPresent() {
return ClassUtils.isPresent("org.apache.log4j.PropertyConfigurator",
ClassUtils.getDefaultClassLoader());
}
private String getDefaultConfigLocation() {
String defaultPath = ClassUtils.getPackageName(LoggingInitializer.class).replace(
".", "/")
+ "/";
if (isLog4jPresent()) {
String path = "log4j.xml";
if (!new ClassPathResource(path).exists()) {
path = "log4j.properties";
if (!new ClassPathResource(path).exists()) {
path = defaultPath + path;
}
}
return "classpath:" + path;
}
if (isLogbackPresent()) {
String path = "logback.xml";
if (!new ClassPathResource(path).exists()) {
path = defaultPath + path;
}
return "classpath:" + path;
}
String path = "logging.properties";
if (!new ClassPathResource(path).exists()) {
path = defaultPath + path;
}
return "classpath:" + path;
}
private String getPid() {
String name = ManagementFactory.getRuntimeMXBean().getName();
if (name != null) {
return name.split("@")[0];
}
return "????";
}
public void setOrder(int order) {
this.order = order;
}
@Override
public int getOrder() {
return this.order;
}
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.maven;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Properties;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import org.apache.maven.plugins.shade.relocation.Relocator;
import org.apache.maven.plugins.shade.resource.ResourceTransformer;
/**
* Extension for the <a href="http://maven.apache.org/plugins/maven-shade-plugin/">Maven
* shade plugin</a> to allow properties files (e.g. <code>META-INF/spring.factories</code>
* ) to be merged without losing any information.
*
* @author Dave Syer
*/
public class PropertiesMergingResourceTransformer implements ResourceTransformer {
String resource; // Set this in pom configuration with <resource>...</resource>
private Properties data = new Properties();
@Override
public boolean canTransformResource(String resource) {
if (this.resource != null && this.resource.equalsIgnoreCase(resource)) {
return true;
}
return false;
}
@Override
public void processResource(String resource, InputStream is,
List<Relocator> relocators) throws IOException {
Properties properties = new Properties();
properties.load(is);
is.close();
for (Object key : properties.keySet()) {
String name = (String) key;
String value = properties.getProperty(name);
String existing = this.data.getProperty(name);
this.data
.setProperty(name, existing == null ? value : existing + "," + value);
}
}
@Override
public boolean hasTransformedResource() {
return this.data.size() > 0;
}
@Override
public void modifyOutputStream(JarOutputStream os) throws IOException {
os.putNextEntry(new JarEntry(this.resource));
this.data.store(os, "Merged by PropertiesMergingResourceTransformer");
os.flush();
this.data.clear();
}
}