Provide support for context hierarchies in the TCF

Prior to this commit the Spring TestContext Framework supported creating
only flat, non-hierarchical contexts. There was no easy way to create
contexts with parent-child relationships.

This commit addresses this issue by introducing a new @ContextHierarchy
annotation that can be used in conjunction with @ContextConfiguration
for declaring hierarchies of application contexts, either within a
single test class or within a test class hierarchy. In addition,
@DirtiesContext now supports a new 'hierarchyMode' attribute for
controlling context cache clearing for context hierarchies.

- Introduced a new @ContextHierarchy annotation.
- Introduced 'name' attribute in @ContextConfiguration.
- Introduced 'name' property in ContextConfigurationAttributes.
- TestContext is now aware of @ContextHierarchy in addition to
  @ContextConfiguration.
- Introduced findAnnotationDeclaringClassForTypes() in AnnotationUtils.
- Introduced resolveContextHierarchyAttributes() in ContextLoaderUtils.
- Introduced buildContextHierarchyMap() in ContextLoaderUtils.
- @ContextConfiguration and @ContextHierarchy may not be used as
  top-level, class-level annotations simultaneously.
- Introduced reference to the parent configuration in
  MergedContextConfiguration and WebMergedContextConfiguration.
- Introduced overloaded buildMergedContextConfiguration() methods in
  ContextLoaderUtils in order to handle context hierarchies separately
  from conventional, non-hierarchical contexts.
- Introduced hashCode() and equals() in ContextConfigurationAttributes.
- ContextLoaderUtils ensures uniqueness of @ContextConfiguration
  elements within a single @ContextHierarchy declaration.
- Introduced CacheAwareContextLoaderDelegate that can be used for
  loading contexts with transparent support for interacting with the
  context cache -- for example, for retrieving the parent application
  context in a context hierarchy.
- TestContext now delegates to CacheAwareContextLoaderDelegate for
  loading contexts.
- Introduced getParentApplicationContext() in MergedContextConfiguration
- The loadContext(MergedContextConfiguration) methods in
  AbstractGenericContextLoader and AbstractGenericWebContextLoader now
  set the parent context as appropriate.
- Introduced 'hierarchyMode' attribute in @DirtiesContext with a
  corresponding HierarchyMode enum that defines EXHAUSTIVE and
  CURRENT_LEVEL cache removal modes.
- ContextCache now internally tracks the relationships between contexts
  that make up a context hierarchy. Furthermore, when a context is
  removed, if it is part of a context hierarchy all corresponding
  contexts will be removed from the cache according to the supplied
  HierarchyMode.
- AbstractGenericWebContextLoader will set a loaded context as the
  ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE in the MockServletContext when
  context hierarchies are used if the context has no parent or if the
  context has a parent that is not a WAC.
- Where appropriate, updated Javadoc to refer to the
  ServletTestExecutionListener, which was introduced in 3.2.0.
- Updated Javadoc to avoid and/or suppress warnings in spring-test.
- Suppressed remaining warnings in code in spring-test.

Issue: SPR-5613, SPR-9863
This commit is contained in:
Sam Brannen
2013-01-11 01:02:08 +01:00
parent 7bc5353e07
commit 98074e7762
55 changed files with 3919 additions and 551 deletions

View File

@@ -40,9 +40,9 @@ import org.springframework.util.ClassUtils;
*
* <p>There are various choices for DataSource implementations:
* <ul>
* <li>SingleConnectionDataSource (using the same Connection for all getConnection calls);
* <li>DriverManagerDataSource (creating a new Connection on each getConnection call);
* <li>Apache's Jakarta Commons DBCP offers BasicDataSource (a real pool).
* <li>{@code SingleConnectionDataSource} (using the same Connection for all getConnection calls)
* <li>{@code DriverManagerDataSource} (creating a new Connection on each getConnection call)
* <li>Apache's Jakarta Commons DBCP offers {@code org.apache.commons.dbcp.BasicDataSource} (a real pool)
* </ul>
*
* <p>Typical usage in bootstrap code:
@@ -77,7 +77,6 @@ import org.springframework.util.ClassUtils;
* @see SimpleNamingContext
* @see org.springframework.jdbc.datasource.SingleConnectionDataSource
* @see org.springframework.jdbc.datasource.DriverManagerDataSource
* @see org.apache.commons.dbcp.BasicDataSource
*/
public class SimpleNamingContextBuilder implements InitialContextFactoryBuilder {

View File

@@ -163,6 +163,7 @@ public class MockServletContext implements ServletContext {
* @param resourceLoader the ResourceLoader to use (or null for the default)
* @see #registerNamedDispatcher
*/
@SuppressWarnings("javadoc")
public MockServletContext(String resourceBasePath, ResourceLoader resourceLoader) {
this.resourceLoader = (resourceLoader != null ? resourceLoader : new DefaultResourceLoader());
this.resourceBasePath = (resourceBasePath != null ? resourceBasePath : "");
@@ -344,6 +345,7 @@ public class MockServletContext implements ServletContext {
* <p>Defaults to {@linkplain #COMMON_DEFAULT_SERVLET_NAME "default"}.
* @see #setDefaultServletName
*/
@SuppressWarnings("javadoc")
public String getDefaultServletName() {
return this.defaultServletName;
}

View File

@@ -197,6 +197,7 @@ public abstract class AbstractDependencyInjectionSpringContextTests extends Abst
* test instance has not been configured
* @see #populateProtectedVariables()
*/
@SuppressWarnings("javadoc")
protected void injectDependencies() throws Exception {
Assert.state(getApplicationContext() != null,
"injectDependencies() called without first configuring an ApplicationContext");

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -36,13 +36,13 @@ import java.lang.annotation.Target;
* mode set to {@link ClassMode#AFTER_CLASS AFTER_CLASS}</li>
* </ul>
* <p>
* Use this annotation if a test has modified the context (for example, by
* replacing a bean definition). Subsequent tests will be supplied a new
* context.
* Use this annotation if a test has modified the context &mdash; for example, by
* replacing a bean definition or changing the state of a singleton bean.
* Subsequent tests will be supplied a new context.
* </p>
* <p>
* {@code &#064;DirtiesContext} may be used as a class-level and
* method-level annotation within the same class. In such scenarios, the
* {@code @DirtiesContext} may be used as a class-level and method-level
* annotation within the same class. In such scenarios, the
* {@code ApplicationContext} will be marked as <em>dirty</em> after any
* such annotated method as well as after the entire class. If the
* {@link ClassMode} is set to {@link ClassMode#AFTER_EACH_TEST_METHOD
@@ -53,16 +53,19 @@ import java.lang.annotation.Target;
* @author Sam Brannen
* @author Rod Johnson
* @since 2.0
* @see org.springframework.test.context.ContextConfiguration
*/
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Target({ ElementType.TYPE, ElementType.METHOD })
public @interface DirtiesContext {
/**
* Defines <i>modes</i> which determine how {@code &#064;DirtiesContext}
* is interpreted when used to annotate a test class.
* Defines <i>modes</i> which determine how {@code @DirtiesContext} is
* interpreted when used to annotate a test class.
*
* @since 3.0
*/
static enum ClassMode {
@@ -76,18 +79,64 @@ public @interface DirtiesContext {
* The associated {@code ApplicationContext} will be marked as
* <em>dirty</em> after each test method in the class.
*/
AFTER_EACH_TEST_METHOD
AFTER_EACH_TEST_METHOD;
}
/**
* Defines <i>modes</i> which determine how the context cache is cleared
* when {@code @DirtiesContext} is used in a test whose context is
* configured as part of a hierarchy via
* {@link org.springframework.test.context.ContextHierarchy @ContextHierarchy}.
*
* @since 3.2.2
*/
static enum HierarchyMode {
/**
* The context cache will be cleared using an <em>exhaustive</em> algorithm
* that includes not only the {@linkplain HierarchyMode#CURRENT_LEVEL current level}
* but also all other context hierarchies that share an ancestor context
* common to the current test.
*
* <p>All {@code ApplicationContexts} that reside in a subhierarchy of
* the common ancestor context will be removed from the context cache and
* closed.
*/
EXHAUSTIVE,
/**
* The {@code ApplicationContext} for the <em>current level</em> in the
* context hierarchy and all contexts in subhierarchies of the current
* level will be removed from the context cache and closed.
*
* <p>The <em>current level</em> refers to the {@code ApplicationContext}
* at the lowest level in the context hierarchy that is visible from the
* current test.
*/
CURRENT_LEVEL;
}
/**
* The <i>mode</i> to use when a test class is annotated with
* {@code &#064;DirtiesContext}.
* {@code @DirtiesContext}.
* <p>Defaults to {@link ClassMode#AFTER_CLASS AFTER_CLASS}.
* <p>Note: Setting the class mode on an annotated test method has no meaning,
* since the mere presence of the {@code &#064;DirtiesContext}
* annotation on a test method is sufficient.
* since the mere presence of the {@code @DirtiesContext} annotation on a
* test method is sufficient.
*
* @since 3.0
*/
ClassMode classMode() default ClassMode.AFTER_CLASS;
/**
* The context cache clearing <em>mode</em> to use when a context is
* configured as part of a hierarchy via
* {@link org.springframework.test.context.ContextHierarchy @ContextHierarchy}.
* <p>Defaults to {@link HierarchyMode#EXHAUSTIVE EXHAUSTIVE}.
*
* @since 3.2.2
*/
HierarchyMode hierarchyMode() default HierarchyMode.EXHAUSTIVE;
}

View File

@@ -0,0 +1,112 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.test.context;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.util.Assert;
/**
* {@code CacheAwareContextLoaderDelegate} loads application contexts from
* {@link MergedContextConfiguration} by delegating to the
* {@link ContextLoader} configured in the {@code MergedContextConfiguration}
* and interacting transparently with the {@link ContextCache} behind the scenes.
*
* <p>Note: {@code CacheAwareContextLoaderDelegate} does not implement the
* {@link ContextLoader} or {@link SmartContextLoader} interface.
*
* @author Sam Brannen
* @since 3.2.2
*/
public class CacheAwareContextLoaderDelegate {
private static final Log logger = LogFactory.getLog(CacheAwareContextLoaderDelegate.class);
private final ContextCache contextCache;
CacheAwareContextLoaderDelegate(ContextCache contextCache) {
Assert.notNull(contextCache, "ContextCache must not be null");
this.contextCache = contextCache;
}
/**
* Load the {@code ApplicationContext} for the supplied merged context
* configuration. Supports both the {@link SmartContextLoader} and
* {@link ContextLoader} SPIs.
* @throws Exception if an error occurs while loading the application context
*/
private ApplicationContext loadContextInternal(MergedContextConfiguration mergedContextConfiguration)
throws Exception {
ContextLoader contextLoader = mergedContextConfiguration.getContextLoader();
Assert.notNull(contextLoader, "Cannot load an ApplicationContext with a NULL 'contextLoader'. "
+ "Consider annotating your test class with @ContextConfiguration or @ContextHierarchy.");
ApplicationContext applicationContext;
if (contextLoader instanceof SmartContextLoader) {
SmartContextLoader smartContextLoader = (SmartContextLoader) contextLoader;
applicationContext = smartContextLoader.loadContext(mergedContextConfiguration);
}
else {
String[] locations = mergedContextConfiguration.getLocations();
Assert.notNull(locations, "Cannot load an ApplicationContext with a NULL 'locations' array. "
+ "Consider annotating your test class with @ContextConfiguration or @ContextHierarchy.");
applicationContext = contextLoader.loadContext(locations);
}
return applicationContext;
}
/**
* Load the {@link ApplicationContext application context} for the supplied
* merged context configuration.
*
* <p>If the context is present in the cache it will simply be returned;
* otherwise, it will be loaded, stored in the cache, and returned.
* @return the application context
* @throws IllegalStateException if an error occurs while retrieving or
* loading the application context
*/
public ApplicationContext loadContext(MergedContextConfiguration mergedContextConfiguration) {
synchronized (contextCache) {
ApplicationContext context = contextCache.get(mergedContextConfiguration);
if (context == null) {
try {
context = loadContextInternal(mergedContextConfiguration);
if (logger.isDebugEnabled()) {
logger.debug(String.format("Storing ApplicationContext in cache under key [%s].",
mergedContextConfiguration));
}
contextCache.put(mergedContextConfiguration, context);
}
catch (Exception ex) {
throw new IllegalStateException("Failed to load ApplicationContext", ex);
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug(String.format("Retrieved ApplicationContext from cache with key [%s].",
mergedContextConfiguration));
}
}
return context;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,24 +16,30 @@
package org.springframework.test.context;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.style.ToStringCreator;
import org.springframework.test.annotation.DirtiesContext.HierarchyMode;
import org.springframework.util.Assert;
/**
* Cache for Spring {@link ApplicationContext ApplicationContexts} in a test environment.
*
* <p>Maintains a cache of {@link ApplicationContext contexts} keyed by
* {@link MergedContextConfiguration} instances. This has significant performance
* benefits if initializing the context would take time. While initializing a
* Spring context itself is very quick, some beans in a context, such as a
* {@code LocalSessionFactoryBean} for working with Hibernate, may take some time
* to initialize. Hence it often makes sense to perform that initialization only
* once per test suite.
* <p>Maintains a cache of {@code ApplicationContexts} keyed by
* {@link MergedContextConfiguration} instances.
*
* <p>This has significant performance benefits if initializing the context would take time.
* While initializing a Spring context itself is very quick, some beans in a context, such
* as a {@code LocalSessionFactoryBean} for working with Hibernate, may take some time to
* initialize. Hence it often makes sense to perform that initialization only once per
* test suite.
*
* @author Sam Brannen
* @author Juergen Hoeller
@@ -41,11 +47,22 @@ import org.springframework.util.Assert;
*/
class ContextCache {
private final Object monitor = new Object();
/**
* Map of context keys to Spring ApplicationContext instances.
* Map of context keys to Spring {@code ApplicationContext} instances.
*/
private final Map<MergedContextConfiguration, ApplicationContext> contextMap =
new ConcurrentHashMap<MergedContextConfiguration, ApplicationContext>(64);
private final Map<MergedContextConfiguration, ApplicationContext> contextMap = new ConcurrentHashMap<MergedContextConfiguration, ApplicationContext>(
64);
/**
* Map of parent keys to sets of children keys, representing a top-down <em>tree</em>
* of context hierarchies. This information is used for determining which subtrees
* need to be recursively removed and closed when removing a context that is a parent
* of other contexts.
*/
private final Map<MergedContextConfiguration, Set<MergedContextConfiguration>> hierarchyMap = new ConcurrentHashMap<MergedContextConfiguration, Set<MergedContextConfiguration>>(
64);
private int hitCount;
@@ -53,15 +70,18 @@ class ContextCache {
/**
* Clears all contexts from the cache.
* Clears all contexts from the cache and clears context hierarchy information as
* well.
*/
void clear() {
this.contextMap.clear();
synchronized (monitor) {
this.contextMap.clear();
this.hierarchyMap.clear();
}
}
/**
* Clears hit and miss count statistics for the cache (i.e., resets counters
* to zero).
* Clears hit and miss count statistics for the cache (i.e., resets counters to zero).
*/
void clearStatistics() {
this.hitCount = 0;
@@ -70,124 +90,210 @@ class ContextCache {
/**
* Return whether there is a cached context for the given key.
*
* @param key the context key (never {@code null})
*/
boolean contains(MergedContextConfiguration key) {
Assert.notNull(key, "Key must not be null");
return this.contextMap.containsKey(key);
synchronized (monitor) {
return this.contextMap.containsKey(key);
}
}
/**
* Obtain a cached ApplicationContext for the given key.
* <p>The {@link #getHitCount() hit} and {@link #getMissCount() miss}
* counts will be updated accordingly.
* Obtain a cached {@code ApplicationContext} for the given key.
*
* <p>The {@link #getHitCount() hit} and {@link #getMissCount() miss} counts will be
* updated accordingly.
*
* @param key the context key (never {@code null})
* @return the corresponding ApplicationContext instance,
* or {@code null} if not found in the cache.
* @return the corresponding {@code ApplicationContext} instance, or {@code null} if
* not found in the cache.
* @see #remove
*/
ApplicationContext get(MergedContextConfiguration key) {
Assert.notNull(key, "Key must not be null");
ApplicationContext context = this.contextMap.get(key);
if (context == null) {
incrementMissCount();
synchronized (monitor) {
ApplicationContext context = this.contextMap.get(key);
if (context == null) {
incrementMissCount();
}
else {
incrementHitCount();
}
return context;
}
else {
incrementHitCount();
}
return context;
}
/**
* Increment the hit count by one. A <em>hit</em> is an access to the
* cache, which returned a non-null context for a queried key.
* Increment the hit count by one. A <em>hit</em> is an access to the cache, which
* returned a non-null context for a queried key.
*/
private void incrementHitCount() {
this.hitCount++;
}
/**
* Increment the miss count by one. A <em>miss</em> is an access to the
* cache, which returned a {@code null} context for a queried key.
* Increment the miss count by one. A <em>miss</em> is an access to the cache, which
* returned a {@code null} context for a queried key.
*/
private void incrementMissCount() {
this.missCount++;
}
/**
* Get the overall hit count for this cache. A <em>hit</em> is an access
* to the cache, which returned a non-null context for a queried key.
* Get the overall hit count for this cache. A <em>hit</em> is an access to the cache,
* which returned a non-null context for a queried key.
*/
int getHitCount() {
return this.hitCount;
}
/**
* Get the overall miss count for this cache. A <em>miss</em> is an
* access to the cache, which returned a {@code null} context for a
* queried key.
* Get the overall miss count for this cache. A <em>miss</em> is an access to the
* cache, which returned a {@code null} context for a queried key.
*/
int getMissCount() {
return this.missCount;
}
/**
* Explicitly add an ApplicationContext instance to the cache under the given key.
* Explicitly add an {@code ApplicationContext} instance to the cache under the given
* key.
*
* @param key the context key (never {@code null})
* @param context the ApplicationContext instance (never {@code null})
* @param context the {@code ApplicationContext} instance (never {@code null})
*/
void put(MergedContextConfiguration key, ApplicationContext context) {
Assert.notNull(key, "Key must not be null");
Assert.notNull(context, "ApplicationContext must not be null");
this.contextMap.put(key, context);
}
/**
* Remove the context with the given key.
* @param key the context key (never {@code null})
* @return the corresponding ApplicationContext instance, or {@code null}
* if not found in the cache.
* @see #setDirty
*/
ApplicationContext remove(MergedContextConfiguration key) {
return this.contextMap.remove(key);
}
synchronized (monitor) {
this.contextMap.put(key, context);
/**
* Mark the context with the given key as dirty, effectively
* {@link #remove removing} the context from the cache and explicitly
* {@link ConfigurableApplicationContext#close() closing} it if it is an
* instance of {@link ConfigurableApplicationContext}.
* <p>Generally speaking, you would only call this method if you change the
* state of a singleton bean, potentially affecting future interaction with
* the context.
* @param key the context key (never {@code null})
* @see #remove
*/
void setDirty(MergedContextConfiguration key) {
Assert.notNull(key, "Key must not be null");
ApplicationContext context = remove(key);
if (context instanceof ConfigurableApplicationContext) {
((ConfigurableApplicationContext) context).close();
MergedContextConfiguration child = key;
MergedContextConfiguration parent = child.getParent();
while (parent != null) {
Set<MergedContextConfiguration> list = hierarchyMap.get(parent);
if (list == null) {
list = new HashSet<MergedContextConfiguration>();
hierarchyMap.put(parent, list);
}
list.add(child);
child = parent;
parent = child.getParent();
}
}
}
/**
* Determine the number of contexts currently stored in the cache. If the
* cache contains more than <tt>Integer.MAX_VALUE</tt> elements, returns
* <tt>Integer.MAX_VALUE</tt>.
* Remove the context with the given key from the cache and explicitly
* {@linkplain ConfigurableApplicationContext#close() close} it if it is an
* instance of {@link ConfigurableApplicationContext}.
*
* <p>Generally speaking, you would only call this method if you change the
* state of a singleton bean, potentially affecting future interaction with
* the context.
*
* <p>In addition, the semantics of the supplied {@code HierarchyMode} will
* be honored. See the Javadoc for {@link HierarchyMode} for details.
*
* @param key the context key; never {@code null}
* @param hierarchyMode the hierarchy mode; may be {@code null} if the context
* is not part of a hierarchy
*/
int size() {
return this.contextMap.size();
void remove(MergedContextConfiguration key, HierarchyMode hierarchyMode) {
Assert.notNull(key, "Key must not be null");
// startKey is the level at which to begin clearing the cache, depending
// on the configured hierarchy mode.
MergedContextConfiguration startKey = key;
if (hierarchyMode == HierarchyMode.EXHAUSTIVE) {
while (startKey.getParent() != null) {
startKey = startKey.getParent();
}
}
synchronized (monitor) {
final List<MergedContextConfiguration> removedContexts = new ArrayList<MergedContextConfiguration>();
remove(removedContexts, startKey);
// Remove all remaining references to any removed contexts from the
// hierarchy map.
for (MergedContextConfiguration currentKey : removedContexts) {
for (Set<MergedContextConfiguration> children : hierarchyMap.values()) {
children.remove(currentKey);
}
}
// Remove empty entries from the hierarchy map.
for (MergedContextConfiguration currentKey : hierarchyMap.keySet()) {
if (hierarchyMap.get(currentKey).isEmpty()) {
hierarchyMap.remove(currentKey);
}
}
}
}
private void remove(List<MergedContextConfiguration> removedContexts, MergedContextConfiguration key) {
Assert.notNull(key, "Key must not be null");
synchronized (monitor) {
Set<MergedContextConfiguration> children = hierarchyMap.get(key);
if (children != null) {
for (MergedContextConfiguration child : children) {
// Recurse through lower levels
remove(removedContexts, child);
}
// Remove the set of children for the current context from the
// hierarchy map.
hierarchyMap.remove(key);
}
// Physically remove and close leaf nodes first (i.e., on the way back up the
// stack as opposed to prior to the recursive call).
ApplicationContext context = contextMap.remove(key);
if (context instanceof ConfigurableApplicationContext) {
((ConfigurableApplicationContext) context).close();
}
removedContexts.add(key);
}
}
/**
* Generates a text string, which contains the {@link #size() size} as well
* as the {@link #hitCount hit} and {@link #missCount miss} counts.
* Determine the number of contexts currently stored in the cache. If the cache
* contains more than <tt>Integer.MAX_VALUE</tt> elements, returns
* <tt>Integer.MAX_VALUE</tt>.
*/
int size() {
synchronized (monitor) {
return this.contextMap.size();
}
}
/**
* Determine the number of parent contexts currently tracked within the cache.
*/
int getParentContextCount() {
synchronized (monitor) {
return this.hierarchyMap.size();
}
}
/**
* Generates a text string, which contains the {@linkplain #size() size} as well
* as the {@linkplain #getHitCount() hit}, {@linkplain #getMissCount() miss}, and
* {@linkplain #getParentContextCount() parent context} counts.
*/
@Override
public String toString() {
return new ToStringCreator(this).append("size", size()).append("hitCount", getHitCount()).
append("missCount", getMissCount()).toString();
return new ToStringCreator(this)//
.append("size", size())//
.append("hitCount", getHitCount())//
.append("missCount", getMissCount())//
.append("parentContextCount", getParentContextCount())//
.toString();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -66,11 +66,12 @@ import org.springframework.context.ConfigurableApplicationContext;
*
* @author Sam Brannen
* @since 2.5
* @see ContextHierarchy
* @see ActiveProfiles
* @see ContextLoader
* @see SmartContextLoader
* @see ContextConfigurationAttributes
* @see MergedContextConfiguration
* @see ActiveProfiles
* @see org.springframework.context.ApplicationContext
*/
@Documented
@@ -283,4 +284,19 @@ public @interface ContextConfiguration {
*/
Class<? extends ContextLoader> loader() default ContextLoader.class;
/**
* The name of the context hierarchy level represented by this configuration.
*
* <p>If not specified the name will be inferred based on the numerical level within all
* declared contexts within the hierarchy.
*
* <p>This attribute is only applicable when used within a test class hierarchy that is
* configured using {@link ContextHierarchy @ContextHierarchy}, in which case the name
* can be used for merging or overriding this configuration with configuration of the
* same name in hierarchy levels defined in superclasses.
*
* @since 3.2.2
*/
String name() default "";
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,8 @@
package org.springframework.test.context;
import java.util.Arrays;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -24,6 +26,7 @@ import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.style.ToStringCreator;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
* {@code ContextConfigurationAttributes} encapsulates the context
@@ -54,6 +57,8 @@ public class ContextConfigurationAttributes {
private final boolean inheritInitializers;
private final String name;
/**
* Resolve resource locations from the {@link ContextConfiguration#locations() locations}
@@ -75,7 +80,8 @@ public class ContextConfigurationAttributes {
ObjectUtils.nullSafeToString(valueLocations), ObjectUtils.nullSafeToString(locations));
logger.error(msg);
throw new IllegalStateException(msg);
} else if (!ObjectUtils.isEmpty(valueLocations)) {
}
else if (!ObjectUtils.isEmpty(valueLocations)) {
locations = valueLocations;
}
@@ -92,7 +98,7 @@ public class ContextConfigurationAttributes {
public ContextConfigurationAttributes(Class<?> declaringClass, ContextConfiguration contextConfiguration) {
this(declaringClass, resolveLocations(declaringClass, contextConfiguration), contextConfiguration.classes(),
contextConfiguration.inheritLocations(), contextConfiguration.initializers(),
contextConfiguration.inheritInitializers(), contextConfiguration.loader());
contextConfiguration.inheritInitializers(), contextConfiguration.name(), contextConfiguration.loader());
}
/**
@@ -109,13 +115,13 @@ public class ContextConfigurationAttributes {
* @throws IllegalArgumentException if the {@code declaringClass} or {@code contextLoaderClass} is
* {@code null}, or if the {@code locations} and {@code classes} are both non-empty
* @deprecated as of Spring 3.2, use
* {@link #ContextConfigurationAttributes(Class, String[], Class[], boolean, Class[], boolean, Class)}
* {@link #ContextConfigurationAttributes(Class, String[], Class[], boolean, Class[], boolean, String, Class)}
* instead
*/
@Deprecated
public ContextConfigurationAttributes(Class<?> declaringClass, String[] locations, Class<?>[] classes,
boolean inheritLocations, Class<? extends ContextLoader> contextLoaderClass) {
this(declaringClass, locations, classes, inheritLocations, null, true, contextLoaderClass);
this(declaringClass, locations, classes, inheritLocations, null, true, null, contextLoaderClass);
}
/**
@@ -138,6 +144,31 @@ public class ContextConfigurationAttributes {
boolean inheritLocations,
Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>[] initializers,
boolean inheritInitializers, Class<? extends ContextLoader> contextLoaderClass) {
this(declaringClass, locations, classes, inheritLocations, initializers, inheritInitializers, null,
contextLoaderClass);
}
/**
* Construct a new {@link ContextConfigurationAttributes} instance for the
* {@linkplain Class test class} that declared the
* {@link ContextConfiguration @ContextConfiguration} annotation and its
* corresponding attributes.
*
* @param declaringClass the test class that declared {@code @ContextConfiguration}
* @param locations the resource locations declared via {@code @ContextConfiguration}
* @param classes the annotated classes declared via {@code @ContextConfiguration}
* @param inheritLocations the {@code inheritLocations} flag declared via {@code @ContextConfiguration}
* @param initializers the context initializers declared via {@code @ContextConfiguration}
* @param inheritInitializers the {@code inheritInitializers} flag declared via {@code @ContextConfiguration}
* @param name the name of level in the context hierarchy, or {@code null} if not applicable
* @param contextLoaderClass the {@code ContextLoader} class declared via {@code @ContextConfiguration}
* @throws IllegalArgumentException if the {@code declaringClass} or {@code contextLoaderClass} is
* {@code null}, or if the {@code locations} and {@code classes} are both non-empty
*/
public ContextConfigurationAttributes(Class<?> declaringClass, String[] locations, Class<?>[] classes,
boolean inheritLocations,
Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>[] initializers,
boolean inheritInitializers, String name, Class<? extends ContextLoader> contextLoaderClass) {
Assert.notNull(declaringClass, "declaringClass must not be null");
Assert.notNull(contextLoaderClass, "contextLoaderClass must not be null");
@@ -158,6 +189,7 @@ public class ContextConfigurationAttributes {
this.inheritLocations = inheritLocations;
this.initializers = initializers;
this.inheritInitializers = inheritInitializers;
this.name = StringUtils.hasText(name) ? name : null;
this.contextLoaderClass = contextLoaderClass;
}
@@ -305,6 +337,101 @@ public class ContextConfigurationAttributes {
return contextLoaderClass;
}
/**
* Get the name of the context hierarchy level that was declared via
* {@link ContextConfiguration @ContextConfiguration}.
*
* @return the name of the context hierarchy level or {@code null} if not applicable
* @see ContextConfiguration#name()
* @since 3.2.2
*/
public String getName() {
return this.name;
}
/**
* Generate a unique hash code for all properties of this
* {@code ContextConfigurationAttributes} instance excluding the
* {@linkplain #getName() name}.
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + declaringClass.hashCode();
result = prime * result + Arrays.hashCode(locations);
result = prime * result + Arrays.hashCode(classes);
result = prime * result + (inheritLocations ? 1231 : 1237);
result = prime * result + Arrays.hashCode(initializers);
result = prime * result + (inheritInitializers ? 1231 : 1237);
result = prime * result + contextLoaderClass.hashCode();
return result;
}
/**
* Determine if the supplied object is equal to this
* {@code ContextConfigurationAttributes} instance by comparing both object's
* {@linkplain #getDeclaringClass() declaring class},
* {@linkplain #getLocations() locations},
* {@linkplain #getClasses() annotated classes},
* {@linkplain #isInheritLocations() inheritLocations flag},
* {@linkplain #getInitializers() context initializer classes},
* {@linkplain #isInheritInitializers() inheritInitializers flag}, and the
* {@link #getContextLoaderClass() ContextLoader class}.
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof ContextConfigurationAttributes)) {
return false;
}
final ContextConfigurationAttributes that = (ContextConfigurationAttributes) obj;
if (this.declaringClass == null) {
if (that.declaringClass != null) {
return false;
}
}
else if (!this.declaringClass.equals(that.declaringClass)) {
return false;
}
if (!Arrays.equals(this.locations, that.locations)) {
return false;
}
if (!Arrays.equals(this.classes, that.classes)) {
return false;
}
if (this.inheritLocations != that.inheritLocations) {
return false;
}
if (!Arrays.equals(this.initializers, that.initializers)) {
return false;
}
if (this.inheritInitializers != that.inheritInitializers) {
return false;
}
if (this.contextLoaderClass == null) {
if (that.contextLoaderClass != null) {
return false;
}
}
else if (!this.contextLoaderClass.equals(that.contextLoaderClass)) {
return false;
}
return true;
}
/**
* Provide a String representation of the context configuration attributes
* and declaring class.
@@ -318,6 +445,7 @@ public class ContextConfigurationAttributes {
.append("inheritLocations", inheritLocations)//
.append("initializers", ObjectUtils.nullSafeToString(initializers))//
.append("inheritInitializers", inheritInitializers)//
.append("name", name)//
.append("contextLoaderClass", contextLoaderClass.getName())//
.toString();
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.test.context;
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;
/**
* {@code @ContextHierarchy} is a class-level annotation that is used to define
* a hierarchy of {@link org.springframework.context.ApplicationContext
* ApplicationContexts} for integration tests.
*
* @author Sam Brannen
* @since 3.2.2
* @see ContextConfiguration
* @see org.springframework.context.ApplicationContext
*/
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ContextHierarchy {
/**
* A list of {@link ContextConfiguration @ContextConfiguration} instances,
* each of which defines a level in the context hierarchy.
*
* <p>If you need to merge or override the configuration for a given level
* of the context hierarchy within a test class hierarchy, you must explicitly
* name that level by supplying the same value to the {@link ContextConfiguration#name
* name} attribute in {@code @ContextConfiguration} at each level in the
* class hierarchy.
*/
ContextConfiguration[] value();
}

View File

@@ -16,20 +16,24 @@
package org.springframework.test.context;
import static org.springframework.beans.BeanUtils.*;
import static org.springframework.core.annotation.AnnotationUtils.*;
import static org.springframework.beans.BeanUtils.instantiateClass;
import static org.springframework.core.annotation.AnnotationUtils.findAnnotationDeclaringClass;
import static org.springframework.core.annotation.AnnotationUtils.findAnnotationDeclaringClassForTypes;
import static org.springframework.core.annotation.AnnotationUtils.isAnnotationDeclaredLocally;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
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.core.annotation.AnnotationUtils;
@@ -52,10 +56,13 @@ import org.springframework.util.StringUtils;
* @see ContextConfigurationAttributes
* @see ActiveProfiles
* @see ApplicationContextInitializer
* @see ContextHierarchy
* @see MergedContextConfiguration
*/
abstract class ContextLoaderUtils {
static final String GENERATED_CONTEXT_HIERARCHY_LEVEL_PREFIX = "ContextHierarchyLevel#";
private static final Log logger = LogFactory.getLog(ContextLoaderUtils.class);
private static final String DEFAULT_CONTEXT_LOADER_CLASS_NAME = "org.springframework.test.context.support.DelegatingSmartContextLoader";
@@ -70,30 +77,29 @@ abstract class ContextLoaderUtils {
}
/**
* Resolve the {@link ContextLoader} {@linkplain Class class} to use for the
* supplied list of {@link ContextConfigurationAttributes} and then
* instantiate and return that {@code ContextLoader}.
* Resolve the {@link ContextLoader} {@linkplain Class class} to use for the supplied
* list of {@link ContextConfigurationAttributes} and then instantiate and return that
* {@code ContextLoader}.
*
* <p>If the supplied {@code defaultContextLoaderClassName} is
* {@code null} or <em>empty</em>, depending on the absence or presence
* of {@link org.springframework.test.context.web.WebAppConfiguration @WebAppConfiguration}
* either {@value #DEFAULT_CONTEXT_LOADER_CLASS_NAME}
* or {@value #DEFAULT_WEB_CONTEXT_LOADER_CLASS_NAME} will be used as the
* default context loader class name. For details on the class resolution
* process, see {@link #resolveContextLoaderClass}.
* <p>If the supplied {@code defaultContextLoaderClassName} is {@code null} or
* <em>empty</em>, depending on the absence or presence of
* {@link org.springframework.test.context.web.WebAppConfiguration @WebAppConfiguration} either
* {@code "org.springframework.test.context.support.DelegatingSmartContextLoader"} or
* {@code "org.springframework.test.context.web.WebDelegatingSmartContextLoader"} will
* be used as the default context loader class name. For details on the class
* resolution process, see {@link #resolveContextLoaderClass}.
*
* @param testClass the test class for which the {@code ContextLoader}
* should be resolved; must not be {@code null}
* @param configAttributesList the list of configuration attributes to process;
* must not be {@code null} or <em>empty</em>; must be ordered <em>bottom-up</em>
* @param testClass the test class for which the {@code ContextLoader} should be
* resolved; must not be {@code null}
* @param configAttributesList the list of configuration attributes to process; must
* not be {@code null} or <em>empty</em>; must be ordered <em>bottom-up</em>
* (i.e., as if we were traversing up the class hierarchy)
* @param defaultContextLoaderClassName the name of the default
* {@code ContextLoader} class to use; may be {@code null} or <em>empty</em>
* @return the resolved {@code ContextLoader} for the supplied
* {@code testClass} (never {@code null})
* @param defaultContextLoaderClassName the name of the default {@code ContextLoader}
* class to use; may be {@code null} or <em>empty</em>
* @return the resolved {@code ContextLoader} for the supplied {@code testClass}
* (never {@code null})
* @see #resolveContextLoaderClass
*/
@SuppressWarnings("javadoc")
static ContextLoader resolveContextLoader(Class<?> testClass,
List<ContextConfigurationAttributes> configAttributesList, String defaultContextLoaderClassName) {
Assert.notNull(testClass, "Class must not be null");
@@ -113,36 +119,35 @@ abstract class ContextLoaderUtils {
}
/**
* Resolve the {@link ContextLoader} {@linkplain Class class} to use for the
* supplied list of {@link ContextConfigurationAttributes}.
* Resolve the {@link ContextLoader} {@linkplain Class class} to use for the supplied
* list of {@link ContextConfigurationAttributes}.
*
* <p>Beginning with the first level in the context configuration attributes
* hierarchy:
* <p>Beginning with the first level in the context configuration attributes hierarchy:
*
* <ol>
* <li>If the {@link ContextConfigurationAttributes#getContextLoaderClass()
* contextLoaderClass} property of {@link ContextConfigurationAttributes} is
* configured with an explicit class, that class will be returned.</li>
* <li>If an explicit {@code ContextLoader} class is not specified at the
* current level in the hierarchy, traverse to the next level in the hierarchy
* and return to step #1.</li>
* <li>If no explicit {@code ContextLoader} class is found after traversing
* the hierarchy, an attempt will be made to load and return the class
* with the supplied {@code defaultContextLoaderClassName}.</li>
* <li>If an explicit {@code ContextLoader} class is not specified at the current
* level in the hierarchy, traverse to the next level in the hierarchy and return to
* step #1.</li>
* <li>If no explicit {@code ContextLoader} class is found after traversing the
* hierarchy, an attempt will be made to load and return the class with the supplied
* {@code defaultContextLoaderClassName}.</li>
* </ol>
*
* @param testClass the class for which to resolve the {@code ContextLoader}
* class; must not be {@code null}; only used for logging purposes
* @param configAttributesList the list of configuration attributes to process;
* must not be {@code null} or <em>empty</em>; must be ordered <em>bottom-up</em>
* @param testClass the class for which to resolve the {@code ContextLoader} class;
* must not be {@code null}; only used for logging purposes
* @param configAttributesList the list of configuration attributes to process; must
* not be {@code null} or <em>empty</em>; must be ordered <em>bottom-up</em>
* (i.e., as if we were traversing up the class hierarchy)
* @param defaultContextLoaderClassName the name of the default
* {@code ContextLoader} class to use; must not be {@code null} or empty
* @param defaultContextLoaderClassName the name of the default {@code ContextLoader}
* class to use; must not be {@code null} or empty
* @return the {@code ContextLoader} class to use for the supplied test class
* @throws IllegalArgumentException if {@code @ContextConfiguration} is not
* <em>present</em> on the supplied test class
* @throws IllegalStateException if the default {@code ContextLoader} class
* could not be loaded
* @throws IllegalStateException if the default {@code ContextLoader} class could not
* be loaded
*/
@SuppressWarnings("unchecked")
static Class<? extends ContextLoader> resolveContextLoaderClass(Class<?> testClass,
@@ -184,22 +189,201 @@ abstract class ContextLoaderUtils {
}
/**
* Resolve the list of {@link ContextConfigurationAttributes configuration
* attributes} for the supplied {@link Class class} and its superclasses.
* Convenience method for creating a {@link ContextConfigurationAttributes} instance
* from the supplied {@link ContextConfiguration} and declaring class and then adding
* the attributes to the supplied list.
*/
private static void convertContextConfigToConfigAttributesAndAddToList(ContextConfiguration contextConfiguration,
Class<?> declaringClass, final List<ContextConfigurationAttributes> attributesList) {
if (logger.isTraceEnabled()) {
logger.trace(String.format("Retrieved @ContextConfiguration [%s] for declaring class [%s].",
contextConfiguration, declaringClass.getName()));
}
ContextConfigurationAttributes attributes = new ContextConfigurationAttributes(declaringClass,
contextConfiguration);
if (logger.isTraceEnabled()) {
logger.trace("Resolved context configuration attributes: " + attributes);
}
attributesList.add(attributes);
}
/**
* Resolve the list of lists of {@linkplain ContextConfigurationAttributes context
* configuration attributes} for the supplied {@linkplain Class test class} and its
* superclasses, taking into account context hierarchies declared via
* {@link ContextHierarchy @ContextHierarchy} and
* {@link ContextConfiguration @ContextConfiguration}.
*
* <p>Note that the {@link ContextConfiguration#inheritLocations
* inheritLocations} and {@link ContextConfiguration#inheritInitializers()
* inheritInitializers} flags of {@link ContextConfiguration
* &#064;ContextConfiguration} will <strong>not</strong> be taken into
* consideration. If these flags need to be honored, that must be handled
* manually when traversing the list returned by this method.
* <p>The outer list represents a top-down ordering of context configuration
* attributes, where each element in the list represents the context configuration
* declared on a given test class in the class hierarchy. Each nested list
* contains the context configuration attributes declared either via a single
* instance of {@code @ContextConfiguration} on the particular class or via
* multiple instances of {@code @ContextConfiguration} declared within a
* single {@code @ContextHierarchy} instance on the particular class.
* Furthermore, each nested list maintains the order in which
* {@code @ContextConfiguration} instances are declared.
*
* <p>Note that the {@link ContextConfiguration#inheritLocations inheritLocations} and
* {@link ContextConfiguration#inheritInitializers() inheritInitializers} flags of
* {@link ContextConfiguration @ContextConfiguration} will <strong>not</strong>
* be taken into consideration. If these flags need to be honored, that must be
* handled manually when traversing the nested lists returned by this method.
*
* @param testClass the class for which to resolve the context hierarchy attributes
* (must not be {@code null})
* @return the list of lists of configuration attributes for the specified class;
* never {@code null}
* @throws IllegalArgumentException if the supplied class is {@code null}; if
* neither {@code @ContextConfiguration} nor {@code @ContextHierarchy} is
* <em>present</em> on the supplied class; if a given class in the class hierarchy
* declares both {@code @ContextConfiguration} and {@code @ContextHierarchy} as
* top-level annotations; or if individual {@code @ContextConfiguration}
* elements within a {@code @ContextHierarchy} declaration on a given class
* in the class hierarchy do not define unique context configuration.
*
* @since 3.2.2
* @see #buildContextHierarchyMap(Class)
* @see #resolveContextConfigurationAttributes(Class)
*/
static List<List<ContextConfigurationAttributes>> resolveContextHierarchyAttributes(Class<?> testClass) {
Assert.notNull(testClass, "Class must not be null");
final Class<ContextConfiguration> contextConfigType = ContextConfiguration.class;
final Class<ContextHierarchy> contextHierarchyType = ContextHierarchy.class;
final List<Class<? extends Annotation>> annotationTypes = Arrays.asList(contextConfigType, contextHierarchyType);
final List<List<ContextConfigurationAttributes>> hierarchyAttributes = new ArrayList<List<ContextConfigurationAttributes>>();
Class<?> declaringClass = findAnnotationDeclaringClassForTypes(annotationTypes, testClass);
Assert.notNull(declaringClass, String.format(
"Could not find an 'annotation declaring class' for annotation type [%s] or [%s] and test class [%s]",
contextConfigType.getName(), contextHierarchyType.getName(), testClass.getName()));
while (declaringClass != null) {
boolean contextConfigDeclaredLocally = isAnnotationDeclaredLocally(contextConfigType, declaringClass);
boolean contextHierarchyDeclaredLocally = isAnnotationDeclaredLocally(contextHierarchyType, declaringClass);
if (contextConfigDeclaredLocally && contextHierarchyDeclaredLocally) {
String msg = String.format("Test class [%s] has been configured with both @ContextConfiguration "
+ "and @ContextHierarchy as class-level annotations. Only one of these annotations may "
+ "be declared as a top-level annotation per test class.", declaringClass.getName());
logger.error(msg);
throw new IllegalStateException(msg);
}
final List<ContextConfigurationAttributes> configAttributesList = new ArrayList<ContextConfigurationAttributes>();
if (contextConfigDeclaredLocally) {
ContextConfiguration contextConfiguration = declaringClass.getAnnotation(contextConfigType);
convertContextConfigToConfigAttributesAndAddToList(contextConfiguration, declaringClass,
configAttributesList);
}
else if (contextHierarchyDeclaredLocally) {
ContextHierarchy contextHierarchy = declaringClass.getAnnotation(contextHierarchyType);
for (ContextConfiguration contextConfiguration : contextHierarchy.value()) {
convertContextConfigToConfigAttributesAndAddToList(contextConfiguration, declaringClass,
configAttributesList);
}
// Check for uniqueness
Set<ContextConfigurationAttributes> configAttributesSet = new HashSet<ContextConfigurationAttributes>(
configAttributesList);
if (configAttributesSet.size() != configAttributesList.size()) {
String msg = String.format("The @ContextConfiguration elements configured via "
+ "@ContextHierarchy in test class [%s] must define unique contexts to load.",
declaringClass.getName());
logger.error(msg);
throw new IllegalStateException(msg);
}
}
else {
// This should theoretically actually never happen...
String msg = String.format("Test class [%s] has been configured with neither @ContextConfiguration "
+ "nor @ContextHierarchy as a class-level annotation.", declaringClass.getName());
logger.error(msg);
throw new IllegalStateException(msg);
}
hierarchyAttributes.add(0, configAttributesList);
declaringClass = findAnnotationDeclaringClassForTypes(annotationTypes, declaringClass.getSuperclass());
}
return hierarchyAttributes;
}
/**
* Build a <em>context hierarchy map</em> for the supplied {@linkplain Class
* test class} and its superclasses, taking into account context hierarchies
* declared via {@link ContextHierarchy @ContextHierarchy} and
* {@link ContextConfiguration @ContextConfiguration}.
*
* <p>Each value in the map represents the consolidated list of {@linkplain
* ContextConfigurationAttributes context configuration attributes} for a
* given level in the context hierarchy (potentially across the test class
* hierarchy), keyed by the {@link ContextConfiguration#name() name} of the
* context hierarchy level.
*
* <p>If a given level in the context hierarchy does not have an explicit
* name (i.e., configured via {@link ContextConfiguration#name}), a name will
* be generated for that hierarchy level by appending the numerical level to
* the {@link #GENERATED_CONTEXT_HIERARCHY_LEVEL_PREFIX}.
*
* @param testClass the class for which to resolve the context hierarchy map
* (must not be {@code null})
* @return a map of context configuration attributes for the context hierarchy,
* keyed by context hierarchy level name; never {@code null}
*
* @since 3.2.2
* @see #resolveContextHierarchyAttributes(Class)
*/
static Map<String, List<ContextConfigurationAttributes>> buildContextHierarchyMap(Class<?> testClass) {
final Map<String, List<ContextConfigurationAttributes>> map = new LinkedHashMap<String, List<ContextConfigurationAttributes>>();
int hierarchyLevel = 1;
for (List<ContextConfigurationAttributes> configAttributesList : resolveContextHierarchyAttributes(testClass)) {
for (ContextConfigurationAttributes configAttributes : configAttributesList) {
String name = configAttributes.getName();
// Assign a generated name?
if (!StringUtils.hasText(name)) {
name = GENERATED_CONTEXT_HIERARCHY_LEVEL_PREFIX + hierarchyLevel;
}
// Encountered a new context hierarchy level?
if (!map.containsKey(name)) {
hierarchyLevel++;
map.put(name, new ArrayList<ContextConfigurationAttributes>());
}
map.get(name).add(configAttributes);
}
}
return map;
}
/**
* Resolve the list of {@linkplain ContextConfigurationAttributes context
* configuration attributes} for the supplied {@linkplain Class test class} and its
* superclasses.
*
* <p>Note that the {@link ContextConfiguration#inheritLocations inheritLocations} and
* {@link ContextConfiguration#inheritInitializers() inheritInitializers} flags of
* {@link ContextConfiguration @ContextConfiguration} will <strong>not</strong>
* be taken into consideration. If these flags need to be honored, that must be
* handled manually when traversing the list returned by this method.
*
* @param testClass the class for which to resolve the configuration attributes (must
* not be {@code null})
* @return the list of configuration attributes for the specified class, ordered <em>bottom-up</em>
* (i.e., as if we were traversing up the class hierarchy); never {@code null}
* @throws IllegalArgumentException if the supplied class is {@code null} or
* if {@code @ContextConfiguration} is not <em>present</em> on the supplied class
* @return the list of configuration attributes for the specified class, ordered
* <em>bottom-up</em> (i.e., as if we were traversing up the class hierarchy);
* never {@code null}
* @throws IllegalArgumentException if the supplied class is {@code null} or if
* {@code @ContextConfiguration} is not <em>present</em> on the supplied class
*/
static List<ContextConfigurationAttributes> resolveContextConfigurationAttributes(Class<?> testClass) {
Assert.notNull(testClass, "Class must not be null");
@@ -214,19 +398,7 @@ abstract class ContextLoaderUtils {
while (declaringClass != null) {
ContextConfiguration contextConfiguration = declaringClass.getAnnotation(annotationType);
if (logger.isTraceEnabled()) {
logger.trace(String.format("Retrieved @ContextConfiguration [%s] for declaring class [%s].",
contextConfiguration, declaringClass.getName()));
}
ContextConfigurationAttributes attributes = new ContextConfigurationAttributes(declaringClass,
contextConfiguration);
if (logger.isTraceEnabled()) {
logger.trace("Resolved context configuration attributes: " + attributes);
}
attributesList.add(attributes);
convertContextConfigToConfigAttributesAndAddToList(contextConfiguration, declaringClass, attributesList);
declaringClass = findAnnotationDeclaringClass(annotationType, declaringClass.getSuperclass());
}
@@ -234,22 +406,21 @@ abstract class ContextLoaderUtils {
}
/**
* Resolve the list of merged {@code ApplicationContextInitializer} classes
* for the supplied list of {@code ContextConfigurationAttributes}.
* Resolve the list of merged {@code ApplicationContextInitializer} classes for the
* supplied list of {@code ContextConfigurationAttributes}.
*
* <p>Note that the {@link ContextConfiguration#inheritInitializers inheritInitializers}
* flag of {@link ContextConfiguration @ContextConfiguration} will be taken into
* consideration. Specifically, if the {@code inheritInitializers} flag is
* set to {@code true} for a given level in the class hierarchy represented by
* the provided configuration attributes, context initializer classes defined
* at the given level will be merged with those defined in higher levels
* of the class hierarchy.
* consideration. Specifically, if the {@code inheritInitializers} flag is set to
* {@code true} for a given level in the class hierarchy represented by the provided
* configuration attributes, context initializer classes defined at the given level
* will be merged with those defined in higher levels of the class hierarchy.
*
* @param configAttributesList the list of configuration attributes to process;
* must not be {@code null} or <em>empty</em>; must be ordered <em>bottom-up</em>
* @param configAttributesList the list of configuration attributes to process; must
* not be {@code null} or <em>empty</em>; must be ordered <em>bottom-up</em>
* (i.e., as if we were traversing up the class hierarchy)
* @return the set of merged context initializer classes, including those
* from superclasses if appropriate (never {@code null})
* @return the set of merged context initializer classes, including those from
* superclasses if appropriate (never {@code null})
* @since 3.2
*/
static Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> resolveInitializerClasses(
@@ -278,16 +449,15 @@ abstract class ContextLoaderUtils {
/**
* Resolve <em>active bean definition profiles</em> for the supplied {@link Class}.
*
* <p>Note that the {@link ActiveProfiles#inheritProfiles inheritProfiles}
* flag of {@link ActiveProfiles &#064;ActiveProfiles} will be taken into
* consideration. Specifically, if the {@code inheritProfiles} flag is
* set to {@code true}, profiles defined in the test class will be
* merged with those defined in superclasses.
* <p>Note that the {@link ActiveProfiles#inheritProfiles inheritProfiles} flag of
* {@link ActiveProfiles @ActiveProfiles} will be taken into consideration.
* Specifically, if the {@code inheritProfiles} flag is set to {@code true}, profiles
* defined in the test class will be merged with those defined in superclasses.
*
* @param testClass the class for which to resolve the active profiles (must
* not be {@code null})
* @return the set of active profiles for the specified class, including
* active profiles from superclasses if appropriate (never {@code null})
* @param testClass the class for which to resolve the active profiles (must not be
* {@code null})
* @return the set of active profiles for the specified class, including active
* profiles from superclasses if appropriate (never {@code null})
* @see ActiveProfiles
* @see org.springframework.context.annotation.Profile
*/
@@ -342,14 +512,72 @@ abstract class ContextLoaderUtils {
}
/**
* Build the {@link MergedContextConfiguration merged context configuration}
* for the supplied {@link Class testClass} and
* {@code defaultContextLoaderClassName}.
* Build the {@link MergedContextConfiguration merged context configuration} for
* the supplied {@link Class testClass} and {@code defaultContextLoaderClassName},
* taking into account context hierarchies declared via
* {@link ContextHierarchy @ContextHierarchy} and
* {@link ContextConfiguration @ContextConfiguration}.
*
* @param testClass the test class for which the {@code MergedContextConfiguration}
* should be built (must not be {@code null})
* @param defaultContextLoaderClassName the name of the default
* {@code ContextLoader} class to use (may be {@code null})
* @param defaultContextLoaderClassName the name of the default {@code ContextLoader}
* class to use (may be {@code null})
* @param cacheAwareContextLoaderDelegate the cache-aware context loader delegate to
* be passed to the {@code MergedContextConfiguration} constructor
* @return the merged context configuration
* @see #buildContextHierarchyMap(Class)
* @see #buildMergedContextConfiguration(Class, List, String, MergedContextConfiguration, CacheAwareContextLoaderDelegate)
*/
@SuppressWarnings("javadoc")
static MergedContextConfiguration buildMergedContextConfiguration(Class<?> testClass,
String defaultContextLoaderClassName, CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate) {
if (testClass.isAnnotationPresent(ContextHierarchy.class)) {
Map<String, List<ContextConfigurationAttributes>> hierarchyMap = buildContextHierarchyMap(testClass);
MergedContextConfiguration parentConfig = null;
MergedContextConfiguration mergedConfig = null;
for (List<ContextConfigurationAttributes> list : hierarchyMap.values()) {
List<ContextConfigurationAttributes> reversedList = new ArrayList<ContextConfigurationAttributes>(list);
Collections.reverse(reversedList);
// Don't use the supplied testClass; instead ensure that we are
// building the MCC for the actual test class that declared the
// configuration for the current level in the context hierarchy.
Assert.notEmpty(reversedList, "ContextConfigurationAttributes list must not be empty");
Class<?> declaringClass = reversedList.get(0).getDeclaringClass();
mergedConfig = buildMergedContextConfiguration(declaringClass, reversedList,
defaultContextLoaderClassName, parentConfig, cacheAwareContextLoaderDelegate);
parentConfig = mergedConfig;
}
// Return the last level in the context hierarchy
return mergedConfig;
}
else {
return buildMergedContextConfiguration(testClass, resolveContextConfigurationAttributes(testClass),
defaultContextLoaderClassName, null, cacheAwareContextLoaderDelegate);
}
}
/**
* Build the {@link MergedContextConfiguration merged context configuration} for the
* supplied {@link Class testClass}, context configuration attributes,
* {@code defaultContextLoaderClassName}, and parent context configuration.
*
* @param testClass the test class for which the {@code MergedContextConfiguration}
* should be built (must not be {@code null})
* @param configAttributesList the list of context configuration attributes for the
* specified test class, ordered <em>bottom-up</em> (i.e., as if we were
* traversing up the class hierarchy); never {@code null} or empty
* @param defaultContextLoaderClassName the name of the default {@code ContextLoader}
* class to use (may be {@code null})
* @param parentConfig the merged context configuration for the parent application
* context in a context hierarchy, or {@code null} if there is no parent
* @param cacheAwareContextLoaderDelegate the cache-aware context loader delegate to
* be passed to the {@code MergedContextConfiguration} constructor
* @return the merged context configuration
* @see #resolveContextLoader
* @see #resolveContextConfigurationAttributes
@@ -358,10 +586,11 @@ abstract class ContextLoaderUtils {
* @see #resolveActiveProfiles
* @see MergedContextConfiguration
*/
static MergedContextConfiguration buildMergedContextConfiguration(Class<?> testClass,
String defaultContextLoaderClassName) {
private static MergedContextConfiguration buildMergedContextConfiguration(final Class<?> testClass,
final List<ContextConfigurationAttributes> configAttributesList,
final String defaultContextLoaderClassName, MergedContextConfiguration parentConfig,
CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate) {
final List<ContextConfigurationAttributes> configAttributesList = resolveContextConfigurationAttributes(testClass);
final ContextLoader contextLoader = resolveContextLoader(testClass, configAttributesList,
defaultContextLoaderClassName);
final List<String> locationsList = new ArrayList<String>();
@@ -397,22 +626,21 @@ abstract class ContextLoaderUtils {
String[] activeProfiles = resolveActiveProfiles(testClass);
MergedContextConfiguration mergedConfig = buildWebMergedContextConfiguration(testClass, locations, classes,
initializerClasses, activeProfiles, contextLoader);
initializerClasses, activeProfiles, contextLoader, cacheAwareContextLoaderDelegate, parentConfig);
if (mergedConfig == null) {
mergedConfig = new MergedContextConfiguration(testClass, locations, classes, initializerClasses,
activeProfiles, contextLoader);
activeProfiles, contextLoader, cacheAwareContextLoaderDelegate, parentConfig);
}
return mergedConfig;
}
/**
* Load the {@link org.springframework.test.context.web.WebAppConfiguration @WebAppConfiguration}
* Load the {@link org.springframework.test.context.web.WebAppConfiguration}
* class, using reflection in order to avoid package cycles.
*
* @return the {@code @WebAppConfiguration} class or {@code null} if it
* cannot be loaded
* @return the {@code @WebAppConfiguration} class or {@code null} if it cannot be loaded
* @since 3.2
*/
@SuppressWarnings("unchecked")
@@ -431,12 +659,10 @@ abstract class ContextLoaderUtils {
}
/**
* Attempt to build a {@link org.springframework.test.context.web.WebMergedContextConfiguration
* WebMergedContextConfiguration} from the supplied arguments, using reflection
* in order to avoid package cycles.
* Attempt to build a {@link org.springframework.test.context.web.WebMergedContextConfiguration}
* from the supplied arguments, using reflection in order to avoid package cycles.
*
* @return the {@code WebMergedContextConfiguration} or {@code null} if
* it could not be built
* @return the {@code WebMergedContextConfiguration} or {@code null} if it could not be built
* @since 3.2
*/
@SuppressWarnings("unchecked")
@@ -445,7 +671,8 @@ abstract class ContextLoaderUtils {
String[] locations,
Class<?>[] classes,
Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializerClasses,
String[] activeProfiles, ContextLoader contextLoader) {
String[] activeProfiles, ContextLoader contextLoader,
CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, MergedContextConfiguration parentConfig) {
Class<? extends Annotation> webAppConfigClass = loadWebAppConfigurationClass();
@@ -459,11 +686,12 @@ abstract class ContextLoaderUtils {
Constructor<? extends MergedContextConfiguration> constructor = ClassUtils.getConstructorIfAvailable(
webMergedConfigClass, Class.class, String[].class, Class[].class, Set.class, String[].class,
String.class, ContextLoader.class);
String.class, ContextLoader.class, CacheAwareContextLoaderDelegate.class,
MergedContextConfiguration.class);
if (constructor != null) {
return instantiateClass(constructor, testClass, locations, classes, initializerClasses,
activeProfiles, resourceBasePath, contextLoader);
activeProfiles, resourceBasePath, contextLoader, cacheAwareContextLoaderDelegate, parentConfig);
}
}
catch (Throwable t) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,9 +23,11 @@ import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.style.ToStringCreator;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
@@ -72,6 +74,8 @@ public class MergedContextConfiguration implements Serializable {
private final Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> contextInitializerClasses;
private final String[] activeProfiles;
private final ContextLoader contextLoader;
private final CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate;
private final MergedContextConfiguration parent;
private static String[] processLocations(String[] locations) {
@@ -149,6 +153,7 @@ public class MergedContextConfiguration implements Serializable {
* @param contextInitializerClasses the merged context initializer classes
* @param activeProfiles the merged active bean definition profiles
* @param contextLoader the resolved {@code ContextLoader}
* @see #MergedContextConfiguration(Class, String[], Class[], Set, String[], ContextLoader, CacheAwareContextLoaderDelegate, MergedContextConfiguration)
*/
public MergedContextConfiguration(
Class<?> testClass,
@@ -156,12 +161,48 @@ public class MergedContextConfiguration implements Serializable {
Class<?>[] classes,
Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> contextInitializerClasses,
String[] activeProfiles, ContextLoader contextLoader) {
this(testClass, locations, classes, contextInitializerClasses, activeProfiles, contextLoader, null, null);
}
/**
* Create a new {@code MergedContextConfiguration} instance for the
* supplied test class, resource locations, annotated classes, context
* initializers, active profiles, {@code ContextLoader}, and parent
* configuration.
*
* <p>If a {@code null} value is supplied for {@code locations},
* {@code classes}, or {@code activeProfiles} an empty array will
* be stored instead. If a {@code null} value is supplied for the
* {@code contextInitializerClasses} an empty set will be stored instead.
* Furthermore, active profiles will be sorted, and duplicate profiles will
* be removed.
*
* @param testClass the test class for which the configuration was merged
* @param locations the merged resource locations
* @param classes the merged annotated classes
* @param contextInitializerClasses the merged context initializer classes
* @param activeProfiles the merged active bean definition profiles
* @param contextLoader the resolved {@code ContextLoader}
* @param cacheAwareContextLoaderDelegate a cache-aware context loader
* delegate with which to retrieve the parent context
* @param parent the parent configuration or {@code null} if there is no parent
* @since 3.2.2
*/
public MergedContextConfiguration(
Class<?> testClass,
String[] locations,
Class<?>[] classes,
Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> contextInitializerClasses,
String[] activeProfiles, ContextLoader contextLoader,
CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, MergedContextConfiguration parent) {
this.testClass = testClass;
this.locations = processLocations(locations);
this.classes = processClasses(classes);
this.contextInitializerClasses = processContextInitializerClasses(contextInitializerClasses);
this.activeProfiles = processActiveProfiles(activeProfiles);
this.contextLoader = contextLoader;
this.cacheAwareContextLoaderDelegate = cacheAwareContextLoaderDelegate;
this.parent = parent;
}
/**
@@ -207,6 +248,39 @@ public class MergedContextConfiguration implements Serializable {
return contextLoader;
}
/**
* Get the {@link MergedContextConfiguration} for the parent application context in a
* context hierarchy.
*
* @return the parent configuration or {@code null} if there is no parent
* @see #getParentApplicationContext()
* @since 3.2.2
*/
public MergedContextConfiguration getParent() {
return this.parent;
}
/**
* Get the parent {@link ApplicationContext} for the context defined by this
* {@code MergedContextConfiguration} from the context cache.
* <p>
* If the parent context has not yet been loaded, it will be loaded, stored in the
* cache, and then returned.
*
* @return the parent {@code ApplicationContext} or {@code null} if there is no parent
* @see #getParent()
* @since 3.2.2
*/
public ApplicationContext getParentApplicationContext() {
if (parent == null) {
return null;
}
Assert.state(cacheAwareContextLoaderDelegate != null,
"Cannot retrieve a parent application context without access to the CacheAwareContextLoaderDelegate.");
return cacheAwareContextLoaderDelegate.loadContext(parent);
}
/**
* Generate a unique hash code for all properties of this
* {@code MergedContextConfiguration} excluding the
@@ -220,6 +294,7 @@ public class MergedContextConfiguration implements Serializable {
result = prime * result + Arrays.hashCode(classes);
result = prime * result + contextInitializerClasses.hashCode();
result = prime * result + Arrays.hashCode(activeProfiles);
result = prime * result + (parent == null ? 0 : parent.hashCode());
result = prime * result + nullSafeToString(contextLoader).hashCode();
return result;
}
@@ -229,8 +304,9 @@ public class MergedContextConfiguration implements Serializable {
* instance by comparing both object's {@linkplain #getLocations() locations},
* {@linkplain #getClasses() annotated classes},
* {@linkplain #getContextInitializerClasses() context initializer classes},
* {@linkplain #getActiveProfiles() active profiles}, and the fully qualified
* names of their {@link #getContextLoader() ContextLoaders}.
* {@linkplain #getActiveProfiles() active profiles},
* {@linkplain #getParent() parents}, and the fully qualified names of their
* {@link #getContextLoader() ContextLoaders}.
*/
@Override
public boolean equals(Object obj) {
@@ -247,15 +323,28 @@ public class MergedContextConfiguration implements Serializable {
if (!Arrays.equals(this.locations, that.locations)) {
return false;
}
if (!Arrays.equals(this.classes, that.classes)) {
return false;
}
if (!this.contextInitializerClasses.equals(that.contextInitializerClasses)) {
return false;
}
if (!Arrays.equals(this.activeProfiles, that.activeProfiles)) {
return false;
}
if (this.parent == null) {
if (that.parent != null) {
return false;
}
}
else if (!this.parent.equals(that.parent)) {
return false;
}
if (!nullSafeToString(this.contextLoader).equals(nullSafeToString(that.contextLoader))) {
return false;
}
@@ -267,8 +356,9 @@ public class MergedContextConfiguration implements Serializable {
* 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}, and the name of the
* {@link #getContextLoader() ContextLoader}.
* {@linkplain #getActiveProfiles() active profiles}, the name of the
* {@link #getContextLoader() ContextLoader}, and the
* {@linkplain #getParent() parent configuration}.
*/
@Override
public String toString() {
@@ -279,6 +369,7 @@ public class MergedContextConfiguration implements Serializable {
.append("contextInitializerClasses", ObjectUtils.nullSafeToString(contextInitializerClasses))//
.append("activeProfiles", ObjectUtils.nullSafeToString(activeProfiles))//
.append("contextLoader", nullSafeToString(contextLoader))//
.append("parent", parent)//
.toString();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,8 +29,7 @@ import org.springframework.context.ApplicationContext;
* context that it loads (see {@link MergedContextConfiguration#getActiveProfiles()}
* and {@link #loadContext(MergedContextConfiguration)}).
*
* <p>See the Javadoc for
* {@link ContextConfiguration @ContextConfiguration}
* <p>See the Javadoc for {@link ContextConfiguration @ContextConfiguration}
* for a definition of <em>annotated class</em>.
*
* <p>Clients of a {@code SmartContextLoader} should call
@@ -48,8 +47,8 @@ import org.springframework.context.ApplicationContext;
* <p>Even though {@code SmartContextLoader} extends {@code ContextLoader},
* clients should favor {@code SmartContextLoader}-specific methods over those
* defined in {@code ContextLoader}, particularly because a
* {@code SmartContextLoader} may choose not to support methods defined in
* the {@code ContextLoader} SPI.
* {@code SmartContextLoader} may choose not to support methods defined in the
* {@code ContextLoader} SPI.
*
* <p>Concrete implementations must provide a {@code public} no-args constructor.
*
@@ -59,6 +58,9 @@ import org.springframework.context.ApplicationContext;
* <li>{@link org.springframework.test.context.support.AnnotationConfigContextLoader AnnotationConfigContextLoader}</li>
* <li>{@link org.springframework.test.context.support.GenericXmlContextLoader GenericXmlContextLoader}</li>
* <li>{@link org.springframework.test.context.support.GenericPropertiesContextLoader GenericPropertiesContextLoader}</li>
* <li>{@link org.springframework.test.context.web.WebDelegatingSmartContextLoader WebDelegatingSmartContextLoader}</li>
* <li>{@link org.springframework.test.context.web.AnnotationConfigWebContextLoader AnnotationConfigWebContextLoader}</li>
* <li>{@link org.springframework.test.context.web.GenericXmlWebContextLoader GenericXmlWebContextLoader}</li>
* </ul>
*
* @author Sam Brannen

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,9 +20,11 @@ import java.lang.reflect.Method;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.core.AttributeAccessorSupport;
import org.springframework.core.style.ToStringCreator;
import org.springframework.test.annotation.DirtiesContext.HierarchyMode;
import org.springframework.util.Assert;
/**
@@ -41,6 +43,8 @@ public class TestContext extends AttributeAccessorSupport {
private final ContextCache contextCache;
private final CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate;
private final MergedContextConfiguration mergedContextConfiguration;
private final Class<?> testClass;
@@ -61,16 +65,17 @@ public class TestContext extends AttributeAccessorSupport {
}
/**
* Construct a new test context for the supplied {@link Class test class}
* and {@link ContextCache context cache} and parse the corresponding
* {@link ContextConfiguration &#064;ContextConfiguration} annotation, if
* present.
* Construct a new test context for the supplied {@linkplain Class test class}
* and {@linkplain ContextCache context cache} and parse the corresponding
* {@link ContextConfiguration &#064;ContextConfiguration} or
* {@link ContextHierarchy &#064;ContextHierarchy} annotation, if present.
* <p>If the supplied class name for the default {@code ContextLoader}
* is {@code null} or <em>empty</em> and no concrete {@code ContextLoader}
* class is explicitly supplied via the {@code @ContextConfiguration}
* annotation, a
* class is explicitly supplied via {@code @ContextConfiguration}, a
* {@link org.springframework.test.context.support.DelegatingSmartContextLoader
* DelegatingSmartContextLoader} will be used instead.
* DelegatingSmartContextLoader} or
* {@link org.springframework.test.context.web.WebDelegatingSmartContextLoader
* WebDelegatingSmartContextLoader} will be used instead.
* @param testClass the test class for which the test context should be
* constructed (must not be {@code null})
* @param contextCache the context cache from which the constructed test
@@ -83,54 +88,27 @@ public class TestContext extends AttributeAccessorSupport {
Assert.notNull(testClass, "Test class must not be null");
Assert.notNull(contextCache, "ContextCache must not be null");
MergedContextConfiguration mergedContextConfiguration;
ContextConfiguration contextConfiguration = testClass.getAnnotation(ContextConfiguration.class);
this.testClass = testClass;
this.contextCache = contextCache;
this.cacheAwareContextLoaderDelegate = new CacheAwareContextLoaderDelegate(contextCache);
if (contextConfiguration == null) {
MergedContextConfiguration mergedContextConfiguration;
if (testClass.isAnnotationPresent(ContextConfiguration.class)
|| testClass.isAnnotationPresent(ContextHierarchy.class)) {
mergedContextConfiguration = ContextLoaderUtils.buildMergedContextConfiguration(testClass,
defaultContextLoaderClassName, cacheAwareContextLoaderDelegate);
}
else {
if (logger.isInfoEnabled()) {
logger.info(String.format("@ContextConfiguration not found for class [%s]", testClass));
logger.info(String.format(
"Neither @ContextConfiguration nor @ContextHierarchy found for test class [%s]",
testClass.getName()));
}
mergedContextConfiguration = new MergedContextConfiguration(testClass, null, null, null, null);
}
else {
if (logger.isTraceEnabled()) {
logger.trace(String.format("Retrieved @ContextConfiguration [%s] for class [%s]", contextConfiguration,
testClass));
}
mergedContextConfiguration = ContextLoaderUtils.buildMergedContextConfiguration(testClass,
defaultContextLoaderClassName);
}
this.contextCache = contextCache;
this.mergedContextConfiguration = mergedContextConfiguration;
this.testClass = testClass;
}
/**
* Load an {@code ApplicationContext} for this test context using the
* configured {@code ContextLoader} and merged context configuration. Supports
* both the {@link SmartContextLoader} and {@link ContextLoader} SPIs.
* @throws Exception if an error occurs while loading the application context
*/
private ApplicationContext loadApplicationContext() throws Exception {
ContextLoader contextLoader = mergedContextConfiguration.getContextLoader();
Assert.notNull(contextLoader, "Cannot load an ApplicationContext with a NULL 'contextLoader'. "
+ "Consider annotating your test class with @ContextConfiguration.");
ApplicationContext applicationContext;
if (contextLoader instanceof SmartContextLoader) {
SmartContextLoader smartContextLoader = (SmartContextLoader) contextLoader;
applicationContext = smartContextLoader.loadContext(mergedContextConfiguration);
}
else {
String[] locations = mergedContextConfiguration.getLocations();
Assert.notNull(locations, "Cannot load an ApplicationContext with a NULL 'locations' array. "
+ "Consider annotating your test class with @ContextConfiguration.");
applicationContext = contextLoader.loadContext(locations);
}
return applicationContext;
}
/**
@@ -141,31 +119,7 @@ public class TestContext extends AttributeAccessorSupport {
* application context
*/
public ApplicationContext getApplicationContext() {
synchronized (contextCache) {
ApplicationContext context = contextCache.get(mergedContextConfiguration);
if (context == null) {
try {
context = loadApplicationContext();
if (logger.isDebugEnabled()) {
logger.debug(String.format(
"Storing ApplicationContext for test class [%s] in cache under key [%s].", testClass,
mergedContextConfiguration));
}
contextCache.put(mergedContextConfiguration, context);
}
catch (Exception ex) {
throw new IllegalStateException("Failed to load ApplicationContext", ex);
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug(String.format(
"Retrieved ApplicationContext for test class [%s] from cache with key [%s].", testClass,
mergedContextConfiguration));
}
}
return context;
}
return cacheAwareContextLoaderDelegate.loadContext(mergedContextConfiguration);
}
/**
@@ -209,15 +163,27 @@ public class TestContext extends AttributeAccessorSupport {
}
/**
* Call this method to signal that the {@link ApplicationContext application
* context} associated with this test context is <em>dirty</em> and should
* be reloaded. Do this if a test has modified the context (for example, by
* replacing a bean definition).
* Call this method to signal that the {@linkplain ApplicationContext application
* context} associated with this test context is <em>dirty</em> and should be
* discarded. Do this if a test has modified the context &mdash; for example,
* by replacing a bean definition or modifying the state of a singleton bean.
* @deprecated As of Spring 3.2.2, use {@link #markApplicationContextDirty(HierarchyMode)} instead.
*/
@Deprecated
public void markApplicationContextDirty() {
synchronized (contextCache) {
contextCache.setDirty(mergedContextConfiguration);
}
markApplicationContextDirty((HierarchyMode) null);
}
/**
* Call this method to signal that the {@linkplain ApplicationContext application
* context} associated with this test context is <em>dirty</em> and should be
* discarded. Do this if a test has modified the context &mdash; for example,
* by replacing a bean definition or modifying the state of a singleton bean.
* @param hierarchyMode the context cache clearing mode to be applied if the
* context is part of a hierarchy (may be {@code null})
*/
public void markApplicationContextDirty(HierarchyMode hierarchyMode) {
contextCache.remove(mergedContextConfiguration, hierarchyMode);
}
/**

View File

@@ -104,15 +104,14 @@ public class TestContextManager {
}
/**
* Constructs a new {@code TestContextManager} for the specified {@link Class test class}
* and automatically {@link #registerTestExecutionListeners registers} the
* Constructs a new {@code TestContextManager} for the specified {@linkplain Class
* test class} and automatically {@link #registerTestExecutionListeners registers} the
* {@link TestExecutionListener TestExecutionListeners} configured for the test class
* via the {@link TestExecutionListeners &#064;TestExecutionListeners} annotation.
* @param testClass the test class to be managed
* @param defaultContextLoaderClassName the name of the default
* {@code ContextLoader} class to use (may be {@code null})
* @param defaultContextLoaderClassName the name of the default {@code ContextLoader}
* class to use (may be {@code null})
* @see #registerTestExecutionListeners(TestExecutionListener...)
* @see #retrieveTestExecutionListeners(Class)
*/
public TestContextManager(Class<?> testClass, String defaultContextLoaderClassName) {
this.testContext = new TestContext(testClass, contextCache, defaultContextLoaderClassName);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,29 +17,24 @@
package org.springframework.test.context;
/**
* {@code TestExecutionListener} defines a <em>listener</em> API for reacting to
* test execution events published by the {@link TestContextManager} with which
* the listener is registered.
* <p>
* {@code TestExecutionListener} defines a <em>listener</em> API for
* reacting to test execution events published by the {@link TestContextManager}
* with which the listener is registered.
* </p>
* <p>
* Concrete implementations must provide a {@code public} no-args
* constructor, so that listeners can be instantiated transparently by tools and
* configuration mechanisms.
* </p>
* Concrete implementations must provide a {@code public} no-args constructor,
* so that listeners can be instantiated transparently by tools and configuration
* mechanisms.
* <p>
* Spring provides the following out-of-the-box implementations:
* </p>
* <ul>
* <li>
* {@link org.springframework.test.context.support.DependencyInjectionTestExecutionListener
* <li>{@link org.springframework.test.context.support.DependencyInjectionTestExecutionListener
* DependencyInjectionTestExecutionListener}</li>
* <li>
* {@link org.springframework.test.context.support.DirtiesContextTestExecutionListener
* <li>{@link org.springframework.test.context.support.DirtiesContextTestExecutionListener
* DirtiesContextTestExecutionListener}</li>
* <li>
* {@link org.springframework.test.context.transaction.TransactionalTestExecutionListener
* <li>{@link org.springframework.test.context.transaction.TransactionalTestExecutionListener
* TransactionalTestExecutionListener}</li>
* <li>{@link org.springframework.test.context.web.ServletTestExecutionListener
* ServletTestExecutionListener}</li>
* </ul>
*
* @author Sam Brannen

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,9 +26,10 @@ import java.lang.annotation.Target;
/**
* {@code TestExecutionListeners} defines class-level metadata for
* configuring which {@link TestExecutionListener TestExecutionListeners} should
* be registered with a {@link TestContextManager}. Typically,
* {@code &#064;TestExecutionListeners} will be used in conjunction with
* {@link ContextConfiguration &#064;ContextConfiguration}.
* be registered with a {@link TestContextManager}.
*
* <p>Typically, {@code @TestExecutionListeners} will be used in conjunction with
* {@link ContextConfiguration @ContextConfiguration}.
*
* @author Sam Brannen
* @since 2.5
@@ -43,11 +44,10 @@ import java.lang.annotation.Target;
public @interface TestExecutionListeners {
/**
* <p>
* The {@link TestExecutionListener TestExecutionListeners} to register with
* a {@link TestContextManager}.
* </p>
*
* @see org.springframework.test.context.web.ServletTestExecutionListener
* @see org.springframework.test.context.support.DependencyInjectionTestExecutionListener
* @see org.springframework.test.context.support.DirtiesContextTestExecutionListener
* @see org.springframework.test.context.transaction.TransactionalTestExecutionListener
@@ -60,10 +60,8 @@ public @interface TestExecutionListeners {
Class<? extends TestExecutionListener>[] value() default {};
/**
* <p>
* Whether or not {@link #value() TestExecutionListeners} from superclasses
* should be <em>inherited</em>.
* </p>
* <p>
* The default value is {@code true}, which means that an annotated
* class will <em>inherit</em> the listeners defined by an annotated
@@ -77,11 +75,12 @@ public @interface TestExecutionListeners {
* {@code DependencyInjectionTestExecutionListener},
* {@code DirtiesContextTestExecutionListener}, <strong>and</strong>
* {@code TransactionalTestExecutionListener}, in that order.
* </p>
*
* <pre class="code">
* &#064;TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,
* DirtiesContextTestExecutionListener.class })
* &#064;TestExecutionListeners({
* DependencyInjectionTestExecutionListener.class,
* DirtiesContextTestExecutionListener.class
* })
* public abstract class AbstractBaseTest {
* // ...
* }
@@ -89,14 +88,12 @@ public @interface TestExecutionListeners {
* &#064;TestExecutionListeners(TransactionalTestExecutionListener.class)
* public class TransactionalTest extends AbstractBaseTest {
* // ...
* }
* </pre>
* }</pre>
*
* <p>
* If {@code inheritListeners} is set to {@code false}, the
* listeners for the annotated class will <em>shadow</em> and effectively
* replace any listeners defined by a superclass.
* </p>
* If {@code inheritListeners} is set to {@code false}, the listeners for the
* annotated class will <em>shadow</em> and effectively replace any listeners
* defined by a superclass.
*/
boolean inheritListeners() default true;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,10 +16,13 @@
package org.springframework.test.context.support;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.support.BeanDefinitionReader;
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.context.support.GenericApplicationContext;
@@ -66,6 +69,12 @@ public abstract class AbstractGenericContextLoader extends AbstractContextLoader
*
* <ul>
* <li>Creates a {@link GenericApplicationContext} instance.</li>
* <li>If the supplied {@code MergedContextConfiguration} references a
* {@linkplain MergedContextConfiguration#getParent() parent configuration},
* the corresponding {@link MergedContextConfiguration#getParentApplicationContext()
* ApplicationContext} will be retrieved and
* {@linkplain GenericApplicationContext#setParent(ApplicationContext) set as the parent}
* for the context created by this method.</li>
* <li>Calls {@link #prepareContext(GenericApplicationContext)} for backwards
* compatibility with the {@link org.springframework.test.context.ContextLoader
* ContextLoader} SPI.</li>
@@ -97,6 +106,11 @@ public abstract class AbstractGenericContextLoader extends AbstractContextLoader
}
GenericApplicationContext context = new GenericApplicationContext();
ApplicationContext parent = mergedConfig.getParentApplicationContext();
if (parent != null) {
context.setParent(parent);
}
prepareContext(context);
prepareContext(context, mergedConfig);
customizeBeanFactory(context.getDefaultListableBeanFactory());

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.annotation.DirtiesContext.ClassMode;
import org.springframework.test.annotation.DirtiesContext.HierarchyMode;
import org.springframework.test.context.TestContext;
import org.springframework.util.Assert;
@@ -43,26 +44,47 @@ public class DirtiesContextTestExecutionListener extends AbstractTestExecutionLi
/**
* Marks the {@link ApplicationContext application context} of the supplied
* {@link TestContext test context} as
* {@link TestContext#markApplicationContextDirty() dirty}, and sets the
* Marks the {@linkplain ApplicationContext application context} of the supplied
* {@linkplain TestContext test context} as
* {@linkplain TestContext#markApplicationContextDirty() dirty}, and sets the
* {@link DependencyInjectionTestExecutionListener#REINJECT_DEPENDENCIES_ATTRIBUTE
* REINJECT_DEPENDENCIES_ATTRIBUTE} in the test context to {@code true}.
* @param testContext the test context whose application context should
* marked as dirty
* @deprecated as of Spring 3.2.2, use {@link #dirtyContext(TestContext, HierarchyMode)} instead.
*/
@Deprecated
protected void dirtyContext(TestContext testContext) {
testContext.markApplicationContextDirty();
testContext.setAttribute(DependencyInjectionTestExecutionListener.REINJECT_DEPENDENCIES_ATTRIBUTE, Boolean.TRUE);
}
/**
* If the current test method of the supplied {@link TestContext test
* Marks the {@linkplain ApplicationContext application context} of the supplied
* {@linkplain TestContext test context} as {@linkplain
* TestContext#markApplicationContextDirty(HierarchyMode) dirty} and sets the
* {@link DependencyInjectionTestExecutionListener#REINJECT_DEPENDENCIES_ATTRIBUTE
* REINJECT_DEPENDENCIES_ATTRIBUTE} in the test context to {@code true}.
* @param testContext the test context whose application context should
* marked as dirty
* @param hierarchyMode the context cache clearing mode to be applied if the
* context is part of a hierarchy; may be {@code null}
* @since 3.2.2
*/
protected void dirtyContext(TestContext testContext, HierarchyMode hierarchyMode) {
testContext.markApplicationContextDirty(hierarchyMode);
testContext.setAttribute(DependencyInjectionTestExecutionListener.REINJECT_DEPENDENCIES_ATTRIBUTE, Boolean.TRUE);
}
/**
* If the current test method of the supplied {@linkplain TestContext test
* context} is annotated with {@link DirtiesContext &#064;DirtiesContext},
* or if the test class is annotated with {@link DirtiesContext
* &#064;DirtiesContext} and the {@link DirtiesContext#classMode() class
* &#064;DirtiesContext} and the {@linkplain DirtiesContext#classMode() class
* mode} is set to {@link ClassMode#AFTER_EACH_TEST_METHOD
* AFTER_EACH_TEST_METHOD}, the {@link ApplicationContext application
* AFTER_EACH_TEST_METHOD}, the {@linkplain ApplicationContext application
* context} of the test context will be
* {@link TestContext#markApplicationContextDirty() marked as dirty} and the
* {@linkplain TestContext#markApplicationContextDirty() marked as dirty} and the
* {@link DependencyInjectionTestExecutionListener#REINJECT_DEPENDENCIES_ATTRIBUTE
* REINJECT_DEPENDENCIES_ATTRIBUTE} in the test context will be set to
* {@code true}.
@@ -88,15 +110,17 @@ public class DirtiesContextTestExecutionListener extends AbstractTestExecutionLi
}
if (methodDirtiesContext || (classDirtiesContext && classMode == ClassMode.AFTER_EACH_TEST_METHOD)) {
dirtyContext(testContext);
HierarchyMode hierarchyMode = methodDirtiesContext ? testMethod.getAnnotation(annotationType).hierarchyMode()
: classDirtiesContextAnnotation.hierarchyMode();
dirtyContext(testContext, hierarchyMode);
}
}
/**
* If the test class of the supplied {@link TestContext test context} is
* If the test class of the supplied {@linkplain TestContext test context} is
* annotated with {@link DirtiesContext &#064;DirtiesContext}, the
* {@link ApplicationContext application context} of the test context will
* be {@link TestContext#markApplicationContextDirty() marked as dirty} ,
* {@linkplain ApplicationContext application context} of the test context will
* be {@linkplain TestContext#markApplicationContextDirty() marked as dirty} ,
* and the
* {@link DependencyInjectionTestExecutionListener#REINJECT_DEPENDENCIES_ATTRIBUTE
* REINJECT_DEPENDENCIES_ATTRIBUTE} in the test context will be set to
@@ -107,12 +131,15 @@ public class DirtiesContextTestExecutionListener extends AbstractTestExecutionLi
Class<?> testClass = testContext.getTestClass();
Assert.notNull(testClass, "The test class of the supplied TestContext must not be null");
boolean dirtiesContext = testClass.isAnnotationPresent(DirtiesContext.class);
final Class<DirtiesContext> annotationType = DirtiesContext.class;
boolean dirtiesContext = testClass.isAnnotationPresent(annotationType);
if (logger.isDebugEnabled()) {
logger.debug("After test class: context [" + testContext + "], dirtiesContext [" + dirtiesContext + "].");
}
if (dirtiesContext) {
dirtyContext(testContext);
HierarchyMode hierarchyMode = testClass.getAnnotation(annotationType).hierarchyMode();
dirtyContext(testContext, hierarchyMode);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31,6 +31,7 @@ import org.springframework.core.io.ResourceLoader;
import org.springframework.mock.web.MockServletContext;
import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.test.context.support.AbstractContextLoader;
import org.springframework.util.Assert;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.GenericWebApplicationContext;
@@ -71,6 +72,12 @@ public abstract class AbstractGenericWebContextLoader extends AbstractContextLoa
*
* <ul>
* <li>Creates a {@link GenericWebApplicationContext} instance.</li>
* <li>If the supplied {@code MergedContextConfiguration} references a
* {@linkplain MergedContextConfiguration#getParent() parent configuration},
* the corresponding {@link MergedContextConfiguration#getParentApplicationContext()
* ApplicationContext} will be retrieved and
* {@linkplain GenericWebApplicationContext#setParent(ApplicationContext) set as the parent}
* for the context created by this method.</li>
* <li>Delegates to {@link #configureWebResources} to create the
* {@link MockServletContext} and set it in the {@code WebApplicationContext}.</li>
* <li>Calls {@link #prepareContext} to allow for customizing the context
@@ -107,6 +114,11 @@ public abstract class AbstractGenericWebContextLoader extends AbstractContextLoa
}
GenericWebApplicationContext context = new GenericWebApplicationContext();
ApplicationContext parent = mergedConfig.getParentApplicationContext();
if (parent != null) {
context.setParent(parent);
}
configureWebResources(context, webMergedConfig);
prepareContext(context, webMergedConfig);
customizeBeanFactory(context.getDefaultListableBeanFactory(), webMergedConfig);
@@ -119,9 +131,20 @@ public abstract class AbstractGenericWebContextLoader extends AbstractContextLoa
}
/**
* Configures web resources for the supplied web application context.
* Configures web resources for the supplied web application context (WAC).
*
* <p>Implementation details:
* <h4>Implementation Details</h4>
*
* <p>If the supplied WAC has no parent or its parent is not a WAC, the
* supplied WAC will be configured as the Root WAC (see "<em>Root WAC
* Configuration</em>" below).
*
* <p>Otherwise the context hierarchy of the supplied WAC will be traversed
* to find the top-most WAC (i.e., the root); and the {@link ServletContext}
* of the Root WAC will be set as the {@code ServletContext} for the supplied
* WAC.
*
* <h4>Root WAC Configuration</h4>
*
* <ul>
* <li>The resource base path is retrieved from the supplied
@@ -146,13 +169,33 @@ public abstract class AbstractGenericWebContextLoader extends AbstractContextLoa
protected void configureWebResources(GenericWebApplicationContext context,
WebMergedContextConfiguration webMergedConfig) {
String resourceBasePath = webMergedConfig.getResourceBasePath();
ResourceLoader resourceLoader = resourceBasePath.startsWith(ResourceLoader.CLASSPATH_URL_PREFIX) ? new DefaultResourceLoader()
: new FileSystemResourceLoader();
ApplicationContext parent = context.getParent();
ServletContext servletContext = new MockServletContext(resourceBasePath, resourceLoader);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, context);
context.setServletContext(servletContext);
// if the WAC has no parent or the parent is not a WAC, set the WAC as
// the Root WAC:
if (parent == null || (!(parent instanceof WebApplicationContext))) {
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);
}
else {
ServletContext servletContext = null;
// find the Root WAC
while (parent != null) {
if (parent instanceof WebApplicationContext && !(parent.getParent() instanceof WebApplicationContext)) {
servletContext = ((WebApplicationContext) parent).getServletContext();
break;
}
parent = parent.getParent();
}
Assert.state(servletContext != null, "Failed to find Root WebApplicationContext in the context hierarchy");
context.setServletContext(servletContext);
}
}
/**

View File

@@ -69,6 +69,7 @@ public class ServletTestExecutionListener extends AbstractTestExecutionListener
* @see TestExecutionListener#prepareTestInstance(TestContext)
* @see #setUpRequestContextIfNecessary(TestContext)
*/
@SuppressWarnings("javadoc")
public void prepareTestInstance(TestContext testContext) throws Exception {
setUpRequestContextIfNecessary(testContext);
}
@@ -80,6 +81,7 @@ public class ServletTestExecutionListener extends AbstractTestExecutionListener
* @see TestExecutionListener#beforeTestMethod(TestContext)
* @see #setUpRequestContextIfNecessary(TestContext)
*/
@SuppressWarnings("javadoc")
public void beforeTestMethod(TestContext testContext) throws Exception {
setUpRequestContextIfNecessary(testContext);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,6 +21,7 @@ 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.CacheAwareContextLoaderDelegate;
import org.springframework.test.context.ContextLoader;
import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.util.ObjectUtils;
@@ -77,7 +78,11 @@ public class WebMergedContextConfiguration extends MergedContextConfiguration {
* @param activeProfiles the merged active bean definition profiles
* @param resourceBasePath the resource path to the root directory of the web application
* @param contextLoader the resolved {@code ContextLoader}
* @see #WebMergedContextConfiguration(Class, String[], Class[], Set, String[], String, ContextLoader, CacheAwareContextLoaderDelegate, MergedContextConfiguration)
* @deprecated as of Spring 3.2.2, use
* {@link #WebMergedContextConfiguration(Class, String[], Class[], Set, String[], String, ContextLoader, CacheAwareContextLoaderDelegate, MergedContextConfiguration)} instead.
*/
@Deprecated
public WebMergedContextConfiguration(
Class<?> testClass,
String[] locations,
@@ -85,7 +90,45 @@ public class WebMergedContextConfiguration extends MergedContextConfiguration {
Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> contextInitializerClasses,
String[] activeProfiles, String resourceBasePath, ContextLoader contextLoader) {
super(testClass, locations, classes, contextInitializerClasses, activeProfiles, contextLoader);
this(testClass, locations, classes, contextInitializerClasses, activeProfiles, resourceBasePath, contextLoader,
null, null);
}
/**
* Create a new {@code WebMergedContextConfiguration} instance for the
* supplied test class, resource locations, annotated classes, context
* initializers, active profiles, resource base path, and {@code ContextLoader}.
*
* <p>If a {@code null} value is supplied for {@code locations},
* {@code classes}, or {@code activeProfiles} an empty array will
* be stored instead. If a {@code null} value is supplied for the
* {@code contextInitializerClasses} an empty set will be stored instead.
* If an <em>empty</em> value is supplied for the {@code resourceBasePath}
* an empty string will be used. Furthermore, active profiles will be sorted,
* and duplicate profiles will be removed.
*
* @param testClass the test class for which the configuration was merged
* @param locations the merged resource locations
* @param classes the merged annotated classes
* @param contextInitializerClasses the merged context initializer classes
* @param activeProfiles the merged active bean definition profiles
* @param resourceBasePath the resource path to the root directory of the web application
* @param contextLoader the resolved {@code ContextLoader}
* @param cacheAwareContextLoaderDelegate a cache-aware context loader
* delegate with which to retrieve the parent context
* @param parent the parent configuration or {@code null} if there is no parent
* @since 3.2.2
*/
public WebMergedContextConfiguration(
Class<?> testClass,
String[] locations,
Class<?>[] classes,
Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> contextInitializerClasses,
String[] activeProfiles, String resourceBasePath, ContextLoader contextLoader,
CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, MergedContextConfiguration parent) {
super(testClass, locations, classes, contextInitializerClasses, activeProfiles, contextLoader,
cacheAwareContextLoaderDelegate, parent);
this.resourceBasePath = !StringUtils.hasText(resourceBasePath) ? "" : resourceBasePath;
}
@@ -118,8 +161,9 @@ public class WebMergedContextConfiguration extends MergedContextConfiguration {
* {@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}.
* {@linkplain #getResourceBasePath() resource base path},
* {@linkplain #getParent() parents}, and the fully qualified names of their
* {@link #getContextLoader() ContextLoaders}.
*/
@Override
public boolean equals(Object obj) {
@@ -141,8 +185,9 @@ public class WebMergedContextConfiguration extends MergedContextConfiguration {
* {@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}.
* {@linkplain #getResourceBasePath() resource base path}, the name of the
* {@link #getContextLoader() ContextLoader}, and the
* {@linkplain #getParent() parent configuration}.
*/
@Override
public String toString() {
@@ -154,6 +199,7 @@ public class WebMergedContextConfiguration extends MergedContextConfiguration {
.append("activeProfiles", ObjectUtils.nullSafeToString(getActiveProfiles()))//
.append("resourceBasePath", getResourceBasePath())//
.append("contextLoader", nullSafeToString(getContextLoader()))//
.append("parent", getParent())//
.toString();
}