From 5373b43a5301a7a2f4980b9732cacb553fc5e58c Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Sun, 3 Apr 2011 17:00:45 +0000 Subject: [PATCH] [SPR-6184] Extracted ContextLoader resolution functionality from TestContext into a new ContextLoaderUtils utility class. --- .../test/context/ContextLoaderUtils.java | 231 ++++++++++++++++++ .../test/context/TestContext.java | 186 +------------- 2 files changed, 237 insertions(+), 180 deletions(-) create mode 100644 org.springframework.test/src/main/java/org/springframework/test/context/ContextLoaderUtils.java diff --git a/org.springframework.test/src/main/java/org/springframework/test/context/ContextLoaderUtils.java b/org.springframework.test/src/main/java/org/springframework/test/context/ContextLoaderUtils.java new file mode 100644 index 00000000000..32be47057a4 --- /dev/null +++ b/org.springframework.test/src/main/java/org/springframework/test/context/ContextLoaderUtils.java @@ -0,0 +1,231 @@ +/* + * Copyright 2002-2011 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.test.context; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.BeanUtils; +import org.springframework.context.ApplicationContext; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +/** + * Utility methods for working with {@link ContextLoader ContextLoaders}. + * + *

TODO: Consider refactoring into a stateful ContextLoaderResolver. + * + * @author Sam Brannen + * @since 3.1 + * @see ContextLoader + */ +public abstract class ContextLoaderUtils { + + private static final Log logger = LogFactory.getLog(ContextLoaderUtils.class); + + private static final String STANDARD_DEFAULT_CONTEXT_LOADER_CLASS_NAME = "org.springframework.test.context.support.GenericXmlContextLoader"; + + + /** + * TODO Document resolveContextLoader(). + * + * @param testClass the test class for which the ContextLoader + * should be resolved (must not be null) + * @param defaultContextLoaderClassName the name of the default + * ContextLoader class to use (may be null) + * @return the resolved ContextLoader for the supplied + * testClass + * @see #resolveContextLoaderClass(Class, String) + */ + public static ContextLoader resolveContextLoader(Class testClass, String defaultContextLoaderClassName) { + Assert.notNull(testClass, "Test class must not be null"); + + if (!StringUtils.hasText(defaultContextLoaderClassName)) { + defaultContextLoaderClassName = STANDARD_DEFAULT_CONTEXT_LOADER_CLASS_NAME; + } + + Class contextLoaderClass = resolveContextLoaderClass(testClass, + defaultContextLoaderClassName); + + return (ContextLoader) BeanUtils.instantiateClass(contextLoaderClass); + } + + /** + * Retrieve the {@link ContextLoader} {@link Class} to use for the supplied + * {@link Class test class}. + *

    + *
  1. If the {@link ContextConfiguration#loader() loader} attribute of + * {@link ContextConfiguration @ContextConfiguration} is configured + * with an explicit class, that class will be returned.
  2. + *
  3. If a loader class is not specified, the class hierarchy + * will be traversed to find a parent class annotated with + * @ContextConfiguration; go to step #1.
  4. + *
  5. If no explicit loader class is found after traversing + * the class hierarchy, an attempt will be made to load and return the class + * with the supplied defaultContextLoaderClassName.
  6. + *
+ * @param clazz the class for which to retrieve ContextLoader + * class; must not be null + * @param defaultContextLoaderClassName the name of the default + * ContextLoader class to use; must not be null or empty + * @return the ContextLoader class to use for the specified class + * @throws IllegalArgumentException if {@link ContextConfiguration + * @ContextConfiguration} is not present on the supplied class + */ + @SuppressWarnings("unchecked") + static Class resolveContextLoaderClass(Class clazz, String defaultContextLoaderClassName) { + Assert.notNull(clazz, "Class must not be null"); + Assert.hasText(defaultContextLoaderClassName, "Default ContextLoader class name must not be null or empty"); + + Class annotationType = ContextConfiguration.class; + Class declaringClass = AnnotationUtils.findAnnotationDeclaringClass(annotationType, clazz); + Assert.notNull(declaringClass, "Could not find an 'annotation declaring class' for annotation type [" + + annotationType + "] and class [" + clazz + "]"); + + while (declaringClass != null) { + ContextConfiguration contextConfiguration = declaringClass.getAnnotation(annotationType); + if (logger.isTraceEnabled()) { + logger.trace("Processing ContextLoader for @ContextConfiguration [" + contextConfiguration + + "] and declaring class [" + declaringClass + "]"); + } + + Class contextLoaderClass = contextConfiguration.loader(); + if (!ContextLoader.class.equals(contextLoaderClass)) { + if (logger.isDebugEnabled()) { + logger.debug("Found explicit ContextLoader [" + contextLoaderClass + + "] for @ContextConfiguration [" + contextConfiguration + "] and declaring class [" + + declaringClass + "]"); + } + return contextLoaderClass; + } + + declaringClass = AnnotationUtils.findAnnotationDeclaringClass(annotationType, + declaringClass.getSuperclass()); + } + + try { + ContextConfiguration contextConfiguration = clazz.getAnnotation(ContextConfiguration.class); + if (logger.isTraceEnabled()) { + logger.trace("Using default ContextLoader class [" + defaultContextLoaderClassName + + "] for @ContextConfiguration [" + contextConfiguration + "] and class [" + clazz + "]"); + } + return (Class) ContextLoaderUtils.class.getClassLoader().loadClass( + defaultContextLoaderClassName); + } + catch (ClassNotFoundException ex) { + throw new IllegalStateException("Could not load default ContextLoader class [" + + defaultContextLoaderClassName + "]. Specify @ContextConfiguration's 'loader' " + + "attribute or make the default loader class available."); + } + } + + /** + * Retrieve {@link ApplicationContext} resource locations for the supplied + * {@link Class class}, using the supplied {@link ContextLoader} to + * {@link ContextLoader#processLocations(Class, String...) process} the + * locations. + *

Note that the {@link ContextConfiguration#inheritLocations() + * inheritLocations} flag of {@link ContextConfiguration + * @ContextConfiguration} will be taken into consideration. + * Specifically, if the inheritLocations flag is set to + * true, locations defined in the annotated class will be + * appended to the locations defined in superclasses. + * @param contextLoader the ContextLoader to use for processing the + * locations (must not be null) + * @param clazz the class for which to retrieve the resource locations (must + * not be null) + * @return the list of ApplicationContext resource locations for the + * specified class, including locations from superclasses if appropriate + * @throws IllegalArgumentException if {@link ContextConfiguration + * @ContextConfiguration} is not present on the supplied class + */ + public static String[] resolveContextLocations(ContextLoader contextLoader, Class clazz) { + Assert.notNull(contextLoader, "ContextLoader must not be null"); + Assert.notNull(clazz, "Class must not be null"); + + List locationsList = new ArrayList(); + Class annotationType = ContextConfiguration.class; + Class declaringClass = AnnotationUtils.findAnnotationDeclaringClass(annotationType, clazz); + Assert.notNull(declaringClass, "Could not find an 'annotation declaring class' for annotation type [" + + annotationType + "] and class [" + clazz + "]"); + + // --- configuration class resources ---------------------------- + + // TODO [SPR-6184] Implement recursive search for configuration classes. + // This needs to integrate seamlessly (i.e., analogous yet mutually + // exclusive) with the existing locations search. + if ((contextLoader instanceof ResourceTypeAwareContextLoader) + && ((ResourceTypeAwareContextLoader) contextLoader).supportsClassResources()) { + + ContextConfiguration cc = declaringClass.getAnnotation(annotationType); + if (logger.isTraceEnabled()) { + logger.trace(String.format("Retrieved @ContextConfiguration [%s] for declaring class [%s].", cc, + declaringClass)); + } + + String[] classNames = null; + + Class[] configClasses = cc.classes(); + if (!ObjectUtils.isEmpty(configClasses)) { + classNames = new String[configClasses.length]; + + for (int i = 0; i < configClasses.length; i++) { + classNames[i] = configClasses[i].getName(); + } + } + + return contextLoader.processLocations(declaringClass, classNames); + } + + // --- location/value resources --------------------------------- + + while (declaringClass != null) { + ContextConfiguration contextConfiguration = declaringClass.getAnnotation(annotationType); + if (logger.isTraceEnabled()) { + logger.trace(String.format("Retrieved @ContextConfiguration [%s] for declaring class [%s].", + contextConfiguration, declaringClass)); + } + + String[] valueLocations = contextConfiguration.value(); + String[] locations = contextConfiguration.locations(); + if (!ObjectUtils.isEmpty(valueLocations) && !ObjectUtils.isEmpty(locations)) { + String msg = String.format( + "Test class [%s] has been configured with @ContextConfiguration's 'value' [%s] and 'locations' [%s] attributes. Only one declaration of resource locations is permitted per @ContextConfiguration annotation.", + declaringClass, ObjectUtils.nullSafeToString(valueLocations), + ObjectUtils.nullSafeToString(locations)); + logger.error(msg); + throw new IllegalStateException(msg); + } + else if (!ObjectUtils.isEmpty(valueLocations)) { + locations = valueLocations; + } + + locations = contextLoader.processLocations(declaringClass, locations); + locationsList.addAll(0, Arrays. asList(locations)); + declaringClass = contextConfiguration.inheritLocations() ? AnnotationUtils.findAnnotationDeclaringClass( + annotationType, declaringClass.getSuperclass()) : null; + } + + return locationsList.toArray(new String[locationsList.size()]); + } + +} diff --git a/org.springframework.test/src/main/java/org/springframework/test/context/TestContext.java b/org.springframework.test/src/main/java/org/springframework/test/context/TestContext.java index e741f233417..4df8d87c671 100644 --- a/org.springframework.test/src/main/java/org/springframework/test/context/TestContext.java +++ b/org.springframework.test/src/main/java/org/springframework/test/context/TestContext.java @@ -18,20 +18,14 @@ package org.springframework.test.context; import java.io.Serializable; import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.beans.BeanUtils; import org.springframework.context.ApplicationContext; import org.springframework.core.AttributeAccessorSupport; -import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.style.ToStringCreator; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; -import org.springframework.util.StringUtils; /** * TestContext encapsulates the context in which a test is executed, agnostic of @@ -45,8 +39,6 @@ public class TestContext extends AttributeAccessorSupport { private static final long serialVersionUID = -5827157174866681233L; - private static final String STANDARD_DEFAULT_CONTEXT_LOADER_CLASS_NAME = "org.springframework.test.context.support.GenericXmlContextLoader"; - private static final Log logger = LogFactory.getLog(TestContext.class); private final ContextCache contextCache; @@ -96,29 +88,22 @@ public class TestContext extends AttributeAccessorSupport { Assert.notNull(testClass, "Test class must not be null"); Assert.notNull(contextCache, "ContextCache must not be null"); - if (!StringUtils.hasText(defaultContextLoaderClassName)) { - defaultContextLoaderClassName = STANDARD_DEFAULT_CONTEXT_LOADER_CLASS_NAME; - } - ContextConfiguration contextConfiguration = testClass.getAnnotation(ContextConfiguration.class); - String[] locations = null; ContextLoader contextLoader = null; + String[] locations = null; if (contextConfiguration == null) { if (logger.isInfoEnabled()) { - logger.info("@ContextConfiguration not found for class [" + testClass + "]"); + logger.info(String.format("@ContextConfiguration not found for class [%s]", testClass)); } } else { if (logger.isTraceEnabled()) { - logger.trace("Retrieved @ContextConfiguration [" + contextConfiguration + "] for class [" + testClass - + "]"); + logger.trace(String.format("Retrieved @ContextConfiguration [%s] for class [%s]", contextConfiguration, + testClass)); } - - Class contextLoaderClass = retrieveContextLoaderClass(testClass, - defaultContextLoaderClassName); - contextLoader = (ContextLoader) BeanUtils.instantiateClass(contextLoaderClass); - locations = retrieveContextLocations(contextLoader, testClass); + contextLoader = ContextLoaderUtils.resolveContextLoader(testClass, defaultContextLoaderClassName); + locations = ContextLoaderUtils.resolveContextLocations(contextLoader, testClass); } this.testClass = testClass; @@ -127,165 +112,6 @@ public class TestContext extends AttributeAccessorSupport { this.locations = locations; } - /** - * Retrieve the {@link ContextLoader} {@link Class} to use for the supplied - * {@link Class test class}. - *

    - *
  1. If the {@link ContextConfiguration#loader() loader} attribute of - * {@link ContextConfiguration @ContextConfiguration} is configured - * with an explicit class, that class will be returned.
  2. - *
  3. If a loader class is not specified, the class hierarchy - * will be traversed to find a parent class annotated with - * @ContextConfiguration; go to step #1.
  4. - *
  5. If no explicit loader class is found after traversing - * the class hierarchy, an attempt will be made to load and return the class - * with the supplied defaultContextLoaderClassName.
  6. - *
- * @param clazz the class for which to retrieve ContextLoader - * class; must not be null - * @param defaultContextLoaderClassName the name of the default - * ContextLoader class to use; must not be null or empty - * @return the ContextLoader class to use for the specified class - * @throws IllegalArgumentException if {@link ContextConfiguration - * @ContextConfiguration} is not present on the supplied class - */ - @SuppressWarnings("unchecked") - private Class retrieveContextLoaderClass(Class clazz, - String defaultContextLoaderClassName) { - Assert.notNull(clazz, "Class must not be null"); - Assert.hasText(defaultContextLoaderClassName, "Default ContextLoader class name must not be null or empty"); - - Class annotationType = ContextConfiguration.class; - Class declaringClass = AnnotationUtils.findAnnotationDeclaringClass(annotationType, clazz); - Assert.notNull(declaringClass, "Could not find an 'annotation declaring class' for annotation type [" - + annotationType + "] and class [" + clazz + "]"); - - while (declaringClass != null) { - ContextConfiguration contextConfiguration = declaringClass.getAnnotation(annotationType); - if (logger.isTraceEnabled()) { - logger.trace("Processing ContextLoader for @ContextConfiguration [" + contextConfiguration - + "] and declaring class [" + declaringClass + "]"); - } - - Class contextLoaderClass = contextConfiguration.loader(); - if (!ContextLoader.class.equals(contextLoaderClass)) { - if (logger.isDebugEnabled()) { - logger.debug("Found explicit ContextLoader [" + contextLoaderClass - + "] for @ContextConfiguration [" + contextConfiguration + "] and declaring class [" - + declaringClass + "]"); - } - return contextLoaderClass; - } - - declaringClass = AnnotationUtils.findAnnotationDeclaringClass(annotationType, - declaringClass.getSuperclass()); - } - - try { - ContextConfiguration contextConfiguration = clazz.getAnnotation(ContextConfiguration.class); - if (logger.isTraceEnabled()) { - logger.trace("Using default ContextLoader class [" + defaultContextLoaderClassName - + "] for @ContextConfiguration [" + contextConfiguration + "] and class [" + clazz + "]"); - } - return (Class) getClass().getClassLoader().loadClass(defaultContextLoaderClassName); - } - catch (ClassNotFoundException ex) { - throw new IllegalStateException("Could not load default ContextLoader class [" - + defaultContextLoaderClassName + "]. Specify @ContextConfiguration's 'loader' " - + "attribute or make the default loader class available."); - } - } - - /** - * Retrieve {@link ApplicationContext} resource locations for the supplied - * {@link Class class}, using the supplied {@link ContextLoader} to - * {@link ContextLoader#processLocations(Class, String...) process} the - * locations. - *

Note that the {@link ContextConfiguration#inheritLocations() - * inheritLocations} flag of {@link ContextConfiguration - * @ContextConfiguration} will be taken into consideration. - * Specifically, if the inheritLocations flag is set to - * true, locations defined in the annotated class will be - * appended to the locations defined in superclasses. - * @param contextLoader the ContextLoader to use for processing the - * locations (must not be null) - * @param clazz the class for which to retrieve the resource locations (must - * not be null) - * @return the list of ApplicationContext resource locations for the - * specified class, including locations from superclasses if appropriate - * @throws IllegalArgumentException if {@link ContextConfiguration - * @ContextConfiguration} is not present on the supplied class - */ - private String[] retrieveContextLocations(ContextLoader contextLoader, Class clazz) { - Assert.notNull(contextLoader, "ContextLoader must not be null"); - Assert.notNull(clazz, "Class must not be null"); - - List locationsList = new ArrayList(); - Class annotationType = ContextConfiguration.class; - Class declaringClass = AnnotationUtils.findAnnotationDeclaringClass(annotationType, clazz); - Assert.notNull(declaringClass, "Could not find an 'annotation declaring class' for annotation type [" - + annotationType + "] and class [" + clazz + "]"); - - // --- configuration class resources ---------------------------- - - // TODO [SPR-6184] Implement recursive search for configuration classes. - // This needs to integrate seamlessly (i.e., analogous yet mutually - // exclusive) with the existing locations search. - if ((contextLoader instanceof ResourceTypeAwareContextLoader) - && ((ResourceTypeAwareContextLoader) contextLoader).supportsClassResources()) { - - ContextConfiguration cc = declaringClass.getAnnotation(annotationType); - if (logger.isTraceEnabled()) { - logger.trace(String.format("Retrieved @ContextConfiguration [%s] for declaring class [%s].", cc, - declaringClass)); - } - - String[] classNames = null; - - Class[] configClasses = cc.classes(); - if (!ObjectUtils.isEmpty(configClasses)) { - classNames = new String[configClasses.length]; - - for (int i = 0; i < configClasses.length; i++) { - classNames[i] = configClasses[i].getName(); - } - } - - return contextLoader.processLocations(declaringClass, classNames); - } - - // --- location/value resources --------------------------------- - - while (declaringClass != null) { - ContextConfiguration contextConfiguration = declaringClass.getAnnotation(annotationType); - if (logger.isTraceEnabled()) { - logger.trace(String.format("Retrieved @ContextConfiguration [%s] for declaring class [%s].", - contextConfiguration, declaringClass)); - } - - String[] valueLocations = contextConfiguration.value(); - String[] locations = contextConfiguration.locations(); - if (!ObjectUtils.isEmpty(valueLocations) && !ObjectUtils.isEmpty(locations)) { - String msg = String.format( - "Test class [%s] has been configured with @ContextConfiguration's 'value' [%s] and 'locations' [%s] attributes. Only one declaration of resource locations is permitted per @ContextConfiguration annotation.", - declaringClass, ObjectUtils.nullSafeToString(valueLocations), - ObjectUtils.nullSafeToString(locations)); - logger.error(msg); - throw new IllegalStateException(msg); - } - else if (!ObjectUtils.isEmpty(valueLocations)) { - locations = valueLocations; - } - - locations = contextLoader.processLocations(declaringClass, locations); - locationsList.addAll(0, Arrays. asList(locations)); - declaringClass = contextConfiguration.inheritLocations() ? AnnotationUtils.findAnnotationDeclaringClass( - annotationType, declaringClass.getSuperclass()) : null; - } - - return locationsList.toArray(new String[locationsList.size()]); - } - /** * Load an ApplicationContext for this test context using the * configured ContextLoader and resource locations.