Merge branch '3.2.x'

* 3.2.x: (28 commits)
  Hide 'doc' changes from jdiff reports
  Document @Bean 'lite' mode vs @Configuration
  Final preparations for 3.2.2
  Remove Tiles 3 configuration method
  Polishing
  Extracted buildRequestAttributes template method from FrameworkServlet
  Added "beforeExistingAdvisors" flag to AbstractAdvisingBeanPostProcessor
  Minor refinements along the way of researching static CGLIB callbacks
  Compare Kind references before checking log levels
  Polish Javadoc in RequestAttributes
  Fix copy-n-paste errors in NativeWebRequest
  Fix issue with restoring included attributes
  Add additional test for daylight savings glitch
  Document context hierarchy support in the TCF
  Fix test for daylight savings glitch
  Make the methodParameter field of HandlerMethod final
  Disable AsyncTests in spring-test-mvc
  Reformat the testing chapter
  Document context hierarchy support in the TCF
  Document context hierarchy support in the TCF
  ...
This commit is contained in:
Phillip Webb
2013-03-13 14:01:46 -07:00
267 changed files with 8384 additions and 6246 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 : "");
@@ -241,8 +242,16 @@ public class MockServletContext implements ServletContext {
return this.effectiveMinorVersion;
}
/**
* This method uses the Java Activation framework, which returns
* "application/octet-stream" when the mime type is unknown (i.e. it never returns
* {@code null}). In order to maintain the {@link ServletContext#getMimeType(String)
* contract, as of version 3.2.2, this method returns null if the mimeType is
* "application/octet-stream".
*/
public String getMimeType(String filePath) {
return MimeTypeResolver.getMimeType(filePath);
String mimeType = MimeTypeResolver.getMimeType(filePath);
return ("application/octet-stream".equals(mimeType)) ? null : mimeType;
}
public Set<String> getResourcePaths(String path) {
@@ -344,6 +353,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.
@@ -58,7 +58,7 @@ import org.springframework.context.ConfigurableApplicationContext;
* {@link org.springframework.context.annotation.Bean @Bean}-methods</li>
* </ul>
*
* Consult the Javadoc for
* <p>Consult the Javadoc for
* {@link org.springframework.context.annotation.Configuration @Configuration} and
* {@link org.springframework.context.annotation.Bean @Bean}
* for further information regarding the configuration and semantics of
@@ -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
@@ -269,18 +270,43 @@ public @interface ContextConfiguration {
* explicit loader. If no class in the hierarchy specifies an explicit
* loader, a default loader will be used instead.
*
* <p>The default concrete implementation chosen at runtime will be
* <p>The default concrete implementation chosen at runtime will be either
* {@link org.springframework.test.context.support.DelegatingSmartContextLoader
* DelegatingSmartContextLoader}. For further details on the default behavior
* of various concrete {@code ContextLoaders}, check out the Javadoc for
* DelegatingSmartContextLoader} or
* {@link org.springframework.test.context.web.WebDelegatingSmartContextLoader
* WebDelegatingSmartContextLoader} depending on the absence or presence of
* {@link org.springframework.test.context.web.WebAppConfiguration
* &#064;WebAppConfiguration}. For further details on the default behavior
* of various concrete {@code SmartContextLoaders}, check out the Javadoc for
* {@link org.springframework.test.context.support.AbstractContextLoader
* AbstractContextLoader},
* {@link org.springframework.test.context.support.GenericXmlContextLoader
* GenericXmlContextLoader}, and
* GenericXmlContextLoader},
* {@link org.springframework.test.context.support.AnnotationConfigContextLoader
* AnnotationConfigContextLoader}.
* AnnotationConfigContextLoader},
* {@link org.springframework.test.context.web.GenericXmlWebContextLoader
* GenericXmlWebContextLoader}, and
* {@link org.springframework.test.context.web.AnnotationConfigWebContextLoader
* AnnotationConfigWebContextLoader}.
*
* @since 2.5
*/
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 {@code @ContextHierarchy}, in which case the name
* can be used for <em>merging</em> or <em>overriding</em> this configuration
* with configuration of the same name in hierarchy levels defined in superclasses.
* See the Javadoc for {@link ContextHierarchy @ContextHierarchy} for details.
*
* @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,157 @@
/*
* 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.
*
* <h3>Examples</h3>
* <p>The following JUnit-based examples demonstrate common configuration
* scenarios for integration tests that require the use of context hierarchies.
*
* <h4>Single Test Class with Context Hierarchy</h4>
* <p>{@code ControllerIntegrationTests} represents a typical integration testing
* scenario for a Spring MVC web application by declaring a context hierarchy
* consisting of two levels, one for the <em>root</em> {@code WebApplicationContext}
* (with {@code TestAppConfig}) and one for the <em>dispatcher servlet</em>
* {@code WebApplicationContext} (with {@code WebConfig}). The {@code
* WebApplicationContext} that is <em>autowired</em> into the test instance is
* the one for the child context (i.e., the lowest context in the hierarchy).
*
* <pre class="code">
* &#064;RunWith(SpringJUnit4ClassRunner.class)
* &#064;WebAppConfiguration
* &#064;ContextHierarchy({
* &#064;ContextConfiguration(classes = TestAppConfig.class),
* &#064;ContextConfiguration(classes = WebConfig.class)
* })
* public class ControllerIntegrationTests {
*
* &#064;Autowired
* private WebApplicationContext wac;
*
* // ...
* }</pre>
*
* <h4>Class Hierarchy with Implicit Parent Context</h4>
* <p>The following test classes define a context hierarchy within a test class
* hierarchy. {@code AbstractWebTests} declares the configuration for a root
* {@code WebApplicationContext} in a Spring-powered web application. Note,
* however, that {@code AbstractWebTests} does not declare {@code @ContextHierarchy};
* consequently, subclasses of {@code AbstractWebTests} can optionally participate
* in a context hierarchy or follow the standard semantics for {@code @ContextConfiguration}.
* {@code SoapWebServiceTests} and {@code RestWebServiceTests} both extend
* {@code AbstractWebTests} and define a context hierarchy via {@code @ContextHierarchy}.
* The result is that three application contexts will be loaded (one for each
* declaration of {@code @ContextConfiguration}, and the application context
* loaded based on the configuration in {@code AbstractWebTests} will be set as
* the parent context for each of the contexts loaded for the concrete subclasses.
*
* <pre class="code">
* &#064;RunWith(SpringJUnit4ClassRunner.class)
* &#064;WebAppConfiguration
* &#064;ContextConfiguration("file:src/main/webapp/WEB-INF/applicationContext.xml")
* public abstract class AbstractWebTests {}
*
* &#064;ContextHierarchy(&#064;ContextConfiguration("/spring/soap-ws-config.xml")
* public class SoapWebServiceTests extends AbstractWebTests {}
*
* &#064;ContextHierarchy(&#064;ContextConfiguration("/spring/rest-ws-config.xml")
* public class RestWebServiceTests extends AbstractWebTests {}</pre>
*
* <h4>Class Hierarchy with Merged Context Hierarchy Configuration</h4>
* <p>The following classes demonstrate the use of <em>named</em> hierarchy levels
* in order to <em>merge</em> the configuration for specific levels in a context
* hierarchy. {@code BaseTests} defines two levels in the hierarchy, {@code parent}
* and {@code child}. {@code ExtendedTests} extends {@code BaseTests} and instructs
* the Spring TestContext Framework to merge the context configuration for the
* {@code child} hierarchy level, simply by ensuring that the names declared via
* {@link ContextConfiguration#name} are both {@code "child"}. The result is that
* three application contexts will be loaded: one for {@code "/app-config.xml"},
* one for {@code "/user-config.xml"}, and one for <code>{"/user-config.xml",
* "/order-config.xml"}</code>. As with the previous example, the application
* context loaded from {@code "/app-config.xml"} will be set as the parent context
* for the contexts loaded from {@code "/user-config.xml"} and <code>{"/user-config.xml",
* "/order-config.xml"}</code>.
*
* <pre class="code">
* &#064;RunWith(SpringJUnit4ClassRunner.class)
* &#064;ContextHierarchy({
* &#064;ContextConfiguration(name = "parent", locations = "/app-config.xml"),
* &#064;ContextConfiguration(name = "child", locations = "/user-config.xml")
* })
* public class BaseTests {}
*
* &#064;ContextHierarchy(
* &#064;ContextConfiguration(name = "child", locations = "/order-config.xml")
* )
* public class ExtendedTests extends BaseTests {}</pre>
*
* <h4>Class Hierarchy with Overridden Context Hierarchy Configuration</h4>
* <p>In contrast to the previous example, this example demonstrates how to
* <em>override</em> the configuration for a given named level in a context hierarchy
* by setting the {@link ContextConfiguration#inheritLocations} flag to {@code false}.
* Consequently, the application context for {@code ExtendedTests} will be loaded
* only from {@code "/test-user-config.xml"} and will have its parent set to the
* context loaded from {@code "/app-config.xml"}.
*
* <pre class="code">
* &#064;RunWith(SpringJUnit4ClassRunner.class)
* &#064;ContextHierarchy({
* &#064;ContextConfiguration(name = "parent", locations = "/app-config.xml"),
* &#064;ContextConfiguration(name = "child", locations = "/user-config.xml")
* })
* public class BaseTests {}
*
* &#064;ContextHierarchy(
* &#064;ContextConfiguration(name = "child", locations = "/test-user-config.xml", inheritLocations=false)
* )
* public class ExtendedTests extends BaseTests {}</pre>
*
* @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. See the class-level Javadoc for examples.
*/
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();
}