Browse Source
Work done in conjunction with SPR-5243 and SPR-4588 introduced physical package cycles in the spring-test module. The work performed in conjunction with SPR-9924 uses reflection to resolve these physical package cycles; however, prior to this commit the logical package cycles still remain. Furthermore, over time it has become apparent that the Spring TestContext Framework (TCF) could better serve application developers and especially third-party framework developers by providing a more flexible mechanism for "bootstrapping" the TCF. For example, prior to this commit, default TestExecutionListeners could only be registered by subclassing TestContextManager (and SpringJUnit4ClassRunner if using JUnit). Similarly, the default ContextLoader could only be set by subclassing SpringJUnit4ClassRunner for JUnit and by copying and modifying AbstractTestNGSpringContextTests for TestNG. This commit addresses the aforementioned issues by introducing a bootstrap strategy in the TestContext framework that is responsible for determining default TestExecutionListeners and the default ContextLoader in an extensible fashion. The new TestContextBootstrapper SPI also provides a mechanism for supporting various types of MergedContextConfiguration depending on the feature set of the context loaders supported by the strategy. The following provides an overview of the most significant changes in this commit. - Introduced TestContextBootstrapper strategy SPI, BootstrapContext, and @BootstrapWith. - Introduced AbstractTestContextBootstrapper, DefaultTestContextBootstrapper, and WebTestContextBootstrapper implementations of the TestContextBootstrapper SPI and extracted related reflection code from ContextLoaderUtils & TestContextManager. - Introduced BootstrapUtils for retrieving the TestContextBootstrapper from @BootstrapWith, falling back to a default if @BootstrapWith is not present. - @WebAppConfiguration is now annotated with @BootstrapWith(WebTestContextBootstrapper.class). - CacheAwareContextLoaderDelegate is now an interface with a new DefaultCacheAwareContextLoaderDelegate implementation class. - Introduced closeContext(MergedContextConfiguration, HierarchyMode) in CacheAwareContextLoaderDelegate. - DefaultTestContext now uses CacheAwareContextLoaderDelegate instead of interacting directly with the ContextCache. - DefaultTestContext now delegates to a TestContextBootstrapper for building the MergedContextConfiguration. - TestContextManager now delegates to TestContextBootstrapper for retrieving TestExecutionListeners. - Deleted TestContextManager(Class, String) constructor and SpringJUnit4ClassRunner.getDefaultContextLoaderClassName(Class) method since default ContextLoader support is now implemented by TestContextBootstrappers. - Extracted ActiveProfilesUtils from ContextLoaderUtils. - Extracted ApplicationContextInitializerUtils from ContextLoaderUtils. - MetaAnnotationUtils is now a public utility class in the test.util package. - Removed restriction in @ActiveProfiles that a custom resolver cannot be used with the 'value' or 'profiles' attributes. - Introduced DefaultActiveProfilesResolver. Issue: SPR-9955pull/512/head
Sam Brannen
11 years ago
37 changed files with 2194 additions and 1265 deletions
@ -0,0 +1,42 @@
@@ -0,0 +1,42 @@
|
||||
/* |
||||
* Copyright 2002-2014 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; |
||||
|
||||
/** |
||||
* {@code BootstrapContext} encapsulates the context in which the <em>Spring |
||||
* TestContext Framework</em> is bootstrapped. |
||||
* |
||||
* @author Sam Brannen |
||||
* @since 4.1 |
||||
* @see BootstrapWith |
||||
* @see TestContextBootstrapper |
||||
*/ |
||||
public interface BootstrapContext { |
||||
|
||||
/** |
||||
* Get the {@link Class test class} for this bootstrap context. |
||||
* @return the test class (never {@code null}) |
||||
*/ |
||||
Class<?> getTestClass(); |
||||
|
||||
/** |
||||
* Get the {@link CacheAwareContextLoaderDelegate} to use for transparent |
||||
* interaction with the <em>context cache</em>. |
||||
*/ |
||||
CacheAwareContextLoaderDelegate getCacheAwareContextLoaderDelegate(); |
||||
|
||||
} |
@ -0,0 +1,92 @@
@@ -0,0 +1,92 @@
|
||||
/* |
||||
* Copyright 2002-2014 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.util.ClassUtils; |
||||
|
||||
import static org.springframework.beans.BeanUtils.*; |
||||
import static org.springframework.core.annotation.AnnotationUtils.*; |
||||
|
||||
/** |
||||
* {@code BootstrapUtils} is a collection of utility methods to assist with |
||||
* bootstrapping the <em>Spring TestContext Framework</em>. |
||||
* |
||||
* @author Sam Brannen |
||||
* @since 4.1 |
||||
* @see BootstrapWith |
||||
* @see BootstrapContext |
||||
* @see TestContextBootstrapper |
||||
*/ |
||||
abstract class BootstrapUtils { |
||||
|
||||
private static final String DEFAULT_TEST_CONTEXT_BOOTSTRAPPER_CLASS_NAME = "org.springframework.test.context.support.DefaultTestContextBootstrapper"; |
||||
|
||||
private static final Log logger = LogFactory.getLog(BootstrapUtils.class); |
||||
|
||||
|
||||
private BootstrapUtils() { |
||||
/* no-op */ |
||||
} |
||||
|
||||
/** |
||||
* Resolve the {@link TestContextBootstrapper} type for the test class in the |
||||
* supplied {@link BootstrapContext}, instantiate it, and provide it a reference |
||||
* to the {@link BootstrapContext}. |
||||
* |
||||
* <p>If the {@link BootstrapWith @BootstrapWith} annotation is present on |
||||
* the test class, either directly or as a meta-annotation, then its |
||||
* {@link BootstrapWith#value value} will be used as the bootstrapper type. |
||||
* Otherwise, the {@link org.springframework.test.context.support.DefaultTestContextBootstrapper |
||||
* DefaultTestContextBootstrapper} will be used. |
||||
* |
||||
* @param bootstrapContext the bootstrap context to use |
||||
* @return a fully configured {@code TestContextBootstrapper} |
||||
*/ |
||||
@SuppressWarnings("unchecked") |
||||
static TestContextBootstrapper resolveTestContextBootstrapper(BootstrapContext bootstrapContext) { |
||||
Class<?> testClass = bootstrapContext.getTestClass(); |
||||
|
||||
Class<? extends TestContextBootstrapper> clazz = null; |
||||
try { |
||||
BootstrapWith bootstrapWith = findAnnotation(testClass, BootstrapWith.class); |
||||
if (bootstrapWith != null && !TestContextBootstrapper.class.equals(bootstrapWith.value())) { |
||||
clazz = bootstrapWith.value(); |
||||
} |
||||
else { |
||||
clazz = (Class<? extends TestContextBootstrapper>) ClassUtils.forName( |
||||
DEFAULT_TEST_CONTEXT_BOOTSTRAPPER_CLASS_NAME, BootstrapUtils.class.getClassLoader()); |
||||
} |
||||
|
||||
if (logger.isDebugEnabled()) { |
||||
logger.debug(String.format("Instantiating TestContextBootstrapper from class [%s]", clazz.getName())); |
||||
} |
||||
|
||||
TestContextBootstrapper testContextBootstrapper = instantiateClass(clazz, TestContextBootstrapper.class); |
||||
testContextBootstrapper.setBootstrapContext(bootstrapContext); |
||||
|
||||
return testContextBootstrapper; |
||||
} |
||||
catch (Throwable t) { |
||||
throw new IllegalStateException("Could not load TestContextBootstrapper [" + clazz |
||||
+ "]. Specify @BootstrapWith's 'value' attribute " |
||||
+ "or make the default bootstrapper class available.", t); |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,50 @@
@@ -0,0 +1,50 @@
|
||||
/* |
||||
* Copyright 2002-2014 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 @BootstrapWith} defines class-level metadata that is used to determine |
||||
* how to bootstrap the <em>Spring TestContext Framework</em>. |
||||
* |
||||
* <p>This annotation may also be used as a <em>meta-annotation</em> to create |
||||
* custom <em>composed annotations</em>. |
||||
* |
||||
* @author Sam Brannen |
||||
* @since 4.1 |
||||
* @see BootstrapContext |
||||
* @see TestContextBootstrapper |
||||
*/ |
||||
@Documented |
||||
@Inherited |
||||
@Retention(RetentionPolicy.RUNTIME) |
||||
@Target(ElementType.TYPE) |
||||
public @interface BootstrapWith { |
||||
|
||||
/** |
||||
* The {@link TestContextBootstrapper} to use to bootstrap the <em>Spring |
||||
* TestContext Framework</em>. |
||||
*/ |
||||
Class<? extends TestContextBootstrapper> value() default TestContextBootstrapper.class; |
||||
|
||||
} |
@ -1,807 +0,0 @@
@@ -1,807 +0,0 @@
|
||||
/* |
||||
* Copyright 2002-2014 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.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.AnnotationAttributes; |
||||
import org.springframework.core.annotation.AnnotationUtils; |
||||
import org.springframework.test.context.MetaAnnotationUtils.AnnotationDescriptor; |
||||
import org.springframework.test.context.MetaAnnotationUtils.UntypedAnnotationDescriptor; |
||||
import org.springframework.util.Assert; |
||||
import org.springframework.util.ClassUtils; |
||||
import org.springframework.util.ObjectUtils; |
||||
import org.springframework.util.StringUtils; |
||||
|
||||
import static org.springframework.beans.BeanUtils.*; |
||||
import static org.springframework.core.annotation.AnnotationUtils.*; |
||||
import static org.springframework.test.context.MetaAnnotationUtils.*; |
||||
|
||||
/** |
||||
* Utility methods for working with {@link ContextLoader ContextLoaders} and |
||||
* {@link SmartContextLoader SmartContextLoaders} and resolving resource locations, |
||||
* annotated classes, active bean definition profiles, and application context |
||||
* initializers. |
||||
* |
||||
* @author Sam Brannen |
||||
* @author Michail Nikolaev |
||||
* @since 3.1 |
||||
* @see ContextLoader |
||||
* @see SmartContextLoader |
||||
* @see ContextConfiguration |
||||
* @see ContextConfigurationAttributes |
||||
* @see ActiveProfiles |
||||
* @see ActiveProfilesResolver |
||||
* @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"; |
||||
private static final String DEFAULT_WEB_CONTEXT_LOADER_CLASS_NAME = "org.springframework.test.context.web.WebDelegatingSmartContextLoader"; |
||||
|
||||
private static final String WEB_APP_CONFIGURATION_CLASS_NAME = "org.springframework.test.context.web.WebAppConfiguration"; |
||||
private static final String WEB_MERGED_CONTEXT_CONFIGURATION_CLASS_NAME = "org.springframework.test.context.web.WebMergedContextConfiguration"; |
||||
|
||||
|
||||
private ContextLoaderUtils() { |
||||
/* no-op */ |
||||
} |
||||
|
||||
/** |
||||
* 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 |
||||
* {@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> |
||||
* (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}) |
||||
* @see #resolveContextLoaderClass |
||||
*/ |
||||
static ContextLoader resolveContextLoader(Class<?> testClass, |
||||
List<ContextConfigurationAttributes> configAttributesList, String defaultContextLoaderClassName) { |
||||
Assert.notNull(testClass, "Class must not be null"); |
||||
Assert.notEmpty(configAttributesList, "ContextConfigurationAttributes list must not be empty"); |
||||
|
||||
if (!StringUtils.hasText(defaultContextLoaderClassName)) { |
||||
Class<? extends Annotation> webAppConfigClass = loadWebAppConfigurationClass(); |
||||
defaultContextLoaderClassName = webAppConfigClass != null |
||||
&& findAnnotation(testClass, webAppConfigClass) != null ? DEFAULT_WEB_CONTEXT_LOADER_CLASS_NAME |
||||
: DEFAULT_CONTEXT_LOADER_CLASS_NAME; |
||||
} |
||||
|
||||
Class<? extends ContextLoader> contextLoaderClass = resolveContextLoaderClass(testClass, configAttributesList, |
||||
defaultContextLoaderClassName); |
||||
|
||||
return instantiateClass(contextLoaderClass, ContextLoader.class); |
||||
} |
||||
|
||||
/** |
||||
* 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: |
||||
* |
||||
* <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> |
||||
* </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> |
||||
* (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 |
||||
* @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 |
||||
*/ |
||||
@SuppressWarnings("unchecked") |
||||
static Class<? extends ContextLoader> resolveContextLoaderClass(Class<?> testClass, |
||||
List<ContextConfigurationAttributes> configAttributesList, String defaultContextLoaderClassName) { |
||||
Assert.notNull(testClass, "Class must not be null"); |
||||
Assert.notEmpty(configAttributesList, "ContextConfigurationAttributes list must not be empty"); |
||||
Assert.hasText(defaultContextLoaderClassName, "Default ContextLoader class name must not be null or empty"); |
||||
|
||||
for (ContextConfigurationAttributes configAttributes : configAttributesList) { |
||||
if (logger.isTraceEnabled()) { |
||||
logger.trace(String.format("Processing ContextLoader for context configuration attributes %s", |
||||
configAttributes)); |
||||
} |
||||
|
||||
Class<? extends ContextLoader> contextLoaderClass = configAttributes.getContextLoaderClass(); |
||||
if (!ContextLoader.class.equals(contextLoaderClass)) { |
||||
if (logger.isDebugEnabled()) { |
||||
logger.debug(String.format( |
||||
"Found explicit ContextLoader class [%s] for context configuration attributes %s", |
||||
contextLoaderClass.getName(), configAttributes)); |
||||
} |
||||
return contextLoaderClass; |
||||
} |
||||
} |
||||
|
||||
try { |
||||
if (logger.isTraceEnabled()) { |
||||
logger.trace(String.format("Using default ContextLoader class [%s] for test class [%s]", |
||||
defaultContextLoaderClassName, testClass.getName())); |
||||
} |
||||
return (Class<? extends ContextLoader>) ClassUtils.forName(defaultContextLoaderClassName, |
||||
ContextLoaderUtils.class.getClassLoader()); |
||||
} |
||||
catch (Throwable t) { |
||||
throw new IllegalStateException("Could not load default ContextLoader class [" |
||||
+ defaultContextLoaderClassName + "]. Specify @ContextConfiguration's 'loader' " |
||||
+ "attribute or make the default loader class available.", t); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Convenience method for creating a {@link ContextConfigurationAttributes} |
||||
* instance from the supplied {@link ContextConfiguration} annotation 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); |
||||
} |
||||
|
||||
/** |
||||
* Convenience method for creating a {@link ContextConfigurationAttributes} |
||||
* instance from the supplied {@link AnnotationAttributes} and declaring |
||||
* class and then adding the attributes to the supplied list. |
||||
* @since 4.0 |
||||
*/ |
||||
private static void convertAnnotationAttributesToConfigAttributesAndAddToList(AnnotationAttributes annAttrs, |
||||
Class<?> declaringClass, final List<ContextConfigurationAttributes> attributesList) { |
||||
if (logger.isTraceEnabled()) { |
||||
logger.trace(String.format("Retrieved @ContextConfiguration attributes [%s] for declaring class [%s].", |
||||
annAttrs, declaringClass.getName())); |
||||
} |
||||
|
||||
ContextConfigurationAttributes attributes = new ContextConfigurationAttributes(declaringClass, annAttrs); |
||||
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>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; or if a test class or composed annotation |
||||
* in the class hierarchy declares both {@code @ContextConfiguration} and |
||||
* {@code @ContextHierarchy} as top-level annotations. |
||||
* @throws IllegalStateException if no class in the class hierarchy declares |
||||
* {@code @ContextHierarchy}. |
||||
* |
||||
* @since 3.2.2 |
||||
* @see #buildContextHierarchyMap(Class) |
||||
* @see #resolveContextConfigurationAttributes(Class) |
||||
*/ |
||||
@SuppressWarnings("unchecked") |
||||
static List<List<ContextConfigurationAttributes>> resolveContextHierarchyAttributes(Class<?> testClass) { |
||||
Assert.notNull(testClass, "Class must not be null"); |
||||
Assert.state(findAnnotation(testClass, ContextHierarchy.class) != null, "@ContextHierarchy must be present"); |
||||
|
||||
final Class<ContextConfiguration> contextConfigType = ContextConfiguration.class; |
||||
final Class<ContextHierarchy> contextHierarchyType = ContextHierarchy.class; |
||||
final List<List<ContextConfigurationAttributes>> hierarchyAttributes = new ArrayList<List<ContextConfigurationAttributes>>(); |
||||
|
||||
UntypedAnnotationDescriptor descriptor = findAnnotationDescriptorForTypes(testClass, contextConfigType, |
||||
contextHierarchyType); |
||||
Assert.notNull(descriptor, 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 (descriptor != null) { |
||||
Class<?> rootDeclaringClass = descriptor.getRootDeclaringClass(); |
||||
Class<?> declaringClass = descriptor.getDeclaringClass(); |
||||
|
||||
boolean contextConfigDeclaredLocally = isAnnotationDeclaredLocally(contextConfigType, declaringClass); |
||||
boolean contextHierarchyDeclaredLocally = isAnnotationDeclaredLocally(contextHierarchyType, declaringClass); |
||||
|
||||
if (contextConfigDeclaredLocally && contextHierarchyDeclaredLocally) { |
||||
String msg = String.format("Class [%s] has been configured with both @ContextConfiguration " |
||||
+ "and @ContextHierarchy. Only one of these annotations may be declared on a test class " |
||||
+ "or composed annotation.", declaringClass.getName()); |
||||
logger.error(msg); |
||||
throw new IllegalStateException(msg); |
||||
} |
||||
|
||||
final List<ContextConfigurationAttributes> configAttributesList = new ArrayList<ContextConfigurationAttributes>(); |
||||
|
||||
if (contextConfigDeclaredLocally) { |
||||
convertAnnotationAttributesToConfigAttributesAndAddToList(descriptor.getAnnotationAttributes(), |
||||
rootDeclaringClass, configAttributesList); |
||||
} |
||||
else if (contextHierarchyDeclaredLocally) { |
||||
ContextHierarchy contextHierarchy = getAnnotation(declaringClass, contextHierarchyType); |
||||
for (ContextConfiguration contextConfiguration : contextHierarchy.value()) { |
||||
convertContextConfigToConfigAttributesAndAddToList(contextConfiguration, rootDeclaringClass, |
||||
configAttributesList); |
||||
} |
||||
} |
||||
else { |
||||
// This should theoretically never happen...
|
||||
String msg = String.format("Test class [%s] has been configured with neither @ContextConfiguration " |
||||
+ "nor @ContextHierarchy as a class-level annotation.", rootDeclaringClass.getName()); |
||||
logger.error(msg); |
||||
throw new IllegalStateException(msg); |
||||
} |
||||
|
||||
hierarchyAttributes.add(0, configAttributesList); |
||||
|
||||
descriptor = findAnnotationDescriptorForTypes(rootDeclaringClass.getSuperclass(), contextConfigType, |
||||
contextHierarchyType); |
||||
} |
||||
|
||||
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} |
||||
* @throws IllegalArgumentException if the lists of context configuration |
||||
* attributes for each level in the {@code @ContextHierarchy} do not define |
||||
* unique context configuration within the overall hierarchy. |
||||
* |
||||
* @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); |
||||
} |
||||
} |
||||
|
||||
// Check for uniqueness
|
||||
Set<List<ContextConfigurationAttributes>> set = new HashSet<List<ContextConfigurationAttributes>>(map.values()); |
||||
if (set.size() != map.size()) { |
||||
String msg = String.format("The @ContextConfiguration elements configured via " |
||||
+ "@ContextHierarchy in test class [%s] and its superclasses must " |
||||
+ "define unique contexts per hierarchy level.", testClass.getName()); |
||||
logger.error(msg); |
||||
throw new IllegalStateException(msg); |
||||
} |
||||
|
||||
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 |
||||
*/ |
||||
static List<ContextConfigurationAttributes> resolveContextConfigurationAttributes(Class<?> testClass) { |
||||
Assert.notNull(testClass, "Class must not be null"); |
||||
|
||||
final List<ContextConfigurationAttributes> attributesList = new ArrayList<ContextConfigurationAttributes>(); |
||||
|
||||
Class<ContextConfiguration> annotationType = ContextConfiguration.class; |
||||
|
||||
AnnotationDescriptor<ContextConfiguration> descriptor = findAnnotationDescriptor(testClass, annotationType); |
||||
Assert.notNull(descriptor, String.format( |
||||
"Could not find an 'annotation declaring class' for annotation type [%s] and class [%s]", |
||||
annotationType.getName(), testClass.getName())); |
||||
|
||||
while (descriptor != null) { |
||||
convertAnnotationAttributesToConfigAttributesAndAddToList(descriptor.getAnnotationAttributes(), |
||||
descriptor.getRootDeclaringClass(), attributesList); |
||||
descriptor = findAnnotationDescriptor(descriptor.getRootDeclaringClass().getSuperclass(), annotationType); |
||||
} |
||||
|
||||
return attributesList; |
||||
} |
||||
|
||||
/** |
||||
* 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. |
||||
* |
||||
* @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}) |
||||
* @since 3.2 |
||||
*/ |
||||
static Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> resolveInitializerClasses( |
||||
List<ContextConfigurationAttributes> configAttributesList) { |
||||
Assert.notEmpty(configAttributesList, "ContextConfigurationAttributes list must not be empty"); |
||||
|
||||
final Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializerClasses = //
|
||||
new HashSet<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>>(); |
||||
|
||||
for (ContextConfigurationAttributes configAttributes : configAttributesList) { |
||||
if (logger.isTraceEnabled()) { |
||||
logger.trace(String.format("Processing context initializers for context configuration attributes %s", |
||||
configAttributes)); |
||||
} |
||||
|
||||
initializerClasses.addAll(Arrays.asList(configAttributes.getInitializers())); |
||||
|
||||
if (!configAttributes.isInheritInitializers()) { |
||||
break; |
||||
} |
||||
} |
||||
|
||||
return initializerClasses; |
||||
} |
||||
|
||||
/** |
||||
* Resolve <em>active bean definition profiles</em> for the supplied {@link Class}. |
||||
* |
||||
* <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}) |
||||
* @see ActiveProfiles |
||||
* @see org.springframework.context.annotation.Profile |
||||
*/ |
||||
static String[] resolveActiveProfiles(Class<?> testClass) { |
||||
Assert.notNull(testClass, "Class must not be null"); |
||||
|
||||
Class<ActiveProfiles> annotationType = ActiveProfiles.class; |
||||
|
||||
AnnotationDescriptor<ActiveProfiles> descriptor = findAnnotationDescriptor(testClass, annotationType); |
||||
|
||||
if (descriptor == null && logger.isDebugEnabled()) { |
||||
logger.debug(String.format( |
||||
"Could not find an 'annotation declaring class' for annotation type [%s] and class [%s]", |
||||
annotationType.getName(), testClass.getName())); |
||||
} |
||||
|
||||
final Set<String> activeProfiles = new HashSet<String>(); |
||||
|
||||
while (descriptor != null) { |
||||
Class<?> rootDeclaringClass = descriptor.getRootDeclaringClass(); |
||||
Class<?> declaringClass = descriptor.getDeclaringClass(); |
||||
|
||||
AnnotationAttributes annAttrs = descriptor.getAnnotationAttributes(); |
||||
if (logger.isTraceEnabled()) { |
||||
logger.trace(String.format("Retrieved @ActiveProfiles attributes [%s] for declaring class [%s].", |
||||
annAttrs, declaringClass.getName())); |
||||
} |
||||
validateActiveProfilesConfiguration(declaringClass, annAttrs); |
||||
|
||||
String[] profiles = annAttrs.getStringArray("profiles"); |
||||
String[] valueProfiles = annAttrs.getStringArray("value"); |
||||
Class<? extends ActiveProfilesResolver> resolverClass = annAttrs.getClass("resolver"); |
||||
|
||||
boolean resolverDeclared = !ActiveProfilesResolver.class.equals(resolverClass); |
||||
boolean valueDeclared = !ObjectUtils.isEmpty(valueProfiles); |
||||
|
||||
if (resolverDeclared) { |
||||
ActiveProfilesResolver resolver = null; |
||||
try { |
||||
resolver = instantiateClass(resolverClass, ActiveProfilesResolver.class); |
||||
} |
||||
catch (Exception e) { |
||||
String msg = String.format("Could not instantiate ActiveProfilesResolver of " |
||||
+ "type [%s] for test class [%s].", resolverClass.getName(), rootDeclaringClass.getName()); |
||||
logger.error(msg); |
||||
throw new IllegalStateException(msg, e); |
||||
} |
||||
|
||||
profiles = resolver.resolve(rootDeclaringClass); |
||||
if (profiles == null) { |
||||
String msg = String.format( |
||||
"ActiveProfilesResolver [%s] returned a null array of bean definition profiles.", |
||||
resolverClass.getName()); |
||||
logger.error(msg); |
||||
throw new IllegalStateException(msg); |
||||
} |
||||
} |
||||
else if (valueDeclared) { |
||||
profiles = valueProfiles; |
||||
} |
||||
|
||||
for (String profile : profiles) { |
||||
if (StringUtils.hasText(profile)) { |
||||
activeProfiles.add(profile.trim()); |
||||
} |
||||
} |
||||
|
||||
descriptor = annAttrs.getBoolean("inheritProfiles") ? findAnnotationDescriptor( |
||||
rootDeclaringClass.getSuperclass(), annotationType) : null; |
||||
} |
||||
|
||||
return StringUtils.toStringArray(activeProfiles); |
||||
} |
||||
|
||||
private static void validateActiveProfilesConfiguration(Class<?> declaringClass, AnnotationAttributes annAttrs) { |
||||
String[] valueProfiles = annAttrs.getStringArray("value"); |
||||
String[] profiles = annAttrs.getStringArray("profiles"); |
||||
Class<? extends ActiveProfilesResolver> resolverClass = annAttrs.getClass("resolver"); |
||||
boolean valueDeclared = !ObjectUtils.isEmpty(valueProfiles); |
||||
boolean profilesDeclared = !ObjectUtils.isEmpty(profiles); |
||||
boolean resolverDeclared = !ActiveProfilesResolver.class.equals(resolverClass); |
||||
|
||||
String msg = null; |
||||
|
||||
if (valueDeclared && profilesDeclared) { |
||||
msg = String.format("Test class [%s] has been configured with @ActiveProfiles' 'value' [%s] " |
||||
+ "and 'profiles' [%s] attributes. Only one declaration of active bean " |
||||
+ "definition profiles is permitted per @ActiveProfiles annotation.", declaringClass.getName(), |
||||
ObjectUtils.nullSafeToString(valueProfiles), ObjectUtils.nullSafeToString(profiles)); |
||||
} |
||||
else if (valueDeclared && resolverDeclared) { |
||||
msg = String.format("Test class [%s] has been configured with @ActiveProfiles' 'value' [%s] " |
||||
+ "and 'resolver' [%s] attributes. Only one source of active bean " |
||||
+ "definition profiles is permitted per @ActiveProfiles annotation, " |
||||
+ "either declaritively or programmatically.", declaringClass.getName(), |
||||
ObjectUtils.nullSafeToString(valueProfiles), resolverClass.getName()); |
||||
} |
||||
else if (profilesDeclared && resolverDeclared) { |
||||
msg = String.format("Test class [%s] has been configured with @ActiveProfiles' 'profiles' [%s] " |
||||
+ "and 'resolver' [%s] attributes. Only one source of active bean " |
||||
+ "definition profiles is permitted per @ActiveProfiles annotation, " |
||||
+ "either declaritively or programmatically.", declaringClass.getName(), |
||||
ObjectUtils.nullSafeToString(profiles), resolverClass.getName()); |
||||
} |
||||
|
||||
if (msg != null) { |
||||
logger.error(msg); |
||||
throw new IllegalStateException(msg); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 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 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({ "unchecked" }) |
||||
static MergedContextConfiguration buildMergedContextConfiguration(Class<?> testClass, |
||||
String defaultContextLoaderClassName, CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate) { |
||||
|
||||
if (findAnnotationDescriptorForTypes(testClass, ContextConfiguration.class, ContextHierarchy.class) == null) { |
||||
if (logger.isInfoEnabled()) { |
||||
logger.info(String.format( |
||||
"Neither @ContextConfiguration nor @ContextHierarchy found for test class [%s]", |
||||
testClass.getName())); |
||||
} |
||||
return new MergedContextConfiguration(testClass, null, null, null, null); |
||||
} |
||||
|
||||
if (findAnnotation(testClass, ContextHierarchy.class) != null) { |
||||
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 |
||||
* @see SmartContextLoader#processContextConfiguration |
||||
* @see ContextLoader#processLocations |
||||
* @see #resolveActiveProfiles |
||||
* @see MergedContextConfiguration |
||||
*/ |
||||
private static MergedContextConfiguration buildMergedContextConfiguration(final Class<?> testClass, |
||||
final List<ContextConfigurationAttributes> configAttributesList, |
||||
final String defaultContextLoaderClassName, MergedContextConfiguration parentConfig, |
||||
CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate) { |
||||
|
||||
final ContextLoader contextLoader = resolveContextLoader(testClass, configAttributesList, |
||||
defaultContextLoaderClassName); |
||||
final List<String> locationsList = new ArrayList<String>(); |
||||
final List<Class<?>> classesList = new ArrayList<Class<?>>(); |
||||
|
||||
for (ContextConfigurationAttributes configAttributes : configAttributesList) { |
||||
if (logger.isTraceEnabled()) { |
||||
logger.trace(String.format("Processing locations and classes for context configuration attributes %s", |
||||
configAttributes)); |
||||
} |
||||
|
||||
if (contextLoader instanceof SmartContextLoader) { |
||||
SmartContextLoader smartContextLoader = (SmartContextLoader) contextLoader; |
||||
smartContextLoader.processContextConfiguration(configAttributes); |
||||
locationsList.addAll(0, Arrays.asList(configAttributes.getLocations())); |
||||
classesList.addAll(0, Arrays.asList(configAttributes.getClasses())); |
||||
} |
||||
else { |
||||
String[] processedLocations = contextLoader.processLocations(configAttributes.getDeclaringClass(), |
||||
configAttributes.getLocations()); |
||||
locationsList.addAll(0, Arrays.asList(processedLocations)); |
||||
// Legacy ContextLoaders don't know how to process classes
|
||||
} |
||||
|
||||
if (!configAttributes.isInheritLocations()) { |
||||
break; |
||||
} |
||||
} |
||||
|
||||
String[] locations = StringUtils.toStringArray(locationsList); |
||||
Class<?>[] classes = ClassUtils.toClassArray(classesList); |
||||
Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializerClasses = resolveInitializerClasses(configAttributesList); |
||||
String[] activeProfiles = resolveActiveProfiles(testClass); |
||||
|
||||
MergedContextConfiguration mergedConfig = buildWebMergedContextConfiguration(testClass, locations, classes, |
||||
initializerClasses, activeProfiles, contextLoader, cacheAwareContextLoaderDelegate, parentConfig); |
||||
|
||||
if (mergedConfig == null) { |
||||
mergedConfig = new MergedContextConfiguration(testClass, locations, classes, initializerClasses, |
||||
activeProfiles, contextLoader, cacheAwareContextLoaderDelegate, parentConfig); |
||||
} |
||||
|
||||
return mergedConfig; |
||||
} |
||||
|
||||
/** |
||||
* 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 |
||||
* @since 3.2 |
||||
*/ |
||||
@SuppressWarnings("unchecked") |
||||
private static Class<? extends Annotation> loadWebAppConfigurationClass() { |
||||
Class<? extends Annotation> webAppConfigClass = null; |
||||
try { |
||||
webAppConfigClass = (Class<? extends Annotation>) ClassUtils.forName(WEB_APP_CONFIGURATION_CLASS_NAME, |
||||
ContextLoaderUtils.class.getClassLoader()); |
||||
} |
||||
catch (Throwable t) { |
||||
if (logger.isDebugEnabled()) { |
||||
logger.debug("Could not load @WebAppConfiguration class [" + WEB_APP_CONFIGURATION_CLASS_NAME + "].", t); |
||||
} |
||||
} |
||||
return webAppConfigClass; |
||||
} |
||||
|
||||
/** |
||||
* 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 |
||||
* @since 3.2 |
||||
*/ |
||||
@SuppressWarnings("unchecked") |
||||
private static MergedContextConfiguration buildWebMergedContextConfiguration( |
||||
Class<?> testClass, |
||||
String[] locations, |
||||
Class<?>[] classes, |
||||
Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializerClasses, |
||||
String[] activeProfiles, ContextLoader contextLoader, |
||||
CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, MergedContextConfiguration parentConfig) { |
||||
|
||||
Class<? extends Annotation> webAppConfigClass = loadWebAppConfigurationClass(); |
||||
if (webAppConfigClass != null) { |
||||
|
||||
Annotation annotation = findAnnotation(testClass, webAppConfigClass); |
||||
if (annotation != null) { |
||||
|
||||
String resourceBasePath = (String) AnnotationUtils.getValue(annotation); |
||||
|
||||
try { |
||||
Class<? extends MergedContextConfiguration> webMergedConfigClass = (Class<? extends MergedContextConfiguration>) ClassUtils.forName( |
||||
WEB_MERGED_CONTEXT_CONFIGURATION_CLASS_NAME, ContextLoaderUtils.class.getClassLoader()); |
||||
|
||||
Constructor<? extends MergedContextConfiguration> constructor = ClassUtils.getConstructorIfAvailable( |
||||
webMergedConfigClass, Class.class, String[].class, Class[].class, Set.class, String[].class, |
||||
String.class, ContextLoader.class, CacheAwareContextLoaderDelegate.class, |
||||
MergedContextConfiguration.class); |
||||
|
||||
if (constructor != null) { |
||||
return instantiateClass(constructor, testClass, locations, classes, initializerClasses, |
||||
activeProfiles, resourceBasePath, contextLoader, cacheAwareContextLoaderDelegate, |
||||
parentConfig); |
||||
} |
||||
} |
||||
catch (Throwable t) { |
||||
if (logger.isDebugEnabled()) { |
||||
logger.debug("Could not instantiate [" + WEB_MERGED_CONTEXT_CONFIGURATION_CLASS_NAME + "].", t); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
return null; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,78 @@
@@ -0,0 +1,78 @@
|
||||
/* |
||||
* Copyright 2002-2014 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.springframework.core.style.ToStringCreator; |
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* Default implementation of the {@link BootstrapContext} interface. |
||||
* |
||||
* @author Sam Brannen |
||||
* @since 4.1 |
||||
*/ |
||||
class DefaultBootstrapContext implements BootstrapContext { |
||||
|
||||
private final Class<?> testClass; |
||||
private final CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate; |
||||
|
||||
|
||||
DefaultBootstrapContext(Class<?> testClass, CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate) { |
||||
Assert.notNull(testClass, "Test class must not be null"); |
||||
Assert.notNull(cacheAwareContextLoaderDelegate, "CacheAwareContextLoaderDelegate must not be null"); |
||||
this.testClass = testClass; |
||||
this.cacheAwareContextLoaderDelegate = cacheAwareContextLoaderDelegate; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritDoc} |
||||
*/ |
||||
@Override |
||||
public Class<?> getTestClass() { |
||||
return this.testClass; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritDoc} |
||||
*/ |
||||
@Override |
||||
public CacheAwareContextLoaderDelegate getCacheAwareContextLoaderDelegate() { |
||||
return this.cacheAwareContextLoaderDelegate; |
||||
} |
||||
|
||||
/** |
||||
* Provide a String representation of this bootstrap context's state. |
||||
*/ |
||||
@Override |
||||
public String toString() { |
||||
return new ToStringCreator(this)//
|
||||
.append("testClass", testClass)//
|
||||
.append("cacheAwareContextLoaderDelegate", nullSafeToString(cacheAwareContextLoaderDelegate))//
|
||||
.toString(); |
||||
} |
||||
|
||||
/** |
||||
* Generate a null-safe {@link String} representation of the supplied |
||||
* {@link CacheAwareContextLoaderDelegate} based solely on the fully qualified |
||||
* name of the delegate or "null" if the supplied delegate is |
||||
* {@code null}. |
||||
*/ |
||||
private static String nullSafeToString(CacheAwareContextLoaderDelegate delegate) { |
||||
return delegate == null ? "null" : delegate.getClass().getName(); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,112 @@
@@ -0,0 +1,112 @@
|
||||
/* |
||||
* Copyright 2002-2014 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.test.annotation.DirtiesContext.HierarchyMode; |
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* Default implementation of the {@link CacheAwareContextLoaderDelegate} interface. |
||||
* |
||||
* <p>Although {@code DefaultCacheAwareContextLoaderDelegate} was first introduced |
||||
* in Spring Framework 4.1, the initial implementation of this class was extracted |
||||
* from the existing code base for {@code CacheAwareContextLoaderDelegate} when |
||||
* {@code CacheAwareContextLoaderDelegate} was converted into an interface. |
||||
* |
||||
* @author Sam Brannen |
||||
* @since 4.1 |
||||
*/ |
||||
class DefaultCacheAwareContextLoaderDelegate implements CacheAwareContextLoaderDelegate { |
||||
|
||||
private static final Log logger = LogFactory.getLog(DefaultCacheAwareContextLoaderDelegate.class); |
||||
|
||||
private final ContextCache contextCache; |
||||
|
||||
|
||||
DefaultCacheAwareContextLoaderDelegate(ContextCache contextCache) { |
||||
Assert.notNull(contextCache, "ContextCache must not be null"); |
||||
this.contextCache = contextCache; |
||||
} |
||||
|
||||
/** |
||||
* Load the {@code ApplicationContext} for the supplied merged context configuration. |
||||
* <p>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; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritDoc} |
||||
*/ |
||||
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; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* {@inheritDoc} |
||||
*/ |
||||
@Override |
||||
public void closeContext(MergedContextConfiguration mergedContextConfiguration, HierarchyMode hierarchyMode) { |
||||
contextCache.remove(mergedContextConfiguration, hierarchyMode); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,105 @@
@@ -0,0 +1,105 @@
|
||||
/* |
||||
* Copyright 2002-2014 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.util.List; |
||||
|
||||
/** |
||||
* {@code TestContextBootstrapper} defines a strategy SPI for bootstrapping the |
||||
* <em>Spring TestContext Framework</em>. |
||||
* |
||||
* <p>A custom bootstrapping strategy can be configured for a test class via |
||||
* {@link BootstrapWith @BootstrapWith}, either directly or as a meta-annotation. |
||||
* See {@link org.springframework.test.context.web.WebAppConfiguration @WebAppConfiguration} |
||||
* for an example. |
||||
* |
||||
* <p>The {@link TestContextManager} uses a {@code TestContextBootstrapper} to |
||||
* {@linkplain #getTestExecutionListeners get the TestExecutionListeners} for the |
||||
* current test and to {@linkplain #buildMergedContextConfiguration build the |
||||
* merged context configuration} necessary to create the {@link TestContext} that |
||||
* it manages. |
||||
* |
||||
* <p>Concrete implementations must provide a {@code public} no-args constructor. |
||||
* |
||||
* <p><strong>Note</strong>: this SPI might potentially change in the future in |
||||
* order to accommodate new requirements. Implementers are therefore strongly encouraged |
||||
* <em>not</em> to implement this interface directly but rather to <em>extend</em> |
||||
* {@link org.springframework.test.context.support.AbstractTestContextBootstrapper |
||||
* AbstractTestContextBootstrapper} or one of its concrete subclasses instead. |
||||
* |
||||
* @author Sam Brannen |
||||
* @since 4.1 |
||||
* @see BootstrapWith |
||||
* @see BootstrapContext |
||||
*/ |
||||
public interface TestContextBootstrapper { |
||||
|
||||
/** |
||||
* Set the {@link BootstrapContext} to be used by this bootstrapper. |
||||
*/ |
||||
void setBootstrapContext(BootstrapContext bootstrapContext); |
||||
|
||||
/** |
||||
* Get the {@link BootstrapContext} associated with this bootstrapper. |
||||
*/ |
||||
BootstrapContext getBootstrapContext(); |
||||
|
||||
/** |
||||
* Get a list of newly instantiated {@link TestExecutionListener TestExecutionListeners} |
||||
* for the test class in the {@link BootstrapContext} associated with this bootstrapper. |
||||
* <p>If {@link TestExecutionListeners @TestExecutionListeners} is not |
||||
* <em>present</em> on the test class in the {@code BootstrapContext}, |
||||
* <em>default</em> listeners should be returned. Concrete implementations |
||||
* are free to determine what comprises the set of default listeners. |
||||
* <p>The {@link TestExecutionListeners#inheritListeners() inheritListeners} |
||||
* flag of {@link TestExecutionListeners @TestExecutionListeners} must be |
||||
* taken into consideration. Specifically, if the {@code inheritListeners} |
||||
* flag is set to {@code true}, listeners declared for a given test class must |
||||
* be appended to the end of the list of listeners declared in superclasses. |
||||
* @return a list of {@code TestExecutionListener} instances |
||||
*/ |
||||
List<TestExecutionListener> getTestExecutionListeners(); |
||||
|
||||
/** |
||||
* Build the {@linkplain MergedContextConfiguration merged context configuration} |
||||
* for the test class in the {@link BootstrapContext} associated with this |
||||
* bootstrapper. |
||||
* <p>Implementations must take the following into account when building the |
||||
* merged configuration: |
||||
* <ul> |
||||
* <li>Context hierarchies declared via {@link ContextHierarchy @ContextHierarchy} |
||||
* and {@link ContextConfiguration @ContextConfiguration}</li> |
||||
* <li>Active bean definition profiles declared via {@link ActiveProfiles @ActiveProfiles}</li> |
||||
* <li>{@linkplain org.springframework.context.ApplicationContextInitializer |
||||
* Context initializers} declared via {@link ContextConfiguration#initializers}</li> |
||||
* <p>Consult the Javadoc for the aforementioned annotations for details on |
||||
* the required semantics. |
||||
* <p>When determining which {@link ContextLoader} to use for a given test |
||||
* class, the following algorithm should be used: |
||||
* <ol> |
||||
* <li>If a {@code ContextLoader} class has been explicitly declared via |
||||
* {@link ContextConfiguration#loader}, use it.</li> |
||||
* <li>Otherwise, if the name of a {@linkplain BootstrapContext#getCustomDefaultContextLoaderClassName |
||||
* custom default ContextLoader class} has been provided in the {@link BootstrapContext}, |
||||
* use it.</li> |
||||
* <li>Otherwise, concrete implementations are free to determine which |
||||
* {@code ContextLoader} class to use as as default.</li> |
||||
* @return the merged context configuration, never {@code null} |
||||
*/ |
||||
MergedContextConfiguration buildMergedContextConfiguration(); |
||||
|
||||
} |
@ -0,0 +1,435 @@
@@ -0,0 +1,435 @@
|
||||
/* |
||||
* Copyright 2002-2014 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.test.context.support; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.Arrays; |
||||
import java.util.Collections; |
||||
import java.util.LinkedHashSet; |
||||
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.beans.BeanUtils; |
||||
import org.springframework.context.ApplicationContextInitializer; |
||||
import org.springframework.context.ConfigurableApplicationContext; |
||||
import org.springframework.core.annotation.AnnotationAttributes; |
||||
import org.springframework.core.annotation.AnnotationUtils; |
||||
import org.springframework.test.context.BootstrapContext; |
||||
import org.springframework.test.context.CacheAwareContextLoaderDelegate; |
||||
import org.springframework.test.context.ContextConfiguration; |
||||
import org.springframework.test.context.ContextConfigurationAttributes; |
||||
import org.springframework.test.context.ContextHierarchy; |
||||
import org.springframework.test.context.ContextLoader; |
||||
import org.springframework.test.context.MergedContextConfiguration; |
||||
import org.springframework.test.context.SmartContextLoader; |
||||
import org.springframework.test.context.TestContextBootstrapper; |
||||
import org.springframework.test.context.TestExecutionListener; |
||||
import org.springframework.test.context.TestExecutionListeners; |
||||
import org.springframework.test.util.MetaAnnotationUtils; |
||||
import org.springframework.test.util.MetaAnnotationUtils.AnnotationDescriptor; |
||||
import org.springframework.util.Assert; |
||||
import org.springframework.util.ClassUtils; |
||||
import org.springframework.util.ObjectUtils; |
||||
import org.springframework.util.StringUtils; |
||||
|
||||
/** |
||||
* Abstract implementation of the {@link TestContextBootstrapper} interface which |
||||
* provides most of the behavior required by a bootstrapper. |
||||
* |
||||
* <p>Concrete subclasses typically will only need to provide implementations for |
||||
* the following {@code abstract} methods: |
||||
* <ul> |
||||
* <li>{@link #getDefaultTestExecutionListenerClassNames()} |
||||
* <li>{@link #getDefaultContextLoaderClass(Class)} |
||||
* <li>{@link #buildMergedContextConfiguration(Class, String[], Class[], Set, String[], ContextLoader, CacheAwareContextLoaderDelegate, MergedContextConfiguration)} |
||||
* |
||||
* @author Sam Brannen |
||||
* @since 4.1 |
||||
*/ |
||||
public abstract class AbstractTestContextBootstrapper implements TestContextBootstrapper { |
||||
|
||||
private static final Log logger = LogFactory.getLog(AbstractTestContextBootstrapper.class); |
||||
|
||||
private BootstrapContext bootstrapContext; |
||||
|
||||
|
||||
/** |
||||
* {@inheritDoc} |
||||
*/ |
||||
@Override |
||||
public void setBootstrapContext(BootstrapContext bootstrapContext) { |
||||
this.bootstrapContext = bootstrapContext; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritDoc} |
||||
*/ |
||||
@Override |
||||
public BootstrapContext getBootstrapContext() { |
||||
return this.bootstrapContext; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritDoc} |
||||
*/ |
||||
@SuppressWarnings("unchecked") |
||||
@Override |
||||
public final List<TestExecutionListener> getTestExecutionListeners() { |
||||
Class<?> clazz = getBootstrapContext().getTestClass(); |
||||
Class<TestExecutionListeners> annotationType = TestExecutionListeners.class; |
||||
List<Class<? extends TestExecutionListener>> classesList = new ArrayList<Class<? extends TestExecutionListener>>(); |
||||
|
||||
AnnotationDescriptor<TestExecutionListeners> descriptor = MetaAnnotationUtils.findAnnotationDescriptor(clazz, |
||||
annotationType); |
||||
|
||||
// Use defaults?
|
||||
if (descriptor == null) { |
||||
if (logger.isDebugEnabled()) { |
||||
logger.debug("@TestExecutionListeners is not present for class [" + clazz + "]: using defaults."); |
||||
} |
||||
classesList.addAll(getDefaultTestExecutionListenerClasses()); |
||||
} |
||||
else { |
||||
// Traverse the class hierarchy...
|
||||
while (descriptor != null) { |
||||
Class<?> declaringClass = descriptor.getDeclaringClass(); |
||||
|
||||
AnnotationAttributes annAttrs = descriptor.getAnnotationAttributes(); |
||||
if (logger.isTraceEnabled()) { |
||||
logger.trace(String.format( |
||||
"Retrieved @TestExecutionListeners attributes [%s] for declaring class [%s].", annAttrs, |
||||
declaringClass)); |
||||
} |
||||
|
||||
Class<? extends TestExecutionListener>[] valueListenerClasses = (Class<? extends TestExecutionListener>[]) annAttrs.getClassArray("value"); |
||||
Class<? extends TestExecutionListener>[] listenerClasses = (Class<? extends TestExecutionListener>[]) annAttrs.getClassArray("listeners"); |
||||
if (!ObjectUtils.isEmpty(valueListenerClasses) && !ObjectUtils.isEmpty(listenerClasses)) { |
||||
String msg = String.format( |
||||
"Class [%s] has been configured with @TestExecutionListeners' 'value' [%s] " |
||||
+ "and 'listeners' [%s] attributes. Use one or the other, but not both.", |
||||
declaringClass, ObjectUtils.nullSafeToString(valueListenerClasses), |
||||
ObjectUtils.nullSafeToString(listenerClasses)); |
||||
logger.error(msg); |
||||
throw new IllegalStateException(msg); |
||||
} |
||||
else if (!ObjectUtils.isEmpty(valueListenerClasses)) { |
||||
listenerClasses = valueListenerClasses; |
||||
} |
||||
|
||||
if (listenerClasses != null) { |
||||
classesList.addAll(0, Arrays.<Class<? extends TestExecutionListener>> asList(listenerClasses)); |
||||
} |
||||
|
||||
descriptor = (annAttrs.getBoolean("inheritListeners") ? MetaAnnotationUtils.findAnnotationDescriptor( |
||||
descriptor.getRootDeclaringClass().getSuperclass(), annotationType) : null); |
||||
} |
||||
} |
||||
|
||||
List<TestExecutionListener> listeners = new ArrayList<TestExecutionListener>(classesList.size()); |
||||
for (Class<? extends TestExecutionListener> listenerClass : classesList) { |
||||
try { |
||||
listeners.add(BeanUtils.instantiateClass(listenerClass)); |
||||
} |
||||
catch (NoClassDefFoundError err) { |
||||
if (logger.isInfoEnabled()) { |
||||
logger.info(String.format("Could not instantiate TestExecutionListener [%s]. " |
||||
+ "Specify custom listener classes or make the default listener classes " |
||||
+ "(and their dependencies) available.", listenerClass.getName())); |
||||
} |
||||
} |
||||
} |
||||
return listeners; |
||||
} |
||||
|
||||
/** |
||||
* Get the default {@link TestExecutionListener} classes for this bootstrapper. |
||||
* <p>This method is invoked by {@link #getTestExecutionListeners()} and |
||||
* delegates to {@link #getDefaultTestExecutionListenerClassNames()} to |
||||
* retrieve the class names. |
||||
* <p>If a particular class cannot be loaded, a {@code DEBUG} message will |
||||
* be logged, but the associated exception will not be rethrown. |
||||
*/ |
||||
@SuppressWarnings("unchecked") |
||||
protected Set<Class<? extends TestExecutionListener>> getDefaultTestExecutionListenerClasses() { |
||||
Set<Class<? extends TestExecutionListener>> defaultListenerClasses = new LinkedHashSet<Class<? extends TestExecutionListener>>(); |
||||
for (String className : getDefaultTestExecutionListenerClassNames()) { |
||||
try { |
||||
defaultListenerClasses.add((Class<? extends TestExecutionListener>) getClass().getClassLoader().loadClass( |
||||
className)); |
||||
} |
||||
catch (Throwable t) { |
||||
if (logger.isDebugEnabled()) { |
||||
logger.debug("Could not load default TestExecutionListener class [" + className |
||||
+ "]. Specify custom listener classes or make the default listener classes available.", t); |
||||
} |
||||
} |
||||
} |
||||
return defaultListenerClasses; |
||||
} |
||||
|
||||
/** |
||||
* {@inheritDoc} |
||||
*/ |
||||
@SuppressWarnings({ "unchecked" }) |
||||
@Override |
||||
public final MergedContextConfiguration buildMergedContextConfiguration() { |
||||
Class<?> testClass = getBootstrapContext().getTestClass(); |
||||
CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate = getBootstrapContext().getCacheAwareContextLoaderDelegate(); |
||||
|
||||
if (MetaAnnotationUtils.findAnnotationDescriptorForTypes(testClass, ContextConfiguration.class, |
||||
ContextHierarchy.class) == null) { |
||||
if (logger.isInfoEnabled()) { |
||||
logger.info(String.format( |
||||
"Neither @ContextConfiguration nor @ContextHierarchy found for test class [%s]", |
||||
testClass.getName())); |
||||
} |
||||
return new MergedContextConfiguration(testClass, null, null, null, null); |
||||
} |
||||
|
||||
if (AnnotationUtils.findAnnotation(testClass, ContextHierarchy.class) != null) { |
||||
Map<String, List<ContextConfigurationAttributes>> hierarchyMap = ContextLoaderUtils.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, parentConfig, |
||||
cacheAwareContextLoaderDelegate); |
||||
parentConfig = mergedConfig; |
||||
} |
||||
|
||||
// Return the last level in the context hierarchy
|
||||
return mergedConfig; |
||||
} |
||||
else { |
||||
return buildMergedContextConfiguration(testClass, |
||||
ContextLoaderUtils.resolveContextConfigurationAttributes(testClass), null, |
||||
cacheAwareContextLoaderDelegate); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Build the {@link MergedContextConfiguration merged context configuration} |
||||
* for the supplied {@link Class testClass}, context configuration attributes, |
||||
* 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 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 ContextLoaderUtils#resolveContextConfigurationAttributes |
||||
* @see SmartContextLoader#processContextConfiguration |
||||
* @see ContextLoader#processLocations |
||||
* @see ActiveProfilesUtils#resolveActiveProfiles |
||||
* @see ApplicationContextInitializerUtils#resolveInitializerClasses |
||||
* @see MergedContextConfiguration |
||||
*/ |
||||
private MergedContextConfiguration buildMergedContextConfiguration(final Class<?> testClass, |
||||
final List<ContextConfigurationAttributes> configAttributesList, MergedContextConfiguration parentConfig, |
||||
CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate) { |
||||
|
||||
final ContextLoader contextLoader = resolveContextLoader(testClass, configAttributesList); |
||||
final List<String> locationsList = new ArrayList<String>(); |
||||
final List<Class<?>> classesList = new ArrayList<Class<?>>(); |
||||
|
||||
for (ContextConfigurationAttributes configAttributes : configAttributesList) { |
||||
if (logger.isTraceEnabled()) { |
||||
logger.trace(String.format("Processing locations and classes for context configuration attributes %s", |
||||
configAttributes)); |
||||
} |
||||
|
||||
if (contextLoader instanceof SmartContextLoader) { |
||||
SmartContextLoader smartContextLoader = (SmartContextLoader) contextLoader; |
||||
smartContextLoader.processContextConfiguration(configAttributes); |
||||
locationsList.addAll(0, Arrays.asList(configAttributes.getLocations())); |
||||
classesList.addAll(0, Arrays.asList(configAttributes.getClasses())); |
||||
} |
||||
else { |
||||
String[] processedLocations = contextLoader.processLocations(configAttributes.getDeclaringClass(), |
||||
configAttributes.getLocations()); |
||||
locationsList.addAll(0, Arrays.asList(processedLocations)); |
||||
// Legacy ContextLoaders don't know how to process classes
|
||||
} |
||||
|
||||
if (!configAttributes.isInheritLocations()) { |
||||
break; |
||||
} |
||||
} |
||||
|
||||
String[] locations = StringUtils.toStringArray(locationsList); |
||||
Class<?>[] classes = ClassUtils.toClassArray(classesList); |
||||
Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializerClasses = //
|
||||
ApplicationContextInitializerUtils.resolveInitializerClasses(configAttributesList); |
||||
String[] activeProfiles = ActiveProfilesUtils.resolveActiveProfiles(testClass); |
||||
|
||||
return buildMergedContextConfiguration(testClass, locations, classes, initializerClasses, activeProfiles, |
||||
contextLoader, cacheAwareContextLoaderDelegate, parentConfig); |
||||
} |
||||
|
||||
/** |
||||
* 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 user has not explicitly declared which loader to use, the value |
||||
* returned from {@link #getDefaultContextLoaderClass} will be used as the |
||||
* default context loader class. For details on the class resolution process, |
||||
* see {@link #resolveExplicitContextLoaderClass} and |
||||
* {@link #getDefaultContextLoaderClass}. |
||||
* |
||||
* @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) |
||||
* @return the resolved {@code ContextLoader} for the supplied {@code testClass} |
||||
* (never {@code null}) |
||||
*/ |
||||
private ContextLoader resolveContextLoader(Class<?> testClass, |
||||
List<ContextConfigurationAttributes> configAttributesList) { |
||||
Assert.notNull(testClass, "Class must not be null"); |
||||
Assert.notEmpty(configAttributesList, "ContextConfigurationAttributes list must not be empty"); |
||||
|
||||
Class<? extends ContextLoader> contextLoaderClass = resolveExplicitContextLoaderClass(configAttributesList); |
||||
if (contextLoaderClass == null) { |
||||
contextLoaderClass = getDefaultContextLoaderClass(testClass); |
||||
} |
||||
|
||||
if (logger.isTraceEnabled()) { |
||||
logger.trace(String.format("Using ContextLoader class [%s] for test class [%s]", |
||||
contextLoaderClass.getName(), testClass.getName())); |
||||
} |
||||
|
||||
return BeanUtils.instantiateClass(contextLoaderClass, ContextLoader.class); |
||||
} |
||||
|
||||
/** |
||||
* 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: |
||||
* |
||||
* <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> |
||||
* </ol> |
||||
* |
||||
* @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 {@code ContextLoader} class to use for the supplied configuration |
||||
* attributes, or {@code null} if no explicit loader is found |
||||
* @throws IllegalArgumentException if supplied configuration attributes are |
||||
* {@code null} or <em>empty</em> |
||||
*/ |
||||
private Class<? extends ContextLoader> resolveExplicitContextLoaderClass( |
||||
List<ContextConfigurationAttributes> configAttributesList) { |
||||
Assert.notEmpty(configAttributesList, "ContextConfigurationAttributes list must not be empty"); |
||||
|
||||
for (ContextConfigurationAttributes configAttributes : configAttributesList) { |
||||
if (logger.isTraceEnabled()) { |
||||
logger.trace(String.format("Resolving ContextLoader for context configuration attributes %s", |
||||
configAttributes)); |
||||
} |
||||
|
||||
Class<? extends ContextLoader> contextLoaderClass = configAttributes.getContextLoaderClass(); |
||||
if (!ContextLoader.class.equals(contextLoaderClass)) { |
||||
if (logger.isDebugEnabled()) { |
||||
logger.debug(String.format( |
||||
"Found explicit ContextLoader class [%s] for context configuration attributes %s", |
||||
contextLoaderClass.getName(), configAttributes)); |
||||
} |
||||
return contextLoaderClass; |
||||
} |
||||
} |
||||
|
||||
return null; |
||||
} |
||||
|
||||
/** |
||||
* Get the names of the default {@link TestExecutionListener} classes for |
||||
* this bootstrapper. |
||||
* <p>This method is invoked by {@link #getDefaultTestExecutionListenerClasses()}. |
||||
* @return an <em>unmodifiable</em> list of names of default {@code |
||||
* TestExecutionListener} classes |
||||
*/ |
||||
protected abstract List<String> getDefaultTestExecutionListenerClassNames(); |
||||
|
||||
/** |
||||
* Determine the default {@link ContextLoader} class to use for the supplied |
||||
* test class. |
||||
* <p>The class returned by this method will only be used if a {@code ContextLoader} |
||||
* class has not been explicitly declared via {@link ContextConfiguration#loader}. |
||||
* @param testClass the test class for which to retrieve the default |
||||
* {@code ContextLoader} class |
||||
*/ |
||||
protected abstract Class<? extends ContextLoader> getDefaultContextLoaderClass(Class<?> testClass); |
||||
|
||||
/** |
||||
* Build a {@link MergedContextConfiguration} instance from the supplied, |
||||
* merged values. |
||||
* |
||||
* <p>Concrete subclasses typically will only need to instantiate |
||||
* {@link MergedContextConfiguration} (or a specialized subclass thereof) |
||||
* from the provided values; further processing and merging of values is likely |
||||
* unnecessary. |
||||
* |
||||
* @param testClass the test class for which the {@code MergedContextConfiguration} |
||||
* should be built (must not be {@code null}) |
||||
* @param locations the merged resource locations |
||||
* @param classes the merged annotated classes |
||||
* @param initializerClasses the merged context initializer classes |
||||
* @param activeProfiles the merged active bean definition profiles |
||||
* @param contextLoader the resolved {@code ContextLoader} |
||||
* @param cacheAwareContextLoaderDelegate the cache-aware context loader delegate to |
||||
* be provided to the instantiated {@code MergedContextConfiguration} |
||||
* @param parentConfig the merged context configuration for the parent application |
||||
* context in a context hierarchy, or {@code null} if there is no parent |
||||
* @return the fully initialized {@code MergedContextConfiguration} |
||||
*/ |
||||
protected abstract MergedContextConfiguration buildMergedContextConfiguration( |
||||
Class<?> testClass, |
||||
String[] locations, |
||||
Class<?>[] classes, |
||||
Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializerClasses, |
||||
String[] activeProfiles, ContextLoader contextLoader, |
||||
CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, MergedContextConfiguration parentConfig); |
||||
|
||||
} |
@ -0,0 +1,152 @@
@@ -0,0 +1,152 @@
|
||||
/* |
||||
* Copyright 2002-2014 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.test.context.support; |
||||
|
||||
import java.util.HashSet; |
||||
import java.util.Set; |
||||
|
||||
import org.apache.commons.logging.Log; |
||||
import org.apache.commons.logging.LogFactory; |
||||
import org.springframework.beans.BeanUtils; |
||||
import org.springframework.core.annotation.AnnotationAttributes; |
||||
import org.springframework.test.context.ActiveProfiles; |
||||
import org.springframework.test.context.ActiveProfilesResolver; |
||||
import org.springframework.test.util.MetaAnnotationUtils; |
||||
import org.springframework.test.util.MetaAnnotationUtils.AnnotationDescriptor; |
||||
import org.springframework.util.Assert; |
||||
import org.springframework.util.ObjectUtils; |
||||
import org.springframework.util.StringUtils; |
||||
|
||||
/** |
||||
* Utility methods for working with {@link ActiveProfiles @ActiveProfiles} and |
||||
* {@link ActiveProfilesResolver ActiveProfilesResolvers}. |
||||
* |
||||
* <p>Although {@code ActiveProfilesUtils} was first introduced in Spring Framework |
||||
* 4.1, the initial implementations of methods in this class were based on the |
||||
* existing code base in {@code ContextLoaderUtils}. |
||||
* |
||||
* @author Sam Brannen |
||||
* @author Michail Nikolaev |
||||
* @since 4.1 |
||||
* @see ActiveProfiles |
||||
* @see ActiveProfilesResolver |
||||
*/ |
||||
abstract class ActiveProfilesUtils { |
||||
|
||||
private static final Log logger = LogFactory.getLog(ActiveProfilesUtils.class); |
||||
|
||||
|
||||
private ActiveProfilesUtils() { |
||||
/* no-op */ |
||||
} |
||||
|
||||
/** |
||||
* Resolve <em>active bean definition profiles</em> for the supplied {@link Class}. |
||||
* |
||||
* <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}) |
||||
* @see ActiveProfiles |
||||
* @see ActiveProfilesResolver |
||||
* @see org.springframework.context.annotation.Profile |
||||
*/ |
||||
static String[] resolveActiveProfiles(Class<?> testClass) { |
||||
Assert.notNull(testClass, "Class must not be null"); |
||||
|
||||
final Set<String> activeProfiles = new HashSet<String>(); |
||||
|
||||
Class<ActiveProfiles> annotationType = ActiveProfiles.class; |
||||
AnnotationDescriptor<ActiveProfiles> descriptor = MetaAnnotationUtils.findAnnotationDescriptor(testClass, |
||||
annotationType); |
||||
if (descriptor == null && logger.isDebugEnabled()) { |
||||
logger.debug(String.format( |
||||
"Could not find an 'annotation declaring class' for annotation type [%s] and class [%s]", |
||||
annotationType.getName(), testClass.getName())); |
||||
} |
||||
|
||||
while (descriptor != null) { |
||||
Class<?> rootDeclaringClass = descriptor.getRootDeclaringClass(); |
||||
Class<?> declaringClass = descriptor.getDeclaringClass(); |
||||
|
||||
AnnotationAttributes annAttrs = descriptor.getAnnotationAttributes(); |
||||
if (logger.isTraceEnabled()) { |
||||
logger.trace(String.format("Retrieved @ActiveProfiles attributes [%s] for declaring class [%s].", |
||||
annAttrs, declaringClass.getName())); |
||||
} |
||||
validateActiveProfilesConfiguration(declaringClass, annAttrs); |
||||
|
||||
Class<? extends ActiveProfilesResolver> resolverClass = annAttrs.getClass("resolver"); |
||||
if (ActiveProfilesResolver.class.equals(resolverClass)) { |
||||
resolverClass = DefaultActiveProfilesResolver.class; |
||||
} |
||||
|
||||
ActiveProfilesResolver resolver = null; |
||||
try { |
||||
resolver = BeanUtils.instantiateClass(resolverClass, ActiveProfilesResolver.class); |
||||
} |
||||
catch (Exception e) { |
||||
String msg = String.format("Could not instantiate ActiveProfilesResolver of " |
||||
+ "type [%s] for test class [%s].", resolverClass.getName(), rootDeclaringClass.getName()); |
||||
logger.error(msg); |
||||
throw new IllegalStateException(msg, e); |
||||
} |
||||
|
||||
String[] profiles = resolver.resolve(rootDeclaringClass); |
||||
if (profiles == null) { |
||||
String msg = String.format( |
||||
"ActiveProfilesResolver [%s] returned a null array of bean definition profiles.", |
||||
resolverClass.getName()); |
||||
logger.error(msg); |
||||
throw new IllegalStateException(msg); |
||||
} |
||||
|
||||
for (String profile : profiles) { |
||||
if (StringUtils.hasText(profile)) { |
||||
activeProfiles.add(profile.trim()); |
||||
} |
||||
} |
||||
|
||||
descriptor = annAttrs.getBoolean("inheritProfiles") ? MetaAnnotationUtils.findAnnotationDescriptor( |
||||
rootDeclaringClass.getSuperclass(), annotationType) : null; |
||||
} |
||||
|
||||
return StringUtils.toStringArray(activeProfiles); |
||||
} |
||||
|
||||
private static void validateActiveProfilesConfiguration(Class<?> declaringClass, AnnotationAttributes annAttrs) { |
||||
String[] valueProfiles = annAttrs.getStringArray("value"); |
||||
String[] profiles = annAttrs.getStringArray("profiles"); |
||||
boolean valueDeclared = !ObjectUtils.isEmpty(valueProfiles); |
||||
boolean profilesDeclared = !ObjectUtils.isEmpty(profiles); |
||||
|
||||
if (valueDeclared && profilesDeclared) { |
||||
String msg = String.format("Class [%s] has been configured with @ActiveProfiles' 'value' [%s] " |
||||
+ "and 'profiles' [%s] attributes. Only one declaration of active bean " |
||||
+ "definition profiles is permitted per @ActiveProfiles annotation.", declaringClass.getName(), |
||||
ObjectUtils.nullSafeToString(valueProfiles), ObjectUtils.nullSafeToString(profiles)); |
||||
logger.error(msg); |
||||
throw new IllegalStateException(msg); |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,94 @@
@@ -0,0 +1,94 @@
|
||||
/* |
||||
* Copyright 2002-2014 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.test.context.support; |
||||
|
||||
import java.util.Arrays; |
||||
import java.util.HashSet; |
||||
import java.util.List; |
||||
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.test.context.ContextConfiguration; |
||||
import org.springframework.test.context.ContextConfigurationAttributes; |
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* Utility methods for working with {@link ApplicationContextInitializer |
||||
* ApplicationContextInitializers}. |
||||
* |
||||
* <p>Although {@code ApplicationContextInitializerUtils} was first introduced |
||||
* in Spring Framework 4.1, the initial implementations of methods in this class |
||||
* were based on the existing code base in {@code ContextLoaderUtils}. |
||||
* |
||||
* @author Sam Brannen |
||||
* @since 4.1 |
||||
* @see ContextConfiguration#initializers |
||||
*/ |
||||
abstract class ApplicationContextInitializerUtils { |
||||
|
||||
private static final Log logger = LogFactory.getLog(ApplicationContextInitializerUtils.class); |
||||
|
||||
|
||||
private ApplicationContextInitializerUtils() { |
||||
/* no-op */ |
||||
} |
||||
|
||||
/** |
||||
* Resolve the set 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. |
||||
* |
||||
* @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}) |
||||
* @since 3.2 |
||||
*/ |
||||
static Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> resolveInitializerClasses( |
||||
List<ContextConfigurationAttributes> configAttributesList) { |
||||
Assert.notEmpty(configAttributesList, "ContextConfigurationAttributes list must not be empty"); |
||||
|
||||
final Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializerClasses = //
|
||||
new HashSet<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>>(); |
||||
|
||||
for (ContextConfigurationAttributes configAttributes : configAttributesList) { |
||||
if (logger.isTraceEnabled()) { |
||||
logger.trace(String.format("Processing context initializers for context configuration attributes %s", |
||||
configAttributes)); |
||||
} |
||||
|
||||
initializerClasses.addAll(Arrays.asList(configAttributes.getInitializers())); |
||||
|
||||
if (!configAttributes.isInheritInitializers()) { |
||||
break; |
||||
} |
||||
} |
||||
|
||||
return initializerClasses; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,311 @@
@@ -0,0 +1,311 @@
|
||||
/* |
||||
* Copyright 2002-2014 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.test.context.support; |
||||
|
||||
import java.util.ArrayList; |
||||
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.core.annotation.AnnotationAttributes; |
||||
import org.springframework.test.context.ContextConfiguration; |
||||
import org.springframework.test.context.ContextConfigurationAttributes; |
||||
import org.springframework.test.context.ContextHierarchy; |
||||
import org.springframework.test.context.ContextLoader; |
||||
import org.springframework.test.context.SmartContextLoader; |
||||
import org.springframework.test.util.MetaAnnotationUtils.AnnotationDescriptor; |
||||
import org.springframework.test.util.MetaAnnotationUtils.UntypedAnnotationDescriptor; |
||||
import org.springframework.util.Assert; |
||||
import org.springframework.util.StringUtils; |
||||
|
||||
import static org.springframework.core.annotation.AnnotationUtils.*; |
||||
import static org.springframework.test.util.MetaAnnotationUtils.*; |
||||
|
||||
/** |
||||
* Utility methods for working with {@link ContextLoader ContextLoaders} and |
||||
* {@link SmartContextLoader SmartContextLoaders} and resolving resource locations, |
||||
* annotated classes, and application context initializers. |
||||
* |
||||
* @author Sam Brannen |
||||
* @since 3.1 |
||||
* @see ContextLoader |
||||
* @see SmartContextLoader |
||||
* @see ContextConfiguration |
||||
* @see ContextConfigurationAttributes |
||||
* @see ApplicationContextInitializer |
||||
* @see ContextHierarchy |
||||
*/ |
||||
abstract class ContextLoaderUtils { |
||||
|
||||
static final String GENERATED_CONTEXT_HIERARCHY_LEVEL_PREFIX = "ContextHierarchyLevel#"; |
||||
|
||||
private static final Log logger = LogFactory.getLog(ContextLoaderUtils.class); |
||||
|
||||
|
||||
private ContextLoaderUtils() { |
||||
/* no-op */ |
||||
} |
||||
|
||||
/** |
||||
* Convenience method for creating a {@link ContextConfigurationAttributes} |
||||
* instance from the supplied {@link ContextConfiguration} annotation 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); |
||||
} |
||||
|
||||
/** |
||||
* Convenience method for creating a {@link ContextConfigurationAttributes} |
||||
* instance from the supplied {@link AnnotationAttributes} and declaring |
||||
* class and then adding the attributes to the supplied list. |
||||
* @since 4.0 |
||||
*/ |
||||
private static void convertAnnotationAttributesToConfigAttributesAndAddToList(AnnotationAttributes annAttrs, |
||||
Class<?> declaringClass, final List<ContextConfigurationAttributes> attributesList) { |
||||
if (logger.isTraceEnabled()) { |
||||
logger.trace(String.format("Retrieved @ContextConfiguration attributes [%s] for declaring class [%s].", |
||||
annAttrs, declaringClass.getName())); |
||||
} |
||||
|
||||
ContextConfigurationAttributes attributes = new ContextConfigurationAttributes(declaringClass, annAttrs); |
||||
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>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; or if a test class or composed annotation |
||||
* in the class hierarchy declares both {@code @ContextConfiguration} and |
||||
* {@code @ContextHierarchy} as top-level annotations. |
||||
* @throws IllegalStateException if no class in the class hierarchy declares |
||||
* {@code @ContextHierarchy}. |
||||
* |
||||
* @since 3.2.2 |
||||
* @see #buildContextHierarchyMap(Class) |
||||
* @see #resolveContextConfigurationAttributes(Class) |
||||
*/ |
||||
@SuppressWarnings("unchecked") |
||||
static List<List<ContextConfigurationAttributes>> resolveContextHierarchyAttributes(Class<?> testClass) { |
||||
Assert.notNull(testClass, "Class must not be null"); |
||||
Assert.state(findAnnotation(testClass, ContextHierarchy.class) != null, "@ContextHierarchy must be present"); |
||||
|
||||
final Class<ContextConfiguration> contextConfigType = ContextConfiguration.class; |
||||
final Class<ContextHierarchy> contextHierarchyType = ContextHierarchy.class; |
||||
final List<List<ContextConfigurationAttributes>> hierarchyAttributes = new ArrayList<List<ContextConfigurationAttributes>>(); |
||||
|
||||
UntypedAnnotationDescriptor descriptor = findAnnotationDescriptorForTypes(testClass, contextConfigType, |
||||
contextHierarchyType); |
||||
Assert.notNull(descriptor, 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 (descriptor != null) { |
||||
Class<?> rootDeclaringClass = descriptor.getRootDeclaringClass(); |
||||
Class<?> declaringClass = descriptor.getDeclaringClass(); |
||||
|
||||
boolean contextConfigDeclaredLocally = isAnnotationDeclaredLocally(contextConfigType, declaringClass); |
||||
boolean contextHierarchyDeclaredLocally = isAnnotationDeclaredLocally(contextHierarchyType, declaringClass); |
||||
|
||||
if (contextConfigDeclaredLocally && contextHierarchyDeclaredLocally) { |
||||
String msg = String.format("Class [%s] has been configured with both @ContextConfiguration " |
||||
+ "and @ContextHierarchy. Only one of these annotations may be declared on a test class " |
||||
+ "or composed annotation.", declaringClass.getName()); |
||||
logger.error(msg); |
||||
throw new IllegalStateException(msg); |
||||
} |
||||
|
||||
final List<ContextConfigurationAttributes> configAttributesList = new ArrayList<ContextConfigurationAttributes>(); |
||||
|
||||
if (contextConfigDeclaredLocally) { |
||||
convertAnnotationAttributesToConfigAttributesAndAddToList(descriptor.getAnnotationAttributes(), |
||||
rootDeclaringClass, configAttributesList); |
||||
} |
||||
else if (contextHierarchyDeclaredLocally) { |
||||
ContextHierarchy contextHierarchy = getAnnotation(declaringClass, contextHierarchyType); |
||||
for (ContextConfiguration contextConfiguration : contextHierarchy.value()) { |
||||
convertContextConfigToConfigAttributesAndAddToList(contextConfiguration, rootDeclaringClass, |
||||
configAttributesList); |
||||
} |
||||
} |
||||
else { |
||||
// This should theoretically never happen...
|
||||
String msg = String.format("Test class [%s] has been configured with neither @ContextConfiguration " |
||||
+ "nor @ContextHierarchy as a class-level annotation.", rootDeclaringClass.getName()); |
||||
logger.error(msg); |
||||
throw new IllegalStateException(msg); |
||||
} |
||||
|
||||
hierarchyAttributes.add(0, configAttributesList); |
||||
|
||||
descriptor = findAnnotationDescriptorForTypes(rootDeclaringClass.getSuperclass(), contextConfigType, |
||||
contextHierarchyType); |
||||
} |
||||
|
||||
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} |
||||
* @throws IllegalArgumentException if the lists of context configuration |
||||
* attributes for each level in the {@code @ContextHierarchy} do not define |
||||
* unique context configuration within the overall hierarchy. |
||||
* |
||||
* @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); |
||||
} |
||||
} |
||||
|
||||
// Check for uniqueness
|
||||
Set<List<ContextConfigurationAttributes>> set = new HashSet<List<ContextConfigurationAttributes>>(map.values()); |
||||
if (set.size() != map.size()) { |
||||
String msg = String.format("The @ContextConfiguration elements configured via " |
||||
+ "@ContextHierarchy in test class [%s] and its superclasses must " |
||||
+ "define unique contexts per hierarchy level.", testClass.getName()); |
||||
logger.error(msg); |
||||
throw new IllegalStateException(msg); |
||||
} |
||||
|
||||
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 |
||||
*/ |
||||
static List<ContextConfigurationAttributes> resolveContextConfigurationAttributes(Class<?> testClass) { |
||||
Assert.notNull(testClass, "Class must not be null"); |
||||
|
||||
final List<ContextConfigurationAttributes> attributesList = new ArrayList<ContextConfigurationAttributes>(); |
||||
|
||||
Class<ContextConfiguration> annotationType = ContextConfiguration.class; |
||||
|
||||
AnnotationDescriptor<ContextConfiguration> descriptor = findAnnotationDescriptor(testClass, annotationType); |
||||
Assert.notNull(descriptor, String.format( |
||||
"Could not find an 'annotation declaring class' for annotation type [%s] and class [%s]", |
||||
annotationType.getName(), testClass.getName())); |
||||
|
||||
while (descriptor != null) { |
||||
convertAnnotationAttributesToConfigAttributesAndAddToList(descriptor.getAnnotationAttributes(), |
||||
descriptor.getRootDeclaringClass(), attributesList); |
||||
descriptor = findAnnotationDescriptor(descriptor.getRootDeclaringClass().getSuperclass(), annotationType); |
||||
} |
||||
|
||||
return attributesList; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,122 @@
@@ -0,0 +1,122 @@
|
||||
/* |
||||
* Copyright 2002-2014 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.test.context.support; |
||||
|
||||
import java.util.HashSet; |
||||
import java.util.Set; |
||||
|
||||
import org.apache.commons.logging.Log; |
||||
import org.apache.commons.logging.LogFactory; |
||||
import org.springframework.core.annotation.AnnotationAttributes; |
||||
import org.springframework.test.context.ActiveProfiles; |
||||
import org.springframework.test.context.ActiveProfilesResolver; |
||||
import org.springframework.test.util.MetaAnnotationUtils.AnnotationDescriptor; |
||||
import org.springframework.util.Assert; |
||||
import org.springframework.util.ObjectUtils; |
||||
import org.springframework.util.StringUtils; |
||||
|
||||
import static org.springframework.test.util.MetaAnnotationUtils.*; |
||||
|
||||
/** |
||||
* Default implementation of the {@link ActiveProfilesResolver} strategy that |
||||
* resolves <em>active bean definition profiles</em> based solely on profiles |
||||
* configured declaratively via {@link ActiveProfiles#profiles} or |
||||
* {@link ActiveProfiles#value}. |
||||
* |
||||
* @author Sam Brannen |
||||
* @since 4.1 |
||||
* @see ActiveProfiles |
||||
* @see ActiveProfilesResolver |
||||
*/ |
||||
public class DefaultActiveProfilesResolver implements ActiveProfilesResolver { |
||||
|
||||
private static final Log logger = LogFactory.getLog(DefaultActiveProfilesResolver.class); |
||||
|
||||
|
||||
/** |
||||
* Resolve the <em>bean definition profiles</em> for the given {@linkplain |
||||
* Class test class} based on profiles configured declaratively via |
||||
* {@link ActiveProfiles#profiles} or {@link ActiveProfiles#value}. |
||||
* @param testClass the test class for which the profiles should be resolved; |
||||
* never {@code null} |
||||
* @return the list of bean definition profiles to use when loading the |
||||
* {@code ApplicationContext}; never {@code null} |
||||
*/ |
||||
@Override |
||||
public String[] resolve(Class<?> testClass) { |
||||
Assert.notNull(testClass, "Class must not be null"); |
||||
|
||||
final Set<String> activeProfiles = new HashSet<String>(); |
||||
|
||||
Class<ActiveProfiles> annotationType = ActiveProfiles.class; |
||||
AnnotationDescriptor<ActiveProfiles> descriptor = findAnnotationDescriptor(testClass, annotationType); |
||||
|
||||
if (descriptor == null) { |
||||
if (logger.isDebugEnabled()) { |
||||
logger.debug(String.format( |
||||
"Could not find an 'annotation declaring class' for annotation type [%s] and class [%s]", |
||||
annotationType.getName(), testClass.getName())); |
||||
} |
||||
} |
||||
else { |
||||
Class<?> rootDeclaringClass = descriptor.getRootDeclaringClass(); |
||||
Class<?> declaringClass = descriptor.getDeclaringClass(); |
||||
|
||||
AnnotationAttributes annAttrs = descriptor.getAnnotationAttributes(); |
||||
if (logger.isTraceEnabled()) { |
||||
logger.trace(String.format("Retrieved @ActiveProfiles attributes [%s] for declaring class [%s].", |
||||
annAttrs, declaringClass.getName())); |
||||
} |
||||
|
||||
Class<? extends ActiveProfilesResolver> resolverClass = annAttrs.getClass("resolver"); |
||||
if (!ActiveProfilesResolver.class.equals(resolverClass)) { |
||||
String msg = String.format("Configuration error for test class [%s]: %s cannot be used " |
||||
+ "in conjunction with custom resolver [%s].", rootDeclaringClass.getName(), |
||||
getClass().getSimpleName(), resolverClass.getName()); |
||||
logger.error(msg); |
||||
throw new IllegalStateException(msg); |
||||
} |
||||
|
||||
String[] profiles = annAttrs.getStringArray("profiles"); |
||||
String[] valueProfiles = annAttrs.getStringArray("value"); |
||||
boolean valueDeclared = !ObjectUtils.isEmpty(valueProfiles); |
||||
boolean profilesDeclared = !ObjectUtils.isEmpty(profiles); |
||||
|
||||
if (valueDeclared && profilesDeclared) { |
||||
String msg = String.format("Class [%s] has been configured with @ActiveProfiles' 'value' [%s] " |
||||
+ "and 'profiles' [%s] attributes. Only one declaration of active bean " |
||||
+ "definition profiles is permitted per @ActiveProfiles annotation.", declaringClass.getName(), |
||||
ObjectUtils.nullSafeToString(valueProfiles), ObjectUtils.nullSafeToString(profiles)); |
||||
logger.error(msg); |
||||
throw new IllegalStateException(msg); |
||||
} |
||||
|
||||
if (valueDeclared) { |
||||
profiles = valueProfiles; |
||||
} |
||||
|
||||
for (String profile : profiles) { |
||||
if (StringUtils.hasText(profile)) { |
||||
activeProfiles.add(profile.trim()); |
||||
} |
||||
} |
||||
} |
||||
|
||||
return StringUtils.toStringArray(activeProfiles); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,93 @@
@@ -0,0 +1,93 @@
|
||||
/* |
||||
* Copyright 2002-2014 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.test.context.support; |
||||
|
||||
import java.util.Arrays; |
||||
import java.util.Collections; |
||||
import java.util.List; |
||||
import java.util.Set; |
||||
|
||||
import org.springframework.context.ApplicationContextInitializer; |
||||
import org.springframework.context.ConfigurableApplicationContext; |
||||
import org.springframework.test.context.CacheAwareContextLoaderDelegate; |
||||
import org.springframework.test.context.ContextLoader; |
||||
import org.springframework.test.context.MergedContextConfiguration; |
||||
import org.springframework.test.context.TestContextBootstrapper; |
||||
import org.springframework.test.context.TestExecutionListener; |
||||
|
||||
/** |
||||
* Default implementation of the {@link TestContextBootstrapper} SPI. |
||||
* |
||||
* <ul> |
||||
* <li>Uses the following default {@link TestExecutionListener TestExecutionListeners}: |
||||
* <ol> |
||||
* <li>{@link org.springframework.test.context.support.DependencyInjectionTestExecutionListener} |
||||
* <li>{@link org.springframework.test.context.support.DirtiesContextTestExecutionListener} |
||||
* <li>{@link org.springframework.test.context.transaction.TransactionalTestExecutionListener} |
||||
* </ol> |
||||
* <li>Uses {@link DelegatingSmartContextLoader} as the default {@link ContextLoader}. |
||||
* <li>Builds a standard {@link MergedContextConfiguration}. |
||||
* </ul> |
||||
* |
||||
* @author Sam Brannen |
||||
* @since 4.1 |
||||
*/ |
||||
public class DefaultTestContextBootstrapper extends AbstractTestContextBootstrapper { |
||||
|
||||
private static final List<String> DEFAULT_TEST_EXECUTION_LISTENER_CLASS_NAMES = Collections.unmodifiableList(Arrays.asList( |
||||
"org.springframework.test.context.support.DependencyInjectionTestExecutionListener", |
||||
"org.springframework.test.context.support.DirtiesContextTestExecutionListener", |
||||
"org.springframework.test.context.transaction.TransactionalTestExecutionListener")); |
||||
|
||||
|
||||
/** |
||||
* Returns an unmodifiable list of fully qualified class names for the following |
||||
* default {@link TestExecutionListener TestExecutionListeners}: |
||||
* <ol> |
||||
* <li>{@link org.springframework.test.context.support.DependencyInjectionTestExecutionListener} |
||||
* <li>{@link org.springframework.test.context.support.DirtiesContextTestExecutionListener} |
||||
* <li>{@link org.springframework.test.context.transaction.TransactionalTestExecutionListener} |
||||
* </ol> |
||||
*/ |
||||
protected List<String> getDefaultTestExecutionListenerClassNames() { |
||||
return DEFAULT_TEST_EXECUTION_LISTENER_CLASS_NAMES; |
||||
} |
||||
|
||||
/** |
||||
* Returns {@link DelegatingSmartContextLoader}. |
||||
*/ |
||||
@Override |
||||
protected Class<? extends ContextLoader> getDefaultContextLoaderClass(Class<?> testClass) { |
||||
return DelegatingSmartContextLoader.class; |
||||
} |
||||
|
||||
/** |
||||
* Builds a standard {@link MergedContextConfiguration}. |
||||
*/ |
||||
protected MergedContextConfiguration buildMergedContextConfiguration( |
||||
Class<?> testClass, |
||||
String[] locations, |
||||
Class<?>[] classes, |
||||
Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializerClasses, |
||||
String[] activeProfiles, ContextLoader contextLoader, |
||||
CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, MergedContextConfiguration parentConfig) { |
||||
|
||||
return new MergedContextConfiguration(testClass, locations, classes, initializerClasses, activeProfiles, |
||||
contextLoader, cacheAwareContextLoaderDelegate, parentConfig); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,108 @@
@@ -0,0 +1,108 @@
|
||||
/* |
||||
* Copyright 2002-2014 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.test.context.web; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.Collections; |
||||
import java.util.List; |
||||
import java.util.Set; |
||||
|
||||
import org.springframework.context.ApplicationContextInitializer; |
||||
import org.springframework.context.ConfigurableApplicationContext; |
||||
import org.springframework.core.annotation.AnnotationUtils; |
||||
import org.springframework.test.context.CacheAwareContextLoaderDelegate; |
||||
import org.springframework.test.context.ContextLoader; |
||||
import org.springframework.test.context.MergedContextConfiguration; |
||||
import org.springframework.test.context.TestContextBootstrapper; |
||||
import org.springframework.test.context.TestExecutionListener; |
||||
import org.springframework.test.context.support.DefaultTestContextBootstrapper; |
||||
|
||||
/** |
||||
* Web-specific implementation of the {@link TestContextBootstrapper} SPI. |
||||
* |
||||
* <ul> |
||||
* <li>Prepends {@link org.springframework.test.context.web.ServletTestExecutionListener} |
||||
* to the list of default {@link TestExecutionListener TestExecutionListeners} supported by |
||||
* the superclass. |
||||
* <li>Uses {@link WebDelegatingSmartContextLoader} as the default {@link ContextLoader} |
||||
* if the test class is annotated with {@link WebAppConfiguration @WebAppConfiguration} |
||||
* and otherwise delegates to the superclass. |
||||
* <li>Builds a {@link WebMergedContextConfiguration} if the test class is annotated |
||||
* with {@link WebAppConfiguration @WebAppConfiguration} and otherwise delegates to |
||||
* the superclass. |
||||
* </ul> |
||||
* |
||||
* @author Sam Brannen |
||||
* @since 4.1 |
||||
*/ |
||||
public class WebTestContextBootstrapper extends DefaultTestContextBootstrapper { |
||||
|
||||
/** |
||||
* Prepends {@link org.springframework.test.context.web.ServletTestExecutionListener} |
||||
* to the list of default {@link TestExecutionListener TestExecutionListeners} |
||||
* supported by the superclass and returns an unmodifiable, updated list. |
||||
*/ |
||||
@Override |
||||
protected List<String> getDefaultTestExecutionListenerClassNames() { |
||||
List<String> classNames = new ArrayList<String>(super.getDefaultTestExecutionListenerClassNames()); |
||||
classNames.add(0, "org.springframework.test.context.web.ServletTestExecutionListener"); |
||||
return Collections.unmodifiableList(classNames); |
||||
} |
||||
|
||||
/** |
||||
* Returns {@link WebDelegatingSmartContextLoader} if the supplied class is |
||||
* annotated with {@link WebAppConfiguration @WebAppConfiguration} and |
||||
* otherwise delegates to the superclass. |
||||
*/ |
||||
@Override |
||||
protected Class<? extends ContextLoader> getDefaultContextLoaderClass(Class<?> testClass) { |
||||
if (AnnotationUtils.findAnnotation(testClass, WebAppConfiguration.class) != null) { |
||||
return WebDelegatingSmartContextLoader.class; |
||||
} |
||||
|
||||
// else...
|
||||
return super.getDefaultContextLoaderClass(testClass); |
||||
} |
||||
|
||||
/** |
||||
* Builds a {@link WebMergedContextConfiguration} if the supplied class is |
||||
* annotated with {@link WebAppConfiguration @WebAppConfiguration} and |
||||
* otherwise delegates to the superclass. |
||||
*/ |
||||
@Override |
||||
protected MergedContextConfiguration buildMergedContextConfiguration( |
||||
Class<?> testClass, |
||||
String[] locations, |
||||
Class<?>[] classes, |
||||
Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializerClasses, |
||||
String[] activeProfiles, ContextLoader contextLoader, |
||||
CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, MergedContextConfiguration parentConfig) { |
||||
|
||||
WebAppConfiguration webAppConfiguration = AnnotationUtils.findAnnotation(testClass, WebAppConfiguration.class); |
||||
if (webAppConfiguration != null) { |
||||
String resourceBasePath = webAppConfiguration.value(); |
||||
|
||||
return new WebMergedContextConfiguration(testClass, locations, classes, initializerClasses, activeProfiles, |
||||
resourceBasePath, contextLoader, cacheAwareContextLoaderDelegate, parentConfig); |
||||
} |
||||
|
||||
// else...
|
||||
return super.buildMergedContextConfiguration(testClass, locations, classes, initializerClasses, activeProfiles, |
||||
contextLoader, cacheAwareContextLoaderDelegate, parentConfig); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,41 @@
@@ -0,0 +1,41 @@
|
||||
/* |
||||
* Copyright 2002-2014 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; |
||||
|
||||
/** |
||||
* Collection of test-related utility methods for working with {@link BootstrapContext |
||||
* BootstrapContexts} and {@link TestContextBootstrapper TestContextBootstrappers}. |
||||
* |
||||
* @author Sam Brannen |
||||
* @since 4.1 |
||||
*/ |
||||
public abstract class BootstrapTestUtils { |
||||
|
||||
private BootstrapTestUtils() { |
||||
/* no-op */ |
||||
} |
||||
|
||||
public static BootstrapContext buildBootstrapContext(Class<?> testClass, |
||||
CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate) { |
||||
return new DefaultBootstrapContext(testClass, cacheAwareContextLoaderDelegate); |
||||
} |
||||
|
||||
public static TestContextBootstrapper resolveTestContextBootstrapper(BootstrapContext bootstrapContext) { |
||||
return BootstrapUtils.resolveTestContextBootstrapper(bootstrapContext); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,45 @@
@@ -0,0 +1,45 @@
|
||||
/* |
||||
* Copyright 2002-2014 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; |
||||
|
||||
/** |
||||
* Collection of test-related utility methods for working with {@link TestContext TestContexts}. |
||||
* |
||||
* @author Sam Brannen |
||||
* @since 4.1 |
||||
*/ |
||||
public abstract class TestContextTestUtils { |
||||
|
||||
private TestContextTestUtils() { |
||||
/* no-op */ |
||||
} |
||||
|
||||
public static TestContext buildTestContext(Class<?> testClass, ContextCache contextCache) { |
||||
CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate = new DefaultCacheAwareContextLoaderDelegate( |
||||
contextCache); |
||||
return buildTestContext(testClass, null, cacheAwareContextLoaderDelegate); |
||||
} |
||||
|
||||
public static TestContext buildTestContext(Class<?> testClass, String customDefaultContextLoaderClassName, |
||||
CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate) { |
||||
BootstrapContext bootstrapContext = new DefaultBootstrapContext(testClass, cacheAwareContextLoaderDelegate); |
||||
TestContextBootstrapper testContextBootstrapper = BootstrapUtils.resolveTestContextBootstrapper(bootstrapContext); |
||||
|
||||
return new DefaultTestContext(testContextBootstrapper); |
||||
} |
||||
|
||||
} |
Loading…
Reference in new issue