diff --git a/org.springframework.context/src/main/java/org/springframework/context/ApplicationContextInitializer.java b/org.springframework.context/src/main/java/org/springframework/context/ApplicationContextInitializer.java new file mode 100644 index 00000000000..0cb5b44afd3 --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/context/ApplicationContextInitializer.java @@ -0,0 +1,40 @@ +/* + * Copyright 2002-2011 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.context; + +/** + * Callback interface for initializing a Spring {@link ConfigurableApplicationContext} + * prior to being {@linkplain ConfigurableApplicationContext#refresh() refreshed}. + * + *

{@code ApplicationContextInitializer} processors are encouraged to detect + * whether Spring's {@link org.springframework.core.Ordered Ordered} or + * {@link org.springframework.core.PriorityOrdered PriorityOrdered} interfaces are also + * implemented and sort instances accordingly if so prior to invocation. + * + * @author Chris Beams + * @since 3.1 + * @see org.springframework.web.context.ContextLoader#customizeContext + */ +public interface ApplicationContextInitializer { + + /** + * Initialize the given application context. + * @param applicationContext the application to configure + */ + void initialize(C applicationContext); + +} diff --git a/org.springframework.core/src/main/java/org/springframework/core/env/PropertySource.java b/org.springframework.core/src/main/java/org/springframework/core/env/PropertySource.java index 0a4fb9996ef..a46ceb14ef5 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/env/PropertySource.java +++ b/org.springframework.core/src/main/java/org/springframework/core/env/PropertySource.java @@ -63,6 +63,18 @@ public abstract class PropertySource { this.source = source; } + /** + * Create a new {@code PropertySource} with the given name and with a new {@code Object} + * instance as the underlying source. + *

Often useful in testing scenarios when creating + * anonymous implementations that never query an actual source, but rather return + * hard-coded values. + */ + @SuppressWarnings("unchecked") + public PropertySource(String name) { + this(name, (T) new Object()); + } + /** * Return the name of this {@code PropertySource} */ diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/context/ContextLoaderTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/context/ContextLoaderTests.java index 971e66941ef..e68907bf7d6 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/context/ContextLoaderTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/context/ContextLoaderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2011 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. @@ -16,15 +16,24 @@ package org.springframework.web.context; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + import java.io.FileNotFoundException; import java.io.IOException; + import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; -import static org.junit.Assert.*; import org.junit.Test; - import org.springframework.beans.BeansException; import org.springframework.beans.TestBean; import org.springframework.beans.factory.BeanCreationException; @@ -33,9 +42,14 @@ import org.springframework.beans.factory.LifecycleBean; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextException; +import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.PropertySource; import org.springframework.mock.web.MockServletConfig; import org.springframework.mock.web.MockServletContext; +import org.springframework.util.StringUtils; import org.springframework.web.context.support.XmlWebApplicationContext; import org.springframework.web.servlet.DispatcherServlet; import org.springframework.web.servlet.SimpleWebApplicationContext; @@ -100,6 +114,39 @@ public final class ContextLoaderTests { assertEquals("customizeContext() should have been called.", expectedContents, buffer.toString()); } + @Test + public void testContextLoaderListenerWithRegisteredContextConfigurer() { + MockServletContext sc = new MockServletContext(""); + sc.addInitParameter(ContextLoader.CONFIG_LOCATION_PARAM, + "org/springframework/web/context/WEB-INF/ContextLoaderTests-acc-context.xml"); + sc.addInitParameter(ContextLoader.CONTEXT_INITIALIZER_CLASSES_PARAM, + StringUtils.arrayToCommaDelimitedString( + new Object[]{TestContextInitializer.class.getName(), TestWebContextInitializer.class.getName()})); + ContextLoaderListener listener = new ContextLoaderListener(); + listener.contextInitialized(new ServletContextEvent(sc)); + WebApplicationContext wac = ContextLoaderListener.getCurrentWebApplicationContext(); + TestBean testBean = wac.getBean(TestBean.class); + assertThat(testBean.getName(), equalTo("testName")); + assertThat(wac.getServletContext().getAttribute("initialized"), notNullValue()); + } + + @Test + public void testContextLoaderListenerWithUnkownContextConfigurer() { + MockServletContext sc = new MockServletContext(""); + // config file doesn't matter. just a placeholder + sc.addInitParameter(ContextLoader.CONFIG_LOCATION_PARAM, + "/org/springframework/web/context/WEB-INF/empty-context.xml"); + sc.addInitParameter(ContextLoader.CONTEXT_INITIALIZER_CLASSES_PARAM, + StringUtils.arrayToCommaDelimitedString(new Object[]{UnknownContextInitializer.class.getName()})); + ContextLoaderListener listener = new ContextLoaderListener(); + try { + listener.contextInitialized(new ServletContextEvent(sc)); + fail("expected exception"); + } catch (IllegalArgumentException ex) { + assertTrue(ex.getMessage().contains("not assignable")); + } + } + @Test public void testContextLoaderWithDefaultContextAndParent() throws Exception { MockServletContext sc = new MockServletContext(""); @@ -257,4 +304,33 @@ public final class ContextLoaderTests { } } + private static class TestContextInitializer implements ApplicationContextInitializer { + public void initialize(ConfigurableApplicationContext applicationContext) { + ConfigurableEnvironment environment = applicationContext.getEnvironment(); + environment.getPropertySources().addFirst(new PropertySource("testPropertySource") { + @Override + public Object getProperty(String key) { + return "name".equals(key) ? "testName" : null; + } + }); + } + } + + private static class TestWebContextInitializer implements ApplicationContextInitializer { + public void initialize(ConfigurableWebApplicationContext applicationContext) { + ServletContext ctx = applicationContext.getServletContext(); // type-safe access to servlet-specific methods + ctx.setAttribute("initialized", true); + } + } + + private static interface UnknownApplicationContext extends ConfigurableApplicationContext { + void unheardOf(); + } + + private static class UnknownContextInitializer implements ApplicationContextInitializer { + public void initialize(UnknownApplicationContext applicationContext) { + applicationContext.unheardOf(); + } + } + } diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/context/WEB-INF/ContextLoaderTests-acc-context.xml b/org.springframework.web.servlet/src/test/java/org/springframework/web/context/WEB-INF/ContextLoaderTests-acc-context.xml new file mode 100644 index 00000000000..e25d3317379 --- /dev/null +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/context/WEB-INF/ContextLoaderTests-acc-context.xml @@ -0,0 +1,12 @@ + + + + + + + + + + diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/context/WEB-INF/empty-context.xml b/org.springframework.web.servlet/src/test/java/org/springframework/web/context/WEB-INF/empty-context.xml new file mode 100644 index 00000000000..704859f0492 --- /dev/null +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/context/WEB-INF/empty-context.xml @@ -0,0 +1,6 @@ + + + + diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/ContextLoader.java b/org.springframework.web/src/main/java/org/springframework/web/context/ContextLoader.java index e2b06108ff6..7c8d5d20907 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/context/ContextLoader.java +++ b/org.springframework.web/src/main/java/org/springframework/web/context/ContextLoader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2011 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. @@ -17,24 +17,33 @@ package org.springframework.web.context; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.Properties; import java.util.concurrent.ConcurrentHashMap; + import javax.servlet.ServletContext; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.access.BeanFactoryLocator; import org.springframework.beans.factory.access.BeanFactoryReference; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextException; +import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.access.ContextSingletonBeanFactoryLocator; +import org.springframework.core.GenericTypeResolver; +import org.springframework.core.OrderComparator; +import org.springframework.core.Ordered; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.support.PropertiesLoaderUtils; +import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; /** * Performs the actual initialization work for the root application context. @@ -78,14 +87,23 @@ public class ContextLoader { /** * Config param for the root WebApplicationContext implementation class to - * use: "contextClass" + * use: {@value} + * @see #determineContextClass(ServletContext) + * @see #createWebApplicationContext(ServletContext, ApplicationContext) */ public static final String CONTEXT_CLASS_PARAM = "contextClass"; /** - * Name of servlet context parameter (i.e., "contextConfigLocation") - * that can specify the config location for the root context, falling back - * to the implementation's default otherwise. + * Config param for which {@link ApplicationContextInitializer} classes to use + * for initializing the web application context: {@value} + * @see #customizeContext(ServletContext, ConfigurableWebApplicationContext) + */ + public static final String CONTEXT_INITIALIZER_CLASSES_PARAM = "contextInitializerClasses"; + + /** + * Name of servlet context parameter (i.e., {@value}) that can specify the + * config location for the root context, falling back to the implementation's + * default otherwise. * @see org.springframework.web.context.support.XmlWebApplicationContext#DEFAULT_CONFIG_LOCATION */ public static final String CONFIG_LOCATION_PARAM = "contextConfigLocation"; @@ -308,18 +326,73 @@ public class ContextLoader { } } + /** + * Return the {@link ApplicationContextInitializer} implementation classes to use + * if any have been specified by {@link #CONTEXT_INITIALIZER_CLASSES_PARAM}. + * @param servletContext current servlet context + * @see #CONTEXT_INITIALIZER_CLASSES_PARAM + */ + @SuppressWarnings("unchecked") + protected List>> + determineContextInitializerClasses(ServletContext servletContext) { + String classNames = servletContext.getInitParameter(CONTEXT_INITIALIZER_CLASSES_PARAM); + List>> classes = + new ArrayList>>(); + if (classNames != null) { + 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>)clazz); + } + catch (ClassNotFoundException ex) { + throw new ApplicationContextException( + "Failed to load context configurer class [" + className + "]", ex); + } + } + } + return classes; + } + /** * Customize the {@link ConfigurableWebApplicationContext} created by this * ContextLoader after config locations have been supplied to the context * but before the context is refreshed. - *

The default implementation is empty but can be overridden in subclasses - * to customize the application context. + *

The default implementation {@linkplain #determineContextInitializerClasses(ServletContext) + * determines} what (if any) context initializer classes have been specified through + * {@linkplain #CONTEXT_INITIALIZER_CLASSES_PARAM context init parameters} and + * {@linkplain ApplicationContextInitializer#initialize invokes each} with the + * given web application context. + *

Any {@link Ordered} {@code ApplicationContextInitializer} will be sorted + * appropriately. * @param servletContext the current servlet context * @param applicationContext the newly created application context * @see #createWebApplicationContext(ServletContext, ApplicationContext) + * @see #CONTEXT_INITIALIZER_CLASSES_PARAM + * @see ApplicationContextInitializer#initialize(ConfigurableApplicationContext) */ - protected void customizeContext( - ServletContext servletContext, ConfigurableWebApplicationContext applicationContext) { + protected void customizeContext(ServletContext servletContext, ConfigurableWebApplicationContext applicationContext) { + List> initializerInstances = + new ArrayList>(); + + for (Class> initializerClass : + determineContextInitializerClasses(servletContext)) { + Class contextClass = applicationContext.getClass(); + 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, contextClass)); + initializerInstances.add(BeanUtils.instantiateClass(initializerClass)); + } + + OrderComparator.sort(initializerInstances); + + for (ApplicationContextInitializer initializer : initializerInstances) { + initializer.initialize(applicationContext); + } } /**