Merge pull request #160 from sbrannen/SPR-5243

Support loading WebApplicationContexts in the TCF
This commit is contained in:
Sam Brannen 2012-10-07 15:29:18 -07:00
commit 9937f840d5
29 changed files with 1474 additions and 601 deletions

View File

@ -1,94 +0,0 @@
/*
* Copyright 2002-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.test.web.mock.servlet.samples.context;
import javax.servlet.RequestDispatcher;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotatedBeanDefinitionReader;
import org.springframework.context.annotation.AnnotationConfigUtils;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.FileSystemResourceLoader;
import org.springframework.core.io.ResourceLoader;
import org.springframework.mock.web.MockRequestDispatcher;
import org.springframework.mock.web.MockServletContext;
import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.test.context.support.AbstractContextLoader;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.GenericWebApplicationContext;
/**
* This class is here temporarily until the TestContext framework provides
* support for WebApplicationContext yet:
*
* https://jira.springsource.org/browse/SPR-5243
*
* <p>After that this class will no longer be needed. It's provided here as an example
* and to serve as a temporary solution.
*/
public class GenericWebContextLoader extends AbstractContextLoader {
protected final MockServletContext servletContext;
public GenericWebContextLoader(String warRootDir, boolean isClasspathRelative) {
ResourceLoader resourceLoader = isClasspathRelative ? new DefaultResourceLoader() : new FileSystemResourceLoader();
this.servletContext = initServletContext(warRootDir, resourceLoader);
}
private MockServletContext initServletContext(String warRootDir, ResourceLoader resourceLoader) {
return new MockServletContext(warRootDir, resourceLoader) {
// Required for DefaultServletHttpRequestHandler...
public RequestDispatcher getNamedDispatcher(String path) {
return (path.equals("default")) ? new MockRequestDispatcher(path) : super.getNamedDispatcher(path);
}
};
}
public ApplicationContext loadContext(MergedContextConfiguration mergedConfig) throws Exception {
GenericWebApplicationContext context = new GenericWebApplicationContext();
context.getEnvironment().setActiveProfiles(mergedConfig.getActiveProfiles());
prepareContext(context);
loadBeanDefinitions(context, mergedConfig);
return context;
}
public ApplicationContext loadContext(String... locations) throws Exception {
// should never be called
throw new UnsupportedOperationException();
}
protected void prepareContext(GenericWebApplicationContext context) {
this.servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, context);
context.setServletContext(this.servletContext);
}
protected void loadBeanDefinitions(GenericWebApplicationContext context, String[] locations) {
new XmlBeanDefinitionReader(context).loadBeanDefinitions(locations);
AnnotationConfigUtils.registerAnnotationConfigProcessors(context);
context.refresh();
context.registerShutdownHook();
}
protected void loadBeanDefinitions(GenericWebApplicationContext context, MergedContextConfiguration mergedConfig) {
new AnnotatedBeanDefinitionReader(context).register(mergedConfig.getClasses());
loadBeanDefinitions(context, mergedConfig.getLocations());
}
@Override
protected String getResourceSuffix() {
return "-context.xml";
}
}

View File

@ -16,9 +16,8 @@
package org.springframework.test.web.mock.servlet.samples.context;
import static org.springframework.test.web.mock.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.forwardedUrl;
import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.mock.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.*;
import org.junit.Before;
import org.junit.Test;
@ -26,21 +25,20 @@ import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.mock.servlet.MockMvc;
import org.springframework.test.web.mock.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.ContextLoader;
import org.springframework.web.context.WebApplicationContext;
/**
* Tests with Java configuration.
*
* The TestContext framework doesn't support WebApplicationContext yet:
* https://jira.springsource.org/browse/SPR-5243
*
* A custom {@link ContextLoader} is used to load the WebApplicationContext.
* @author Rossen Stoyanchev
* @author Sam Brannen
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader=WebContextLoader.class, classes={WebConfig.class})
@WebAppConfiguration("src/test/resources/META-INF/web-resources")
@ContextConfiguration(classes = WebConfig.class)
public class JavaTestContextTests {
@Autowired
@ -48,6 +46,7 @@ public class JavaTestContextTests {
private MockMvc mockMvc;
@Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
@ -55,9 +54,9 @@ public class JavaTestContextTests {
@Test
public void tilesDefinitions() throws Exception {
this.mockMvc.perform(get("/"))
.andExpect(status().isOk())
.andExpect(forwardedUrl("/WEB-INF/layouts/standardLayout.jsp"));
this.mockMvc.perform(get("/"))//
.andExpect(status().isOk())//
.andExpect(forwardedUrl("/WEB-INF/layouts/standardLayout.jsp"));
}
}

View File

@ -10,6 +10,7 @@
* 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.test.web.mock.servlet.samples.context;
import static org.springframework.test.web.mock.servlet.request.MockMvcRequestBuilders.get;
@ -34,6 +35,7 @@ import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.mock.servlet.MockMvc;
import org.springframework.test.web.mock.servlet.MvcResult;
import org.springframework.test.web.mock.servlet.ResultMatcher;
@ -44,32 +46,25 @@ import org.springframework.web.context.WebApplicationContext;
/**
* Basic example that includes Spring Security configuration.
*
* <p>Note that currently there are no {@link ResultMatcher}' built specifically
* for asserting the Spring Security context. However, it's quite easy to put
* them together as shown below and Spring Security extensions will become
* available in the near future.
* <p>Note that currently there are no {@linkplain ResultMatcher ResultMatchers}
* built specifically for asserting the Spring Security context. However, it's
* quite easy to put them together as shown below, and Spring Security extensions
* will become available in the near future.
*
* <p>This also demonstrates a custom {@link RequestPostProcessor} which authenticates
* a user to a particular {@link HttpServletRequest}.
*
* <p>Also see the Javadoc of {@link GenericWebContextLoader}, a class that
* provides temporary support for loading WebApplicationContext by extending
* the TestContext framework.
*
* @author Rob Winch
* @author Rossen Stoyanchev
* @author Sam Brannen
* @see SecurityRequestPostProcessors
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
loader=WebContextLoader.class,
value={
"classpath:org/springframework/test/web/mock/servlet/samples/context/security.xml",
"classpath:org/springframework/test/web/mock/servlet/samples/servlet-context.xml"
})
@WebAppConfiguration("src/test/resources/META-INF/web-resources")
@ContextConfiguration({ "security.xml", "../servlet-context.xml" })
public class SpringSecurityTests {
private static String SEC_CONTEXT_ATTR = HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY;
private static final String SEC_CONTEXT_ATTR = HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY;
@Autowired
private FilterChainProxy springSecurityFilterChain;
@ -79,57 +74,67 @@ public class SpringSecurityTests {
private MockMvc mockMvc;
@Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac)
.addFilters(this.springSecurityFilterChain).build();
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac)//
.addFilters(this.springSecurityFilterChain)//
.build();
}
@Test
public void requiresAuthentication() throws Exception {
mockMvc.perform(get("/user"))
.andExpect(redirectedUrl("http://localhost/spring_security_login"));
mockMvc.perform(get("/user")).//
andExpect(redirectedUrl("http://localhost/spring_security_login"));
}
@Test
public void accessGranted() throws Exception {
this.mockMvc.perform(get("/").with(userDeatilsService("user")))
.andExpect(status().isOk())
.andExpect(forwardedUrl("/WEB-INF/layouts/standardLayout.jsp"));
this.mockMvc.perform(get("/").//
with(userDeatilsService("user"))).//
andExpect(status().isOk()).//
andExpect(forwardedUrl("/WEB-INF/layouts/standardLayout.jsp"));
}
@Test
public void accessDenied() throws Exception {
this.mockMvc.perform(get("/").with(user("user").roles("DENIED")))
.andExpect(status().isForbidden());
this.mockMvc.perform(get("/")//
.with(user("user").roles("DENIED")))//
.andExpect(status().isForbidden());
}
@Test
public void userAuthenticates() throws Exception {
final String username = "user";
mockMvc.perform(post("/j_spring_security_check").param("j_username", username).param("j_password", "password"))
.andExpect(redirectedUrl("/"))
.andExpect(new ResultMatcher() {
public void match(MvcResult mvcResult) throws Exception {
HttpSession session = mvcResult.getRequest().getSession();
SecurityContext securityContext = (SecurityContext) session.getAttribute(SEC_CONTEXT_ATTR);
Assert.assertEquals(securityContext.getAuthentication().getName(), username);
}
});
mockMvc.perform(post("/j_spring_security_check").//
param("j_username", username).//
param("j_password", "password")).//
andExpect(redirectedUrl("/")).//
andExpect(new ResultMatcher() {
public void match(MvcResult mvcResult) throws Exception {
HttpSession session = mvcResult.getRequest().getSession();
SecurityContext securityContext = (SecurityContext) session.getAttribute(SEC_CONTEXT_ATTR);
Assert.assertEquals(securityContext.getAuthentication().getName(), username);
}
});
}
@Test
public void userAuthenticateFails() throws Exception {
final String username = "user";
mockMvc.perform(post("/j_spring_security_check").param("j_username", username).param("j_password", "invalid"))
.andExpect(redirectedUrl("/spring_security_login?login_error"))
.andExpect(new ResultMatcher() {
public void match(MvcResult mvcResult) throws Exception {
HttpSession session = mvcResult.getRequest().getSession();
SecurityContext securityContext = (SecurityContext) session.getAttribute(SEC_CONTEXT_ATTR);
Assert.assertNull(securityContext);
}
});
mockMvc.perform(post("/j_spring_security_check").//
param("j_username", username).//
param("j_password", "invalid")).//
andExpect(redirectedUrl("/spring_security_login?login_error")).//
andExpect(new ResultMatcher() {
public void match(MvcResult mvcResult) throws Exception {
HttpSession session = mvcResult.getRequest().getSession();
SecurityContext securityContext = (SecurityContext) session.getAttribute(SEC_CONTEXT_ATTR);
Assert.assertNull(securityContext);
}
});
}
}

View File

@ -16,9 +16,8 @@
package org.springframework.test.web.mock.servlet.samples.context;
import static org.springframework.test.web.mock.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.forwardedUrl;
import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.mock.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.*;
import org.junit.Before;
import org.junit.Test;
@ -26,23 +25,20 @@ import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.mock.servlet.MockMvc;
import org.springframework.test.web.mock.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.ContextLoader;
import org.springframework.web.context.WebApplicationContext;
/**
* Tests with XML configuration.
*
* The TestContext framework doesn't support WebApplicationContext yet:
* https://jira.springsource.org/browse/SPR-5243
*
* A custom {@link ContextLoader} is used to load the WebApplicationContext.
* @author Rossen Stoyanchev
* @author Sam Brannen
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
loader=WebContextLoader.class,
locations={"/org/springframework/test/web/mock/servlet/samples/servlet-context.xml"})
@WebAppConfiguration("src/test/resources/META-INF/web-resources")
@ContextConfiguration("../servlet-context.xml")
public class XmlTestContextTests {
@Autowired
@ -50,6 +46,7 @@ public class XmlTestContextTests {
private MockMvc mockMvc;
@Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
@ -57,10 +54,9 @@ public class XmlTestContextTests {
@Test
public void tilesDefinitions() throws Exception {
this.mockMvc.perform(get("/"))
.andExpect(status().isOk())
.andExpect(forwardedUrl("/WEB-INF/layouts/standardLayout.jsp"));
this.mockMvc.perform(get("/"))//
.andExpect(status().isOk())//
.andExpect(forwardedUrl("/WEB-INF/layouts/standardLayout.jsp"));
}
}

View File

@ -9,6 +9,7 @@
<configs>
<config>src/test/java/org/springframework/test/context/junit4/profile/xml/DefaultProfileXmlConfigTests-context.xml</config>
<config>src/test/java/org/springframework/test/context/junit4/aci/xml/MultipleInitializersXmlConfigTests-context.xml</config>
<config>src/test/resources/org/springframework/test/context/web/BasicXmlWacTests-config.xml</config>
</configs>
<configSets>
</configSets>

View File

@ -16,8 +16,8 @@
package org.springframework.test.context;
import static org.springframework.beans.BeanUtils.instantiateClass;
import static org.springframework.core.annotation.AnnotationUtils.findAnnotationDeclaringClass;
import static org.springframework.beans.BeanUtils.*;
import static org.springframework.core.annotation.AnnotationUtils.*;
import java.util.ArrayList;
import java.util.Arrays;
@ -30,6 +30,8 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.context.web.WebMergedContextConfiguration;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
@ -54,6 +56,7 @@ abstract class ContextLoaderUtils {
private static final Log logger = LogFactory.getLog(ContextLoaderUtils.class);
private static final String DEFAULT_CONTEXT_LOADER_CLASS_NAME = "org.springframework.test.context.support.DelegatingSmartContextLoader";
private static final String DEFAULT_WEB_CONTEXT_LOADER_CLASS_NAME = "org.springframework.test.context.support.WebDelegatingSmartContextLoader";
private ContextLoaderUtils() {
@ -83,7 +86,8 @@ abstract class ContextLoaderUtils {
Assert.notNull(testClass, "Test class must not be null");
if (!StringUtils.hasText(defaultContextLoaderClassName)) {
defaultContextLoaderClassName = DEFAULT_CONTEXT_LOADER_CLASS_NAME;
defaultContextLoaderClassName = testClass.isAnnotationPresent(WebAppConfiguration.class) ? DEFAULT_WEB_CONTEXT_LOADER_CLASS_NAME
: DEFAULT_CONTEXT_LOADER_CLASS_NAME;
}
Class<? extends ContextLoader> contextLoaderClass = resolveContextLoaderClass(testClass,
@ -394,6 +398,14 @@ abstract class ContextLoaderUtils {
Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializerClasses = resolveInitializerClasses(configAttributesList);
String[] activeProfiles = resolveActiveProfiles(testClass);
if (testClass.isAnnotationPresent(WebAppConfiguration.class)) {
WebAppConfiguration webAppConfig = testClass.getAnnotation(WebAppConfiguration.class);
String resourceBasePath = webAppConfig.value();
return new WebMergedContextConfiguration(testClass, locations, classes, initializerClasses, activeProfiles,
resourceBasePath, contextLoader);
}
// else
return new MergedContextConfiguration(testClass, locations, classes, initializerClasses, activeProfiles,
contextLoader);
}

View File

@ -105,7 +105,7 @@ public class MergedContextConfiguration implements Serializable {
* {@link ContextLoader} based solely on the fully qualified name of the
* loader or &quot;null&quot; if the supplied loaded is <code>null</code>.
*/
private static String nullSafeToString(ContextLoader contextLoader) {
protected static String nullSafeToString(ContextLoader contextLoader) {
return contextLoader == null ? "null" : contextLoader.getClass().getName();
}

View File

@ -114,7 +114,7 @@ public class TestContext extends AttributeAccessorSupport {
*/
private ApplicationContext loadApplicationContext() throws Exception {
ContextLoader contextLoader = mergedContextConfiguration.getContextLoader();
Assert.notNull(contextLoader, "Can not load an ApplicationContext with a NULL 'contextLoader'. "
Assert.notNull(contextLoader, "Cannot load an ApplicationContext with a NULL 'contextLoader'. "
+ "Consider annotating your test class with @ContextConfiguration.");
ApplicationContext applicationContext;
@ -125,7 +125,7 @@ public class TestContext extends AttributeAccessorSupport {
}
else {
String[] locations = mergedContextConfiguration.getLocations();
Assert.notNull(locations, "Can not load an ApplicationContext with a NULL 'locations' array. "
Assert.notNull(locations, "Cannot load an ApplicationContext with a NULL 'locations' array. "
+ "Consider annotating your test class with @ContextConfiguration.");
applicationContext = contextLoader.loadContext(locations);
}

View File

@ -76,6 +76,7 @@ import org.springframework.util.ObjectUtils;
public class TestContextManager {
private static final String[] DEFAULT_TEST_EXECUTION_LISTENER_CLASS_NAMES = new String[] {
"org.springframework.test.context.web.WebTestExecutionListener",
"org.springframework.test.context.support.DependencyInjectionTestExecutionListener",
"org.springframework.test.context.support.DirtiesContextTestExecutionListener",
"org.springframework.test.context.transaction.TransactionalTestExecutionListener" };
@ -126,7 +127,6 @@ public class TestContextManager {
return this.testContext;
}
/**
* Register the supplied {@link TestExecutionListener TestExecutionListeners}
* by appending them to the set of listeners used by this <code>TestContextManager</code>.
@ -155,8 +155,8 @@ public class TestContextManager {
* registered for this <code>TestContextManager</code> in reverse order.
*/
private List<TestExecutionListener> getReversedTestExecutionListeners() {
List<TestExecutionListener> listenersReversed =
new ArrayList<TestExecutionListener>(getTestExecutionListeners());
List<TestExecutionListener> listenersReversed = new ArrayList<TestExecutionListener>(
getTestExecutionListeners());
Collections.reverse(listenersReversed);
return listenersReversed;
}
@ -186,8 +186,7 @@ public class TestContextManager {
}
classesList.addAll(getDefaultTestExecutionListenerClasses());
defaultListeners = true;
}
else {
} else {
// Traverse the class hierarchy...
while (declaringClass != null) {
TestExecutionListeners testExecutionListeners = declaringClass.getAnnotation(annotationType);
@ -200,22 +199,21 @@ public class TestContextManager {
Class<? extends TestExecutionListener>[] listenerClasses = testExecutionListeners.listeners();
if (!ObjectUtils.isEmpty(valueListenerClasses) && !ObjectUtils.isEmpty(listenerClasses)) {
String msg = String.format(
"Test class [%s] has been configured with @TestExecutionListeners' 'value' [%s] " +
"and 'listeners' [%s] attributes. Use one or the other, but not both.",
"Test class [%s] has been configured with @TestExecutionListeners' 'value' [%s] "
+ "and 'listeners' [%s] attributes. Use one or the other, but not both.",
declaringClass, ObjectUtils.nullSafeToString(valueListenerClasses),
ObjectUtils.nullSafeToString(listenerClasses));
logger.error(msg);
throw new IllegalStateException(msg);
}
else if (!ObjectUtils.isEmpty(valueListenerClasses)) {
} else if (!ObjectUtils.isEmpty(valueListenerClasses)) {
listenerClasses = valueListenerClasses;
}
if (listenerClasses != null) {
classesList.addAll(0, Arrays.<Class<? extends TestExecutionListener>> asList(listenerClasses));
}
declaringClass = (testExecutionListeners.inheritListeners() ?
AnnotationUtils.findAnnotationDeclaringClass(annotationType, declaringClass.getSuperclass()) : null);
declaringClass = (testExecutionListeners.inheritListeners() ? AnnotationUtils.findAnnotationDeclaringClass(
annotationType, declaringClass.getSuperclass()) : null);
}
}
@ -223,16 +221,14 @@ public class TestContextManager {
for (Class<? extends TestExecutionListener> listenerClass : classesList) {
try {
listeners.add(BeanUtils.instantiateClass(listenerClass));
}
catch (NoClassDefFoundError err) {
} catch (NoClassDefFoundError err) {
if (defaultListeners) {
if (logger.isDebugEnabled()) {
logger.debug("Could not instantiate default TestExecutionListener class ["
+ listenerClass.getName()
+ "]. Specify custom listener classes or make the default listener classes available.");
}
}
else {
} else {
throw err;
}
}
@ -245,24 +241,21 @@ public class TestContextManager {
*/
@SuppressWarnings("unchecked")
protected Set<Class<? extends TestExecutionListener>> getDefaultTestExecutionListenerClasses() {
Set<Class<? extends TestExecutionListener>> defaultListenerClasses =
new LinkedHashSet<Class<? extends TestExecutionListener>>();
Set<Class<? extends TestExecutionListener>> defaultListenerClasses = new LinkedHashSet<Class<? extends TestExecutionListener>>();
for (String className : DEFAULT_TEST_EXECUTION_LISTENER_CLASS_NAMES) {
try {
defaultListenerClasses.add(
(Class<? extends TestExecutionListener>) getClass().getClassLoader().loadClass(className));
}
catch (Throwable ex) {
defaultListenerClasses.add((Class<? extends TestExecutionListener>) getClass().getClassLoader().loadClass(
className));
} catch (Throwable t) {
if (logger.isDebugEnabled()) {
logger.debug("Could not load default TestExecutionListener class [" + className
+ "]. Specify custom listener classes or make the default listener classes available.");
+ "]. Specify custom listener classes or make the default listener classes available.", t);
}
}
}
return defaultListenerClasses;
}
/**
* Hook for pre-processing a test class <em>before</em> execution of any
* tests within the class. Should be called prior to any framework-specific
@ -286,8 +279,7 @@ public class TestContextManager {
for (TestExecutionListener testExecutionListener : getTestExecutionListeners()) {
try {
testExecutionListener.beforeTestClass(getTestContext());
}
catch (Exception ex) {
} catch (Exception ex) {
logger.warn("Caught exception while allowing TestExecutionListener [" + testExecutionListener
+ "] to process 'before class' callback for test class [" + testClass + "]", ex);
throw ex;
@ -319,8 +311,7 @@ public class TestContextManager {
for (TestExecutionListener testExecutionListener : getTestExecutionListeners()) {
try {
testExecutionListener.prepareTestInstance(getTestContext());
}
catch (Exception ex) {
} catch (Exception ex) {
logger.error("Caught exception while allowing TestExecutionListener [" + testExecutionListener
+ "] to prepare test instance [" + testInstance + "]", ex);
throw ex;
@ -356,8 +347,7 @@ public class TestContextManager {
for (TestExecutionListener testExecutionListener : getTestExecutionListeners()) {
try {
testExecutionListener.beforeTestMethod(getTestContext());
}
catch (Exception ex) {
} catch (Exception ex) {
logger.warn("Caught exception while allowing TestExecutionListener [" + testExecutionListener
+ "] to process 'before' execution of test method [" + testMethod + "] for test instance ["
+ testInstance + "]", ex);
@ -404,8 +394,7 @@ public class TestContextManager {
for (TestExecutionListener testExecutionListener : getReversedTestExecutionListeners()) {
try {
testExecutionListener.afterTestMethod(getTestContext());
}
catch (Exception ex) {
} catch (Exception ex) {
logger.warn("Caught exception while allowing TestExecutionListener [" + testExecutionListener
+ "] to process 'after' execution for test: method [" + testMethod + "], instance ["
+ testInstance + "], exception [" + exception + "]", ex);
@ -446,8 +435,7 @@ public class TestContextManager {
for (TestExecutionListener testExecutionListener : getReversedTestExecutionListeners()) {
try {
testExecutionListener.afterTestClass(getTestContext());
}
catch (Exception ex) {
} catch (Exception ex) {
logger.warn("Caught exception while allowing TestExecutionListener [" + testExecutionListener
+ "] to process 'after class' callback for test class [" + testClass + "]", ex);
if (afterTestClassException == null) {

View File

@ -16,13 +16,24 @@
package org.springframework.test.context.support;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.support.ResourcePatternUtils;
import org.springframework.test.context.ContextConfigurationAttributes;
import org.springframework.test.context.ContextLoader;
import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.test.context.SmartContextLoader;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
@ -81,6 +92,64 @@ public abstract class AbstractContextLoader implements SmartContextLoader {
configAttributes.setLocations(processedLocations);
}
/**
* Prepare the {@link ConfigurableApplicationContext} created by this
* {@code SmartContextLoader} <i>before</i> bean definitions are read.
*
* <p>The default implementation:
* <ul>
* <li>Sets the <em>active bean definition profiles</em> from the supplied
* <code>MergedContextConfiguration</code> in the
* {@link org.springframework.core.env.Environment Environment} of the context.</li>
* <li>Determines what (if any) context initializer classes have been supplied
* via the {@code MergedContextConfiguration} and
* {@linkplain ApplicationContextInitializer#initialize invokes each} with the
* given application context.</li>
* </ul>
*
* <p>Any {@code ApplicationContextInitializers} implementing
* {@link org.springframework.core.Ordered Ordered} or marked with {@link
* org.springframework.core.annotation.Order @Order} will be sorted appropriately.
*
* @param context the newly created application context
* @param mergedConfig the merged context configuration
* @see ApplicationContextInitializer#initialize(ConfigurableApplicationContext)
* @see #loadContext(MergedContextConfiguration)
* @see ConfigurableApplicationContext#setId
* @since 3.2
*/
@SuppressWarnings("unchecked")
protected void prepareContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) {
context.getEnvironment().setActiveProfiles(mergedConfig.getActiveProfiles());
Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializerClasses = mergedConfig.getContextInitializerClasses();
if (initializerClasses.size() == 0) {
// no ApplicationContextInitializers have been declared -> nothing to do
return;
}
final List<ApplicationContextInitializer<ConfigurableApplicationContext>> initializerInstances = new ArrayList<ApplicationContextInitializer<ConfigurableApplicationContext>>();
final Class<?> contextClass = context.getClass();
for (Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>> initializerClass : initializerClasses) {
Class<?> initializerContextClass = GenericTypeResolver.resolveTypeArgument(initializerClass,
ApplicationContextInitializer.class);
Assert.isAssignable(initializerContextClass, contextClass, String.format(
"Could not add context initializer [%s] since 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((ApplicationContextInitializer<ConfigurableApplicationContext>) BeanUtils.instantiateClass(initializerClass));
}
Collections.sort(initializerInstances, new AnnotationAwareOrderComparator());
for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : initializerInstances) {
initializer.initialize(context);
}
}
// --- ContextLoader -------------------------------------------------------
/**
@ -185,12 +254,10 @@ public abstract class AbstractContextLoader implements SmartContextLoader {
String path = locations[i];
if (path.startsWith(SLASH)) {
modifiedLocations[i] = ResourceUtils.CLASSPATH_URL_PREFIX + path;
}
else if (!ResourcePatternUtils.isUrl(path)) {
} else if (!ResourcePatternUtils.isUrl(path)) {
modifiedLocations[i] = ResourceUtils.CLASSPATH_URL_PREFIX + SLASH
+ StringUtils.cleanPath(ClassUtils.classPackageAsResourcePath(clazz) + SLASH + path);
}
else {
} else {
modifiedLocations[i] = StringUtils.cleanPath(path);
}
}

View File

@ -0,0 +1,287 @@
/*
* Copyright 2002-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.test.context.support;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.ContextConfigurationAttributes;
import org.springframework.test.context.ContextLoader;
import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.test.context.SmartContextLoader;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
* {@code AbstractDelegatingSmartContextLoader} serves as an abstract base class
* for implementations of the {@link SmartContextLoader} SPI that delegate to a
* set of <em>candidate</em> SmartContextLoaders (i.e., one that supports XML
* configuration files and one that supports annotated classes) to determine which
* context loader is appropriate for a given test class's configuration. Each
* candidate is given a chance to {@link #processContextConfiguration process} the
* {@link ContextConfigurationAttributes} for each class in the test class hierarchy
* that is annotated with {@link ContextConfiguration @ContextConfiguration}, and
* the candidate that supports the merged, processed configuration will be used to
* actually {@link #loadContext load} the context.
*
* <p>Placing an empty {@code @ContextConfiguration} annotation on a test class signals
* that default resource locations (i.e., XML configuration files) or default
* {@link org.springframework.context.annotation.Configuration configuration classes}
* should be detected. Furthermore, if a specific {@link ContextLoader} or
* {@link SmartContextLoader} is not explicitly declared via
* {@code @ContextConfiguration}, a concrete subclass of
* {@code AbstractDelegatingSmartContextLoader} will be used as the default loader,
* thus providing automatic support for either XML configuration files or annotated
* classes, but not both simultaneously.
*
* <p>As of Spring 3.2, a test class may optionally declare neither XML configuration
* files nor annotated classes and instead declare only {@linkplain
* ContextConfiguration#initializers application context initializers}. In such
* cases, an attempt will still be made to detect defaults, but their absence will
* not result an an exception.
*
* @author Sam Brannen
* @since 3.2
* @see SmartContextLoader
*/
abstract class AbstractDelegatingSmartContextLoader implements SmartContextLoader {
private static final Log logger = LogFactory.getLog(AbstractDelegatingSmartContextLoader.class);
/**
* Get the delegate {@code SmartContextLoader} that supports XML configuration files.
*/
protected abstract SmartContextLoader getXmlLoader();
/**
* Get the delegate {@code SmartContextLoader} that supports annotated classes.
*/
protected abstract SmartContextLoader getAnnotationConfigLoader();
// --- SmartContextLoader --------------------------------------------------
private static String name(SmartContextLoader loader) {
return loader.getClass().getSimpleName();
}
private static void delegateProcessing(SmartContextLoader loader, ContextConfigurationAttributes configAttributes) {
if (logger.isDebugEnabled()) {
logger.debug(String.format("Delegating to %s to process context configuration %s.", name(loader),
configAttributes));
}
loader.processContextConfiguration(configAttributes);
}
private static ApplicationContext delegateLoading(SmartContextLoader loader, MergedContextConfiguration mergedConfig)
throws Exception {
if (logger.isDebugEnabled()) {
logger.debug(String.format("Delegating to %s to load context from %s.", name(loader), mergedConfig));
}
return loader.loadContext(mergedConfig);
}
private boolean supports(SmartContextLoader loader, MergedContextConfiguration mergedConfig) {
if (loader == getAnnotationConfigLoader()) {
return ObjectUtils.isEmpty(mergedConfig.getLocations()) && !ObjectUtils.isEmpty(mergedConfig.getClasses());
} else {
return !ObjectUtils.isEmpty(mergedConfig.getLocations()) && ObjectUtils.isEmpty(mergedConfig.getClasses());
}
}
/**
* Delegates to candidate {@code SmartContextLoaders} to process the supplied
* {@link ContextConfigurationAttributes}.
*
* <p>Delegation is based on explicit knowledge of the implementations of the
* default loaders for {@link #getXmlLoader() XML configuration files} and
* {@link #getAnnotationConfigLoader() annotated classes}. Specifically, the
* delegation algorithm is as follows:
*
* <ul>
* <li>If the resource locations or annotated classes in the supplied
* {@code ContextConfigurationAttributes} are not empty, the appropriate
* candidate loader will be allowed to process the configuration <em>as is</em>,
* without any checks for detection of defaults.</li>
* <li>Otherwise, the XML-based loader will be allowed to process
* the configuration in order to detect default resource locations. If
* the XML-based loader detects default resource locations,
* an {@code info} message will be logged.</li>
* <li>Subsequently, the annotation-based loader will be allowed to
* process the configuration in order to detect default configuration classes.
* If the annotation-based loader detects default configuration
* classes, an {@code info} message will be logged.</li>
* </ul>
*
* @param configAttributes the context configuration attributes to process
* @throws IllegalArgumentException if the supplied configuration attributes are
* <code>null</code>, or if the supplied configuration attributes include both
* resource locations and annotated classes
* @throws IllegalStateException if the XML-based loader detects default
* configuration classes; if the annotation-based loader detects default
* resource locations; if neither candidate loader detects defaults for the supplied
* context configuration; or if both candidate loaders detect defaults for the
* supplied context configuration
*/
public void processContextConfiguration(final ContextConfigurationAttributes configAttributes) {
Assert.notNull(configAttributes, "configAttributes must not be null");
Assert.isTrue(!(configAttributes.hasLocations() && configAttributes.hasClasses()), String.format(
"Cannot process locations AND classes for context "
+ "configuration %s; configure one or the other, but not both.", configAttributes));
// If the original locations or classes were not empty, there's no
// need to bother with default detection checks; just let the
// appropriate loader process the configuration.
if (configAttributes.hasLocations()) {
delegateProcessing(getXmlLoader(), configAttributes);
} else if (configAttributes.hasClasses()) {
delegateProcessing(getAnnotationConfigLoader(), configAttributes);
} else {
// Else attempt to detect defaults...
// Let the XML loader process the configuration.
delegateProcessing(getXmlLoader(), configAttributes);
boolean xmlLoaderDetectedDefaults = configAttributes.hasLocations();
if (xmlLoaderDetectedDefaults) {
if (logger.isInfoEnabled()) {
logger.info(String.format("%s detected default locations for context configuration %s.",
name(getXmlLoader()), configAttributes));
}
}
if (configAttributes.hasClasses()) {
throw new IllegalStateException(String.format(
"%s should NOT have detected default configuration classes for context configuration %s.",
name(getXmlLoader()), configAttributes));
}
// Now let the annotation config loader process the configuration.
delegateProcessing(getAnnotationConfigLoader(), configAttributes);
if (configAttributes.hasClasses()) {
if (logger.isInfoEnabled()) {
logger.info(String.format(
"%s detected default configuration classes for context configuration %s.",
name(getAnnotationConfigLoader()), configAttributes));
}
}
if (!xmlLoaderDetectedDefaults && configAttributes.hasLocations()) {
throw new IllegalStateException(String.format(
"%s should NOT have detected default locations for context configuration %s.",
name(getAnnotationConfigLoader()), configAttributes));
}
// If neither loader detected defaults and no initializers were declared,
// throw an exception.
if (!configAttributes.hasResources() && ObjectUtils.isEmpty(configAttributes.getInitializers())) {
throw new IllegalStateException(String.format(
"Neither %s nor %s was able to detect defaults, and no ApplicationContextInitializers "
+ "were declared for context configuration %s", name(getXmlLoader()),
name(getAnnotationConfigLoader()), configAttributes));
}
if (configAttributes.hasLocations() && configAttributes.hasClasses()) {
String message = String.format(
"Configuration error: both default locations AND default configuration classes "
+ "were detected for context configuration %s; configure one or the other, but not both.",
configAttributes);
logger.error(message);
throw new IllegalStateException(message);
}
}
}
/**
* Delegates to an appropriate candidate {@code SmartContextLoader} to load
* an {@link ApplicationContext}.
*
* <p>Delegation is based on explicit knowledge of the implementations of the
* default loaders for {@link #getXmlLoader() XML configuration files} and
* {@link #getAnnotationConfigLoader() annotated classes}. Specifically, the
* delegation algorithm is as follows:
*
* <ul>
* <li>If the resource locations in the supplied {@code MergedContextConfiguration}
* are not empty and the annotated classes are empty,
* the XML-based loader will load the {@code ApplicationContext}.</li>
* <li>If the annotated classes in the supplied {@code MergedContextConfiguration}
* are not empty and the resource locations are empty,
* the annotation-based loader will load the {@code ApplicationContext}.</li>
* </ul>
*
* @param mergedConfig the merged context configuration to use to load the application context
* @throws IllegalArgumentException if the supplied merged configuration is <code>null</code>
* @throws IllegalStateException if neither candidate loader is capable of loading an
* {@code ApplicationContext} from the supplied merged context configuration
*/
public ApplicationContext loadContext(MergedContextConfiguration mergedConfig) throws Exception {
Assert.notNull(mergedConfig, "mergedConfig must not be null");
List<SmartContextLoader> candidates = Arrays.asList(getXmlLoader(), getAnnotationConfigLoader());
for (SmartContextLoader loader : candidates) {
// Determine if each loader can load a context from the mergedConfig. If it
// can, let it; otherwise, keep iterating.
if (supports(loader, mergedConfig)) {
return delegateLoading(loader, mergedConfig);
}
}
// If neither of the candidates supports the mergedConfig based on resources but
// ACIs were declared, then delegate to the annotation config loader.
if (!mergedConfig.getContextInitializerClasses().isEmpty()) {
return delegateLoading(getAnnotationConfigLoader(), mergedConfig);
}
throw new IllegalStateException(String.format(
"Neither %s nor %s was able to load an ApplicationContext from %s.", name(getXmlLoader()),
name(getAnnotationConfigLoader()), mergedConfig));
}
// --- ContextLoader -------------------------------------------------------
/**
* {@code AbstractDelegatingSmartContextLoader} does not support the
* {@link ContextLoader#processLocations(Class, String...)} method. Call
* {@link #processContextConfiguration(ContextConfigurationAttributes)} instead.
* @throws UnsupportedOperationException
*/
public final String[] processLocations(Class<?> clazz, String... locations) {
throw new UnsupportedOperationException("DelegatingSmartContextLoaders do not support the ContextLoader SPI. "
+ "Call processContextConfiguration(ContextConfigurationAttributes) instead.");
}
/**
* {@code AbstractDelegatingSmartContextLoader} does not support the
* {@link ContextLoader#loadContext(String...) } method. Call
* {@link #loadContext(MergedContextConfiguration)} instead.
* @throws UnsupportedOperationException
*/
public final ApplicationContext loadContext(String... locations) throws Exception {
throw new UnsupportedOperationException("DelegatingSmartContextLoaders do not support the ContextLoader SPI. "
+ "Call loadContext(MergedContextConfiguration) instead.");
}
}

View File

@ -16,25 +16,14 @@
package org.springframework.test.context.support;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.support.BeanDefinitionReader;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigUtils;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
@ -77,7 +66,10 @@ public abstract class AbstractGenericContextLoader extends AbstractContextLoader
*
* <ul>
* <li>Creates a {@link GenericApplicationContext} instance.</li>
* <li>Calls {@link #prepareContext(GenericApplicationContext, MergedContextConfiguration)}
* <li>Calls {@link #prepareContext(GenericApplicationContext)} for backwards
* compatibility with the {@link org.springframework.test.context.ContextLoader
* ContextLoader} SPI.</li>
* <li>Calls {@link #prepareContext(ConfigurableApplicationContext, MergedContextConfiguration)}
* to allow for customizing the context before bean definitions are loaded.</li>
* <li>Calls {@link #customizeBeanFactory(DefaultListableBeanFactory)} to allow for customizing the
* context's <code>DefaultListableBeanFactory</code>.</li>
@ -105,6 +97,7 @@ public abstract class AbstractGenericContextLoader extends AbstractContextLoader
}
GenericApplicationContext context = new GenericApplicationContext();
prepareContext(context);
prepareContext(context, mergedConfig);
customizeBeanFactory(context.getDefaultListableBeanFactory());
loadBeanDefinitions(context, mergedConfig);
@ -183,72 +176,6 @@ public abstract class AbstractGenericContextLoader extends AbstractContextLoader
protected void prepareContext(GenericApplicationContext context) {
}
/**
* Prepare the {@link GenericApplicationContext} created by this
* {@code SmartContextLoader} <i>before</i> bean definitions are read.
*
* <p>The default implementation:
* <ul>
* <li>Calls {@link #prepareContext(GenericApplicationContext)} for backwards
* compatibility with the {@link org.springframework.test.context.ContextLoader
* ContextLoader} SPI.</li>
* <li>Sets the <em>active bean definition profiles</em> from the supplied
* <code>MergedContextConfiguration</code> in the
* {@link org.springframework.core.env.Environment Environment} of the context.</li>
* <li>Determines what (if any) context initializer classes have been supplied
* via the {@code MergedContextConfiguration} and
* {@linkplain ApplicationContextInitializer#initialize invokes each} with the
* given application context.</li>
* </ul>
*
* <p>Any {@code ApplicationContextInitializers} implementing
* {@link org.springframework.core.Ordered Ordered} or marked with {@link
* org.springframework.core.annotation.Order @Order} will be sorted appropriately.
*
* @param applicationContext the newly created application context
* @param mergedConfig the merged context configuration
* @see ApplicationContextInitializer#initialize(GenericApplicationContext)
* @see #loadContext(MergedContextConfiguration)
* @see GenericApplicationContext#setAllowBeanDefinitionOverriding
* @see GenericApplicationContext#setResourceLoader
* @see GenericApplicationContext#setId
* @since 3.2
*/
@SuppressWarnings("unchecked")
protected void prepareContext(GenericApplicationContext applicationContext,
MergedContextConfiguration mergedConfig) {
prepareContext(applicationContext);
applicationContext.getEnvironment().setActiveProfiles(mergedConfig.getActiveProfiles());
Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializerClasses = mergedConfig.getContextInitializerClasses();
if (initializerClasses.size() == 0) {
// no ApplicationContextInitializers have been declared -> nothing to do
return;
}
final List<ApplicationContextInitializer<ConfigurableApplicationContext>> initializerInstances = new ArrayList<ApplicationContextInitializer<ConfigurableApplicationContext>>();
final Class<?> contextClass = applicationContext.getClass();
for (Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>> initializerClass : initializerClasses) {
Class<?> initializerContextClass = GenericTypeResolver.resolveTypeArgument(initializerClass,
ApplicationContextInitializer.class);
Assert.isAssignable(initializerContextClass, contextClass, String.format(
"Could not add context initializer [%s] since 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((ApplicationContextInitializer<ConfigurableApplicationContext>) BeanUtils.instantiateClass(initializerClass));
}
Collections.sort(initializerInstances, new AnnotationAwareOrderComparator());
for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : initializerInstances) {
initializer.initialize(applicationContext);
}
}
/**
* Customize the internal bean factory of the ApplicationContext created by
* this <code>ContextLoader</code>.

View File

@ -0,0 +1,126 @@
/*
* Copyright 2002-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.test.context.support;
import javax.servlet.ServletContext;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigUtils;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.FileSystemResourceLoader;
import org.springframework.core.io.ResourceLoader;
import org.springframework.mock.web.MockServletContext;
import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.test.context.web.WebMergedContextConfiguration;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.GenericWebApplicationContext;
/**
* TODO [SPR-5243] Document AbstractGenericWebContextLoader.
*
* @author Sam Brannen
* @since 3.2
*/
public abstract class AbstractGenericWebContextLoader extends AbstractContextLoader {
private static final Log logger = LogFactory.getLog(AbstractGenericWebContextLoader.class);
// --- SmartContextLoader -----------------------------------------------
/**
* TODO [SPR-5243] Document overridden loadContext(MergedContextConfiguration).
*
* @see org.springframework.test.context.SmartContextLoader#loadContext(org.springframework.test.context.MergedContextConfiguration)
*/
public final ConfigurableApplicationContext loadContext(MergedContextConfiguration mergedConfig) throws Exception {
if (!(mergedConfig instanceof WebMergedContextConfiguration)) {
throw new IllegalArgumentException(String.format(
"Cannot load WebApplicationContext from non-web merged context configuration %s. "
+ "Consider annotating your test class with @WebAppConfiguration.", mergedConfig));
}
WebMergedContextConfiguration webMergedConfig = (WebMergedContextConfiguration) mergedConfig;
if (logger.isDebugEnabled()) {
logger.debug(String.format("Loading WebApplicationContext for merged context configuration %s.",
webMergedConfig));
}
GenericWebApplicationContext context = new GenericWebApplicationContext();
configureWebResources(context, webMergedConfig);
prepareContext(context, webMergedConfig);
customizeBeanFactory(context.getDefaultListableBeanFactory(), webMergedConfig);
loadBeanDefinitions(context, webMergedConfig);
AnnotationConfigUtils.registerAnnotationConfigProcessors(context);
customizeContext(context, webMergedConfig);
context.refresh();
context.registerShutdownHook();
return context;
}
/**
* TODO [SPR-5243] Document configureWebResources().
*/
protected void configureWebResources(GenericWebApplicationContext context,
WebMergedContextConfiguration webMergedConfig) {
String resourceBasePath = webMergedConfig.getResourceBasePath();
ResourceLoader resourceLoader = resourceBasePath.startsWith(ResourceLoader.CLASSPATH_URL_PREFIX) ? new DefaultResourceLoader()
: new FileSystemResourceLoader();
ServletContext servletContext = new MockServletContext(resourceBasePath, resourceLoader);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, context);
context.setServletContext(servletContext);
}
/**
* TODO [SPR-5243] Document customizeBeanFactory().
*/
protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory,
WebMergedContextConfiguration webMergedConfig) {
}
/**
* TODO [SPR-5243] Document loadBeanDefinitions().
*/
protected abstract void loadBeanDefinitions(GenericWebApplicationContext context,
WebMergedContextConfiguration webMergedConfig);
/**
* TODO [SPR-5243] Document customizeContext().
*/
protected void customizeContext(GenericWebApplicationContext context, WebMergedContextConfiguration webMergedConfig) {
}
// --- ContextLoader -------------------------------------------------------
/**
* TODO [SPR-5243] Document overridden loadContext(String...).
*
* @see org.springframework.test.context.ContextLoader#loadContext(java.lang.String[])
*/
public final ApplicationContext loadContext(String... locations) throws Exception {
throw new UnsupportedOperationException(
"AbstractGenericWebContextLoader does not support the loadContext(String... locations) method");
}
}

View File

@ -16,19 +16,13 @@
package org.springframework.test.context.support;
import java.lang.reflect.Modifier;
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.support.BeanDefinitionReader;
import org.springframework.context.annotation.AnnotatedBeanDefinitionReader;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.test.context.ContextConfigurationAttributes;
import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
@ -89,80 +83,19 @@ public class AnnotationConfigContextLoader extends AbstractGenericContextLoader
// --- AnnotationConfigContextLoader ---------------------------------------
private boolean isStaticNonPrivateAndNonFinal(Class<?> clazz) {
Assert.notNull(clazz, "Class must not be null");
int modifiers = clazz.getModifiers();
return (Modifier.isStatic(modifiers) && !Modifier.isPrivate(modifiers) && !Modifier.isFinal(modifiers));
}
/**
* Determine if the supplied {@link Class} meets the criteria for being
* considered a <em>default configuration class</em> candidate.
*
* <p>Specifically, such candidates:
*
* <ul>
* <li>must not be <code>null</code></li>
* <li>must not be <code>private</code></li>
* <li>must not be <code>final</code></li>
* <li>must be <code>static</code></li>
* <li>must be annotated with {@code @Configuration}</li>
* </ul>
*
* @param clazz the class to check
* @return <code>true</code> if the supplied class meets the candidate criteria
*/
private boolean isDefaultConfigurationClassCandidate(Class<?> clazz) {
return clazz != null && isStaticNonPrivateAndNonFinal(clazz) && clazz.isAnnotationPresent(Configuration.class);
}
/**
* Detect the default configuration classes for the supplied test class.
*
* <p>The returned class array will contain all static inner classes of
* the supplied class that meet the requirements for {@code @Configuration}
* class implementations as specified in the documentation for
* {@link Configuration @Configuration}.
* <p>The default implementation simply delegates to
* {@link AnnotationConfigContextLoaderUtils#detectDefaultConfigurationClasses(Class)}.
*
* <p>The implementation of this method adheres to the contract defined in the
* {@link org.springframework.test.context.SmartContextLoader SmartContextLoader}
* SPI. Specifically, this method uses introspection to detect default
* configuration classes that comply with the constraints required of
* {@code @Configuration} class implementations. If a potential candidate
* configuration class does not meet these requirements, this method will log a
* warning, and the potential candidate class will be ignored.
* @param declaringClass the test class that declared {@code @ContextConfiguration}
* @return an array of default configuration classes, potentially empty but
* never <code>null</code>
* @see AnnotationConfigContextLoaderUtils
*/
protected Class<?>[] detectDefaultConfigurationClasses(Class<?> declaringClass) {
Assert.notNull(declaringClass, "Declaring class must not be null");
List<Class<?>> configClasses = new ArrayList<Class<?>>();
for (Class<?> candidate : declaringClass.getDeclaredClasses()) {
if (isDefaultConfigurationClassCandidate(candidate)) {
configClasses.add(candidate);
}
else {
if (logger.isDebugEnabled()) {
logger.debug(String.format(
"Ignoring class [%s]; it must be static, non-private, non-final, and annotated "
+ "with @Configuration to be considered a default configuration class.",
candidate.getName()));
}
}
}
if (configClasses.isEmpty()) {
if (logger.isInfoEnabled()) {
logger.info(String.format("Could not detect default configuration classes for test class [%s]: "
+ "%s does not declare any static, non-private, non-final, inner classes "
+ "annotated with @Configuration.", declaringClass.getName(), declaringClass.getSimpleName()));
}
}
return configClasses.toArray(new Class<?>[configClasses.size()]);
return AnnotationConfigContextLoaderUtils.detectDefaultConfigurationClasses(declaringClass);
}
// --- AbstractContextLoader -----------------------------------------------

View File

@ -0,0 +1,118 @@
/*
* Copyright 2002-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.test.context.support;
import java.lang.reflect.Modifier;
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.Configuration;
import org.springframework.util.Assert;
/**
* TODO [SPR-5243] Document AnnotationConfigContextLoaderUtils.
*
* @author Sam Brannen
* @since 3.2
*/
abstract class AnnotationConfigContextLoaderUtils {
private static final Log logger = LogFactory.getLog(AnnotationConfigContextLoaderUtils.class);
private AnnotationConfigContextLoaderUtils() {
/* no-op */
}
private static boolean isStaticNonPrivateAndNonFinal(Class<?> clazz) {
Assert.notNull(clazz, "Class must not be null");
int modifiers = clazz.getModifiers();
return (Modifier.isStatic(modifiers) && !Modifier.isPrivate(modifiers) && !Modifier.isFinal(modifiers));
}
/**
* Determine if the supplied {@link Class} meets the criteria for being
* considered a <em>default configuration class</em> candidate.
*
* <p>Specifically, such candidates:
*
* <ul>
* <li>must not be <code>null</code></li>
* <li>must not be <code>private</code></li>
* <li>must not be <code>final</code></li>
* <li>must be <code>static</code></li>
* <li>must be annotated with {@code @Configuration}</li>
* </ul>
*
* @param clazz the class to check
* @return <code>true</code> if the supplied class meets the candidate criteria
*/
private static boolean isDefaultConfigurationClassCandidate(Class<?> clazz) {
return clazz != null && isStaticNonPrivateAndNonFinal(clazz) && clazz.isAnnotationPresent(Configuration.class);
}
/**
* Detect the default configuration classes for the supplied test class.
*
* <p>The returned class array will contain all static inner classes of
* the supplied class that meet the requirements for {@code @Configuration}
* class implementations as specified in the documentation for
* {@link Configuration @Configuration}.
*
* <p>The implementation of this method adheres to the contract defined in the
* {@link org.springframework.test.context.SmartContextLoader SmartContextLoader}
* SPI. Specifically, this method uses introspection to detect default
* configuration classes that comply with the constraints required of
* {@code @Configuration} class implementations. If a potential candidate
* configuration class does not meet these requirements, this method will log a
* warning, and the potential candidate class will be ignored.
* @param declaringClass the test class that declared {@code @ContextConfiguration}
* @return an array of default configuration classes, potentially empty but
* never <code>null</code>
*/
static Class<?>[] detectDefaultConfigurationClasses(Class<?> declaringClass) {
Assert.notNull(declaringClass, "Declaring class must not be null");
List<Class<?>> configClasses = new ArrayList<Class<?>>();
for (Class<?> candidate : declaringClass.getDeclaredClasses()) {
if (isDefaultConfigurationClassCandidate(candidate)) {
configClasses.add(candidate);
} else {
if (logger.isDebugEnabled()) {
logger.debug(String.format(
"Ignoring class [%s]; it must be static, non-private, non-final, and annotated "
+ "with @Configuration to be considered a default configuration class.",
candidate.getName()));
}
}
}
if (configClasses.isEmpty()) {
if (logger.isInfoEnabled()) {
logger.info(String.format("Could not detect default configuration classes for test class [%s]: "
+ "%s does not declare any static, non-private, non-final, inner classes "
+ "annotated with @Configuration.", declaringClass.getName(), declaringClass.getSimpleName()));
}
}
return configClasses.toArray(new Class<?>[configClasses.size()]);
}
}

View File

@ -0,0 +1,151 @@
/*
* Copyright 2002-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.test.context.support;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.annotation.AnnotatedBeanDefinitionReader;
import org.springframework.test.context.ContextConfigurationAttributes;
import org.springframework.test.context.web.WebMergedContextConfiguration;
import org.springframework.util.ObjectUtils;
import org.springframework.web.context.support.GenericWebApplicationContext;
/**
* TODO [SPR-5243] Document AnnotationConfigWebContextLoader.
*
* @author Sam Brannen
* @since 3.2
*/
public class AnnotationConfigWebContextLoader extends AbstractGenericWebContextLoader {
private static final Log logger = LogFactory.getLog(AnnotationConfigWebContextLoader.class);
// --- SmartContextLoader -----------------------------------------------
/**
* Process <em>annotated classes</em> in the supplied {@link ContextConfigurationAttributes}.
*
* <p>If the <em>annotated classes</em> are <code>null</code> or empty and
* {@link #isGenerateDefaultLocations()} returns <code>true</code>, this
* <code>SmartContextLoader</code> will attempt to {@link
* #detectDefaultConfigurationClasses detect default configuration classes}.
* If defaults are detected they will be
* {@link ContextConfigurationAttributes#setClasses(Class[]) set} in the
* supplied configuration attributes. Otherwise, properties in the supplied
* configuration attributes will not be modified.
*
* @param configAttributes the context configuration attributes to process
* @see org.springframework.test.context.SmartContextLoader#processContextConfiguration(ContextConfigurationAttributes)
* @see #isGenerateDefaultLocations()
* @see #detectDefaultConfigurationClasses(Class)
*/
public void processContextConfiguration(ContextConfigurationAttributes configAttributes) {
if (ObjectUtils.isEmpty(configAttributes.getClasses()) && isGenerateDefaultLocations()) {
Class<?>[] defaultConfigClasses = detectDefaultConfigurationClasses(configAttributes.getDeclaringClass());
configAttributes.setClasses(defaultConfigClasses);
}
}
/**
* Detect the default configuration classes for the supplied test class.
*
* <p>The default implementation simply delegates to
* {@link AnnotationConfigContextLoaderUtils#detectDefaultConfigurationClasses(Class)}.
*
* @param declaringClass the test class that declared {@code @ContextConfiguration}
* @return an array of default configuration classes, potentially empty but
* never <code>null</code>
* @see AnnotationConfigContextLoaderUtils
*/
protected Class<?>[] detectDefaultConfigurationClasses(Class<?> declaringClass) {
return AnnotationConfigContextLoaderUtils.detectDefaultConfigurationClasses(declaringClass);
}
// --- AbstractContextLoader -----------------------------------------------
/**
* {@code AnnotationConfigWebContextLoader} should be used as a
* {@link org.springframework.test.context.SmartContextLoader SmartContextLoader},
* not as a legacy {@link org.springframework.test.context.ContextLoader ContextLoader}.
* Consequently, this method is not supported.
*
* @see AbstractContextLoader#modifyLocations
* @throws UnsupportedOperationException
*/
@Override
protected String[] modifyLocations(Class<?> clazz, String... locations) {
throw new UnsupportedOperationException(
"AnnotationConfigWebContextLoader does not support the modifyLocations(Class, String...) method");
}
/**
* {@code AnnotationConfigWebContextLoader} should be used as a
* {@link org.springframework.test.context.SmartContextLoader SmartContextLoader},
* not as a legacy {@link org.springframework.test.context.ContextLoader ContextLoader}.
* Consequently, this method is not supported.
*
* @see AbstractContextLoader#generateDefaultLocations
* @throws UnsupportedOperationException
*/
@Override
protected String[] generateDefaultLocations(Class<?> clazz) {
throw new UnsupportedOperationException(
"AnnotationConfigWebContextLoader does not support the generateDefaultLocations(Class) method");
}
/**
* {@code AnnotationConfigWebContextLoader} should be used as a
* {@link org.springframework.test.context.SmartContextLoader SmartContextLoader},
* not as a legacy {@link org.springframework.test.context.ContextLoader ContextLoader}.
* Consequently, this method is not supported.
*
* @see AbstractContextLoader#getResourceSuffix
* @throws UnsupportedOperationException
*/
@Override
protected String getResourceSuffix() {
throw new UnsupportedOperationException(
"AnnotationConfigWebContextLoader does not support the getResourceSuffix() method");
}
// --- AbstractGenericWebContextLoader -------------------------------------
/**
* Register classes in the supplied {@link GenericWebApplicationContext context}
* from the classes in the supplied {@link WebMergedContextConfiguration}.
*
* <p>Each class must represent an <em>annotated class</em>. An
* {@link AnnotatedBeanDefinitionReader} is used to register the appropriate
* bean definitions.
*
* @param context the context in which the annotated classes should be registered
* @param webMergedConfig the merged configuration from which the classes should be retrieved
*
* @see AbstractGenericWebContextLoader#loadBeanDefinitions
*/
@Override
protected void loadBeanDefinitions(GenericWebApplicationContext context,
WebMergedContextConfiguration webMergedConfig) {
Class<?>[] annotatedClasses = webMergedConfig.getClasses();
if (logger.isDebugEnabled()) {
logger.debug("Registering annotated classes: " + ObjectUtils.nullSafeToString(annotatedClasses));
}
new AnnotatedBeanDefinitionReader(context).register(annotatedClasses);
}
}

View File

@ -16,263 +16,32 @@
package org.springframework.test.context.support;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.ContextConfigurationAttributes;
import org.springframework.test.context.ContextLoader;
import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.test.context.SmartContextLoader;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
* {@code DelegatingSmartContextLoader} is an implementation of the {@link SmartContextLoader}
* SPI that delegates to a set of <em>candidate</em> SmartContextLoaders (i.e.,
* {@link GenericXmlContextLoader} and {@link AnnotationConfigContextLoader}) to
* determine which context loader is appropriate for a given test class's configuration.
* Each candidate is given a chance to {@link #processContextConfiguration process} the
* {@link ContextConfigurationAttributes} for each class in the test class hierarchy that
* is annotated with {@link ContextConfiguration @ContextConfiguration}, and the candidate
* that supports the merged, processed configuration will be used to actually
* {@link #loadContext load} the context.
*
* <p>Placing an empty {@code @ContextConfiguration} annotation on a test class signals
* that default resource locations (i.e., XML configuration files) or default
* {@link org.springframework.context.annotation.Configuration configuration classes}
* should be detected. Furthermore, if a specific {@link ContextLoader} or
* {@link SmartContextLoader} is not explicitly declared via
* {@code @ContextConfiguration}, {@code DelegatingSmartContextLoader} will be used as
* the default loader, thus providing automatic support for either XML configuration
* files or annotated classes, but not both simultaneously.
*
* <p>As of Spring 3.2, a test class may optionally declare neither XML configuration
* files nor annotated classes and instead declare only {@linkplain
* ContextConfiguration#initializers application context initializers}. In such
* cases, an attempt will still be made to detect defaults, but their absence will
* not result an an exception.
* {@code DelegatingSmartContextLoader} is a concrete implementation of
* {@link AbstractDelegatingSmartContextLoader} that delegates to a
* {@link GenericXmlContextLoader} and an {@link AnnotationConfigContextLoader}.
*
* @author Sam Brannen
* @since 3.1
* @see SmartContextLoader
* @see AbstractDelegatingSmartContextLoader
* @see GenericXmlContextLoader
* @see AnnotationConfigContextLoader
*/
public class DelegatingSmartContextLoader implements SmartContextLoader {
private static final Log logger = LogFactory.getLog(DelegatingSmartContextLoader.class);
public class DelegatingSmartContextLoader extends AbstractDelegatingSmartContextLoader {
private final SmartContextLoader xmlLoader = new GenericXmlContextLoader();
private final SmartContextLoader annotationConfigLoader = new AnnotationConfigContextLoader();
// --- SmartContextLoader --------------------------------------------------
private static String name(SmartContextLoader loader) {
return loader.getClass().getSimpleName();
protected SmartContextLoader getXmlLoader() {
return this.xmlLoader;
}
private static void delegateProcessing(SmartContextLoader loader, ContextConfigurationAttributes configAttributes) {
if (logger.isDebugEnabled()) {
logger.debug(String.format("Delegating to %s to process context configuration %s.", name(loader),
configAttributes));
}
loader.processContextConfiguration(configAttributes);
}
private static ApplicationContext delegateLoading(SmartContextLoader loader, MergedContextConfiguration mergedConfig)
throws Exception {
if (logger.isDebugEnabled()) {
logger.debug(String.format("Delegating to %s to load context from %s.", name(loader), mergedConfig));
}
return loader.loadContext(mergedConfig);
}
private static boolean supports(SmartContextLoader loader, MergedContextConfiguration mergedConfig) {
if (loader instanceof AnnotationConfigContextLoader) {
return ObjectUtils.isEmpty(mergedConfig.getLocations()) && !ObjectUtils.isEmpty(mergedConfig.getClasses());
} else {
return !ObjectUtils.isEmpty(mergedConfig.getLocations()) && ObjectUtils.isEmpty(mergedConfig.getClasses());
}
}
/**
* Delegates to candidate {@code SmartContextLoaders} to process the supplied
* {@link ContextConfigurationAttributes}.
*
* <p>Delegation is based on explicit knowledge of the implementations of
* {@link GenericXmlContextLoader} and {@link AnnotationConfigContextLoader}.
* Specifically, the delegation algorithm is as follows:
*
* <ul>
* <li>If the resource locations or annotated classes in the supplied
* {@code ContextConfigurationAttributes} are not empty, the appropriate
* candidate loader will be allowed to process the configuration <em>as is</em>,
* without any checks for detection of defaults.</li>
* <li>Otherwise, {@code GenericXmlContextLoader} will be allowed to process
* the configuration in order to detect default resource locations. If
* {@code GenericXmlContextLoader} detects default resource locations,
* an {@code info} message will be logged.</li>
* <li>Subsequently, {@code AnnotationConfigContextLoader} will be allowed to
* process the configuration in order to detect default configuration classes.
* If {@code AnnotationConfigContextLoader} detects default configuration
* classes, an {@code info} message will be logged.</li>
* </ul>
*
* @param configAttributes the context configuration attributes to process
* @throws IllegalArgumentException if the supplied configuration attributes are
* <code>null</code>, or if the supplied configuration attributes include both
* resource locations and annotated classes
* @throws IllegalStateException if {@code GenericXmlContextLoader} detects default
* configuration classes; if {@code AnnotationConfigContextLoader} detects default
* resource locations; if neither candidate loader detects defaults for the supplied
* context configuration; or if both candidate loaders detect defaults for the
* supplied context configuration
*/
public void processContextConfiguration(final ContextConfigurationAttributes configAttributes) {
Assert.notNull(configAttributes, "configAttributes must not be null");
Assert.isTrue(!(configAttributes.hasLocations() && configAttributes.hasClasses()), String.format(
"Cannot process locations AND classes for context "
+ "configuration %s; configure one or the other, but not both.", configAttributes));
// If the original locations or classes were not empty, there's no
// need to bother with default detection checks; just let the
// appropriate loader process the configuration.
if (configAttributes.hasLocations()) {
delegateProcessing(xmlLoader, configAttributes);
} else if (configAttributes.hasClasses()) {
delegateProcessing(annotationConfigLoader, configAttributes);
} else {
// Else attempt to detect defaults...
// Let the XML loader process the configuration.
delegateProcessing(xmlLoader, configAttributes);
boolean xmlLoaderDetectedDefaults = configAttributes.hasLocations();
if (xmlLoaderDetectedDefaults) {
if (logger.isInfoEnabled()) {
logger.info(String.format("%s detected default locations for context configuration %s.",
name(xmlLoader), configAttributes));
}
}
if (configAttributes.hasClasses()) {
throw new IllegalStateException(String.format(
"%s should NOT have detected default configuration classes for context configuration %s.",
name(xmlLoader), configAttributes));
}
// Now let the annotation config loader process the configuration.
delegateProcessing(annotationConfigLoader, configAttributes);
if (configAttributes.hasClasses()) {
if (logger.isInfoEnabled()) {
logger.info(String.format(
"%s detected default configuration classes for context configuration %s.",
name(annotationConfigLoader), configAttributes));
}
}
if (!xmlLoaderDetectedDefaults && configAttributes.hasLocations()) {
throw new IllegalStateException(String.format(
"%s should NOT have detected default locations for context configuration %s.",
name(annotationConfigLoader), configAttributes));
}
// If neither loader detected defaults and no initializers were declared,
// throw an exception.
if (!configAttributes.hasResources() && ObjectUtils.isEmpty(configAttributes.getInitializers())) {
throw new IllegalStateException(String.format(
"Neither %s nor %s was able to detect defaults, and no ApplicationContextInitializers "
+ "were declared for context configuration %s", name(xmlLoader),
name(annotationConfigLoader), configAttributes));
}
if (configAttributes.hasLocations() && configAttributes.hasClasses()) {
String message = String.format(
"Configuration error: both default locations AND default configuration classes "
+ "were detected for context configuration %s; configure one or the other, but not both.",
configAttributes);
logger.error(message);
throw new IllegalStateException(message);
}
}
}
/**
* Delegates to an appropriate candidate {@code SmartContextLoader} to load
* an {@link ApplicationContext}.
*
* <p>Delegation is based on explicit knowledge of the implementations of
* {@link GenericXmlContextLoader} and {@link AnnotationConfigContextLoader}.
* Specifically, the delegation algorithm is as follows:
*
* <ul>
* <li>If the resource locations in the supplied {@code MergedContextConfiguration}
* are not empty and the annotated classes are empty,
* {@code GenericXmlContextLoader} will load the {@code ApplicationContext}.</li>
* <li>If the annotated classes in the supplied {@code MergedContextConfiguration}
* are not empty and the resource locations are empty,
* {@code AnnotationConfigContextLoader} will load the {@code ApplicationContext}.</li>
* </ul>
*
* @param mergedConfig the merged context configuration to use to load the application context
* @throws IllegalArgumentException if the supplied merged configuration is <code>null</code>
* @throws IllegalStateException if neither candidate loader is capable of loading an
* {@code ApplicationContext} from the supplied merged context configuration
*/
public ApplicationContext loadContext(MergedContextConfiguration mergedConfig) throws Exception {
Assert.notNull(mergedConfig, "mergedConfig must not be null");
List<SmartContextLoader> candidates = Arrays.asList(xmlLoader, annotationConfigLoader);
for (SmartContextLoader loader : candidates) {
// Determine if each loader can load a context from the mergedConfig. If it
// can, let it; otherwise, keep iterating.
if (supports(loader, mergedConfig)) {
return delegateLoading(loader, mergedConfig);
}
}
// If neither of the candidates supports the mergedConfig based on resources but
// ACIs were declared, then delegate to the ACCL.
if (!mergedConfig.getContextInitializerClasses().isEmpty()) {
return delegateLoading(annotationConfigLoader, mergedConfig);
}
throw new IllegalStateException(String.format(
"Neither %s nor %s was able to load an ApplicationContext from %s.", name(xmlLoader),
name(annotationConfigLoader), mergedConfig));
}
// --- ContextLoader -------------------------------------------------------
/**
* {@code DelegatingSmartContextLoader} does not support the
* {@link ContextLoader#processLocations(Class, String...)} method. Call
* {@link #processContextConfiguration(ContextConfigurationAttributes)} instead.
* @throws UnsupportedOperationException
*/
public String[] processLocations(Class<?> clazz, String... locations) {
throw new UnsupportedOperationException("DelegatingSmartContextLoader does not support the ContextLoader SPI. "
+ "Call processContextConfiguration(ContextConfigurationAttributes) instead.");
}
/**
* {@code DelegatingSmartContextLoader} does not support the
* {@link ContextLoader#loadContext(String...) } method. Call
* {@link #loadContext(MergedContextConfiguration)} instead.
* @throws UnsupportedOperationException
*/
public ApplicationContext loadContext(String... locations) throws Exception {
throw new UnsupportedOperationException("DelegatingSmartContextLoader does not support the ContextLoader SPI. "
+ "Call loadContext(MergedContextConfiguration) instead.");
protected SmartContextLoader getAnnotationConfigLoader() {
return this.annotationConfigLoader;
}
}

View File

@ -0,0 +1,47 @@
/*
* Copyright 2002-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.test.context.support;
import org.springframework.test.context.SmartContextLoader;
/**
* {@code WebDelegatingSmartContextLoader} is a concrete implementation of
* {@link AbstractDelegatingSmartContextLoader} that delegates to an
* {@link XmlWebContextLoader} and an {@link AnnotationConfigWebContextLoader}.
*
* @author Sam Brannen
* @since 3.2
* @see SmartContextLoader
* @see AbstractDelegatingSmartContextLoader
* @see XmlWebContextLoader
* @see AnnotationConfigWebContextLoader
*/
public class WebDelegatingSmartContextLoader extends AbstractDelegatingSmartContextLoader {
private final SmartContextLoader xmlLoader = new XmlWebContextLoader();
private final SmartContextLoader annotationConfigLoader = new AnnotationConfigWebContextLoader();
protected SmartContextLoader getXmlLoader() {
return this.xmlLoader;
}
protected SmartContextLoader getAnnotationConfigLoader() {
return this.annotationConfigLoader;
}
}

View File

@ -0,0 +1,49 @@
/*
* Copyright 2002-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.test.context.support;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.test.context.web.WebMergedContextConfiguration;
import org.springframework.web.context.support.GenericWebApplicationContext;
/**
* TODO [SPR-5243] Document XmlWebContextLoader.
*
* @author Sam Brannen
* @since 3.2
*/
public class XmlWebContextLoader extends AbstractGenericWebContextLoader {
/**
* Returns &quot;<code>-context.xml</code>&quot;.
*/
protected String getResourceSuffix() {
return "-context.xml";
}
/**
* TODO [SPR-5243] Document overridden loadBeanDefinitions().
*
* @see org.springframework.test.context.support.AbstractGenericWebContextLoader#loadBeanDefinitions(org.springframework.web.context.support.GenericWebApplicationContext, org.springframework.test.context.web.WebMergedContextConfiguration)
*/
@Override
protected void loadBeanDefinitions(GenericWebApplicationContext context,
WebMergedContextConfiguration webMergedConfig) {
new XmlBeanDefinitionReader(context).loadBeanDefinitions(webMergedConfig.getLocations());
}
}

View File

@ -0,0 +1,45 @@
/*
* Copyright 2002-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.test.context.web;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* TODO [SPR-5243] Document WebAppConfiguration.
*
* @author Sam Brannen
* @since 3.2
*/
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface WebAppConfiguration {
/**
* The root directory of the web application (i.e., WAR); should not end with a slash.
*
* <p>Defaults to {@code "src/main/webapp"}.
*/
String value() default "src/main/webapp";
}

View File

@ -0,0 +1,122 @@
/*
* Copyright 2002-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.test.context.web;
import java.util.Set;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.style.ToStringCreator;
import org.springframework.test.context.ContextLoader;
import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
* TODO [SPR-5243] Document WebMergedContextConfiguration.
*
* @author Sam Brannen
* @since 3.2
*/
public class WebMergedContextConfiguration extends MergedContextConfiguration {
private static final long serialVersionUID = 7323361588604247458L;
private final String resourceBasePath;
/**
* TODO [SPR-5243] Document WebMergedContextConfiguration constructor.
*/
public WebMergedContextConfiguration(
Class<?> testClass,
String[] locations,
Class<?>[] classes,
Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> contextInitializerClasses,
String[] activeProfiles, String resourceBasePath, ContextLoader contextLoader) {
super(testClass, locations, classes, contextInitializerClasses, activeProfiles, contextLoader);
this.resourceBasePath = !StringUtils.hasText(resourceBasePath) ? "" : resourceBasePath;
}
/**
* TODO [SPR-5243] Document getResourceBasePath().
*/
public String getResourceBasePath() {
return this.resourceBasePath;
}
/**
* Generate a unique hash code for all properties of this
* {@code WebMergedContextConfiguration} excluding the
* {@linkplain #getTestClass() test class}.
*/
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + resourceBasePath.hashCode();
return result;
}
/**
* Determine if the supplied object is equal to this {@code WebMergedContextConfiguration}
* instance by comparing both object's {@linkplain #getLocations() locations},
* {@linkplain #getClasses() annotated classes},
* {@linkplain #getContextInitializerClasses() context initializer classes},
* {@linkplain #getActiveProfiles() active profiles},
* {@linkplain #getResourceBasePath() resource base path}, and the fully
* qualified names of their {@link #getContextLoader() ContextLoaders}.
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof WebMergedContextConfiguration)) {
return false;
}
final WebMergedContextConfiguration that = (WebMergedContextConfiguration) obj;
return super.equals(that) && this.getResourceBasePath().equals(that.getResourceBasePath());
}
/**
* Provide a String representation of the {@linkplain #getTestClass() test class},
* {@linkplain #getLocations() locations}, {@linkplain #getClasses() annotated classes},
* {@linkplain #getContextInitializerClasses() context initializer classes},
* {@linkplain #getActiveProfiles() active profiles},
* {@linkplain #getResourceBasePath() resource base path}, and the name of the
* {@link #getContextLoader() ContextLoader}.
*/
@Override
public String toString() {
return new ToStringCreator(this)//
.append("testClass", getTestClass())//
.append("locations", ObjectUtils.nullSafeToString(getLocations()))//
.append("classes", ObjectUtils.nullSafeToString(getClasses()))//
.append("contextInitializerClasses", ObjectUtils.nullSafeToString(getContextInitializerClasses()))//
.append("activeProfiles", ObjectUtils.nullSafeToString(getActiveProfiles()))//
.append("resourceBasePath", getResourceBasePath())//
.append("contextLoader", nullSafeToString(getContextLoader()))//
.toString();
}
}

View File

@ -0,0 +1,122 @@
/*
* Copyright 2002-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.test.context.web;
import javax.servlet.ServletContext;
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.ConfigurableApplicationContext;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockServletContext;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.support.AbstractTestExecutionListener;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletWebRequest;
/**
* TODO [SPR-5243] Document WebTestExecutionListener.
*
* @author Sam Brannen
* @since 3.2
*/
public class WebTestExecutionListener extends AbstractTestExecutionListener {
private static final Log logger = LogFactory.getLog(WebTestExecutionListener.class);
/**
* TODO [SPR-5243] Document overridden prepareTestInstance().
*
* @see org.springframework.test.context.support.AbstractTestExecutionListener#prepareTestInstance(org.springframework.test.context.TestContext)
*/
@Override
public void prepareTestInstance(TestContext testContext) throws Exception {
setUpRequestContextIfNecessary(testContext);
}
/**
* TODO [SPR-5243] Document overridden beforeTestMethod().
*
* @see org.springframework.test.context.support.AbstractTestExecutionListener#beforeTestMethod(org.springframework.test.context.TestContext)
*/
@Override
public void beforeTestMethod(TestContext testContext) throws Exception {
setUpRequestContextIfNecessary(testContext);
}
/**
* TODO [SPR-5243] Document setUpRequestContext().
*
* @param testContext
* @param servletContext
*/
private void setUpRequestContextIfNecessary(TestContext testContext) {
ApplicationContext context = testContext.getApplicationContext();
if (context instanceof WebApplicationContext) {
WebApplicationContext wac = (WebApplicationContext) context;
ServletContext servletContext = wac.getServletContext();
if (!(servletContext instanceof MockServletContext)) {
throw new IllegalStateException(String.format(
"The WebApplicationContext for test context %s must be configured with a MockServletContext.",
testContext));
}
if (logger.isDebugEnabled()) {
logger.debug(String.format(
"Setting up MockHttpServletRequest, MockHttpServletResponse, ServletWebRequest, and RequestContextHolder for test context %s.",
testContext));
}
if (RequestContextHolder.getRequestAttributes() == null) {
MockServletContext mockServletContext = (MockServletContext) servletContext;
MockHttpServletRequest request = new MockHttpServletRequest(mockServletContext);
MockHttpServletResponse response = new MockHttpServletResponse();
ServletWebRequest servletWebRequest = new ServletWebRequest(request, response);
RequestContextHolder.setRequestAttributes(servletWebRequest);
if (wac instanceof ConfigurableApplicationContext) {
ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) wac;
ConfigurableListableBeanFactory bf = configurableApplicationContext.getBeanFactory();
bf.registerResolvableDependency(MockHttpServletResponse.class, response);
bf.registerResolvableDependency(ServletWebRequest.class, servletWebRequest);
}
}
}
}
/**
* TODO [SPR-5243] Document overridden afterTestMethod().
*
* @see org.springframework.test.context.support.AbstractTestExecutionListener#afterTestMethod(org.springframework.test.context.TestContext)
*/
@Override
public void afterTestMethod(TestContext testContext) throws Exception {
if (logger.isDebugEnabled()) {
logger.debug(String.format("Resetting RequestContextHolder for test context %s.", testContext));
}
RequestContextHolder.resetRequestAttributes();
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2009 the original author or authors.
* Copyright 2002-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.
@ -42,7 +42,7 @@ public class TestExecutionListenersTests {
@Test
public void verifyNumDefaultListenersRegistered() throws Exception {
TestContextManager testContextManager = new TestContextManager(DefaultListenersExampleTestCase.class);
assertEquals("Verifying the number of registered TestExecutionListeners for DefaultListenersExampleTest.", 3,
assertEquals("Verifying the number of registered TestExecutionListeners for DefaultListenersExampleTest.", 4,
testContextManager.getTestExecutionListeners().size());
}

View File

@ -0,0 +1,92 @@
/*
* Copyright 2002-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.test.context.web;
import static org.junit.Assert.*;
import java.io.File;
import javax.servlet.ServletContext;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockServletContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.web.context.ServletContextAware;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.ServletWebRequest;
/**
* @author Sam Brannen
* @since 3.2
*/
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
public abstract class AbstractBasicWacTests implements ServletContextAware {
protected ServletContext servletContext;
@Autowired
protected WebApplicationContext wac;
@Autowired
protected MockServletContext mockServletContext;
@Autowired
protected MockHttpServletRequest request;
@Autowired
protected MockHttpServletResponse response;
@Autowired
protected ServletWebRequest webRequest;
@Autowired
protected String foo;
public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}
@Test
public void basicWacFeatures() throws Exception {
assertNotNull("ServletContext should be set in the WAC.", wac.getServletContext());
assertNotNull("ServletContext should have been set via ServletContextAware.", servletContext);
assertNotNull("ServletContext should have been autowired from the WAC.", mockServletContext);
assertNotNull("MockHttpServletRequest should have been autowired from the WAC.", request);
assertNotNull("MockHttpServletResponse should have been autowired from the WAC.", response);
assertNotNull("ServletWebRequest should have been autowired from the WAC.", webRequest);
Object rootWac = mockServletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
assertNotNull("Root WAC must be stored in the ServletContext as: "
+ WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, rootWac);
assertSame("test WAC and Root WAC in ServletContext must be the same object.", wac, rootWac);
assertSame("ServletContext instances must be the same object.", mockServletContext, wac.getServletContext());
assertSame("ServletContext in the WAC and in the mock request", mockServletContext, request.getServletContext());
assertEquals("Getting real path for ServletContext resource.",
new File("src/main/webapp/index.jsp").getCanonicalPath(), mockServletContext.getRealPath("index.jsp"));
}
}

View File

@ -0,0 +1,48 @@
/*
* Copyright 2002-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.test.context.web;
import static org.junit.Assert.*;
import org.junit.Test;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
/**
* @author Sam Brannen
* @since 3.2
*/
@ContextConfiguration
public class BasicAnnotationConfigWacTests extends AbstractBasicWacTests {
@Configuration
static class Config {
@Bean
public String foo() {
return "enigma";
}
}
@Test
public void fooEnigmaAutowired() {
assertEquals("enigma", foo);
}
}

View File

@ -13,12 +13,24 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.test.web.mock.servlet.samples.context;
class WebContextLoader extends GenericWebContextLoader {
package org.springframework.test.context.web;
public WebContextLoader() {
super("src/test/resources/META-INF/web-resources", false);
import static org.junit.Assert.*;
import org.junit.Test;
import org.springframework.test.context.ContextConfiguration;
/**
* @author Sam Brannen
* @since 3.2
*/
@ContextConfiguration
public class BasicXmlWacTests extends AbstractBasicWacTests {
@Test
public void fooBarAutowired() {
assertEquals("bar", foo);
}
}

View File

@ -0,0 +1,40 @@
/*
* Copyright 2002-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.test.context.web;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
import org.springframework.test.context.ContextLoader;
import org.springframework.web.context.WebApplicationContext;
/**
* Convenience test suite for integration tests that verify support for
* {@link WebApplicationContext} {@linkplain ContextLoader context loaders}
* in the TestContext framework.
*
* @author Sam Brannen
* @since 3.2
*/
@RunWith(Suite.class)
// Note: the following 'multi-line' layout is for enhanced code readability.
@SuiteClasses({//
BasicXmlWacTests.class,//
BasicAnnotationConfigWacTests.class //
})
public class WebContextLoaderTestSuite {
}

View File

@ -45,6 +45,10 @@
<level value="warn" />
</logger>
<logger name="org.springframework.test.context.web">
<level value="warn" />
</logger>
<logger name="org.springframework.test.context">
<level value="warn" />
</logger>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:c="http://www.springframework.org/schema/c" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="foo" class="java.lang.String" c:_="bar" />
</beans>