Further maven module restructure

This commit is contained in:
Phillip Webb
2013-07-08 11:41:51 -07:00
parent 9fde0a3715
commit cd51f357a3
420 changed files with 92 additions and 92 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.zero;
import java.io.PrintStream;
/**
* Writes the 'Spring Zero' 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) {
printStream.println();
for (String line : BANNER) {
printStream.println(line);
}
printStream.println();
printStream.println();
}
}

View File

@@ -0,0 +1,210 @@
/*
* 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.zero;
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.annotation.AnnotationUtils;
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.stereotype.Component;
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<?>) {
return load((Class<?>) source);
}
if (source instanceof Resource) {
return load((Resource) source);
}
if (source instanceof Package) {
return load((Package) source);
}
if (source instanceof CharSequence) {
return load((CharSequence) source);
}
throw new IllegalArgumentException("Invalid source type " + source.getClass());
}
private int load(Class<?> source) {
if (isComponent(source)) {
this.annotatedReader.register(source);
return 1;
}
return 0;
}
private int load(Resource source) {
return this.xmlReader.loadBeanDefinitions(source);
}
private int load(Package source) {
// FIXME register the scanned package for data to pick up
return this.scanner.scan(source.getName());
}
private int load(CharSequence source) {
try {
return load(Class.forName(source.toString()));
}
catch (ClassNotFoundException ex) {
// swallow exception and continue
}
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 + "'");
}
private boolean isComponent(Class<?> type) {
// This has to be a bit of a guess. The only way to be sure that this type is
// eligible is to make a bean definition out of it and try to instantiate it.
if (AnnotationUtils.findAnnotation(type, Component.class) != null) {
return true;
}
// Nested anonymous classes are not eligible for registration, nor are groovy
// closures
if (type.isAnonymousClass() || type.getName().matches(".*\\$_.*closure.*")
|| type.getConstructors() == null || type.getConstructors().length == 0) {
return false;
}
return true;
}
/**
* 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,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.zero;
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
* @throws Exception on error
*/
void run(String... args) throws Exception;
}

View File

@@ -0,0 +1,35 @@
/*
* 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.zero;
/**
* Interface used to generate an 'exit code' from a running command line
* {@link SpringApplication}.
*
* @author Dave Syer
* @see SpringApplication#exit(org.springframework.context.ApplicationContext,
* ExitCodeGenerator...)
*/
public interface ExitCodeGenerator {
/**
* Returns the exit code that should be returned from the application.
* @return the exit code.
*/
int getExitCode();
}

View File

@@ -0,0 +1,651 @@
/*
* 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.zero;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
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.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.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.MapPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.SpringFactoriesLoader;
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 (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";
public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework.zero."
+ "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;
private String[] defaultCommandLineArgs;
/**
* 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();
}
private 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 && this.environment != null) {
((AbstractApplicationContext) context).setEnvironment(this.environment);
}
if (context instanceof GenericApplicationContext && this.resourceLoader != null) {
((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) {
// Don't use SimpleCommandLinePropertySource (SPR-10579)
PropertySource<?> propertySource = new MapPropertySource(
"commandLineArgs", mergeCommandLineArgs(
this.defaultCommandLineArgs, args));
configurable.getPropertySources().addFirst(propertySource);
}
}
}
/**
* Merge two sets of command lines, the defaults and the ones passed in at run time.
*
* @param defaults the default values
* @param args the ones passed in at runtime
* @return a new command line
*/
protected Map<String, Object> mergeCommandLineArgs(String[] defaults, String[] args) {
if (defaults == null) {
defaults = new String[0];
}
List<String> nonopts = new ArrayList<String>();
Map<String, Object> options = new LinkedHashMap<String, Object>();
for (String arg : defaults) {
if (isOptionArg(arg)) {
addOptionArg(options, arg);
}
else {
nonopts.add(arg);
}
}
for (String arg : args) {
if (isOptionArg(arg)) {
addOptionArg(options, arg);
}
else if (!nonopts.contains(arg)) {
nonopts.add(arg);
}
}
for (String key : nonopts) {
options.put(key, "");
}
return options;
}
private boolean isOptionArg(String arg) {
return arg.startsWith("--");
}
private void addOptionArg(Map<String, Object> map, String arg) {
String optionText = arg.substring(2, arg.length());
String optionName;
String optionValue = "";
if (optionText.contains("=")) {
optionName = optionText.substring(0, optionText.indexOf('='));
optionValue = optionText.substring(optionText.indexOf('=') + 1,
optionText.length());
}
else {
optionName = optionText;
}
if (optionName.isEmpty()) {
throw new IllegalArgumentException("Invalid argument syntax: " + arg);
}
map.put(optionName, optionValue);
}
/**
* Load beans into the application context.
* @param context the context to load beans into
* @param sources the sources to load
*/
protected void load(ApplicationContext context, Object[] sources) {
BeanDefinitionLoader loader = createBeanDefinitionLoader(
getBeanDefinitionRegistry(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();
}
/**
* @param context the application context
* @return the BeanDefinitionRegistry if it can be determined
*/
private BeanDefinitionRegistry getBeanDefinitionRegistry(ApplicationContext context) {
if (context instanceof BeanDefinitionRegistry) {
return (BeanDefinitionRegistry) context;
}
throw new IllegalStateException("Could not locate BeanDefinitionRegistry");
}
/**
* Factory method used to create the {@link BeanDefinitionLoader}.
* @param registry the bean definition registry
* @param sources the sources to load
* @return the {@link BeanDefinitionLoader} that will be used to load beans
*/
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) {
try {
runner.run(args);
}
catch (Exception ex) {
throw new IllegalStateException("Failed to execute CommandLineRunner", ex);
}
}
}
/**
* 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.
* @param webEnvironment if the application is running in a web environment
*/
public void setWebEnvironment(boolean webEnvironment) {
this.webEnvironment = webEnvironment;
}
/**
* Sets if the Spring banner should be displayed when the application runs. Defaults
* to {@code true}.
* @param showBanner if the banner should be shown
* @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}.
* @param addCommandLineProperties if command line arguments should be exposed
*/
public void setAddCommandLineProperties(boolean addCommandLineProperties) {
this.addCommandLineProperties = addCommandLineProperties;
}
/**
* Set some default command line arguments which can be overridden by those passed
* into the run methods.
* @param defaultCommandLineArgs the default command line args to set
*/
public void setDefaultCommandLineArgs(String... defaultCommandLineArgs) {
this.defaultCommandLineArgs = defaultCommandLineArgs;
}
/**
* Sets the bean name generator that should be used when generating bean names.
* @param beanNameGenerator the bean name generator
*/
public void setBeanNameGenerator(BeanNameGenerator beanNameGenerator) {
this.beanNameGenerator = beanNameGenerator;
}
/**
* Sets the underlying environment that should be used when loading.
* @param environment the environment
*/
public void setEnvironment(ConfigurableEnvironment environment) {
this.environment = environment;
}
/**
* Sets the {@link ResourceLoader} that should be used when loading resources.
* @param resourceLoader the resource loader
*/
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 #DEFAULT_WEB_CONTEXT_CLASS} 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 #DEFAULT_WEB_CONTEXT_CLASS} 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;
}
/**
* Sets the {@link ApplicationContextInitializer} that will be applied to the Spring
* {@link ApplicationContext}. Any existing initializers will be replaced.
* @param initializers the initializers to set
*/
public void setInitializers(
Collection<? extends ApplicationContextInitializer<?>> initializers) {
this.initializers = new ArrayList<ApplicationContextInitializer<?>>(initializers);
}
/**
* Add {@link ApplicationContextInitializer}s to be applied to the Spring
* {@link ApplicationContext} .
* @param initializers the initializers to add
*/
public void addInitializers(ApplicationContextInitializer<?>... initializers) {
this.initializers.addAll(Arrays.asList(initializers));
}
/**
* Returns a mutable list of the {@link ApplicationContextInitializer}s that will be
* applied to the Spring {@link ApplicationContext}.
* @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 exit a {@link SpringApplication} and obtain a
* code indicating success (0) or otherwise. Does not throw exceptions but should
* print stack traces of any encountered. Applies the specified
* {@link ExitCodeGenerator} in addition to any Spring beans that implement
* {@link ExitCodeGenerator}. In the case of multiple exit codes the highest value
* will be used (or if all values are negative, the lowest value will be used)
* @param context the context to close if possible
* @param exitCodeGenerators exist code generators
* @return the outcome (0 if successful)
*/
public static int exit(ApplicationContext context,
ExitCodeGenerator... exitCodeGenerators) {
int exitCode = 0;
try {
try {
List<ExitCodeGenerator> generators = new ArrayList<ExitCodeGenerator>();
generators.addAll(Arrays.asList(exitCodeGenerators));
generators.addAll(context.getBeansOfType(ExitCodeGenerator.class)
.values());
exitCode = getExitCode(generators);
}
finally {
close(context);
}
}
catch (Exception ex) {
ex.printStackTrace();
exitCode = (exitCode == 0 ? 1 : exitCode);
}
return exitCode;
}
private static int getExitCode(List<ExitCodeGenerator> exitCodeGenerators) {
int exitCode = 0;
for (ExitCodeGenerator exitCodeGenerator : exitCodeGenerators) {
try {
int value = exitCodeGenerator.getExitCode();
if (value > 0 && value > exitCode || value < 0 && value < exitCode) {
exitCode = value;
}
}
catch (Exception ex) {
exitCode = (exitCode == 0 ? 1 : exitCode);
ex.printStackTrace();
}
}
return exitCode;
}
private static void close(ApplicationContext context) {
if (context instanceof ConfigurableApplicationContext) {
ConfigurableApplicationContext closable = (ConfigurableApplicationContext) context;
closable.close();
}
}
}

View File

@@ -0,0 +1,102 @@
/*
* 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.zero.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.
* @param alias the alias to map
* @param type the type of property
* @param name the property name
*/
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 ex) {
throw new RuntimeException(ex);
}
}
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,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.zero.bind;
import java.beans.PropertyEditor;
import java.beans.PropertyEditorSupport;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* {@link PropertyEditor} for {@link InetAddress} objects.
*
* @author Dave Syer
*/
public class InetAddressEditor extends PropertyEditorSupport implements PropertyEditor {
@Override
public String getAsText() {
return ((InetAddress) getValue()).getHostAddress();
}
@Override
public void setAsText(String text) throws IllegalArgumentException {
try {
setValue(InetAddress.getByName(text));
}
catch (UnknownHostException ex) {
throw new IllegalArgumentException("Cannot locate host", ex);
}
}
}

View File

@@ -0,0 +1,262 @@
/*
* 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.zero.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.convert.ConversionService;
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 final Log logger = LogFactory.getLog(getClass());
private boolean ignoreUnknownFields = true;
private boolean ignoreInvalidFields = false;
private boolean exceptionIfInvalid;
private Properties properties;
private PropertySources propertySources;
private T target;
private Validator validator;
private MessageSource messageSource;
private boolean hasBeenBound = false;
private String targetName;
private ConversionService conversionService;
/**
* @param target the target object to bind too
* @see #PropertiesConfigurationFactory(Class)
*/
public PropertiesConfigurationFactory(T target) {
Assert.notNull(target);
this.target = 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.target = (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.
* @param ignoreUnknownFields if unknown fields should be ignored
*/
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.
* @param ignoreInvalidFields if invalid fields should be ignored
*/
public void setIgnoreInvalidFields(boolean ignoreInvalidFields) {
this.ignoreInvalidFields = ignoreInvalidFields;
}
/**
* @param targetName the target name to set
*/
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 conversionService the conversionService to set
*/
public void setConversionService(ConversionService conversionService) {
this.conversionService = conversionService;
}
/**
* @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 {
bindPropertiesToTarget();
}
@Override
public Class<?> getObjectType() {
if (this.target == null) {
return Object.class;
}
return this.target.getClass();
}
@Override
public boolean isSingleton() {
return true;
}
@Override
public T getObject() throws Exception {
if (!this.hasBeenBound) {
bindPropertiesToTarget();
}
return this.target;
}
public void bindPropertiesToTarget() throws BindException {
Assert.state(this.properties != null || this.propertySources != null,
"Properties or propertySources should not be null");
try {
if (this.logger.isTraceEnabled()) {
if (this.properties != null) {
this.logger.trace("Properties:\n" + this.properties);
}
else {
this.logger.trace("Property Sources: " + this.propertySources);
}
}
this.hasBeenBound = true;
doBindPropertiesToTarget();
}
catch (BindException ex) {
if (this.exceptionIfInvalid) {
throw ex;
}
this.logger.error("Failed to load Properties validation bean. "
+ "Your Properties may be invalid.", ex);
}
}
private void doBindPropertiesToTarget() throws BindException {
RelaxedDataBinder dataBinder = (this.targetName != null ? new RelaxedDataBinder(
this.target, this.targetName) : new RelaxedDataBinder(this.target));
if (this.validator != null) {
dataBinder.setValidator(this.validator);
}
if (this.conversionService != null) {
dataBinder.setConversionService(this.conversionService);
}
dataBinder.setIgnoreInvalidFields(this.ignoreInvalidFields);
dataBinder.setIgnoreUnknownFields(this.ignoreUnknownFields);
customizeBinder(dataBinder);
PropertyValues propertyValues = (this.properties != null ? new MutablePropertyValues(
this.properties)
: new PropertySourcesPropertyValues(this.propertySources));
dataBinder.bind(propertyValues);
if (this.validator != null) {
validate(dataBinder);
}
}
private void validate(RelaxedDataBinder dataBinder) throws BindException {
dataBinder.validate();
BindingResult errors = dataBinder.getBindingResult();
if (errors.hasErrors()) {
this.logger.error("Properties configuration failed validation");
for (ObjectError error : errors.getAllErrors()) {
this.logger.error(this.messageSource != null ? this.messageSource
.getMessage(error, Locale.getDefault()) + " (" + error + ")"
: error);
}
if (this.exceptionIfInvalid) {
BindException summary = new BindException(errors);
throw summary;
}
}
}
/**
* @param dataBinder the data binder that will be used to bind and validate
*/
protected void customizeBinder(DataBinder dataBinder) {
}
}

View File

@@ -0,0 +1,120 @@
/*
* 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.zero.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,278 @@
/*
* 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.zero.bind;
import java.net.InetAddress;
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(wrapTarget(target));
}
private static Object wrapTarget(Object target) {
if (target instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>) target;
target = new MapHolder(map);
}
return 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(wrapTarget(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());
// Harmless additional property editor comes in very handy sometimes...
getPropertyEditorRegistry().registerCustomEditor(InetAddress.class,
new InetAddressEditor());
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) {
propertyValues = getProperyValuesForNamePrefix(propertyValues);
if (target instanceof MapHolder) {
propertyValues = addMapPrefix(propertyValues);
}
BeanWrapper targetWrapper = new BeanWrapperImpl(target);
targetWrapper.setAutoGrowNestedPaths(true);
List<PropertyValue> list = propertyValues.getPropertyValueList();
for (int i = 0; i < list.size(); i++) {
modifyProperty(propertyValues, targetWrapper, list.get(i), i);
}
return propertyValues;
}
private MutablePropertyValues addMapPrefix(MutablePropertyValues propertyValues) {
MutablePropertyValues rtn = new MutablePropertyValues();
for (PropertyValue pv : propertyValues.getPropertyValues()) {
rtn.add("map." + pv.getName(), pv.getValue());
}
return rtn;
}
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)) {
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);
}
modifyPopertiesForMap(nested, propertyValues, index, base);
break;
}
}
}
private void modifyPopertiesForMap(Map<String, Object> target,
MutablePropertyValues propertyValues, int index, String base) {
PropertyValue propertyValue = propertyValues.getPropertyValueList().get(index);
String name = propertyValue.getName();
String suffix = name.substring(base.length());
Map<String, Object> value = new LinkedHashMap<String, Object>();
String[] tree = StringUtils.delimitedListToStringArray(
suffix.startsWith(".") ? suffix.substring(1) : suffix, ".");
for (int j = 0; j < tree.length - 1; j++) {
if (!target.containsKey(tree[j])) {
target.put(tree[j], value);
}
target = value;
value = new LinkedHashMap<String, Object>();
}
String refName = base + suffix.replaceAll("\\.([a-zA-Z0-9]*)", "[$1]");
propertyValues.setPropertyValueAt(
new PropertyValue(refName, propertyValue.getValue()), index);
}
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) {
// swallow and contrinue
}
}
}
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);
}
static class MapHolder {
private Map<String, Object> map;
public MapHolder(Map<String, Object> map) {
this.map = map;
}
public void setMap(Map<String, Object> map) {
this.map = map;
}
public Map<String, Object> getMap() {
return this.map;
}
}
}

View File

@@ -0,0 +1,194 @@
/*
* 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.zero.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 final Log logger = LogFactory.getLog(getClass());
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.
* @param type the root type
*/
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 {
if (this.logger.isTraceEnabled()) {
this.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) {
validate();
}
}
catch (YAMLException ex) {
if (this.exceptionIfInvalid) {
throw ex;
}
this.logger.error("Failed to load YAML validation bean. "
+ "Your YAML file may be invalid.", ex);
}
}
private void validate() throws BindException {
BindingResult errors = new BeanPropertyBindingResult(this.configuration,
"configuration");
this.validator.validate(this.configuration, errors);
if (errors.hasErrors()) {
this.logger.error("YAML configuration failed validation");
for (ObjectError error : errors.getAllErrors()) {
this.logger.error(this.messageSource != null ? this.messageSource
.getMessage(error, Locale.getDefault()) + " (" + error + ")"
: error);
}
if (this.exceptionIfInvalid) {
BindException summary = new BindException(errors);
throw summary;
}
}
}
@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,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.zero.config;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* Thin wrapper to adapt Jackson 2 {@link ObjectMapper} to {@link JsonParser}.
*
* @author Dave Syer
* @see JsonParserFactory
*/
public class JacksonJsonParser implements JsonParser {
@Override
@SuppressWarnings("unchecked")
public Map<String, Object> parseMap(String json) {
try {
return new ObjectMapper().readValue(json, Map.class);
}
catch (Exception ex) {
throw new IllegalArgumentException("Cannot parse JSON", ex);
}
}
@Override
@SuppressWarnings("unchecked")
public List<Object> parseList(String json) {
try {
return new ObjectMapper().readValue(json, List.class);
}
catch (Exception ex) {
throw new IllegalArgumentException("Cannot parse JSON", ex);
}
}
}

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.zero.config;
import java.util.List;
import java.util.Map;
/**
* Parser that can read JSON formatted strings into {@link Map}s or {@link List}s.
*
* @author Dave Syer
* @see JsonParserFactory
* @see SimpleJsonParser
* @see JacksonJsonParser
* @see YamlJsonParser
*/
public interface JsonParser {
/**
* Parse the specified JSON string into a Map.
* @param json the JSON to parse
* @return the parsed JSON as a map
*/
Map<String, Object> parseMap(String json);
/**
* Parse the specified JSON string into a List.
* @param json the JSON to parse
* @return the parsed JSON as a list
*/
List<Object> parseList(String json);
}

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.zero.config;
import org.springframework.util.ClassUtils;
/**
* Factory to create a {@link JsonParser}.
*
* @author Dave Syer
* @see JacksonJsonParser
* @see YamlJsonParser
* @see SimpleJsonParser
*/
public abstract class JsonParserFactory {
/**
* Static factory for the "best" JSON parser available on the classpath. Tries Jackson
* (2), then Snake YAML, and then falls back to the {@link SimpleJsonParser}.
*
* @return a {@link JsonParser}
*/
public static JsonParser getJsonParser() {
if (ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", null)) {
return new JacksonJsonParser();
}
if (ClassUtils.isPresent("org.yaml.snakeyaml.Yaml", null)) {
return new YamlJsonParser();
}
return new SimpleJsonParser();
}
}

View File

@@ -0,0 +1,147 @@
/*
* 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.zero.config;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.springframework.util.StringUtils;
/**
* Really basic JSON parser for when you have nothing else available. Comes with some
* limitations with respect to the JSON specification (e.g. only supports String values),
* so users will probably prefer to have a library handle things instead (Jackson or Snake
* YAML are supported).
*
* @author Dave Syer
* @see JsonParserFactory
*/
public class SimpleJsonParser implements JsonParser {
@Override
public Map<String, Object> parseMap(String json) {
if (json.startsWith("{")) {
return parseMapInternal(json);
}
else if (json.trim().equals("")) {
return new HashMap<String, Object>();
}
return null;
}
@Override
public List<Object> parseList(String json) {
if (json.startsWith("[")) {
return parseListInternal(json);
}
else if (json.trim().equals("")) {
return new ArrayList<Object>();
}
return null;
}
private List<Object> parseListInternal(String json) {
List<Object> list = new ArrayList<Object>();
json = trimLeadingCharacter(trimTrailingCharacter(json, ']'), '[');
;
for (String value : tokenize(json)) {
list.add(parseInternal(value));
}
return list;
}
private Object parseInternal(String json) {
if (json.startsWith("[")) {
return parseListInternal(json);
}
if (json.startsWith("{")) {
return parseMapInternal(json);
}
if (json.startsWith("\"")) {
return trimTrailingCharacter(trimLeadingCharacter(json, '"'), '"');
}
return json; // TODO: numbers maybe?
}
private static String trimTrailingCharacter(String string, char c) {
if (string.length() >= 0 && string.charAt(string.length() - 1) == c) {
return string.substring(0, string.length() - 1);
}
return string;
}
private static String trimLeadingCharacter(String string, char c) {
if (string.length() >= 0 && string.charAt(0) == c) {
return string.substring(1);
}
return string;
}
private Map<String, Object> parseMapInternal(String json) {
Map<String, Object> map = new LinkedHashMap<String, Object>();
json = trimLeadingCharacter(trimTrailingCharacter(json, '}'), '{');
for (String pair : tokenize(json)) {
String[] values = StringUtils.trimArrayElements(StringUtils.split(pair, ":"));
String key = trimLeadingCharacter(trimTrailingCharacter(values[0], '"'), '"');
Object value = null;
if (values.length > 0) {
String string = trimLeadingCharacter(
trimTrailingCharacter(values[1], '"'), '"');
if (string.startsWith("{") && string.endsWith("}")) {
value = parseInternal(string);
}
else {
value = string;
}
}
map.put(key, value);
}
return map;
}
private List<String> tokenize(String json) {
List<String> list = new ArrayList<String>();
int index = 0;
int inObject = 0;
StringBuilder build = new StringBuilder();
while (index < json.length()) {
char current = json.charAt(index);
if (current == '{') {
inObject++;
}
if (current == '}') {
inObject--;
}
if (current == ',' && inObject == 0) {
list.add(build.toString());
build.setLength(0);
}
else {
build.append(current);
}
index++;
}
if (build.length() > 0) {
list.add(build.toString());
}
return list;
}
}

View File

@@ -0,0 +1,44 @@
/*
* 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.zero.config;
import java.util.List;
import java.util.Map;
import org.yaml.snakeyaml.Yaml;
/**
* Thin wrapper to adapt Snake {@link Yaml} to {@link JsonParser}.
*
* @author Dave Syer
* @see JsonParserFactory
*/
public class YamlJsonParser implements JsonParser {
@Override
@SuppressWarnings("unchecked")
public Map<String, Object> parseMap(String json) {
return new Yaml().loadAs(json, Map.class);
}
@Override
@SuppressWarnings("unchecked")
public List<Object> parseList(String json) {
return new Yaml().loadAs(json, List.class);
}
}

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.zero.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
*/
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,300 @@
/*
* 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.zero.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
*/
public class YamlProcessor {
private final Log logger = LogFactory.getLog(getClass());
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 for which all the
* {@link #setDocumentMatchers(List) document matchers} abstain 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 == null ? null : resources.clone());
}
/**
* 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();
for (Resource resource : this.resources) {
boolean found = process(callback, yaml, resource);
if (this.resolutionMethod == ResolutionMethod.FIRST_FOUND && found) {
return;
}
}
}
private boolean process(MatchCallback callback, Yaml yaml, Resource resource) {
int count = 0;
try {
this.logger.info("Loading from YAML: " + resource);
for (Object object : yaml.loadAll(resource.getInputStream())) {
if (object != null && process(asMap(object), callback)) {
count++;
if (this.resolutionMethod == ResolutionMethod.FIRST_FOUND) {
break;
}
}
}
this.logger.info("Loaded " + count + " document" + (count > 1 ? "s" : "")
+ " from YAML resource: " + resource);
}
catch (IOException ex) {
handleProcessError(resource, ex);
}
return count > 0;
}
private void handleProcessError(Resource resource, IOException ex) {
if (this.resolutionMethod != ResolutionMethod.FIRST_FOUND
&& this.resolutionMethod != ResolutionMethod.OVERRIDE_AND_IGNORE) {
throw new IllegalStateException(ex);
}
if (this.logger.isWarnEnabled()) {
this.logger.warn("Could not load map from " + resource + ": "
+ ex.getMessage());
}
}
@SuppressWarnings("unchecked")
private Map<String, Object> asMap(Object object) {
return (Map<String, Object>) object;
}
private boolean process(Map<String, Object> map, MatchCallback callback) {
Properties properties = new Properties();
assignProperties(properties, map, null);
if (this.documentMatchers.isEmpty()) {
this.logger.debug("Merging document (no matchers set)" + map);
callback.process(properties, map);
}
else {
boolean valueFound = false;
MatchStatus result = MatchStatus.ABSTAIN;
for (DocumentMatcher matcher : this.documentMatchers) {
MatchStatus match = matcher.matches(properties);
result = match.ordinal() < result.ordinal() ? match : result;
if (match == MatchStatus.FOUND) {
this.logger.debug("Matched document with document matcher: "
+ properties);
callback.process(properties, map);
valueFound = true;
// No need to check for more matches
break;
}
}
if (result == MatchStatus.ABSTAIN && this.matchDefault) {
this.logger.debug("Matched document with default matcher: " + map);
callback.process(properties, map);
}
else if (!valueFound) {
this.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);
}
}
}
public interface MatchCallback {
void process(Properties properties, Map<String, Object> map);
}
public interface DocumentMatcher {
MatchStatus matches(Properties properties);
}
public static enum ResolutionMethod {
OVERRIDE, OVERRIDE_AND_IGNORE, FIRST_FOUND
}
public static enum MatchStatus {
FOUND, NOT_FOUND, ABSTAIN
}
/**
* 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).
*/
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 MatchStatus matches(Properties properties) {
if (!properties.containsKey(this.key)) {
return MatchStatus.ABSTAIN;
}
Set<String> values = StringUtils.commaDelimitedListToSet(properties
.getProperty(this.key));
for (String pattern : this.patterns) {
for (String value : values) {
if (value.matches(pattern)) {
return MatchStatus.FOUND;
}
}
}
return MatchStatus.NOT_FOUND;
}
}
}

View File

@@ -0,0 +1,109 @@
/*
* 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.zero.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
*/
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,172 @@
/*
* 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.zero.context.condition;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.ConfigurationCondition;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.core.type.MethodMetadata;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.MultiValueMap;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.ReflectionUtils.MethodCallback;
/**
* Base for {@link OnBeanCondition} and {@link OnMissingBeanCondition}.
*
* @author Phillip Webb
* @author Dave Syer
*/
abstract class AbstractOnBeanCondition implements ConfigurationCondition {
private final Log logger = LogFactory.getLog(getClass());
protected abstract Class<?> annotationClass();
@Override
public ConfigurationPhase getConfigurationPhase() {
return ConfigurationPhase.REGISTER_BEAN;
}
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes(
annotationClass().getName(), true);
final List<String> beanClasses = collect(attributes, "value");
final List<String> beanNames = collect(attributes, "name");
if (beanClasses.size() == 0) {
if (metadata instanceof MethodMetadata
&& metadata.isAnnotated(Bean.class.getName())) {
try {
final MethodMetadata methodMetadata = (MethodMetadata) metadata;
// We should be safe to load at this point since we are in the
// REGISTER_BEAN phase
Class<?> configClass = ClassUtils.forName(
methodMetadata.getDeclaringClassName(),
context.getClassLoader());
ReflectionUtils.doWithMethods(configClass, new MethodCallback() {
@Override
public void doWith(Method method)
throws IllegalArgumentException, IllegalAccessException {
if (methodMetadata.getMethodName().equals(method.getName())) {
beanClasses.add(method.getReturnType().getName());
}
}
});
}
catch (Exception ex) {
// swallow exception and continue
}
}
}
Assert.isTrue(beanClasses.size() > 0 || beanNames.size() > 0,
"@" + ClassUtils.getShortName(annotationClass())
+ " annotations must specify at least one bean");
return matches(context, metadata, beanClasses, beanNames);
}
protected boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata,
List<String> beanClasses, List<String> beanNames) throws LinkageError {
String checking = ConditionLogUtils.getPrefix(this.logger, metadata);
Boolean considerHierarchy = (Boolean) metadata.getAnnotationAttributes(
annotationClass().getName()).get("considerHierarchy");
considerHierarchy = (considerHierarchy == null ? false : considerHierarchy);
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)
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
Class<?> type = ClassUtils.forName(beanClass, context.getClassLoader());
String[] beans = (considerHierarchy ? BeanFactoryUtils
.beanNamesForTypeIncludingAncestors(beanFactory, type, false,
false) : beanFactory.getBeanNamesForType(type, false,
false));
if (beans.length != 0) {
beanClassesFound.add(beanClass);
}
}
catch (ClassNotFoundException ex) {
// swallow exception and continue
}
}
for (String beanName : beanNames) {
if (considerHierarchy ? context.getBeanFactory().containsBean(beanName)
: context.getBeanFactory().containsLocalBean(beanName)) {
beanNamesFound.add(beanName);
}
}
boolean result = evaluate(beanClassesFound, beanNamesFound);
if (this.logger.isDebugEnabled()) {
logFoundResults(checking, "class", beanClasses, beanClassesFound);
logFoundResults(checking, "name", beanNames, beanClassesFound);
this.logger.debug(checking + "Match result is: " + result);
}
return result;
}
private void logFoundResults(String prefix, String type, List<?> candidates,
List<?> found) {
if (!candidates.isEmpty()) {
this.logger.debug(prefix + "Looking for beans with " + type + ": "
+ candidates);
if (found.isEmpty()) {
this.logger.debug(prefix + "Found no beans");
}
else {
this.logger.debug(prefix + "Found beans with " + type + ": " + found);
}
}
}
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,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.zero.context.condition;
import org.apache.commons.logging.Log;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.MethodMetadata;
/**
* General utilities for constructing {@code @Conditional} log messages.
*
* @author Dave Syer
*/
public abstract class ConditionLogUtils {
public static String getPrefix(Log logger, AnnotatedTypeMetadata metadata) {
String prefix = "";
if (logger.isDebugEnabled()) {
prefix = metadata instanceof ClassMetadata ? "Processing "
+ ((ClassMetadata) metadata).getClassName() + ". "
: (metadata instanceof MethodMetadata ? "Processing "
+ getMethodName((MethodMetadata) metadata) + ". " : "");
}
return prefix;
}
private static String getMethodName(MethodMetadata metadata) {
return metadata.getDeclaringClassName() + "#" + metadata.getMethodName();
}
}

View File

@@ -0,0 +1,60 @@
/*
* 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.zero.context.condition;
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 {};
/**
* If the application context hierarchy (parent contexts) should be considered.
*/
boolean considerHierarchy() default true;
}

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.zero.context.condition;
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,41 @@
/*
* 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.zero.context.condition;
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 the value of a SpEL
* expression.
*
* @author Dave Syer
*/
@Conditional(OnExpressionCondition.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
public @interface ConditionalOnExpression {
/**
* The SpEL expression.
*/
String value() default "true";
}

View File

@@ -0,0 +1,60 @@
/*
* 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.zero.context.condition;
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 {};
/**
* If the application context hierarchy (parent contexts) should be considered.
*/
boolean considerHierarchy() default true;
}

View File

@@ -0,0 +1,43 @@
/*
* 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.zero.context.condition;
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 Dave Syer
*/
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnMissingClassCondition.class)
public @interface ConditionalOnMissingClass {
/**
* The classes names that must be absent.
* @return the class names that must be absent.
*/
public String[] value() default {};
}

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.zero.context.condition;
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 application context is a not a web
* application context.
*
* @author Dave Syer
*/
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnNotWebApplicationCondition.class)
public @interface ConditionalOnNotWebApplication {
}

View File

@@ -0,0 +1,44 @@
/*
* 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.zero.context.condition;
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 resources are on the
* classpath.
*
* @author Dave Syer
*/
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnResourceCondition.class)
public @interface ConditionalOnResource {
/**
* The resources that must be present.
* @return the resource paths that must be present.
*/
public String[] resources() default {};
}

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.zero.context.condition;
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 application context is a web application
* context.
*
* @author Dave Syer
*/
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnWebApplicationCondition.class)
public @interface ConditionalOnWebApplication {
}

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.zero.context.condition;
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,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.zero.context.condition;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
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 {
private static Log logger = LogFactory.getLog(OnClassCondition.class);
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String checking = ConditionLogUtils.getPrefix(logger, 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 (logger.isDebugEnabled()) {
logger.debug(checking + "Looking for class: " + className);
}
if (!ClassUtils.isPresent(className, context.getClassLoader())) {
if (logger.isDebugEnabled()) {
logger.debug(checking + "Class not found: " + className
+ " (search terminated with matches=false)");
}
return false;
}
}
}
if (logger.isDebugEnabled()) {
logger.debug(checking + "Match result is: true");
}
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,76 @@
/*
* 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.zero.context.condition;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
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.context.expression.StandardBeanExpressionResolver;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.core.type.ClassMetadata;
/**
* A Condition that evaluates a SpEL expression.
*
* @author Dave Syer
* @see ConditionalOnExpression
*/
public class OnExpressionCondition implements Condition {
private static Log logger = LogFactory.getLog(OnExpressionCondition.class);
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String checking = ConditionLogUtils.getPrefix(logger, 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 + "}";
}
if (logger.isDebugEnabled()) {
StringBuilder builder = new StringBuilder(checking)
.append("Evaluating expression");
if (metadata instanceof ClassMetadata) {
builder.append(" on " + ((ClassMetadata) metadata).getClassName());
}
builder.append(": " + value);
logger.debug(builder.toString());
}
// 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;
if (resolver == null) {
resolver = new StandardBeanExpressionResolver();
}
boolean result = (Boolean) resolver.evaluate(value, expressionContext);
if (logger.isDebugEnabled()) {
logger.debug(checking + "Finished matching and result is matches=" + result);
}
return result;
}
}

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.zero.context.condition;
import java.util.List;
import org.springframework.context.annotation.Condition;
/**
* {@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
protected boolean evaluate(List<String> beanClassesFound, List<String> beanNamesFound) {
return !super.evaluate(beanClassesFound, beanNamesFound);
}
}

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.zero.context.condition;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
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 Dave Syer
* @see ConditionalOnMissingClass
*/
class OnMissingClassCondition implements Condition {
private static Log logger = LogFactory.getLog(OnMissingClassCondition.class);
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String checking = ConditionLogUtils.getPrefix(logger, metadata);
MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes(
ConditionalOnMissingClass.class.getName(), true);
if (attributes != null) {
List<String> classNames = new ArrayList<String>();
collectClassNames(classNames, attributes.get("value"));
Assert.isTrue(classNames.size() > 0,
"@ConditionalOnMissingClass annotations must specify at least one class value");
for (String className : classNames) {
if (logger.isDebugEnabled()) {
logger.debug(checking + "Looking for class: " + className);
}
if (ClassUtils.isPresent(className, context.getClassLoader())) {
if (logger.isDebugEnabled()) {
logger.debug(checking + "Found class: " + className
+ " (search terminated with matches=false)");
}
return false;
}
}
}
if (logger.isDebugEnabled()) {
logger.debug(checking + "Match result is: true");
}
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());
}
}
}
// FIXME merge with OnClassCondition
}

View File

@@ -0,0 +1,61 @@
/*
* 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.zero.context.condition;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/**
* {@link Condition} that checks for a web application context and returns false if one is
* found.
*
* @author Dave Syer
* @see ConditionalOnNotWebApplication
*/
class OnNotWebApplicationCondition implements Condition {
private static Log logger = LogFactory.getLog(OnNotWebApplicationCondition.class);
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String checking = ConditionLogUtils.getPrefix(logger, metadata);
if (!ClassUtils.isPresent(
"org.springframework.web.context.support.GenericWebApplicationContext",
null)) {
if (logger.isDebugEnabled()) {
logger.debug(checking + "Web application classes not found");
}
return true;
}
boolean result = !StringUtils.arrayToCommaDelimitedString(
context.getBeanFactory().getRegisteredScopeNames()).contains("session");
if (logger.isDebugEnabled()) {
logger.debug(checking + "Web application context found: " + !result);
}
return result;
}
}

View File

@@ -0,0 +1,85 @@
/*
* 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.zero.context.condition;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.Assert;
import org.springframework.util.MultiValueMap;
/**
* {@link Condition} that checks for specific resources.
*
* @author Dave Syer
* @see ConditionalOnResource
*/
class OnResourceCondition implements Condition {
private static Log logger = LogFactory.getLog(OnResourceCondition.class);
private ResourceLoader defaultResourceLoader = new DefaultResourceLoader();
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String checking = ConditionLogUtils.getPrefix(logger, metadata);
MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes(
ConditionalOnResource.class.getName(), true);
ResourceLoader loader = context.getResourceLoader() == null ? this.defaultResourceLoader
: context.getResourceLoader();
if (attributes != null) {
List<String> locations = new ArrayList<String>();
collectValues(locations, attributes.get("resources"));
Assert.isTrue(locations.size() > 0,
"@ConditionalOnResource annotations must specify at least one resource location");
for (String location : locations) {
if (logger.isDebugEnabled()) {
logger.debug(checking + "Checking for resource: " + location);
}
if (!loader.getResource(location).exists()) {
if (logger.isDebugEnabled()) {
logger.debug(checking + "Resource not found: " + location
+ " (search terminated with matches=false)");
}
return false;
}
}
}
if (logger.isDebugEnabled()) {
logger.debug(checking + "Match result is: true");
}
return true;
}
private void collectValues(List<String> names, List<Object> values) {
for (Object value : values) {
for (Object item : (Object[]) value) {
names.add((String) item);
}
}
}
}

View File

@@ -0,0 +1,61 @@
/*
* 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.zero.context.condition;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.context.support.StandardServletEnvironment;
/**
* {@link Condition} that checks for a web application context.
*
* @author Dave Syer
* @see ConditionalOnWebApplication
*/
class OnWebApplicationCondition implements Condition {
private static Log logger = LogFactory.getLog(OnWebApplicationCondition.class);
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String checking = ConditionLogUtils.getPrefix(logger, metadata);
if (!ClassUtils.isPresent(
"org.springframework.web.context.support.GenericWebApplicationContext",
null)) {
if (logger.isDebugEnabled()) {
logger.debug(checking + "Web application classes not found");
}
return false;
}
boolean result = StringUtils.arrayToCommaDelimitedString(
context.getBeanFactory().getRegisteredScopeNames()).contains("session")
|| context.getEnvironment() instanceof StandardServletEnvironment;
if (logger.isDebugEnabled()) {
logger.debug(checking + "Web application context found: " + result);
}
return result;
}
// FIXME merge with OnNotWeb...
}

View File

@@ -0,0 +1,399 @@
/*
* 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.zero.context.embedded;
import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.security.CodeSource;
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;
/**
* Abstract base class for {@link EmbeddedServletContainerFactory} implementations.
*
* @author Phillip Webb
* @author Dave Syer
*/
public abstract class AbstractEmbeddedServletContainerFactory implements
ConfigurableEmbeddedServletContainerFactory {
private static final String[] COMMON_DOC_ROOTS = { "src/main/webapp", "public",
"static" };
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>();
private InetAddress address;
/**
* 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) {
checkPort(port);
this.port = 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) {
checkContextPath(contextPath);
checkPort(port);
this.contextPath = contextPath;
this.port = 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
*/
@Override
public void setContextPath(String contextPath) {
checkContextPath(contextPath);
this.contextPath = contextPath;
}
private void checkContextPath(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 '/'");
}
}
}
/**
* 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.
*/
@Override
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. Use port 0 to switch off the server completely.
*
* @param port the port to set
*/
@Override
public void setPort(int port) {
checkPort(port);
this.port = port;
}
private void checkPort(int port) {
if (port < 0 || port > 65535) {
throw new IllegalArgumentException("Port must be between 1 and 65535");
}
}
/**
* Returns the port that the embedded servlet container should listen on.
*/
@Override
public int getPort() {
return this.port;
}
/**
* If you need the server to bind to a specific network address, provide one here.
*
* @param address the address to set (defaults to null)
*/
@Override
public void setAddress(InetAddress address) {
this.address = address;
}
/**
* @return the address the embedded container binds to
*/
@Override
public InetAddress getAddress() {
return this.address;
}
/**
* Sets {@link ServletContextInitializer} that should be applied in addition to
* {@link #getEmbeddedServletContainer(ServletContextInitializer...)} parameters. This
* method will replace any previously set or added initializers.
* @param initializers the initializers to set
* @see #addInitializers
* @see #getInitializers
*/
@Override
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 #getEmbeddedServletContainer(ServletContextInitializer...)} parameters.
* @param initializers the initializers to add
* @see #setInitializers
* @see #getInitializers
*/
@Override
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 #getEmbeddedServletContainer(ServletContextInitializer...)}
* parameters.
* @return the initializers
*/
@Override
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
*/
@Override
public void setDocumentRoot(File documentRoot) {
this.documentRoot = documentRoot;
}
/**
* Returns the document root which will be used by the web context to serve static
* files.
*/
@Override
public File getDocumentRoot() {
return this.documentRoot;
}
/**
* Sets the error pages that will be used when handling exceptions.
* @param errorPages the error pages
*/
@Override
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
*/
@Override
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.
*/
@Override
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
*/
@Override
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
*/
@Override
public boolean isRegisterJspServlet() {
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
*/
@Override
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
*/
@Override
public boolean isRegisterDefaultServlet() {
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
*/
@Override
public void setJspServletClassName(String jspServletClassName) {
this.jspServletClassName = jspServletClassName;
}
/**
* @return the JSP 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 file = getDocumentRoot();
file = file != null ? file : getWarFileDocumentRoot();
file = file != null ? file : getCommonDocumentRoot();
if (file == null && this.logger.isWarnEnabled()) {
this.logger.warn("None of the document roots "
+ Arrays.asList(COMMON_DOC_ROOTS)
+ " point to a directory and will be ignored.");
}
return file;
}
private File getWarFileDocumentRoot() {
File warFile = getCodeSourceArchive();
if (warFile.exists() && !warFile.isDirectory()
&& warFile.getName().toLowerCase().endsWith(".war")) {
return warFile.getAbsoluteFile();
}
return null;
}
private File getCommonDocumentRoot() {
for (String commonDocRoot : COMMON_DOC_ROOTS) {
File root = new File(commonDocRoot);
if (root != null && root.exists() && root.isDirectory()) {
return root.getAbsoluteFile();
}
}
return null;
}
private File getCodeSourceArchive() {
try {
CodeSource codeSource = getClass().getProtectionDomain().getCodeSource();
URL location = (codeSource == null ? null : codeSource.getLocation());
if (location == null) {
return null;
}
String path = location.getPath();
URLConnection connection = location.openConnection();
if (connection instanceof JarURLConnection) {
path = ((JarURLConnection) connection).getJarFile().getName();
}
if (path.indexOf("!/") != -1) {
path = path.substring(0, path.indexOf("!/"));
}
return new File(path);
}
catch (IOException ex) {
return null;
}
}
}

View File

@@ -0,0 +1,196 @@
/*
* 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.zero.context.embedded;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
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
* @see #register(Class...)
* @see #scan(String...)
* @see EmbeddedWebApplicationContext
* @see AnnotationConfigWebApplicationContext
*/
public class AnnotationConfigEmbeddedWebApplicationContext extends
EmbeddedWebApplicationContext {
private final AnnotatedBeanDefinitionReader reader;
private final ClassPathBeanDefinitionScanner scanner;
private Class<?>[] annotatedClasses;
private String[] basePackages;
/**
* 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 final void register(Class<?>... annotatedClasses) {
this.annotatedClasses = annotatedClasses;
Assert.notEmpty(annotatedClasses,
"At least one annotated class must be specified");
}
/**
* 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 final void scan(String... basePackages) {
this.basePackages = basePackages;
Assert.notEmpty(basePackages, "At least one base package must be specified");
}
@Override
protected void prepareRefresh() {
this.scanner.clearCache();
super.prepareRefresh();
}
@Override
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
super.postProcessBeanFactory(beanFactory);
if (this.basePackages != null && this.basePackages.length > 0) {
this.scanner.scan(this.basePackages);
}
if (this.annotatedClasses != null && this.annotatedClasses.length > 0) {
this.reader.register(this.annotatedClasses);
}
}
@Override
public final void refresh() throws BeansException, IllegalStateException {
super.refresh();
}
}

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.zero.context.embedded;
import java.io.File;
import java.net.InetAddress;
import java.util.List;
import java.util.Set;
/**
* Simple interface that represents customizations to an
* {@link EmbeddedServletContainerFactory}.
*
* @author Dave Syer
* @see EmbeddedServletContainerFactory
*/
public interface ConfigurableEmbeddedServletContainerFactory extends
EmbeddedServletContainerFactory {
void setContextPath(String contextPath);
String getContextPath();
void setPort(int port);
int getPort();
void setAddress(InetAddress address);
InetAddress getAddress();
void setInitializers(List<? extends ServletContextInitializer> initializers);
void setJspServletClassName(String jspServletClassName);
boolean isRegisterDefaultServlet();
void setRegisterJspServlet(boolean registerJspServlet);
boolean isRegisterJspServlet();
void setRegisterDefaultServlet(boolean registerDefaultServlet);
Set<ErrorPage> getErrorPages();
void addErrorPages(ErrorPage... errorPages);
void setErrorPages(Set<ErrorPage> errorPages);
File getDocumentRoot();
void setDocumentRoot(File documentRoot);
List<ServletContextInitializer> getInitializers();
void addInitializers(ServletContextInitializer... initializers);
}

View File

@@ -0,0 +1,51 @@
/*
* 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.zero.context.embedded;
/**
* Simple interface that represents a fully configured 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
* @author Dave Syer
* @see EmbeddedServletContainerFactory
*/
public interface EmbeddedServletContainer {
/**
* An empty {@link EmbeddedServletContainer} that does nothing.
*/
public static final EmbeddedServletContainer NONE = new EmbeddedServletContainer() {
@Override
public void stop() throws EmbeddedServletContainerException {
// Do nothing
}
};
/**
* 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,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.zero.context.embedded;
/**
* Strategy interface for customizing auto-configured embedded servlet containers. Any
* beans of this type will get a callback with the container factory before the container
* itself is started, so you can set the port, address, error pages etc. Beware: will be
* called from a BeanPostProcessor (so very early in the ApplicationContext lifecycle), so
* it might be safer to lookup dependencies lazily in the enclosing BeanFactory rather
* than injecting them with <code>@Autowired</code>.
*
* @author Dave Syer
* @see EmbeddedServletContainerCustomizerBeanPostProcessor
*/
public interface EmbeddedServletContainerCustomizer {
/**
* Customize the specified {@link ConfigurableEmbeddedServletContainerFactory}.
* @param factory the factory to customize
*/
void customize(ConfigurableEmbeddedServletContainerFactory factory);
}

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.zero.context.embedded;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
/**
* {@link BeanPostProcessor} that apply all {@link EmbeddedServletContainerCustomizer}s
* from the bean factory to {@link ConfigurableEmbeddedServletContainerFactory} beans.
*
* @author Dave Syer
* @author Phillip Webb
*/
public class EmbeddedServletContainerCustomizerBeanPostProcessor implements
BeanPostProcessor, ApplicationContextAware {
// FIXME should we register this by default, Javadoc in
// EmbeddedServletContainerCustomizer suggests so
private ApplicationContext applicationContext;
private List<EmbeddedServletContainerCustomizer> customizers;
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
if (bean instanceof ConfigurableEmbeddedServletContainerFactory) {
postProcessBeforeInitialization((ConfigurableEmbeddedServletContainerFactory) bean);
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
return bean;
}
private void postProcessBeforeInitialization(
ConfigurableEmbeddedServletContainerFactory bean) {
for (EmbeddedServletContainerCustomizer customizer : getCustomizers()) {
customizer.customize(bean);
}
}
private Collection<EmbeddedServletContainerCustomizer> getCustomizers() {
if (this.customizers == null) {
// Look up does not include the parent context
this.customizers = new ArrayList<EmbeddedServletContainerCustomizer>(
this.applicationContext.getBeansOfType(
EmbeddedServletContainerCustomizer.class).values());
Collections.sort(this.customizers, AnnotationAwareOrderComparator.INSTANCE);
this.customizers = Collections.unmodifiableList(this.customizers);
}
return this.customizers;
}
}

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.zero.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,46 @@
/*
* 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.zero.context.embedded;
import org.springframework.zero.context.embedded.jetty.JettyEmbeddedServletContainerFactory;
import org.springframework.zero.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
/**
* Factory interface that can be used to create {@link EmbeddedServletContainer}s.
* Implementations are encouraged to extends
* {@link AbstractEmbeddedServletContainerFactory} when possible.
*
* @author Phillip Webb
* @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 getEmbeddedServletContainer(
ServletContextInitializer... initializers);
}

View File

@@ -0,0 +1,359 @@
/*
* 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.zero.context.embedded;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import javax.servlet.Filter;
import javax.servlet.MultipartConfigElement;
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.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.util.StringUtils;
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.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
* @see AnnotationConfigEmbeddedWebApplicationContext
* @see XmlEmbeddedWebApplicationContext
* @see EmbeddedServletContainerFactory
*/
public class EmbeddedWebApplicationContext extends GenericWebApplicationContext {
/**
* Constant value for the DispatcherServlet bean name. A Servlet bean with this name
* is deemed to be the "main" servlet and is automatically given a mapping of "/" by
* default. To change the default behaviour you can use a
* {@link ServletRegistrationBean} or a different bean name.
*/
public static final String DISPATCHER_SERVLET_NAME = "dispatcherServlet";
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 && getServletContext() == null) {
EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
this.embeddedServletContainer = containerFactory
.getEmbeddedServletContainer(getSelfInitializer());
}
else if (getServletContext() != null) {
try {
getSelfInitializer().onStartup(getServletContext());
}
catch (ServletException ex) {
throw new ApplicationContextException(
"Cannot initialize servlet context", ex);
}
}
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() {
// Use bean names so that we don't consider the hierarchy
String[] beanNames = getBeanFactory().getBeanNamesForType(
EmbeddedServletContainerFactory.class);
if (beanNames.length == 0) {
throw new ApplicationContextException(
"Unable to start EmbeddedWebApplicationContext due to missing "
+ "EmbeddedServletContainerFactory bean.");
}
if (beanNames.length > 1) {
throw new ApplicationContextException(
"Unable to start EmbeddedWebApplicationContext due to multiple "
+ "EmbeddedServletContainerFactory beans : "
+ StringUtils.arrayToCommaDelimitedString(beanNames));
}
return getBeanFactory().getBean(beanNames[0],
EmbeddedServletContainerFactory.class);
}
/**
* 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);
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(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>();
Set<Object> targets = new HashSet<Object>();
for (Entry<String, ServletContextInitializer> initializerBean : getOrderedBeansOfType(ServletContextInitializer.class)) {
ServletContextInitializer initializer = initializerBean.getValue();
if (initializer instanceof RegistrationBean) {
targets.add(((RegistrationBean) initializer).getRegistrationTarget());
}
initializers.add(initializer);
}
List<Entry<String, Servlet>> servletBeans = getOrderedBeansOfType(Servlet.class);
for (Entry<String, Servlet> servletBean : servletBeans) {
final String name = servletBean.getKey();
Servlet servlet = servletBean.getValue();
if (targets.contains(servlet)) {
continue;
}
String url = (servletBeans.size() == 1 ? "/" : "/" + name + "/*");
if (name.equals(DISPATCHER_SERVLET_NAME)) {
url = "/"; // always map the main dispatcherServlet to "/"
}
ServletRegistrationBean registration = new ServletRegistrationBean(servlet,
url);
registration.setName(name);
registration.setMultipartConfig(getMultipartConfig());
initializers.add(registration);
}
for (Entry<String, Filter> filterBean : getOrderedBeansOfType(Filter.class)) {
String name = filterBean.getKey();
Filter filter = filterBean.getValue();
if (targets.contains(filter)) {
continue;
}
FilterRegistrationBean registration = new FilterRegistrationBean(filter);
registration.setName(name);
initializers.add(registration);
}
return initializers;
}
private MultipartConfigElement getMultipartConfig() {
List<Entry<String, MultipartConfigElement>> beans = getOrderedBeansOfType(MultipartConfigElement.class);
if (beans.isEmpty()) {
return null;
}
return beans.get(0).getValue();
}
/**
* 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> List<Entry<String, T>> getOrderedBeansOfType(Class<T> type) {
List<Entry<String, T>> beans = new ArrayList<Entry<String, T>>();
Comparator<Entry<String, T>> comparator = 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());
Collections.sort(beans, comparator);
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,135 @@
/*
* 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.zero.context.embedded;
import org.springframework.http.HttpStatus;
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
*/
public class ErrorPage {
private String path;
private Class<? extends Throwable> exception = null;
private HttpStatus status = null;
public ErrorPage(String path) {
super();
this.path = path;
}
public ErrorPage(HttpStatus 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
*/
public HttpStatus getStatus() {
return this.status;
}
/**
* The HTTP status value that this error page matches.
*
* @return the status value (or 0 for a page that matches any status)
*/
public int getStatusCode() {
return this.status == null ? 0 : this.status.value();
}
/**
* 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 == null && 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.getStatusCode();
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,264 @@
/*
* 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.zero.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;
/**
* 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
* @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) {
Assert.notNull(filter, "Filter must not be null");
Assert.notNull(servletRegistrationBeans,
"ServletRegistrationBeans must not be null");
this.filter = filter;
for (ServletRegistrationBean servletRegistrationBean : servletRegistrationBeans) {
this.servletRegistrationBeans.add(servletRegistrationBean);
}
}
/**
* 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(getName(), this.filter));
}
@Override
public Object getRegistrationTarget() {
return 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,128 @@
/*
* 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.zero.context.embedded;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.servlet.Registration;
import org.springframework.core.Conventions;
import org.springframework.util.Assert;
/**
* Base class for {@link ServletRegistrationBean servlet} and
* {@link FilterRegistrationBean filter} registration beans.
*
* @author Phillip Webb
* @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;
}
/**
* @return the name
*/
public String getName() {
return getOrDeduceName(getRegistrationTarget());
}
/**
* 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 this.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);
}
/**
* The target of the registration (e.g. a Servlet or a Filter) that can be used to
* guess its name if none is supplied explicitly.
*
* @return the target of this registration
*/
public abstract Object getRegistrationTarget();
/**
* 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
*/
private 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,53 @@
/*
* 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.zero.context.embedded;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import org.springframework.web.SpringServletContainerInitializer;
import org.springframework.web.WebApplicationInitializer;
/**
* Interface used to configure a Servlet 3.0+ {@link ServletContext context}
* programmatically. Unlike {@link WebApplicationInitializer}, classes that implement this
* interface (and do not implement {@link WebApplicationInitializer}) will <b>not</b> be
* detected by {@link SpringServletContainerInitializer} and hence will not be
* automatically bootstrapped by the Servlet container.
*
* <p>
* This interface is primarily designed to allow {@link ServletContextInitializer}s to be
* managed by Spring and not the Servlet container.
*
* <p>
* For configuration examples see {@link WebApplicationInitializer}.
*
* @author Phillip Webb
* @see WebApplicationInitializer
*/
public interface ServletContextInitializer {
/**
* Configure the given {@link ServletContext} with any servlets, filters, listeners
* context-params and attributes necessary for initialization.
* @param servletContext the {@code ServletContext} to initialize
* @throws ServletException if any call against the given {@code ServletContext}
* throws a {@code ServletException}
*/
void onStartup(ServletContext servletContext) throws ServletException;
}

View File

@@ -0,0 +1,174 @@
/*
* 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.zero.context.embedded;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Set;
import javax.servlet.MultipartConfigElement;
import javax.servlet.Servlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;
import org.springframework.util.Assert;
/**
* 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
* @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 MultipartConfigElement multipartConfig;
/**
* Create a new {@link ServletRegistrationBean} instance.
*/
public ServletRegistrationBean() {
}
/**
* Create a new {@link ServletRegistrationBean} instance with the specified
* {@link Servlet} and URL mappings.
* @param servlet the servlet being mapped
* @param urlMappings the URLs being mapped
*/
public ServletRegistrationBean(Servlet servlet, String... urlMappings) {
Assert.notNull(servlet, "Servlet must not be null");
Assert.notNull(urlMappings, "UrlMappings must not be null");
this.servlet = servlet;
this.urlMappings.addAll(Arrays.asList(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;
}
/**
* Set the the {@link MultipartConfigElement multi-part configuration}.
* @param multipartConfig the muti-part configuration to set or {@code null}
*/
public void setMultipartConfig(MultipartConfigElement multipartConfig) {
this.multipartConfig = multipartConfig;
}
/**
* Returns the {@link MultipartConfigElement multi-part configuration} to be applied
* or {@code null}.
*/
public MultipartConfigElement getMultipartConfig() {
return this.multipartConfig;
}
/**
* Returns the servlet name that will be registered.
*/
public String getServletName() {
return getName();
}
@Override
public Object getRegistrationTarget() {
return 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));
}
/**
* 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);
if (this.multipartConfig != null) {
registration.setMultipartConfig(this.multipartConfig);
}
}
}

View File

@@ -0,0 +1,56 @@
/*
* 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.zero.context.embedded;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import org.springframework.util.Assert;
import org.springframework.web.context.ConfigurableWebApplicationContext;
import org.springframework.web.context.support.ServletContextAwareProcessor;
/**
* Variant of {@link ServletContextAwareProcessor} for use with a
* {@link ConfigurableWebApplicationContext}. Can be used when registering the processor
* can occur before the {@link ServletContext} or {@link ServletConfig} have been
* initialized.
*
* @author Phillip Webb
*/
public class WebApplicationContextServletContextAwareProcessor extends
ServletContextAwareProcessor {
private final ConfigurableWebApplicationContext webApplicationContext;
public WebApplicationContextServletContextAwareProcessor(
ConfigurableWebApplicationContext webApplicationContext) {
Assert.notNull(webApplicationContext, "WebApplicationContext must not be null");
this.webApplicationContext = webApplicationContext;
}
@Override
protected ServletContext getServletContext() {
ServletContext servletContext = this.webApplicationContext.getServletContext();
return (servletContext != null ? servletContext : super.getServletContext());
}
@Override
protected ServletConfig getServletConfig() {
ServletConfig servletConfig = this.webApplicationContext.getServletConfig();
return (servletConfig != null ? servletConfig : super.getServletConfig());
}
}

View File

@@ -0,0 +1,139 @@
/*
* 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.zero.context.embedded;
import org.springframework.beans.BeansException;
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() {
this.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 final 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 final 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 final 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.reader.loadBeanDefinitions(resources);
}
@Override
public final void refresh() throws BeansException, IllegalStateException {
super.refresh();
}
}

View File

@@ -0,0 +1,77 @@
/*
* 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.zero.context.embedded.jetty;
import org.eclipse.jetty.server.Server;
import org.springframework.util.Assert;
import org.springframework.zero.context.embedded.EmbeddedServletContainer;
import org.springframework.zero.context.embedded.EmbeddedServletContainerException;
/**
* {@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
* @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 (InterruptedException ex) {
Thread.currentThread().interrupt();
// No drama
}
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,288 @@
/*
* 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.zero.context.embedded.jetty;
import java.io.File;
import java.net.InetSocketAddress;
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.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.zero.context.embedded.AbstractEmbeddedServletContainerFactory;
import org.springframework.zero.context.embedded.EmbeddedServletContainer;
import org.springframework.zero.context.embedded.EmbeddedServletContainerFactory;
import org.springframework.zero.context.embedded.ErrorPage;
import org.springframework.zero.context.embedded.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
* listens for HTTP requests on port 8080.
*
* @author Phillip Webb
* @author Dave Syer
* @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;
private WebAppContext context = new WebAppContext();
/**
* 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 EmbeddedServletContainer getEmbeddedServletContainer(
ServletContextInitializer... initializers) {
if (getPort() == 0) {
return EmbeddedServletContainer.NONE;
}
Server server = new Server(new InetSocketAddress(getAddress(), getPort()));
if (this.resourceLoader != null) {
this.context.setClassLoader(this.resourceLoader.getClassLoader());
}
String contextPath = getContextPath();
this.context.setContextPath(StringUtils.hasLength(contextPath) ? contextPath
: "/");
configureDocumentRoot(this.context);
if (isRegisterDefaultServlet()) {
addDefaultServlet(this.context);
}
if (isRegisterJspServlet()
&& ClassUtils.isPresent(getJspServletClassName(), getClass()
.getClassLoader())) {
addJspServlet(this.context);
}
ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
Configuration[] configurations = getWebAppContextConfigurations(this.context,
initializersToUse);
this.context.setConfigurations(configurations);
postProcessWebAppContext(this.context);
server.setHandler(this.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();
addJettyErrorPages(errorHandler, getErrorPages());
}
};
}
/**
* 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 started.
*
* @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));
}
private void addJettyErrorPages(ErrorHandler errorHandler,
Collection<ErrorPage> errorPages) {
if (errorHandler instanceof ErrorPageErrorHandler) {
ErrorPageErrorHandler handler = (ErrorPageErrorHandler) errorHandler;
for (ErrorPage errorPage : errorPages) {
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.getStatusCode(),
errorPage.getPath());
}
}
}
}
}
}

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.zero.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.zero.context.embedded.ServletContextInitializer;
/**
* Jetty {@link Configuration} that calls {@link ServletContextInitializer}s.
*
* @author Phillip Webb
*/
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 = ServletContextInitializerConfiguration.this.contextHandler
.getServletContext();
for (ServletContextInitializer initializer : ServletContextInitializerConfiguration.this.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.zero.context.embedded.EmbeddedServletContainer EmbeddedServletContainers}.
*/
package org.springframework.zero.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.zero.context.embedded.EmbeddedServletContainerFactory
* @see org.springframework.zero.context.embedded.EmbeddedWebApplicationContext
*/
package org.springframework.zero.context.embedded;

View File

@@ -0,0 +1,159 @@
/*
* 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.zero.context.embedded.properties;
import java.io.File;
import java.net.InetAddress;
import javax.validation.constraints.NotNull;
import org.apache.catalina.valves.AccessLogValve;
import org.apache.catalina.valves.RemoteIpValve;
import org.springframework.util.StringUtils;
import org.springframework.zero.context.embedded.ConfigurableEmbeddedServletContainerFactory;
import org.springframework.zero.context.embedded.EmbeddedServletContainerCustomizer;
import org.springframework.zero.context.embedded.EmbeddedServletContainerCustomizerBeanPostProcessor;
import org.springframework.zero.context.embedded.EmbeddedServletContainerFactory;
import org.springframework.zero.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
import org.springframework.zero.context.properties.ConfigurationProperties;
/**
* {@link ConfigurationProperties properties} for a web server (e.g. port and path
* settings). Will be used to customize an {@link EmbeddedServletContainerFactory} when an
* {@link EmbeddedServletContainerCustomizerBeanPostProcessor} is active.
*
* @author Dave Syer
*/
@ConfigurationProperties(name = "server", ignoreUnknownFields = false)
public class ServerProperties implements EmbeddedServletContainerCustomizer {
private int port = 8080;
private InetAddress address;
@NotNull
private String contextPath = "";
private Tomcat tomcat = new Tomcat();
public Tomcat getTomcat() {
return this.tomcat;
}
public String getContextPath() {
return this.contextPath;
}
public void setContextPath(String contextPath) {
this.contextPath = contextPath;
}
public int getPort() {
return this.port;
}
public void setPort(int port) {
this.port = port;
}
public InetAddress getAddress() {
return this.address;
}
public void setAddress(InetAddress address) {
this.address = address;
}
@Override
public void customize(ConfigurableEmbeddedServletContainerFactory factory) {
factory.setPort(getPort());
factory.setAddress(getAddress());
factory.setContextPath(getContextPath());
if (factory instanceof TomcatEmbeddedServletContainerFactory) {
getTomcat().customizeTomcat((TomcatEmbeddedServletContainerFactory) factory);
}
}
public static class Tomcat {
private String accessLogPattern;
private String protocolHeader = "x-forwarded-proto";
private String remoteIpHeader = "x-forwarded-for";
private File basedir;
public File getBasedir() {
return this.basedir;
}
public void setBasedir(File basedir) {
this.basedir = basedir;
}
public String getAccessLogPattern() {
return this.accessLogPattern;
}
public void setAccessLogPattern(String accessLogPattern) {
this.accessLogPattern = accessLogPattern;
}
public String getProtocolHeader() {
return this.protocolHeader;
}
public void setProtocolHeader(String protocolHeader) {
this.protocolHeader = protocolHeader;
}
public String getRemoteIpHeader() {
return this.remoteIpHeader;
}
public void setRemoteIpHeader(String remoteIpHeader) {
this.remoteIpHeader = remoteIpHeader;
}
void customizeTomcat(TomcatEmbeddedServletContainerFactory factory) {
if (getBasedir() != null) {
factory.setBaseDirectory(getBasedir());
}
String remoteIpHeader = getRemoteIpHeader();
String protocolHeader = getProtocolHeader();
if (StringUtils.hasText(remoteIpHeader)
|| StringUtils.hasText(protocolHeader)) {
RemoteIpValve valve = new RemoteIpValve();
valve.setRemoteIpHeader(remoteIpHeader);
valve.setProtocolHeader(protocolHeader);
factory.addContextValves(valve);
}
String accessLogPattern = getAccessLogPattern();
if (accessLogPattern != null) {
AccessLogValve valve = new AccessLogValve();
valve.setPattern(accessLogPattern);
valve.setSuffix(".log");
factory.addContextValves(valve);
}
}
}
}

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.zero.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.zero.context.embedded.ServletContextInitializer;
/**
* Tomcat {@link LifecycleListener} that calls {@link ServletContextInitializer}s.
*
* @author Phillip Webb
*/
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,91 @@
/*
* 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.zero.context.embedded.tomcat;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.startup.Tomcat;
import org.springframework.util.Assert;
import org.springframework.zero.context.embedded.EmbeddedServletContainer;
import org.springframework.zero.context.embedded.EmbeddedServletContainerException;
/**
* {@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
* @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() {
TomcatEmbeddedServletContainer.this.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 ex) {
// swallow and continue
}
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,384 @@
/*
* 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.zero.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.loader.WebappLoader;
import org.apache.catalina.startup.Tomcat;
import org.apache.catalina.startup.Tomcat.FixContextListener;
import org.apache.coyote.AbstractProtocol;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.zero.context.embedded.AbstractEmbeddedServletContainerFactory;
import org.springframework.zero.context.embedded.EmbeddedServletContainer;
import org.springframework.zero.context.embedded.EmbeddedServletContainerException;
import org.springframework.zero.context.embedded.EmbeddedServletContainerFactory;
import org.springframework.zero.context.embedded.ErrorPage;
import org.springframework.zero.context.embedded.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
* listens for HTTP requests on port 8080.
*
* @author Phillip Webb
* @author Dave Syer
* @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 getEmbeddedServletContainer(
ServletContextInitializer... initializers) {
if (getPort() == 0) {
return EmbeddedServletContainer.NONE;
}
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");
customizeConnector(connector);
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());
context.setParentClassLoader(this.resourceLoader != null ? this.resourceLoader
.getClassLoader() : ClassUtils.getDefaultClassLoader());
WebappLoader loader = new WebappLoader(context.getParentClassLoader());
loader.setLoaderClass(TomcatEmbeddedWebappClassLoader.class.getName());
context.setLoader(loader);
if (isRegisterDefaultServlet()) {
addDefaultServlet(context);
}
if (isRegisterJspServlet()
&& 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");
}
// Needs to be protected so it can be used by subclasses
protected void customizeConnector(Connector connector) {
connector.setPort(getPort());
if (connector.getProtocolHandler() instanceof AbstractProtocol
&& getAddress() != null) {
((AbstractProtocol) connector.getProtocolHandler()).setAddress(getAddress());
}
}
/**
* 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.getStatusCode());
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));
}
// FIXME JavaDoc
// FIXME Is this still needed?
public TomcatEmbeddedServletContainerFactory getChildContextFactory(final String name) {
final Server server = this.tomcat.getServer();
return new TomcatEmbeddedServletContainerFactory() {
@Override
public EmbeddedServletContainer getEmbeddedServletContainer(
ServletContextInitializer... initializers) {
if (getPort() == 0) {
return EmbeddedServletContainer.NONE;
}
StandardService service = new StandardService();
service.setName(name);
Connector connector = new Connector(
"org.apache.coyote.http11.Http11NioProtocol");
customizeConnector(connector);
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,59 @@
/*
* 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.zero.context.embedded.tomcat;
import java.net.URL;
import java.net.URLClassLoader;
import org.apache.catalina.loader.WebappClassLoader;
/**
* Extension of Tomcat's {@link WebappClassLoader} that does not consider the
* {@link ClassLoader#getSystemClassLoader() system classloader}. This is required to to
* ensure that any custom context classloader is always used (as is the case with some
* executable archives).
*
* @author Phillip Webb
*/
public class TomcatEmbeddedWebappClassLoader extends WebappClassLoader {
public TomcatEmbeddedWebappClassLoader() {
super();
this.system = new EmbeddedSystemClassLoader();
}
public TomcatEmbeddedWebappClassLoader(ClassLoader parent) {
super(parent);
this.system = new EmbeddedSystemClassLoader();
}
private static class EmbeddedSystemClassLoader extends URLClassLoader {
public EmbeddedSystemClassLoader() {
super(new URL[] {});
}
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(
"System ClassLoader disabled for embedded context, unable to load "
+ name);
}
}
}

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.zero.context.embedded.EmbeddedServletContainer EmbeddedServletContainers}.
*/
package org.springframework.zero.context.embedded.tomcat;

View File

@@ -0,0 +1,269 @@
/*
* 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.zero.context.initializer;
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.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;
import org.springframework.zero.config.YamlProcessor.ArrayDocumentMatcher;
import org.springframework.zero.config.YamlProcessor.DocumentMatcher;
import org.springframework.zero.config.YamlProcessor.MatchStatus;
import org.springframework.zero.config.YamlPropertiesFactoryBean;
/**
* {@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.yml' 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.yml' 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 + 10;
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
List<String> candidates = getCandidateLocations();
// 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() {
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 == null ? null : searchLocations.clone());
}
/**
* 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 ex) {
throw new IllegalStateException("Could not load properties file from "
+ resource, ex);
}
}
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 MatchStatus matches(Properties properties) {
String[] profiles = applicationContext.getEnvironment()
.getActiveProfiles();
if (profiles.length == 0) {
profiles = new String[] { "default" };
}
return new ArrayDocumentMatcher("spring.profiles", profiles)
.matches(properties);
}
});
matchers.add(new DocumentMatcher() {
@Override
public MatchStatus 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 MatchStatus.FOUND;
}
else {
return MatchStatus.NOT_FOUND;
}
}
});
factory.setMatchDefault(false);
factory.setDocumentMatchers(matchers);
factory.setResources(new Resource[] { resource });
return factory.getObject();
}
}
}

View File

@@ -0,0 +1,92 @@
/*
* 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.zero.context.initializer;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.Ordered;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.util.StringUtils;
/**
* {@link ApplicationContextInitializer} that set the Spring
* {@link ApplicationContext#getId() ApplicationContext ID}. The following environment
* properties will be consulted to create the ID:
* <ul>
* <li>spring.application.name</li>
* <li>vcap.application.name</li>
* <li>spring.config.name</li>
* </ul>
* If no property is set the ID 'application' will be used.
*
* <p>
* In addition the following environment properties will be consulted to append a relevant
* port or index:
*
* <ul>
* <li>spring.application.index</li>
* <li>vcap.application.instance_index</li>
* <li>PORT</li>
* </ul>
*
* @author Dave Syer
*/
public class ContextIdApplicationContextInitializer implements
ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {
private String name = "${spring.application.name:${vcap.application.name:${spring.config.name:application}}}";
private int index = -1;
private int order = Integer.MAX_VALUE - 10;
public void setOrder(int order) {
this.order = order;
}
@Override
public int getOrder() {
return this.order;
}
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
applicationContext.setId(getApplicationId(applicationContext.getEnvironment()));
}
private String getApplicationId(ConfigurableEnvironment environment) {
String name = environment.resolvePlaceholders(this.name);
int index = environment.getProperty("PORT", Integer.class, this.index);
index = environment.getProperty("vcap.application.instance_index", Integer.class,
index);
index = environment.getProperty("spring.application.index", Integer.class, index);
if (index >= 0) {
name = name + ":" + index;
}
else {
// FIXME do we want this
String profiles = StringUtils.arrayToCommaDelimitedString(environment
.getActiveProfiles());
if (StringUtils.hasText(profiles)) {
name = name + ":" + profiles;
}
}
return name;
}
}

View File

@@ -0,0 +1,117 @@
/*
* 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.zero.context.initializer;
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,219 @@
/*
* 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.zero.context.initializer;
import java.lang.management.ManagementFactory;
import java.util.HashMap;
import java.util.Map;
import org.springframework.context.ApplicationContext;
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.util.ClassUtils;
import org.springframework.util.Log4jConfigurer;
import org.springframework.zero.logging.JavaLoggerConfigurer;
import org.springframework.zero.logging.LogbackConfigurer;
/**
* 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 LoggingApplicationContextInitializer}.
* </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
* @author Phillip Webb
*/
public class LoggingApplicationContextInitializer implements
ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {
private static final Map<String, String> ENVIRONMENT_SYSTEM_PROPERTY_MAPPING;
static {
ENVIRONMENT_SYSTEM_PROPERTY_MAPPING = new HashMap<String, String>();
ENVIRONMENT_SYSTEM_PROPERTY_MAPPING.put("logging.file", "LOG_FILE");
ENVIRONMENT_SYSTEM_PROPERTY_MAPPING.put("logging.path", "LOG_PATH");
ENVIRONMENT_SYSTEM_PROPERTY_MAPPING.put("PID", "PID");
}
private int order = Integer.MIN_VALUE + 11;
/**
* Initialize the logging system according to preferences expressed through the
* {@link Environment} and the classpath.
*/
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
ConfigurableEnvironment environment = applicationContext.getEnvironment();
for (Map.Entry<String, String> mapping : ENVIRONMENT_SYSTEM_PROPERTY_MAPPING
.entrySet()) {
if (environment.containsProperty(mapping.getKey())) {
System.setProperty(mapping.getValue(),
environment.getProperty(mapping.getKey()));
}
}
if (System.getProperty("PID") == null) {
System.setProperty("PID", getPid());
}
LoggingSystem system = LoggingSystem.get(applicationContext.getClassLoader());
system.init(applicationContext);
}
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;
}
private static enum LoggingSystem {
/**
* Log4J
*/
LOG4J("org.apache.log4j.PropertyConfigurator", "log4j.xml", "log4j.properties") {
@Override
protected void doInit(ApplicationContext applicationContext,
String configLocation) throws Exception {
Log4jConfigurer.initLogging(configLocation);
}
},
/**
* Logback
*/
LOGBACK("ch.qos.logback.core.Appender", "logback.xml") {
@Override
protected void doInit(ApplicationContext applicationContext,
String configLocation) throws Exception {
LogbackConfigurer.initLogging(configLocation);
}
},
/**
* Java Util Logging
*/
JAVA(null, "logging.properties") {
@Override
protected void doInit(ApplicationContext applicationContext,
String configLocation) throws Exception {
JavaLoggerConfigurer.initLogging(configLocation);
}
};
private final String className;
private final String[] paths;
private LoggingSystem(String className, String... paths) {
this.className = className;
this.paths = paths;
}
public void init(ApplicationContext applicationContext) {
String configLocation = getConfigLocation(applicationContext);
try {
doInit(applicationContext, configLocation);
}
catch (Exception ex) {
throw new IllegalStateException("Cannot initialize logging from "
+ configLocation, ex);
}
}
protected abstract void doInit(ApplicationContext applicationContext,
String configLocation) throws Exception;
private String getConfigLocation(ApplicationContext applicationContext) {
Environment environment = applicationContext.getEnvironment();
ClassLoader classLoader = applicationContext.getClassLoader();
// User specified config
if (environment.containsProperty("logging.config")) {
return environment.getProperty("logging.config");
}
// Common patterns
for (String path : this.paths) {
ClassPathResource resource = new ClassPathResource(path, classLoader);
if (resource.exists()) {
return "classpath:" + path;
}
}
// Fallback to the default
String defaultPath = ClassUtils.getPackageName(JavaLoggerConfigurer.class);
defaultPath = defaultPath.replace(".", "/");
defaultPath = defaultPath + "/" + this.paths[this.paths.length - 1];
return "classpath:" + defaultPath;
}
public static LoggingSystem get(ClassLoader classLoader) {
for (LoggingSystem loggingSystem : values()) {
String className = loggingSystem.className;
if (className == null || ClassUtils.isPresent(className, classLoader)) {
return loggingSystem;
}
}
return JAVA;
}
}
}

View File

@@ -0,0 +1,205 @@
/*
* 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.zero.context.initializer;
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 org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.Ordered;
import org.springframework.core.env.CommandLinePropertySource;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.util.StringUtils;
import org.springframework.zero.config.JsonParser;
import org.springframework.zero.config.JsonParserFactory;
/**
* An {@link ApplicationContextInitializer} that knows where to find VCAP (a.k.a. Cloud
* Foundry) meta data in the existing environment. It parses out the VCAP_APPLICATION and
* VCAP_SERVICES meta data and dumps it in a form that is easily consumed by
* {@link Environment} users. If the app is running in Cloud Foundry then both meta data
* items are JSON objects encoded in OS environment variables. VCAP_APPLICATION is a
* shallow hash with basic information about the application (name, instance id, instance
* index, etc.), and VCAP_SERVICES is a hash of lists where the keys are service labels
* and the values are lists of hashes of service instance meta data. Examples are:
*
* <pre>
* VCAP_APPLICATION: {"instance_id":"2ce0ac627a6c8e47e936d829a3a47b5b","instance_index":0,
* "version":"0138c4a6-2a73-416b-aca0-572c09f7ca53","name":"foo",
* "uris":["foo.cfapps.io"], ...}
* VCAP_SERVICES: {"rds-mysql-1.0":[{"name":"mysql","label":"rds-mysql-1.0","plan":"10mb",
* "credentials":{"name":"d04fb13d27d964c62b267bbba1cffb9da","hostname":"mysql-service-public.clqg2e2w3ecf.us-east-1.rds.amazonaws.com",
* "host":"mysql-service-public.clqg2e2w3ecf.us-east-1.rds.amazonaws.com","port":3306,"user":"urpRuqTf8Cpe6",
* "username":"urpRuqTf8Cpe6","password":"pxLsGVpsC9A5S"}
* }]}
* </pre>
*
* These objects are flattened into properties. The VCAP_APPLICATION object goes straight
* to <code>vcap.application.*</code> in a fairly obvious way, and the VCAP_SERVICES
* object is unwrapped so that it is a hash of objects with key equal to the service
* instance name (e.g. "mysql" in the example above), and value equal to that instances
* properties, and then flattened in the smae way. E.g.
*
* <pre>
* vcap.application.instance_id: 2ce0ac627a6c8e47e936d829a3a47b5b
* vcap.application.version: 0138c4a6-2a73-416b-aca0-572c09f7ca53
* vcap.application.name: foo
* vcap.application.uris[0]: foo.cfapps.io
*
* vcap.services.mysql.name: mysql
* vcap.services.mysql.label: rds-mysql-1.0
* vcap.services.mysql.credentials.name: d04fb13d27d964c62b267bbba1cffb9da
* vcap.services.mysql.credentials.port: 3306
* vcap.services.mysql.credentials.host: mysql-service-public.clqg2e2w3ecf.us-east-1.rds.amazonaws.com
* vcap.services.mysql.credentials.username: urpRuqTf8Cpe6
* vcap.services.mysql.credentials.password: pxLsGVpsC9A5S
* ...
* </pre>
*
* @author Dave Syer
*/
public class VcapApplicationContextInitializer implements
ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {
private static final String VCAP_APPLICATION = "VCAP_APPLICATION";
private static final String VCAP_SERVICES = "VCAP_SERVICES";
private int order = Integer.MIN_VALUE + 11;
private JsonParser parser = JsonParserFactory.getJsonParser();
public void setOrder(int order) {
this.order = order;
}
@Override
public int getOrder() {
return this.order;
}
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
ConfigurableEnvironment environment = applicationContext.getEnvironment();
if (!environment.containsProperty(VCAP_APPLICATION)
&& !environment.containsProperty(VCAP_SERVICES)) {
return;
}
Properties properties = new Properties();
addWithPrefix(properties, getPropertiesFromApplication(environment),
"vcap.application.");
addWithPrefix(properties, getPropertiesFromServices(environment),
"vcap.services.");
MutablePropertySources propertySources = environment.getPropertySources();
if (propertySources
.contains(CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME)) {
propertySources.addAfter(
CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME,
new PropertiesPropertySource("vcap", properties));
}
else {
propertySources.addFirst(new PropertiesPropertySource("vcap", properties));
}
}
private void addWithPrefix(Properties properties, Properties other, String prefix) {
for (String key : other.stringPropertyNames()) {
String prefixed = prefix + key;
properties.setProperty(prefixed, other.getProperty(key));
}
}
private Properties getPropertiesFromApplication(Environment environment) {
Map<String, Object> map = this.parser.parseMap(environment.getProperty(
VCAP_APPLICATION, "{}"));
Properties properties = new Properties();
properties.putAll(map);
return properties;
}
private Properties getPropertiesFromServices(Environment environment) {
Map<String, Object> map = this.parser.parseMap(environment.getProperty(
VCAP_SERVICES, "{}"));
Properties properties = new Properties();
for (Object services : map.values()) {
@SuppressWarnings("unchecked")
List<Object> list = (List<Object>) services;
for (Object object : list) {
@SuppressWarnings("unchecked")
Map<String, Object> service = (Map<String, Object>) object;
String key = (String) service.get("name");
if (key == null) {
key = (String) service.get("label");
}
flatten(properties, service, key);
}
}
return properties;
}
private void flatten(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;
flatten(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) {
flatten(properties,
Collections.singletonMap("[" + (count++) + "]", object), key);
}
}
else {
properties.put(key, value == null ? "" : value);
}
}
}
}

View File

@@ -0,0 +1,73 @@
/*
* 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.zero.context.properties;
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;
/**
* 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 ConfigurationPropertiesBindingPostProcessor
*
* @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,151 @@
/*
* 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.zero.context.properties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.PropertySources;
import org.springframework.validation.Validator;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.zero.context.condition.ConditionalOnClass;
import org.springframework.zero.context.condition.ConditionalOnMissingBean;
/**
* Configuration for binding externalized application properties to
* {@link ConfigurationProperties} beans.
*
* @author Dave Syer
*/
@Configuration
public class ConfigurationPropertiesBindingConfiguration {
public final static String VALIDATOR_BEAN_NAME = "configurationPropertiesValidator";
@Autowired(required = false)
private PropertySourcesPlaceholderConfigurer configurer;
@Autowired(required = false)
private Environment environment;
@Autowired(required = false)
@Qualifier(ConfigurableApplicationContext.CONVERSION_SERVICE_BEAN_NAME)
private ConversionService conversionService;
@Autowired(required = false)
@Qualifier(VALIDATOR_BEAN_NAME)
private Validator validator;
@ConditionalOnMissingBean(name = VALIDATOR_BEAN_NAME)
@ConditionalOnClass(name = "javax.validation.Validator")
protected static class ValidatorConfiguration {
@Bean
protected Validator configurationPropertiesValidator() {
return new LocalValidatorFactoryBean();
}
}
/**
* Lifecycle hook that binds application properties to any bean whose type is
* decorated with {@link ConfigurationProperties} annotation.
*
* @return a bean post processor to bind application properties
*/
@Bean
public ConfigurationPropertiesBindingPostProcessor propertySourcesBinder() {
PropertySources propertySources;
if (this.configurer != null) {
propertySources = extractPropertySources(this.configurer);
}
else if (this.environment instanceof ConfigurableEnvironment) {
propertySources = flattenPropertySources(((ConfigurableEnvironment) this.environment)
.getPropertySources());
}
else {
// empty, so not very useful, but fulfils the contract
propertySources = new MutablePropertySources();
}
ConfigurationPropertiesBindingPostProcessor processor = new ConfigurationPropertiesBindingPostProcessor();
processor.setValidator(this.validator);
processor.setConversionService(this.conversionService);
processor.setPropertySources(propertySources);
return processor;
}
/**
* Flatten out a tree of property sources.
*
* @param propertySources some PropertySources, possibly containing environment
* properties
* @return another PropertySources containing the same properties
*/
private PropertySources flattenPropertySources(PropertySources propertySources) {
MutablePropertySources result = new MutablePropertySources();
for (PropertySource<?> propertySource : propertySources) {
flattenPropertySources(propertySource, result);
}
return result;
}
/**
* Convenience method to allow recursive flattening of property sources.
*
* @param propertySource a property source to flatten
* @param result the cumulative result
*/
private void flattenPropertySources(PropertySource<?> propertySource,
MutablePropertySources result) {
Object source = propertySource.getSource();
if (source instanceof ConfigurableEnvironment) {
ConfigurableEnvironment environment = (ConfigurableEnvironment) source;
for (PropertySource<?> childSource : environment.getPropertySources()) {
flattenPropertySources(childSource, result);
}
}
else {
result.addLast(propertySource);
}
}
/**
* Convenience method to extract PropertySources from an existing (and already
* initialized) PropertySourcesPlaceholderConfigurer. As long as this method is
* executed late enough in the context lifecycle it will come back with data. We can
* rely on the fact that PropertySourcesPlaceholderConfigurer is a
* BeanFactoryPostProcessor and is therefore initialized early.
*
* @param configurer a PropertySourcesPlaceholderConfigurer
* @return some PropertySources
*/
private PropertySources extractPropertySources(
PropertySourcesPlaceholderConfigurer configurer) {
PropertySources propertySources = configurer.getAppliedPropertySources();
// Flatten the sources into a single list so they can be iterated
return flattenPropertySources(propertySources);
}
}

View File

@@ -0,0 +1,137 @@
/*
* 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.zero.context.properties;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.env.PropertySources;
import org.springframework.util.StringUtils;
import org.springframework.validation.Validator;
import org.springframework.zero.bind.PropertiesConfigurationFactory;
/**
* {@link BeanPostProcessor} to bind {@link PropertySources} to beans annotated with
* {@link ConfigurationProperties}.
*
* @author Dave Syer
*/
public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProcessor,
BeanFactoryAware {
private PropertySources propertySources;
private Validator validator;
private ConversionService conversionService;
private DefaultConversionService defaultConversionService = new DefaultConversionService();
private BeanFactory beanFactory;
private boolean initialized = false;
/**
* @param propertySources
*/
public void setPropertySources(PropertySources propertySources) {
this.propertySources = propertySources;
}
/**
* @param validator the validator to set
*/
public void setValidator(Validator validator) {
this.validator = validator;
}
/**
* @param conversionService the conversionService to set
*/
public void setConversionService(ConversionService conversionService) {
this.conversionService = conversionService;
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
@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 || bean instanceof ConfigurationPropertiesHolder) {
postProcessAfterInitialization(bean, beanName, annotation);
}
return bean;
}
private void postProcessAfterInitialization(Object bean, String beanName,
ConfigurationProperties annotation) {
Object target = (bean instanceof ConfigurationPropertiesHolder ? ((ConfigurationPropertiesHolder) bean)
.getTarget() : bean);
PropertiesConfigurationFactory<Object> factory = new PropertiesConfigurationFactory<Object>(
target);
factory.setPropertySources(this.propertySources);
factory.setValidator(this.validator);
// If no explicit conversion service is provided we add one so that (at least)
// comma-separated arrays of convertibles can be bound automatically
factory.setConversionService(this.conversionService == null ? getDefaultConversionService()
: this.conversionService);
if (annotation != null) {
factory.setIgnoreInvalidFields(annotation.ignoreInvalidFields());
factory.setIgnoreUnknownFields(annotation.ignoreUnknownFields());
String targetName = (StringUtils.hasLength(annotation.value()) ? annotation
.value() : annotation.name());
if (StringUtils.hasLength(targetName)) {
factory.setTargetName(targetName);
}
}
try {
factory.bindPropertiesToTarget();
}
catch (Exception ex) {
throw new BeanCreationException(beanName, "Could not bind properties", ex);
}
}
private ConversionService getDefaultConversionService() {
if (!this.initialized && this.beanFactory instanceof ListableBeanFactory) {
for (Converter<?, ?> converter : ((ListableBeanFactory) this.beanFactory)
.getBeansOfType(Converter.class).values()) {
this.defaultConversionService.addConverter(converter);
}
}
return this.defaultConversionService;
}
}

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.zero.context.properties;
/**
* Properties holder registered by {@link EnableConfigurationPropertiesImportSelector} to
* be picked up by {@link ConfigurationPropertiesBindingPostProcessor}.
*
* @author Dave Syer
*/
class ConfigurationPropertiesHolder {
private Object target;
public ConfigurationPropertiesHolder(Object target) {
this.target = target;
}
public Object getTarget() {
return this.target;
}
}

View File

@@ -0,0 +1,48 @@
/*
* 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.zero.context.properties;
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.Bean;
import org.springframework.context.annotation.Import;
/**
* Enable support for {@link ConfigurationProperties} annotated beans.
* {@link ConfigurationProperties} beans can be registered in the standard way (for
* example using {@link Bean @Bean} methods) or, for convenience, can be specified
* directly on this annotation.
*
* @author Dave Syer
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EnableConfigurationPropertiesImportSelector.class)
public @interface EnableConfigurationProperties {
/**
* Convenient way to quickly register {@link ConfigurationProperties} beans with
* Spring. Standard Spring Beans will also be scanned regardless of this value.
*/
Class<?>[] value() default {};
}

View File

@@ -0,0 +1,111 @@
/*
* 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.zero.context.properties;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.MultiValueMap;
/**
* Import selector that sets up binding of external properties to configuration classes
* (see {@link ConfigurationProperties}). It either registers a
* {@link ConfigurationProperties} bean or not, depending on whether the enclosing
* {@link EnableConfigurationProperties} explicitly declares one. If none is declared then
* a bean post processor will still kick in for any beans annotated as external
* configuration. If one is declared then it a bean definition is registered with id equal
* to the class name (thus an application context usually only contains one
* {@link ConfigurationProperties} bean of each unique type).
*
* @author Dave Syer
*/
class EnableConfigurationPropertiesImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata metadata) {
MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes(
EnableConfigurationProperties.class.getName(), false);
Object[] type = (Object[]) attributes.getFirst("value");
if (type == null || type.length == 0) {
return new String[] { ConfigurationPropertiesBindingConfiguration.class
.getName() };
}
return new String[] { ConfigurationPropertiesBeanRegistrar.class.getName(),
ConfigurationPropertiesBindingConfiguration.class.getName() };
}
public static class ConfigurationPropertiesBeanRegistrar implements
ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
MultiValueMap<String, Object> attributes = metadata
.getAllAnnotationAttributes(
EnableConfigurationProperties.class.getName(), false);
List<Class<?>> types = collectClasses(attributes.get("value"));
for (Class<?> type : types) {
String name = type.getName();
if (!registry.containsBeanDefinition(name)) {
registerBeanDefinition(registry, type, name);
}
}
}
private List<Class<?>> collectClasses(List<Object> list) {
ArrayList<Class<?>> result = new ArrayList<Class<?>>();
for (Object object : list) {
for (Object value : (Object[]) object) {
if (value instanceof Class && value != void.class) {
result.add((Class<?>) value);
}
}
}
return result;
}
private void registerBeanDefinition(BeanDefinitionRegistry registry,
Class<?> type, String name) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(type);
AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
registry.registerBeanDefinition(name, beanDefinition);
ConfigurationProperties properties = AnnotationUtils.findAnnotation(type,
ConfigurationProperties.class);
if (properties == null) {
registerPropertiesHolder(registry, name);
}
}
private void registerPropertiesHolder(BeanDefinitionRegistry registry, String name) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(ConfigurationPropertiesHolder.class);
builder.addConstructorArgReference(name);
registry.registerBeanDefinition(name + ".HOLDER", builder.getBeanDefinition());
}
}
}

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.zero.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 (Exception ex) {
if (ex instanceof FileNotFoundException) {
throw (FileNotFoundException) ex;
}
throw new IllegalArgumentException("Could not initialize logging from "
+ location, ex);
}
}
}

View File

@@ -0,0 +1,57 @@
/*
* 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.zero.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 ex) {
throw new IllegalArgumentException("Could not initialize logging from "
+ location, ex);
}
}
}

View File

@@ -0,0 +1,90 @@
/*
* 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.zero.web;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.zero.SpringApplication;
import org.springframework.zero.context.embedded.AnnotationConfigEmbeddedWebApplicationContext;
/**
* A handy opinionated {@link WebApplicationInitializer} for applications that only have
* one Spring servlet, and no more than a single filter (which itself is only enabled when
* Spring Security is detected). If your application is more complicated consider using
* one of the other WebApplicationInitializers.
*
* <p/>
* Note that a WebApplicationInitializer is only needed if you are building a war file and
* deploying it. If you prefer to run an embedded container (we do) then you won't need
* this at all.
*
* @author Dave Syer
*/
public abstract class SpringServletInitializer implements WebApplicationInitializer {
private final Log logger = LogFactory.getLog(getClass());
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
WebApplicationContext rootAppContext = this
.createRootApplicationContext(servletContext);
if (rootAppContext != null) {
servletContext.addListener(new ContextLoaderListener(rootAppContext) {
@Override
public void contextInitialized(ServletContextEvent event) {
// no-op because the application context is already initialized
}
});
}
else {
this.logger.debug("No ContextLoaderListener registered, as "
+ "createRootApplicationContext() did not "
+ "return an application context");
}
}
protected WebApplicationContext createRootApplicationContext(
ServletContext servletContext) {
ApplicationContext parent = null;
Object object = servletContext
.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
if (object instanceof ApplicationContext) {
this.logger.info("Root context already created (using as parent).");
parent = (ApplicationContext) object;
servletContext.setAttribute(
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null);
}
SpringApplication application = new SpringApplication(
(Object[]) getConfigClasses());
AnnotationConfigEmbeddedWebApplicationContext context = new AnnotationConfigEmbeddedWebApplicationContext();
context.setParent(parent);
context.setServletContext(servletContext);
application.setApplicationContext(context);
return (WebApplicationContext) application.run();
}
protected abstract Class<?>[] getConfigClasses();
}

View File

@@ -0,0 +1,7 @@
# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.zero.context.initializer.ConfigFileApplicationContextInitializer,\
org.springframework.zero.context.initializer.ContextIdApplicationContextInitializer,\
org.springframework.zero.context.initializer.EnvironmentDelegateApplicationContextInitializer,\
org.springframework.zero.context.initializer.LoggingApplicationContextInitializer,\
org.springframework.zero.context.initializer.VcapApplicationContextInitializer

Binary file not shown.

After

Width:  |  Height:  |  Size: 946 B

View File

@@ -0,0 +1,18 @@
log4j.rootCategory=INFO, CONSOLE, FILE
PID=????
catalina.base=/tmp
LOG_PATH=${catalina.base}/logs
LOG_FILE=${LOG_PATH}/service.log
LOG_PATTERN=[%d{yyyy-MM-dd HH:mm:ss.SSS}] service%X{context} - ${PID} %5p [%t] --- %c{1}: %m%n
# CONSOLE is set to be a ConsoleAppender using a PatternLayout.
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=${LOG_PATTERN}
log4j.appender.FILE=org.apache.log4j.RollingFileAppender
log4j.appender.FILE.File=${LOG_FILE}
log4j.appender.FILE.MaxFileSize=10MB
log4j.appender.FILE.layout = org.apache.log4j.PatternLayout
log4j.appender.FILE.layout.ConversionPattern=${LOG_PATTERN}

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<property name="LOG_PATTERN" value="[%d{yyyy-MM-dd HH:mm:ss.SSS}] - ${PID:-????} %5p [%t] --- %c{1}: %m%n"/>
<property name="LOG_FILE" value="${LOG_FILE:-${LOG_PATH:-/tmp/}spring.log}"/>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${LOG_PATTERN}</pattern>
</encoder>
</appender>
<appender name="FILE"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder>
<pattern>${LOG_PATTERN}</pattern>
</encoder>
<file>${LOG_FILE}</file>
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
<fileNamePattern>${LOG_FILE}.%i</fileNamePattern>
</rollingPolicy>
<triggeringPolicy
class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<MaxFileSize>10MB</MaxFileSize>
</triggeringPolicy>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE" />
<appender-ref ref="FILE" />
</root>
</configuration>

View File

@@ -0,0 +1,9 @@
handlers = java.util.logging.FileHandler, java.util.logging.ConsoleHandler
.level = INFO
# File Logging
java.util.logging.FileHandler.pattern = %t/service.log
java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter
java.util.logging.FileHandler.level = INFO
java.util.logging.FileHandler.limit = 10485760
java.util.logging.FileHandler.count = 10

View File

@@ -0,0 +1,109 @@
/*
* 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.zero;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.support.StaticApplicationContext;
import org.springframework.core.io.ClassPathResource;
import org.springframework.zero.sampleconfig.MyComponent;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
/**
* Tests for {@link BeanDefinitionLoader}.
*
* @author Phillip Webb
*/
public class BeanDefinitionLoaderTests {
// FIXME
private StaticApplicationContext registry;
@Before
public void setup() {
this.registry = new StaticApplicationContext();
}
@Test
public void loadClass() throws Exception {
BeanDefinitionLoader loader = new BeanDefinitionLoader(this.registry,
MyComponent.class);
int loaded = loader.load();
assertThat(loaded, equalTo(1));
assertTrue(this.registry.containsBean("myComponent"));
}
@Test
public void loadXmlResource() throws Exception {
ClassPathResource resource = new ClassPathResource("sample-beans.xml", getClass());
BeanDefinitionLoader loader = new BeanDefinitionLoader(this.registry, resource);
int loaded = loader.load();
assertThat(loaded, equalTo(1));
assertTrue(this.registry.containsBean("myXmlComponent"));
}
@Test
public void loadPackage() throws Exception {
BeanDefinitionLoader loader = new BeanDefinitionLoader(this.registry,
MyComponent.class.getPackage());
int loaded = loader.load();
assertThat(loaded, equalTo(1));
assertTrue(this.registry.containsBean("myComponent"));
}
@Test
public void loadClassName() throws Exception {
BeanDefinitionLoader loader = new BeanDefinitionLoader(this.registry,
MyComponent.class.getName());
int loaded = loader.load();
assertThat(loaded, equalTo(1));
assertTrue(this.registry.containsBean("myComponent"));
}
@Test
public void loadResourceName() throws Exception {
BeanDefinitionLoader loader = new BeanDefinitionLoader(this.registry,
"classpath:org/springframework/zero/sample-beans.xml");
int loaded = loader.load();
assertThat(loaded, equalTo(1));
assertTrue(this.registry.containsBean("myXmlComponent"));
}
@Test
public void loadPackageName() throws Exception {
BeanDefinitionLoader loader = new BeanDefinitionLoader(this.registry,
MyComponent.class.getPackage().getName());
int loaded = loader.load();
assertThat(loaded, equalTo(1));
assertTrue(this.registry.containsBean("myComponent"));
}
@Test
public void loadPackageAndClassDoesNotDoubleAdd() throws Exception {
BeanDefinitionLoader loader = new BeanDefinitionLoader(this.registry,
MyComponent.class.getPackage(), MyComponent.class);
int loaded = loader.load();
assertThat(loaded, equalTo(1));
assertTrue(this.registry.containsBean("myComponent"));
}
}

View File

@@ -0,0 +1,451 @@
/*
* 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.zero;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.After;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.beans.factory.support.DefaultBeanNameGenerator;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.AnnotationConfigUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.StaticApplicationContext;
import org.springframework.core.Ordered;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.ResourceLoader;
import org.springframework.mock.web.MockServletContext;
import org.springframework.web.context.support.StaticWebApplicationContext;
import org.springframework.zero.BeanDefinitionLoader;
import org.springframework.zero.CommandLineRunner;
import org.springframework.zero.ExitCodeGenerator;
import org.springframework.zero.SpringApplication;
import org.springframework.zero.context.embedded.AnnotationConfigEmbeddedWebApplicationContext;
import org.springframework.zero.context.embedded.jetty.JettyEmbeddedServletContainerFactory;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.sameInstance;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link SpringApplication}.
*
* @author Phillip Webb
*/
public class SpringApplicationTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
private ApplicationContext context;
private Environment getEnvironment() {
if (this.context instanceof ConfigurableApplicationContext) {
return ((ConfigurableApplicationContext) this.context).getEnvironment();
}
throw new IllegalStateException("No Environment available");
}
@After
public void close() {
if (this.context instanceof ConfigurableApplicationContext) {
((ConfigurableApplicationContext) this.context).close();
}
}
@Test
public void sourcesMustNotBeNull() throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Sources must not be empty");
new SpringApplication((Object[]) null);
}
@Test
public void sourcesMustNotBeEmpty() throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Sources must not be empty");
new SpringApplication();
}
@Test
public void disableBanner() throws Exception {
SpringApplication application = spy(new SpringApplication(ExampleConfig.class));
application.setWebEnvironment(false);
application.setShowBanner(false);
application.run();
verify(application, never()).printBanner();
}
@Test
public void customBanner() throws Exception {
SpringApplication application = spy(new SpringApplication(ExampleConfig.class));
application.setWebEnvironment(false);
application.run();
verify(application).printBanner();
}
@Test
public void specificApplicationContext() throws Exception {
SpringApplication application = new SpringApplication(ExampleConfig.class);
ApplicationContext applicationContext = new StaticApplicationContext();
application.setApplicationContext(applicationContext);
this.context = application.run();
assertThat(this.context, sameInstance(applicationContext));
}
@Test
public void specificApplicationContextClass() throws Exception {
SpringApplication application = new SpringApplication(ExampleConfig.class);
application.setApplicationContextClass(StaticApplicationContext.class);
this.context = application.run();
assertThat(this.context, instanceOf(StaticApplicationContext.class));
}
@Test
public void specificApplicationContextInitializer() throws Exception {
SpringApplication application = new SpringApplication(ExampleConfig.class);
application.setWebEnvironment(false);
final AtomicReference<ApplicationContext> reference = new AtomicReference<ApplicationContext>();
application
.addInitializers(new ApplicationContextInitializer<ConfigurableApplicationContext>() {
@Override
public void initialize(ConfigurableApplicationContext context) {
reference.set(context);
}
});
this.context = application.run("--foo=bar");
assertThat(this.context, sameInstance(reference.get()));
// Custom initializers do not switch off the defaults
assertThat(getEnvironment().getProperty("foo"), equalTo("bar"));
}
@Test
public void defaultApplicationContext() throws Exception {
SpringApplication application = new SpringApplication(ExampleConfig.class);
application.setWebEnvironment(false);
this.context = application.run();
assertThat(this.context, instanceOf(AnnotationConfigApplicationContext.class));
}
@Test
public void defaultApplicationContextForWeb() throws Exception {
SpringApplication application = new SpringApplication(ExampleWebConfig.class);
application.setWebEnvironment(true);
this.context = application.run();
assertThat(this.context,
instanceOf(AnnotationConfigEmbeddedWebApplicationContext.class));
}
@Test
public void customEnvironment() throws Exception {
TestSpringApplication application = new TestSpringApplication(ExampleConfig.class);
application.setWebEnvironment(false);
StaticApplicationContext applicationContext = spy(new StaticApplicationContext());
ConfigurableEnvironment environment = new StandardEnvironment();
application.setApplicationContext(applicationContext);
application.setEnvironment(environment);
application.run();
verify(applicationContext).setEnvironment(environment);
verify(application.getLoader()).setEnvironment(environment);
}
@Test
public void customResourceLoader() throws Exception {
TestSpringApplication application = new TestSpringApplication(ExampleConfig.class);
application.setWebEnvironment(false);
StaticApplicationContext applicationContext = spy(new StaticApplicationContext());
ResourceLoader resourceLoader = new DefaultResourceLoader();
application.setApplicationContext(applicationContext);
application.setResourceLoader(resourceLoader);
application.run();
verify(applicationContext).setResourceLoader(resourceLoader);
verify(application.getLoader()).setResourceLoader(resourceLoader);
}
@Test
public void customResourceLoaderFromConstructor() throws Exception {
ResourceLoader resourceLoader = new DefaultResourceLoader();
TestSpringApplication application = new TestSpringApplication(resourceLoader,
ExampleWebConfig.class);
StaticApplicationContext applicationContext = spy(new StaticApplicationContext());
application.setApplicationContext(applicationContext);
application.run();
verify(applicationContext).setResourceLoader(resourceLoader);
verify(application.getLoader()).setResourceLoader(resourceLoader);
applicationContext.close();
}
@Test
public void customBeanNameGenerator() throws Exception {
TestSpringApplication application = new TestSpringApplication(
ExampleWebConfig.class);
StaticWebApplicationContext applicationContext = spy(new StaticWebApplicationContext());
applicationContext.setServletContext(new MockServletContext());
BeanNameGenerator beanNameGenerator = new DefaultBeanNameGenerator();
application.setApplicationContext(applicationContext);
application.setBeanNameGenerator(beanNameGenerator);
this.context = application.run();
verify(application.getLoader()).setBeanNameGenerator(beanNameGenerator);
assertThat(
this.context
.getBean(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR),
sameInstance((Object) beanNameGenerator));
}
@Test
public void commandLinePropertySource() throws Exception {
SpringApplication application = new SpringApplication(ExampleConfig.class);
application.setWebEnvironment(false);
ConfigurableEnvironment environment = new StandardEnvironment();
application.setEnvironment(environment);
application.run();
assertThat(
hasPropertySource(environment, MapPropertySource.class, "commandLineArgs"),
equalTo(true));
}
@Test
public void disableCommandLinePropertySource() throws Exception {
SpringApplication application = new SpringApplication(ExampleConfig.class);
application.setWebEnvironment(false);
application.setAddCommandLineProperties(false);
ConfigurableEnvironment environment = new StandardEnvironment();
application.setEnvironment(environment);
application.run();
assertThat(
hasPropertySource(environment, MapPropertySource.class, "commandLineArgs"),
equalTo(false));
}
@Test
public void runCommandLineRunners() throws Exception {
SpringApplication application = new SpringApplication(CommandLineRunConfig.class);
application.setWebEnvironment(false);
this.context = application.run("arg");
assertTrue(this.context.getBean("runnerA", TestCommandLineRunner.class).hasRun());
assertTrue(this.context.getBean("runnerB", TestCommandLineRunner.class).hasRun());
}
@Test
public void loadSources() throws Exception {
Object[] sources = { ExampleConfig.class, "a", TestCommandLineRunner.class };
TestSpringApplication application = new TestSpringApplication(sources);
application.setWebEnvironment(false);
application.setUseMockLoader(true);
application.run();
assertThat(application.getSources(), equalTo(sources));
}
@Test
public void run() throws Exception {
this.context = SpringApplication.run(ExampleWebConfig.class);
assertNotNull(this.context);
}
@Test
public void runComponents() throws Exception {
this.context = SpringApplication.run(new Object[] { ExampleWebConfig.class,
Object.class }, new String[0]);
assertNotNull(this.context);
}
@Test
public void exit() throws Exception {
SpringApplication application = new SpringApplication(ExampleConfig.class);
application.setWebEnvironment(false);
ApplicationContext context = application.run();
assertNotNull(context);
assertEquals(0, SpringApplication.exit(context));
}
@Test
public void exitWithExplicitCode() throws Exception {
SpringApplication application = new SpringApplication(ExampleConfig.class);
application.setWebEnvironment(false);
ApplicationContext context = application.run();
assertNotNull(context);
assertEquals(2, SpringApplication.exit(context, new ExitCodeGenerator() {
@Override
public int getExitCode() {
return 2;
}
}));
}
@Test
public void defaultCommandLineArgs() throws Exception {
SpringApplication application = new SpringApplication(ExampleConfig.class);
application.setDefaultCommandLineArgs("--foo", "--bar=spam", "bucket");
application.setWebEnvironment(false);
this.context = application.run("--bar=foo", "bucket", "crap");
assertThat(this.context, instanceOf(AnnotationConfigApplicationContext.class));
assertThat(getEnvironment().getProperty("bar"), equalTo("foo"));
assertThat(getEnvironment().getProperty("foo"), equalTo(""));
}
private boolean hasPropertySource(ConfigurableEnvironment environment,
Class<?> propertySourceClass, String name) {
for (PropertySource<?> source : environment.getPropertySources()) {
if (propertySourceClass.isInstance(source)
&& (name == null || name.equals(source.getName()))) {
return true;
}
}
return false;
}
// FIXME test initializers
// FIXME test config files?
private static class TestSpringApplication extends SpringApplication {
private BeanDefinitionLoader loader;
private boolean useMockLoader;
private Object[] sources;
public TestSpringApplication(Object... sources) {
super(sources);
}
public TestSpringApplication(ResourceLoader resourceLoader, Object... sources) {
super(resourceLoader, sources);
}
public void setUseMockLoader(boolean useMockLoader) {
this.useMockLoader = useMockLoader;
}
@Override
protected BeanDefinitionLoader createBeanDefinitionLoader(
BeanDefinitionRegistry registry, Object[] sources) {
this.sources = sources;
if (this.useMockLoader) {
this.loader = mock(BeanDefinitionLoader.class);
}
else {
this.loader = spy(super.createBeanDefinitionLoader(registry, sources));
}
return this.loader;
}
public BeanDefinitionLoader getLoader() {
return this.loader;
}
public Object[] getSources() {
return this.sources;
}
}
@Configuration
static class ExampleConfig {
}
@Configuration
static class ExampleWebConfig {
@Bean
public JettyEmbeddedServletContainerFactory container() {
return new JettyEmbeddedServletContainerFactory();
}
}
@Configuration
static class CommandLineRunConfig {
@Bean
public TestCommandLineRunner runnerB() {
return new TestCommandLineRunner(Ordered.LOWEST_PRECEDENCE, "runnerA");
}
@Bean
public TestCommandLineRunner runnerA() {
return new TestCommandLineRunner(Ordered.HIGHEST_PRECEDENCE);
}
}
static class TestCommandLineRunner implements CommandLineRunner,
ApplicationContextAware, Ordered {
private String[] expectedBefore;
private ApplicationContext applicationContext;
private String[] args;
private int order;
public TestCommandLineRunner(int order, String... expectedBefore) {
this.expectedBefore = expectedBefore;
this.order = order;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public int getOrder() {
return this.order;
}
@Override
public void run(String... args) {
this.args = args;
for (String name : this.expectedBefore) {
TestCommandLineRunner bean = this.applicationContext.getBean(name,
TestCommandLineRunner.class);
assertTrue(bean.hasRun());
}
}
public boolean hasRun() {
return this.args != null;
}
}
}

View File

@@ -0,0 +1,45 @@
/*
* 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.zero;
import java.util.HashMap;
import java.util.Map;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.MapPropertySource;
/**
* General test utilities.
*
* @author Dave Syer
*/
public abstract class TestUtils {
public static void addEnviroment(ConfigurableApplicationContext context,
String... pairs) {
Map<String, Object> map = new HashMap<String, Object>();
for (String pair : pairs) {
int index = pair.indexOf(":");
String key = pair.substring(0, index > 0 ? index : pair.length());
String value = index > 0 ? pair.substring(index + 1) : "";
map.put(key, value);
}
context.getEnvironment().getPropertySources()
.addFirst(new MapPropertySource("test", map));
}
}

View File

@@ -0,0 +1,136 @@
/*
* 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.zero.bind;
import javax.validation.Validation;
import javax.validation.constraints.NotNull;
import org.junit.Test;
import org.springframework.beans.NotWritablePropertyException;
import org.springframework.context.support.StaticMessageSource;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.validation.BindException;
import org.springframework.validation.Validator;
import org.springframework.validation.beanvalidation.SpringValidatorAdapter;
import org.springframework.zero.bind.PropertiesConfigurationFactory;
import static org.junit.Assert.assertEquals;
/**
* Tests for {@link PropertiesConfigurationFactory}.
*
* @author Dave Syer
*/
public class PropertiesConfigurationFactoryTests {
private PropertiesConfigurationFactory<Foo> factory;
private Validator validator;
private boolean exceptionIfInvalid = true;
private boolean ignoreUnknownFields = true;
private String targetName = null;
@Test
public void testValidPropertiesLoadsWithNoErrors() throws Exception {
Foo foo = createFoo("name: blah\nbar: blah");
assertEquals("blah", foo.bar);
assertEquals("blah", foo.name);
}
@Test
public void testValidPropertiesLoadsWithUpperCase() throws Exception {
Foo foo = createFoo("NAME: blah\nbar: blah");
assertEquals("blah", foo.bar);
assertEquals("blah", foo.name);
}
@Test
public void testValidPropertiesLoadsWithDash() throws Exception {
Foo foo = createFoo("na-me: blah\nbar: blah");
assertEquals("blah", foo.bar);
assertEquals("blah", foo.name);
}
@Test
public void testUnknownPropertyOkByDefault() throws Exception {
Foo foo = createFoo("hi: hello\nname: foo\nbar: blah");
assertEquals("blah", foo.bar);
}
@Test(expected = NotWritablePropertyException.class)
public void testUnknownPropertyCausesLoadFailure() throws Exception {
this.ignoreUnknownFields = false;
createFoo("hi: hello\nname: foo\nbar: blah");
}
@Test(expected = BindException.class)
public void testMissingPropertyCausesValidationError() throws Exception {
this.validator = new SpringValidatorAdapter(Validation
.buildDefaultValidatorFactory().getValidator());
createFoo("bar: blah");
}
@Test
public void testBindToNamedTarget() throws Exception {
this.targetName = "foo";
Foo foo = createFoo("hi: hello\nfoo.name: foo\nfoo.bar: blah");
assertEquals("blah", foo.bar);
}
private Foo createFoo(final String values) throws Exception {
this.factory = new PropertiesConfigurationFactory<Foo>(Foo.class);
this.factory.setProperties(PropertiesLoaderUtils
.loadProperties(new ByteArrayResource(values.getBytes())));
this.factory.setExceptionIfInvalid(this.exceptionIfInvalid);
this.factory.setValidator(this.validator);
this.factory.setTargetName(this.targetName);
this.factory.setIgnoreUnknownFields(this.ignoreUnknownFields);
this.factory.setMessageSource(new StaticMessageSource());
this.factory.afterPropertiesSet();
return this.factory.getObject();
}
// Foo needs to be public and to have setters for all properties
public static class Foo {
@NotNull
private String name;
private String bar;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String getBar() {
return this.bar;
}
public void setBar(String bar) {
this.bar = bar;
}
}
}

View File

@@ -0,0 +1,104 @@
/*
* 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.zero.bind;
import java.util.Collections;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
import org.springframework.validation.DataBinder;
import static org.junit.Assert.assertEquals;
/**
* Tests for {@link PropertySourcesPropertyValues}.
* @author Dave Syer
*/
public class PropertySourcesPropertyValuesTests {
private MutablePropertySources propertySources = new MutablePropertySources();
@Before
public void init() {
this.propertySources.addFirst(new PropertySource<String>("static", "foo") {
@Override
public Object getProperty(String name) {
if (name.equals(getSource())) {
return "bar";
}
return null;
}
});
this.propertySources.addFirst(new MapPropertySource("map", Collections
.<String, Object> singletonMap("name", "${foo}")));
}
@Test
public void testSize() {
PropertySourcesPropertyValues propertyValues = new PropertySourcesPropertyValues(
this.propertySources);
assertEquals(1, propertyValues.getPropertyValues().length);
}
@Test
public void testNonEnumeratedValue() {
PropertySourcesPropertyValues propertyValues = new PropertySourcesPropertyValues(
this.propertySources);
assertEquals("bar", propertyValues.getPropertyValue("foo").getValue());
}
@Test
public void testEnumeratedValue() {
PropertySourcesPropertyValues propertyValues = new PropertySourcesPropertyValues(
this.propertySources);
assertEquals("bar", propertyValues.getPropertyValue("name").getValue());
}
@Test
public void testOverriddenValue() {
this.propertySources.addFirst(new MapPropertySource("new", Collections
.<String, Object> singletonMap("name", "spam")));
PropertySourcesPropertyValues propertyValues = new PropertySourcesPropertyValues(
this.propertySources);
assertEquals("spam", propertyValues.getPropertyValue("name").getValue());
}
@Test
public void testPlaceholdersBinding() {
TestBean target = new TestBean();
DataBinder binder = new DataBinder(target);
binder.bind(new PropertySourcesPropertyValues(this.propertySources));
assertEquals("bar", target.getName());
}
public static class TestBean {
private String name;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
}

View File

@@ -0,0 +1,378 @@
/*
* 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.zero.bind;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import javax.validation.Constraint;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.Payload;
import javax.validation.constraints.NotNull;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.context.support.StaticMessageSource;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.validation.BindingResult;
import org.springframework.validation.DataBinder;
import org.springframework.validation.FieldError;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.zero.bind.RelaxedDataBinder;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
/**
* Tests for {@link RelaxedDataBinder}.
*
* @author Dave Syer
*/
public class RelaxedDataBinderTests {
@Rule
public ExpectedException expected = ExpectedException.none();
private ConversionService conversionService;
@Test
public void testBindString() throws Exception {
VanillaTarget target = new VanillaTarget();
bind(target, "foo: bar");
assertEquals("bar", target.getFoo());
}
@Test
public void testBindUnderscore() throws Exception {
VanillaTarget target = new VanillaTarget();
bind(target, "foo-bar: bar");
assertEquals("bar", target.getFoo_bar());
}
@Test
public void testBindCamelCase() throws Exception {
VanillaTarget target = new VanillaTarget();
bind(target, "foo-baz: bar");
assertEquals("bar", target.getFooBaz());
}
@Test
public void testBindNumber() throws Exception {
VanillaTarget target = new VanillaTarget();
bind(target, "foo: bar\n" + "value: 123");
assertEquals(123, target.getValue());
}
@Test
public void testSimpleValidation() throws Exception {
ValidatedTarget target = new ValidatedTarget();
BindingResult result = bind(target, "");
assertEquals(1, result.getErrorCount());
}
@Test
public void testRequiredFieldsValidation() throws Exception {
TargetWithValidatedMap target = new TargetWithValidatedMap();
BindingResult result = bind(target, "info[foo]: bar");
assertEquals(2, result.getErrorCount());
for (FieldError error : result.getFieldErrors()) {
System.err.println(new StaticMessageSource().getMessage(error,
Locale.getDefault()));
}
}
@Test
public void testBindNested() throws Exception {
TargetWithNestedObject target = new TargetWithNestedObject();
bind(target, "nested.foo: bar\n" + "nested.value: 123");
assertEquals(123, target.getNested().getValue());
}
@Test
public void testBindNestedList() throws Exception {
TargetWithNestedList target = new TargetWithNestedList();
bind(target, "nested: bar,foo");
bind(target, "nested[0]: bar");
bind(target, "nested[1]: foo");
assertEquals("[bar, foo]", target.getNested().toString());
}
@Test
public void testBindNestedListCommaDelimitedONly() throws Exception {
TargetWithNestedList target = new TargetWithNestedList();
this.conversionService = new DefaultConversionService();
bind(target, "nested: bar,foo");
assertEquals("[bar, foo]", target.getNested().toString());
}
@Test
public void testBindNestedMap() throws Exception {
TargetWithNestedMap target = new TargetWithNestedMap();
bind(target, "nested.foo: bar\n" + "nested.value: 123");
assertEquals("123", target.getNested().get("value"));
}
@Test
public void testBindNestedMapBracketReferenced() throws Exception {
TargetWithNestedMap target = new TargetWithNestedMap();
bind(target, "nested[foo]: bar\n" + "nested[value]: 123");
assertEquals("123", target.getNested().get("value"));
}
@SuppressWarnings("unchecked")
@Test
public void testBindDoubleNestedMap() throws Exception {
TargetWithNestedMap target = new TargetWithNestedMap();
bind(target, "nested.foo: bar\n" + "nested.bar.spam: bucket\n"
+ "nested.bar.value: 123\nnested.bar.foo: crap");
assertEquals(2, target.getNested().size());
assertEquals(3, ((Map<String, Object>) target.getNested().get("bar")).size());
assertEquals("123",
((Map<String, Object>) target.getNested().get("bar")).get("value"));
assertEquals("bar", target.getNested().get("foo"));
assertFalse(target.getNested().containsValue(target.getNested()));
}
@Test
public void testBindErrorTypeMismatch() throws Exception {
VanillaTarget target = new VanillaTarget();
BindingResult result = bind(target, "foo: bar\n" + "value: foo");
assertEquals(1, result.getErrorCount());
}
@Test
public void testBindErrorNotWritable() throws Exception {
this.expected.expectMessage("property 'spam'");
this.expected.expectMessage("not writable");
VanillaTarget target = new VanillaTarget();
BindingResult result = bind(target, "spam: bar\n" + "value: 123");
assertEquals(1, result.getErrorCount());
}
@Test
public void testBindErrorNotWritableWithPrefix() throws Exception {
VanillaTarget target = new VanillaTarget();
BindingResult result = bind(target, "spam: bar\n" + "vanilla.value: 123",
"vanilla");
assertEquals(0, result.getErrorCount());
assertEquals(123, target.getValue());
}
@Test
public void testBindMap() throws Exception {
Map<String, Object> target = new LinkedHashMap<String, Object>();
BindingResult result = bind(target, "spam: bar\n" + "vanilla.value: 123",
"vanilla");
assertEquals(0, result.getErrorCount());
assertEquals("123", target.get("value"));
}
@Test
public void testBindMapNestedInMap() throws Exception {
Map<String, Object> target = new LinkedHashMap<String, Object>();
BindingResult result = bind(target, "spam: bar\n" + "vanilla.foo.value: 123",
"vanilla");
assertEquals(0, result.getErrorCount());
@SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>) target.get("foo");
assertEquals("123", map.get("value"));
}
private BindingResult bind(Object target, String values) throws Exception {
return bind(target, values, null);
}
private BindingResult bind(Object target, String values, String namePrefix)
throws Exception {
Properties properties = PropertiesLoaderUtils
.loadProperties(new ByteArrayResource(values.getBytes()));
DataBinder binder = new RelaxedDataBinder(target, namePrefix);
binder.setIgnoreUnknownFields(false);
LocalValidatorFactoryBean validatorFactoryBean = new LocalValidatorFactoryBean();
validatorFactoryBean.afterPropertiesSet();
binder.setValidator(validatorFactoryBean);
binder.setConversionService(this.conversionService);
binder.bind(new MutablePropertyValues(properties));
binder.validate();
return binder.getBindingResult();
}
@Documented
@Target({ ElementType.FIELD })
@Retention(RUNTIME)
@Constraint(validatedBy = RequiredKeysValidator.class)
public @interface RequiredKeys {
String[] value();
String message() default "Required fields are not provided for field ''{0}''";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public static class RequiredKeysValidator implements
ConstraintValidator<RequiredKeys, Map<String, Object>> {
private String[] fields;
@Override
public void initialize(RequiredKeys constraintAnnotation) {
this.fields = constraintAnnotation.value();
}
@Override
public boolean isValid(Map<String, Object> value,
ConstraintValidatorContext context) {
boolean valid = true;
for (String field : this.fields) {
if (!value.containsKey(field)) {
context.buildConstraintViolationWithTemplate(
"Missing field ''" + field + "''").addConstraintViolation();
valid = false;
}
}
return valid;
}
}
public static class TargetWithValidatedMap {
@RequiredKeys({ "foo", "value" })
private Map<String, Object> info;
public Map<String, Object> getInfo() {
return this.info;
}
public void setInfo(Map<String, Object> nested) {
this.info = nested;
}
}
public static class TargetWithNestedMap {
private Map<String, Object> nested;
public Map<String, Object> getNested() {
return this.nested;
}
public void setNested(Map<String, Object> nested) {
this.nested = nested;
}
}
public static class TargetWithNestedList {
private List<String> nested;
public List<String> getNested() {
return this.nested;
}
public void setNested(List<String> nested) {
this.nested = nested;
}
}
public static class TargetWithNestedObject {
private VanillaTarget nested;
public VanillaTarget getNested() {
return this.nested;
}
public void setNested(VanillaTarget nested) {
this.nested = nested;
}
}
public static class VanillaTarget {
private String foo;
private int value;
private String foo_bar;
private String fooBaz;
public int getValue() {
return this.value;
}
public void setValue(int value) {
this.value = value;
}
public String getFoo() {
return this.foo;
}
public void setFoo(String foo) {
this.foo = foo;
}
public String getFoo_bar() {
return this.foo_bar;
}
public void setFoo_bar(String foo_bar) {
this.foo_bar = foo_bar;
}
public String getFooBaz() {
return this.fooBaz;
}
public void setFooBaz(String fooBaz) {
this.fooBaz = fooBaz;
}
}
public static class ValidatedTarget {
@NotNull
private String foo;
public String getFoo() {
return this.foo;
}
public void setFoo(String foo) {
this.foo = foo;
}
}
}

View File

@@ -0,0 +1,91 @@
/*
* 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.zero.bind;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import javax.validation.Validation;
import javax.validation.constraints.NotNull;
import org.junit.Test;
import org.springframework.context.support.StaticMessageSource;
import org.springframework.validation.BindException;
import org.springframework.validation.Validator;
import org.springframework.validation.beanvalidation.SpringValidatorAdapter;
import org.yaml.snakeyaml.error.YAMLException;
import static org.junit.Assert.assertEquals;
/**
* Tests for {@link YamlConfigurationFactory}
*
* @author Dave Syer
*/
public class YamlConfigurationFactoryTests {
private YamlConfigurationFactory<Foo> factory;
private Validator validator;
private Map<Class<?>, Map<String, String>> aliases = new HashMap<Class<?>, Map<String, String>>();
private Foo createFoo(final String yaml) throws Exception {
this.factory = new YamlConfigurationFactory<Foo>(Foo.class);
this.factory.setYaml(yaml);
this.factory.setExceptionIfInvalid(true);
this.factory.setPropertyAliases(this.aliases);
this.factory.setValidator(this.validator);
this.factory.setMessageSource(new StaticMessageSource());
this.factory.afterPropertiesSet();
return this.factory.getObject();
}
@Test
public void testValidYamlLoadsWithNoErrors() throws Exception {
Foo foo = createFoo("name: blah\nbar: blah");
assertEquals("blah", foo.bar);
}
@Test
public void testValidYamlWithAliases() throws Exception {
this.aliases.put(Foo.class, Collections.singletonMap("foo-name", "name"));
Foo foo = createFoo("foo-name: blah\nbar: blah");
assertEquals("blah", foo.name);
}
@Test(expected = YAMLException.class)
public void unknownPropertyCausesLoadFailure() throws Exception {
createFoo("hi: hello\nname: foo\nbar: blah");
}
@Test(expected = BindException.class)
public void missingPropertyCausesValidationError() throws Exception {
this.validator = new SpringValidatorAdapter(Validation
.buildDefaultValidatorFactory().getValidator());
createFoo("bar: blah");
}
private static class Foo {
@NotNull
public String name;
public String bar;
}
}

View File

@@ -0,0 +1,31 @@
/*
* 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.zero.config;
/**
* Tests for {@link JsonParser}.
*
* @author Dave Syer
*/
public class JacksonParserTests extends SimpleJsonParserTests {
@Override
protected JsonParser getParser() {
return new JacksonJsonParser();
}
}

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.zero.config;
import java.util.List;
import java.util.Map;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
/**
* Tests for {@link SimpleJsonParser}.
*
* @author Dave Syer
*/
public class SimpleJsonParserTests {
private JsonParser parser = getParser();
protected JsonParser getParser() {
return new SimpleJsonParser();
}
@Test
public void testSimpleMap() {
Map<String, Object> map = this.parser.parseMap("{\"foo\":\"bar\",\"spam\":1}");
assertEquals(2, map.size());
assertEquals("bar", map.get("foo"));
assertEquals("1", map.get("spam").toString());
}
@Test
public void testEmptyMap() {
Map<String, Object> map = this.parser.parseMap("{}");
assertEquals(0, map.size());
}
@Test
public void testSimpleList() {
List<Object> list = this.parser.parseList("[\"foo\",\"bar\",1]");
assertEquals(3, list.size());
assertEquals("bar", list.get(1));
}
@Test
public void testEmptyList() {
List<Object> list = this.parser.parseList("[]");
assertEquals(0, list.size());
}
@SuppressWarnings("unchecked")
@Test
public void testListOfMaps() {
List<Object> list = this.parser
.parseList("[{\"foo\":\"bar\",\"spam\":1},{\"foo\":\"baz\",\"spam\":2}]");
assertEquals(2, list.size());
assertEquals(2, ((Map<String, Object>) list.get(1)).size());
}
}

View File

@@ -0,0 +1,31 @@
/*
* 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.zero.config;
/**
* Tests for {@link YamlJsonParser}.
*
* @author Dave Syer
*/
public class YamlJsonParserTests extends SimpleJsonParserTests {
@Override
protected JsonParser getParser() {
return new YamlJsonParser();
}
}

View File

@@ -0,0 +1,92 @@
/*
* 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.zero.config;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import org.junit.Test;
import org.springframework.core.io.AbstractResource;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.zero.config.YamlProcessor.ResolutionMethod;
import static org.junit.Assert.assertEquals;
/**
* Tests for {@link YamlMapFactoryBean}.
*
* @author Dave Syer
*/
public class YamlMapFactoryBeanTests {
private YamlMapFactoryBean factory = new YamlMapFactoryBean();
@Test
public void testSetIgnoreResourceNotFound() throws Exception {
this.factory
.setResolutionMethod(YamlMapFactoryBean.ResolutionMethod.OVERRIDE_AND_IGNORE);
this.factory.setResources(new FileSystemResource[] { new FileSystemResource(
"non-exsitent-file.yml") });
assertEquals(0, this.factory.getObject().size());
}
@Test(expected = IllegalStateException.class)
public void testSetBarfOnResourceNotFound() throws Exception {
this.factory.setResources(new FileSystemResource[] { new FileSystemResource(
"non-exsitent-file.yml") });
assertEquals(0, this.factory.getObject().size());
}
@Test
public void testGetObject() throws Exception {
this.factory.setResources(new ByteArrayResource[] { new ByteArrayResource(
"foo: bar".getBytes()) });
assertEquals(1, this.factory.getObject().size());
}
@SuppressWarnings("unchecked")
@Test
public void testOverrideAndremoveDefaults() throws Exception {
this.factory.setResources(new ByteArrayResource[] {
new ByteArrayResource("foo:\n bar: spam".getBytes()),
new ByteArrayResource("foo:\n spam: bar".getBytes()) });
assertEquals(1, this.factory.getObject().size());
assertEquals(2,
((Map<String, Object>) this.factory.getObject().get("foo")).size());
}
@Test
public void testFirstFound() throws Exception {
this.factory.setResolutionMethod(ResolutionMethod.FIRST_FOUND);
this.factory.setResources(new Resource[] { new AbstractResource() {
@Override
public String getDescription() {
return "non-existent";
}
@Override
public InputStream getInputStream() throws IOException {
throw new IOException("planned");
}
}, new ByteArrayResource("foo:\n spam: bar".getBytes()) });
assertEquals(1, this.factory.getObject().size());
}
}

View File

@@ -0,0 +1,214 @@
/*
* 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.zero.config;
import java.util.Arrays;
import java.util.Map;
import java.util.Properties;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.zero.config.YamlProcessor.DocumentMatcher;
import org.springframework.zero.config.YamlProcessor.MatchStatus;
import org.springframework.zero.config.YamlProcessor.ResolutionMethod;
import org.yaml.snakeyaml.Yaml;
import static org.junit.Assert.assertEquals;
/**
* Tests for {@link YamlPropertiesFactoryBean}.
*
* @author Dave Syer
*/
public class YamlPropertiesFactoryBeanTests {
@Test
public void testLoadResource() throws Exception {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(new Resource[] { new ByteArrayResource(
"foo: bar\nspam:\n foo: baz".getBytes()) });
Properties properties = factory.getObject();
assertEquals("bar", properties.get("foo"));
assertEquals("baz", properties.get("spam.foo"));
}
@Test
public void testLoadResourcesWithOverride() throws Exception {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(new Resource[] {
new ByteArrayResource("foo: bar\nspam:\n foo: baz".getBytes()),
new ByteArrayResource("foo:\n bar: spam".getBytes()) });
Properties properties = factory.getObject();
assertEquals("bar", properties.get("foo"));
assertEquals("baz", properties.get("spam.foo"));
assertEquals("spam", properties.get("foo.bar"));
}
@Test
@Ignore
// We can't fail on duplicate keys because the Map is created by the YAML library
public void testLoadResourcesWithInternalOverride() throws Exception {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(new Resource[] { new ByteArrayResource(
"foo: bar\nspam:\n foo: baz\nfoo: bucket".getBytes()) });
Properties properties = factory.getObject();
assertEquals("bar", properties.get("foo"));
}
@Test
@Ignore
// We can't fail on duplicate keys because the Map is created by the YAML library
public void testLoadResourcesWithNestedInternalOverride() throws Exception {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(new Resource[] { new ByteArrayResource(
"foo:\n bar: spam\n foo: baz\nbreak: it\nfoo: bucket".getBytes()) });
Properties properties = factory.getObject();
assertEquals("spam", properties.get("foo.bar"));
}
@Test
public void testLoadResourceWithMultipleDocuments() throws Exception {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(new Resource[] { new ByteArrayResource(
"foo: bar\nspam: baz\n---\nfoo: bag".getBytes()) });
Properties properties = factory.getObject();
assertEquals("bag", properties.get("foo"));
assertEquals("baz", properties.get("spam"));
}
@Test
public void testLoadResourceWithSelectedDocuments() throws Exception {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(new Resource[] { new ByteArrayResource(
"foo: bar\nspam: baz\n---\nfoo: bag\nspam: bad".getBytes()) });
factory.setDocumentMatchers(Arrays
.<DocumentMatcher> asList(new DocumentMatcher() {
@Override
public MatchStatus matches(Properties properties) {
return "bag".equals(properties.getProperty("foo")) ? MatchStatus.FOUND
: MatchStatus.NOT_FOUND;
}
}));
Properties properties = factory.getObject();
assertEquals("bag", properties.get("foo"));
assertEquals("bad", properties.get("spam"));
}
@Test
public void testLoadResourceWithDefaultMatch() throws Exception {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setMatchDefault(true);
factory.setResources(new Resource[] { new ByteArrayResource(
"one: two\n---\nfoo: bar\nspam: baz\n---\nfoo: bag\nspam: bad".getBytes()) });
factory.setDocumentMatchers(Arrays
.<DocumentMatcher> asList(new DocumentMatcher() {
@Override
public MatchStatus matches(Properties properties) {
if (!properties.containsKey("foo")) {
return MatchStatus.ABSTAIN;
}
return "bag".equals(properties.getProperty("foo")) ? MatchStatus.FOUND
: MatchStatus.NOT_FOUND;
}
}));
Properties properties = factory.getObject();
assertEquals("bag", properties.get("foo"));
assertEquals("bad", properties.get("spam"));
assertEquals("two", properties.get("one"));
}
@Test
public void testLoadResourceWithDefaultMatchSkippingMissedMatch() throws Exception {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setMatchDefault(true);
factory.setResources(new Resource[] { new ByteArrayResource(
"one: two\n---\nfoo: bag\nspam: bad\n---\nfoo: bar\nspam: baz".getBytes()) });
factory.setDocumentMatchers(Arrays
.<DocumentMatcher> asList(new DocumentMatcher() {
@Override
public MatchStatus matches(Properties properties) {
if (!properties.containsKey("foo")) {
return MatchStatus.ABSTAIN;
}
return "bag".equals(properties.getProperty("foo")) ? MatchStatus.FOUND
: MatchStatus.NOT_FOUND;
}
}));
Properties properties = factory.getObject();
assertEquals("bag", properties.get("foo"));
assertEquals("bad", properties.get("spam"));
assertEquals("two", properties.get("one"));
}
@Test
public void testLoadNonExistentResource() throws Exception {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResolutionMethod(ResolutionMethod.OVERRIDE_AND_IGNORE);
factory.setResources(new Resource[] { new ClassPathResource("no-such-file.yml") });
Properties properties = factory.getObject();
assertEquals(0, properties.size());
}
@Test
public void testLoadNull() throws Exception {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(new Resource[] { new ByteArrayResource("foo: bar\nspam:"
.getBytes()) });
Properties properties = factory.getObject();
assertEquals("bar", properties.get("foo"));
assertEquals("", properties.get("spam"));
}
@Test
public void testLoadArrayOfString() throws Exception {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(new Resource[] { new ByteArrayResource("foo:\n- bar\n- baz"
.getBytes()) });
Properties properties = factory.getObject();
assertEquals("bar", properties.get("foo[0]"));
assertEquals("baz", properties.get("foo[1]"));
assertEquals("bar,baz", properties.get("foo"));
}
@Test
public void testLoadArrayOfObject() throws Exception {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(new Resource[] { new ByteArrayResource(
"foo:\n- bar:\n spam: crap\n- baz\n- one: two\n three: four"
.getBytes()) });
Properties properties = factory.getObject();
// System.err.println(properties);
assertEquals("crap", properties.get("foo[0].bar.spam"));
assertEquals("baz", properties.get("foo[1]"));
assertEquals("two", properties.get("foo[2].one"));
assertEquals("four", properties.get("foo[2].three"));
assertEquals("{bar={spam=crap}},baz,{one=two, three=four}", properties.get("foo"));
}
@SuppressWarnings("unchecked")
@Test
public void testYaml() {
Yaml yaml = new Yaml();
Map<String, ?> map = yaml.loadAs("foo: bar\nspam:\n foo: baz", Map.class);
assertEquals("bar", map.get("foo"));
assertEquals("baz", ((Map<String, Object>) map.get("spam")).get("foo"));
}
}

View File

@@ -0,0 +1,62 @@
/*
* 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.zero.context.condition;
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;
import org.springframework.zero.context.condition.ConditionalOnMissingBean;
/**
* {@link Conditional} that only matches when the specified bean classes and/or names are
* not already contained in the {@link BeanFactory}, and throws an exception otherwise.
*
* @author Dave Syer
* @see ConditionalOnMissingBean
*/
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(AssertMissingBeanCondition.class)
public @interface AssertMissingBean {
/**
* 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 {};
/**
* If the application context hierarchy (parent contexts) should be considered.
*/
boolean considerHierarchy() default true;
}

View File

@@ -0,0 +1,51 @@
/*
* 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.zero.context.condition;
import java.util.List;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.zero.context.condition.OnMissingBeanCondition;
/**
* {@link Condition} that checks that specific beans are missing.
*
* @author Dave Syer
* @see AssertMissingBean
*/
class AssertMissingBeanCondition extends OnMissingBeanCondition {
@Override
protected Class<?> annotationClass() {
return AssertMissingBean.class;
}
@Override
protected boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata,
List<String> beanClasses, List<String> beanNames) throws LinkageError {
boolean result = super.matches(context, metadata, beanClasses, beanNames);
if (!result) {
throw new BeanCreationException("Found existing bean for classes="
+ beanClasses + " and names=" + beanNames);
}
return result;
}
}

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.zero.context.condition;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportResource;
import org.springframework.zero.context.condition.ConditionalOnBean;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
/**
* @author Dave Syer
*/
@Ignore
public class OnBeanConditionTests {
private AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
@Test
public void testNameOnBeanCondition() {
this.context.register(FooConfiguration.class, OnBeanNameConfiguration.class);
this.context.refresh();
assertTrue(this.context.containsBean("bar"));
assertEquals("bar", this.context.getBean("bar"));
}
@Test
public void testNameOnBeanConditionReverseOrder() {
this.context.register(OnBeanNameConfiguration.class, FooConfiguration.class);
this.context.refresh();
assertTrue(this.context.containsBean("bar"));
assertEquals("bar", this.context.getBean("bar"));
}
@Test
public void testClassOnBeanCondition() {
this.context.register(OnBeanClassConfiguration.class, FooConfiguration.class);
this.context.refresh();
assertTrue(this.context.containsBean("bar"));
assertEquals("bar", this.context.getBean("bar"));
}
@Test
public void testOnBeanConditionWithXml() {
this.context.register(OnBeanNameConfiguration.class, XmlConfiguration.class);
this.context.refresh();
assertTrue(this.context.containsBean("bar"));
assertEquals("bar", this.context.getBean("bar"));
}
@Test
public void testOnBeanConditionWithCombinedXml() {
this.context.register(CombinedXmlConfiguration.class);
this.context.refresh();
assertTrue(this.context.containsBean("bar"));
assertEquals("bar", this.context.getBean("bar"));
}
@Configuration
@ConditionalOnBean(name = "foo")
protected static class OnBeanNameConfiguration {
@Bean
public String bar() {
return "bar";
}
}
@Configuration
@ConditionalOnBean(String.class)
protected static class OnBeanClassConfiguration {
@Bean
public String bar() {
return "bar";
}
}
@Configuration
protected static class FooConfiguration {
@Bean
public String foo() {
return "foo";
}
}
@Configuration
@ImportResource("org/springframework/zero/context/annotation/foo.xml")
protected static class XmlConfiguration {
}
@Configuration
@Import(OnBeanNameConfiguration.class)
@ImportResource("org/springframework/zero/context/annotation/foo.xml")
protected static class CombinedXmlConfiguration {
}
}

View File

@@ -0,0 +1,107 @@
/*
* 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.zero.context.condition;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportResource;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* Tests for {@link OnClassCondition}.
*
* @author Dave Syer
*/
public class OnClassConditionTests {
private AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
@Test
public void testVanillaOnClassCondition() {
this.context.register(BasicConfiguration.class, FooConfiguration.class);
this.context.refresh();
assertTrue(this.context.containsBean("bar"));
assertEquals("bar", this.context.getBean("bar"));
}
@Test
public void testMissingOnClassCondition() {
this.context.register(MissingConfiguration.class, FooConfiguration.class);
this.context.refresh();
assertFalse(this.context.containsBean("bar"));
assertEquals("foo", this.context.getBean("foo"));
}
@Test
public void testOnClassConditionWithXml() {
this.context.register(BasicConfiguration.class, XmlConfiguration.class);
this.context.refresh();
assertTrue(this.context.containsBean("bar"));
assertEquals("bar", this.context.getBean("bar"));
}
@Test
public void testOnClassConditionWithCombinedXml() {
this.context.register(CombinedXmlConfiguration.class);
this.context.refresh();
assertTrue(this.context.containsBean("bar"));
assertEquals("bar", this.context.getBean("bar"));
}
@Configuration
@ConditionalOnClass(OnClassConditionTests.class)
protected static class BasicConfiguration {
@Bean
public String bar() {
return "bar";
}
}
@Configuration
@ConditionalOnClass(name = "FOO")
protected static class MissingConfiguration {
@Bean
public String bar() {
return "bar";
}
}
@Configuration
protected static class FooConfiguration {
@Bean
public String foo() {
return "foo";
}
}
@Configuration
@ImportResource("org/springframework/zero/context/foo.xml")
protected static class XmlConfiguration {
}
@Configuration
@Import(BasicConfiguration.class)
@ImportResource("org/springframework/zero/context/foo.xml")
protected static class CombinedXmlConfiguration {
}
}

View File

@@ -0,0 +1,69 @@
/*
* 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.zero.context.condition;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* Tests for {@link OnExpressionCondition}.
*
* @author Dave Syer
*/
public class OnExpressionConditionTests {
private AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
@Test
public void testResourceExists() {
this.context.register(BasicConfiguration.class);
this.context.refresh();
assertTrue(this.context.containsBean("foo"));
assertEquals("foo", this.context.getBean("foo"));
}
@Test
public void testResourceNotExists() {
this.context.register(MissingConfiguration.class);
this.context.refresh();
assertFalse(this.context.containsBean("foo"));
}
@Configuration
@ConditionalOnExpression("false")
protected static class MissingConfiguration {
@Bean
public String bar() {
return "bar";
}
}
@Configuration
@ConditionalOnExpression("true")
protected static class BasicConfiguration {
@Bean
public String foo() {
return "foo";
}
}
}

Some files were not shown because too many files have changed in this diff Show More