diff --git a/build.gradle b/build.gradle index 91ea5725e2..715f1c5c3e 100644 --- a/build.gradle +++ b/build.gradle @@ -929,6 +929,7 @@ project("spring-test") { optional("javax.portlet:portlet-api:2.0") optional("javax.el:javax.el-api:2.2.5") optional("org.aspectj:aspectjweaver:${aspectjVersion}") + optional("org.codehaus.groovy:groovy-all:${groovyVersion}") optional("org.hamcrest:hamcrest-core:1.3") optional("com.jayway.jsonpath:json-path:0.9.0") optional("org.skyscreamer:jsonassert:1.2.3") diff --git a/spring-test/src/main/java/org/springframework/test/context/ContextConfiguration.java b/spring-test/src/main/java/org/springframework/test/context/ContextConfiguration.java index a716284a90..f298dc787c 100644 --- a/spring-test/src/main/java/org/springframework/test/context/ContextConfiguration.java +++ b/spring-test/src/main/java/org/springframework/test/context/ContextConfiguration.java @@ -27,48 +27,52 @@ import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext; /** - * {@code @ContextConfiguration} defines class-level metadata that is - * used to determine how to load and configure an - * {@link org.springframework.context.ApplicationContext ApplicationContext} - * for integration tests. + * {@code @ContextConfiguration} defines class-level metadata that is used to determine + * how to load and configure an {@link org.springframework.context.ApplicationContext + * ApplicationContext} for integration tests. * *

Supported Resource Types

* - *

Prior to Spring 3.1, only path-based resource locations were supported. - * As of Spring 3.1, {@linkplain #loader context loaders} may choose to support - * either path-based or class-based resources. As of Spring - * 4.0.4, {@linkplain #loader context loaders} may choose to support path-based + *

+ * Prior to Spring 3.1, only path-based resource locations (typically XML configuration + * files) were supported. As of Spring 3.1, {@linkplain #loader context loaders} may + * choose to support either path-based or class-based resources. As of + * Spring 4.0.4, {@linkplain #loader context loaders} may choose to support path-based * and class-based resources simultaneously. Consequently - * {@code @ContextConfiguration} can be used to declare either path-based - * resource locations (via the {@link #locations} or {@link #value} attribute) - * or annotated classes (via the {@link #classes} attribute). Note, - * however, that most implementations of {@link SmartContextLoader} only support - * a single resource type. + * {@code @ContextConfiguration} can be used to declare either path-based resource + * locations (via the {@link #locations} or {@link #value} attribute) or + * annotated classes (via the {@link #classes} attribute). Note, however, that most + * implementations of {@link SmartContextLoader} only support a single resource type. As + * of Spring 4.1, path-based resource locations may be either XML configuration files or + * Groovy scripts (if Groovy is on the classpath). Of course, third-party frameworks may + * choose to support additional types of path-based resources. * *

Annotated Classes

* - *

The term annotated class can refer to any of the following. + *

+ * The term annotated class can refer to any of the following. * *

* - *

Consult the Javadoc for - * {@link org.springframework.context.annotation.Configuration @Configuration} and - * {@link org.springframework.context.annotation.Bean @Bean} - * for further information regarding the configuration and semantics of + *

+ * Consult the Javadoc for {@link org.springframework.context.annotation.Configuration + * @Configuration} and {@link org.springframework.context.annotation.Bean @Bean} for + * further information regarding the configuration and semantics of * annotated classes. * - *

As of Spring Framework 4.0, this annotation may be used as a - * meta-annotation to create custom composed annotations. + *

+ * As of Spring Framework 4.0, this annotation may be used as a meta-annotation + * to create custom composed annotations. * * @author Sam Brannen * @since 2.5 @@ -104,20 +108,20 @@ public @interface ContextConfiguration { *

Check out the Javadoc for * {@link org.springframework.test.context.support.AbstractContextLoader#modifyLocations * AbstractContextLoader.modifyLocations()} for details on how a location - * String will be interpreted at runtime, in particular in case of a relative + * will be interpreted at runtime, in particular in case of a relative * path. Also, check out the documentation on * {@link org.springframework.test.context.support.AbstractContextLoader#generateDefaultLocations - * AbstractContextLoader.generateDefaultLocations()} for details on the default - * locations that are going to be used if none are specified. + * AbstractContextLoader.generateDefaultLocations()} for details on the + * default locations that are going to be used if none are specified. * - *

Note that the above-mentioned default rules only apply for a standard + *

Note that the aforementioned default rules only apply for a standard * {@link org.springframework.test.context.support.AbstractContextLoader * AbstractContextLoader} subclass such as - * {@link org.springframework.test.context.support.GenericXmlContextLoader - * GenericXmlContextLoader} which is the effective default implementation - * used at runtime if {@code locations} are configured. See the - * documentation for {@link #loader} for further details regarding default - * loaders. + * {@link org.springframework.test.context.support.GenericXmlContextLoader GenericXmlContextLoader} or + * {@link org.springframework.test.context.support.GenericGroovyXmlContextLoader GenericGroovyXmlContextLoader} + * which are the effective default implementations used at runtime if + * {@code locations} are configured. See the documentation for {@link #loader} + * for further details regarding default loaders. * *

This attribute may not be used in conjunction with * {@link #value}, but it may be used instead of {@link #value}. @@ -186,17 +190,17 @@ public @interface ContextConfiguration { *

In the following example that uses path-based resource locations, the * {@link org.springframework.context.ApplicationContext ApplicationContext} * for {@code ExtendedTest} will be loaded from - * "base-context.xml" and - * "extended-context.xml", in that order. Beans defined in - * "extended-context.xml" may therefore override those defined in - * "base-context.xml". + * {@code "base-context.xml"} and + * {@code "extended-context.xml"}, in that order. Beans defined in + * {@code "extended-context.xml"} may therefore override those defined + * in {@code "base-context.xml"}. *

-	 * @ContextConfiguration("base-context.xml")
+	 * @ContextConfiguration("base-context.xml")
 	 * public class BaseTest {
 	 *     // ...
 	 * }
 	 *
-	 * @ContextConfiguration("extended-context.xml")
+	 * @ContextConfiguration("extended-context.xml")
 	 * public class ExtendedTest extends BaseTest {
 	 *     // ...
 	 * }
@@ -281,16 +285,13 @@ public @interface ContextConfiguration {
 	 * {@link org.springframework.test.context.web.WebAppConfiguration
 	 * @WebAppConfiguration}. For further details on the default behavior
 	 * of various concrete {@code SmartContextLoaders}, check out the Javadoc for
-	 * {@link org.springframework.test.context.support.AbstractContextLoader
-	 * AbstractContextLoader},
-	 * {@link org.springframework.test.context.support.GenericXmlContextLoader
-	 * GenericXmlContextLoader},
-	 * {@link org.springframework.test.context.support.AnnotationConfigContextLoader
-	 * AnnotationConfigContextLoader},
-	 * {@link org.springframework.test.context.web.GenericXmlWebContextLoader
-	 * GenericXmlWebContextLoader}, and
-	 * {@link org.springframework.test.context.web.AnnotationConfigWebContextLoader
-	 * AnnotationConfigWebContextLoader}.
+	 * {@link org.springframework.test.context.support.AbstractContextLoader AbstractContextLoader},
+	 * {@link org.springframework.test.context.support.GenericXmlContextLoader GenericXmlContextLoader},
+	 * {@link org.springframework.test.context.support.GenericGroovyXmlContextLoader GenericGroovyXmlContextLoader},
+	 * {@link org.springframework.test.context.support.AnnotationConfigContextLoader AnnotationConfigContextLoader},
+	 * {@link org.springframework.test.context.web.GenericXmlWebContextLoader GenericXmlWebContextLoader},
+	 * {@link org.springframework.test.context.web.GenericGroovyXmlWebContextLoader GenericGroovyXmlWebContextLoader}, and
+	 * {@link org.springframework.test.context.web.AnnotationConfigWebContextLoader AnnotationConfigWebContextLoader}.
 	 *
 	 * @since 2.5
 	 */
diff --git a/spring-test/src/main/java/org/springframework/test/context/support/AbstractContextLoader.java b/spring-test/src/main/java/org/springframework/test/context/support/AbstractContextLoader.java
index e21f4e5e75..23181b8a88 100644
--- a/spring-test/src/main/java/org/springframework/test/context/support/AbstractContextLoader.java
+++ b/spring-test/src/main/java/org/springframework/test/context/support/AbstractContextLoader.java
@@ -55,6 +55,7 @@ import org.springframework.util.ResourceUtils;
  * @author Juergen Hoeller
  * @since 2.5
  * @see #generateDefaultLocations
+ * @see #getResourceSuffixes
  * @see #modifyLocations
  */
 public abstract class AbstractContextLoader implements SmartContextLoader {
@@ -144,14 +145,14 @@ public abstract class AbstractContextLoader implements SmartContextLoader {
 	// --- ContextLoader -------------------------------------------------------
 
 	/**
-	 * If the supplied {@code locations} are {@code null} or
-	 * empty and {@link #isGenerateDefaultLocations()} returns
-	 * {@code true}, default locations will be
-	 * {@link #generateDefaultLocations(Class) generated} for the specified
-	 * {@link Class class} and the configured
-	 * {@link #getResourceSuffix() resource suffix}; otherwise, the supplied
-	 * {@code locations} will be {@link #modifyLocations modified} if
-	 * necessary and returned.
+	 * If the supplied {@code locations} are {@code null} or empty
+	 * and {@link #isGenerateDefaultLocations()} returns {@code true},
+	 * default locations will be {@link #generateDefaultLocations(Class)
+	 * generated} (i.e., detected) for the specified {@link Class class}
+	 * and the configured {@linkplain #getResourceSuffixes() resource suffixes};
+	 * otherwise, the supplied {@code locations} will be
+	 * {@linkplain #modifyLocations modified} if necessary and returned.
+	 *
 	 * @param clazz the class with which the locations are associated: to be
 	 * used when generating default locations
 	 * @param locations the unmodified locations to use for loading the
@@ -173,45 +174,57 @@ public abstract class AbstractContextLoader implements SmartContextLoader {
 	/**
 	 * Generate the default classpath resource locations array based on the
 	 * supplied class.
+	 *
 	 * 

For example, if the supplied class is {@code com.example.MyTest}, * the generated locations will contain a single string with a value of - * "classpath:com/example/MyTest{@code }", - * where {@code } is the value of the - * {@link #getResourceSuffix() resource suffix} string. + * {@code "classpath:com/example/MyTest"}, where {@code } + * is the value of the first configured + * {@linkplain #getResourceSuffixes() resource suffix} for which the + * generated location actually exists in the classpath. + * *

As of Spring 3.1, the implementation of this method adheres to the * contract defined in the {@link SmartContextLoader} SPI. Specifically, * this method will preemptively verify that the generated default * location actually exists. If it does not exist, this method will log a * warning and return an empty array. + * *

Subclasses can override this method to implement a different * default location generation strategy. + * * @param clazz the class for which the default locations are to be generated * @return an array of default application context resource locations * @since 2.5 - * @see #getResourceSuffix() + * @see #getResourceSuffixes() */ protected String[] generateDefaultLocations(Class clazz) { Assert.notNull(clazz, "Class must not be null"); - String suffix = getResourceSuffix(); - Assert.hasText(suffix, "Resource suffix must not be empty"); - String resourcePath = ClassUtils.convertClassNameToResourcePath(clazz.getName()) + suffix; - String prefixedResourcePath = ResourceUtils.CLASSPATH_URL_PREFIX + resourcePath; - ClassPathResource classPathResource = new ClassPathResource(resourcePath); - if (classPathResource.exists()) { - if (logger.isInfoEnabled()) { - logger.info(String.format("Detected default resource location \"%s\" for test class [%s]", - prefixedResourcePath, clazz.getName())); + String[] suffixes = getResourceSuffixes(); + for (String suffix : suffixes) { + Assert.hasText(suffix, "Resource suffix must not be empty"); + String resourcePath = ClassUtils.convertClassNameToResourcePath(clazz.getName()) + suffix; + String prefixedResourcePath = ResourceUtils.CLASSPATH_URL_PREFIX + resourcePath; + ClassPathResource classPathResource = new ClassPathResource(resourcePath); + + if (classPathResource.exists()) { + if (logger.isInfoEnabled()) { + logger.info(String.format("Detected default resource location \"%s\" for test class [%s]", + prefixedResourcePath, clazz.getName())); + } + return new String[] { prefixedResourcePath }; } - return new String[] { prefixedResourcePath }; - } - else { - if (logger.isInfoEnabled()) { - logger.info(String.format("Could not detect default resource locations for test class [%s]: " + else if (logger.isDebugEnabled()) { + logger.debug(String.format("Did not detect default resource location for test class [%s]: " + "%s does not exist", clazz.getName(), classPathResource)); } - return EMPTY_STRING_ARRAY; } + + if (logger.isInfoEnabled()) { + logger.info(String.format("Could not detect default resource locations for test class [%s]: " + + "no resource found for suffixes %s.", clazz.getName(), ObjectUtils.nullSafeToString(suffixes))); + } + + return EMPTY_STRING_ARRAY; } /** @@ -250,13 +263,36 @@ public abstract class AbstractContextLoader implements SmartContextLoader { } /** - * Get the suffix to append to {@link ApplicationContext} resource locations - * when generating default locations. - *

Must be implemented by subclasses. - * @return the resource suffix; should not be {@code null} or empty + * Get the suffix to append to {@link ApplicationContext} resource + * locations when detecting default locations. + * + *

Subclasses must provide an implementation of this method that + * returns a single suffix. Alternatively subclasses may provide a + * no-op implementation of this method and override + * {@link #getResourceSuffixes()} in order to provide multiple custom + * suffixes. + * + * @return the resource suffix; never {@code null} or empty * @since 2.5 * @see #generateDefaultLocations(Class) + * @see #getResourceSuffixes() */ protected abstract String getResourceSuffix(); + /** + * Get the suffixes to append to {@link ApplicationContext} resource + * locations when detecting default locations. + * + *

The default implementation simply wraps the value returned by + * {@link #getResourceSuffix()} in a single-element array, but this + * can be overridden by subclasses in order to support multiple suffixes. + * + * @return the resource suffixes; never {@code null} or empty + * @since 4.1 + * @see #generateDefaultLocations(Class) + */ + protected String[] getResourceSuffixes() { + return new String[] { getResourceSuffix() }; + } + } diff --git a/spring-test/src/main/java/org/springframework/test/context/support/AbstractDelegatingSmartContextLoader.java b/spring-test/src/main/java/org/springframework/test/context/support/AbstractDelegatingSmartContextLoader.java index 1f12c29325..17f6a04a71 100644 --- a/spring-test/src/main/java/org/springframework/test/context/support/AbstractDelegatingSmartContextLoader.java +++ b/spring-test/src/main/java/org/springframework/test/context/support/AbstractDelegatingSmartContextLoader.java @@ -35,26 +35,33 @@ import org.springframework.util.ObjectUtils; * {@code AbstractDelegatingSmartContextLoader} serves as an abstract base class * for implementations of the {@link SmartContextLoader} SPI that delegate to a * set of candidate SmartContextLoaders (i.e., one that supports XML - * configuration files and one that supports annotated classes) to determine which - * context loader is appropriate for a given test class's configuration. Each - * candidate is given a chance to {@link #processContextConfiguration process} the + * configuration files or Groovy scripts and one that supports annotated classes) + * to determine which context loader is appropriate for a given test class's + * configuration. Each candidate is given a chance to + * {@linkplain #processContextConfiguration process} the * {@link ContextConfigurationAttributes} for each class in the test class hierarchy * that is annotated with {@link ContextConfiguration @ContextConfiguration}, and * the candidate that supports the merged, processed configuration will be used to - * actually {@link #loadContext load} the context. + * actually {@linkplain #loadContext load} the context. + * + *

Any reference to an XML-based loader can be interpreted to mean + * a context loader that supports only XML configuration files or one that + * supports both XML configuration files and Groovy scripts simultaneously. * *

Placing an empty {@code @ContextConfiguration} annotation on a test class signals - * that default resource locations (i.e., XML configuration files) or default - * {@link org.springframework.context.annotation.Configuration configuration classes} + * that default resource locations (e.g., XML configuration files or Groovy scripts) + * or default + * {@linkplain org.springframework.context.annotation.Configuration configuration classes} * should be detected. Furthermore, if a specific {@link ContextLoader} or * {@link SmartContextLoader} is not explicitly declared via * {@code @ContextConfiguration}, a concrete subclass of * {@code AbstractDelegatingSmartContextLoader} will be used as the default loader, - * thus providing automatic support for either XML configuration files or annotated - * classes, but not both simultaneously. + * thus providing automatic support for either path-based resource locations + * (e.g., XML configuration files and Groovy scripts) or annotated classes, + * but not both simultaneously. * - *

As of Spring 3.2, a test class may optionally declare neither XML configuration - * files nor annotated classes and instead declare only {@linkplain + *

As of Spring 3.2, a test class may optionally declare neither path-based + * resource locations nor annotated classes and instead declare only {@linkplain * ContextConfiguration#initializers application context initializers}. In such * cases, an attempt will still be made to detect defaults, but their absence will * not result in an exception. @@ -69,7 +76,8 @@ public abstract class AbstractDelegatingSmartContextLoader implements SmartConte /** - * Get the delegate {@code SmartContextLoader} that supports XML configuration files. + * Get the delegate {@code SmartContextLoader} that supports XML configuration + * files and/or Groovy scripts. */ protected abstract SmartContextLoader getXmlLoader(); @@ -115,9 +123,9 @@ public abstract class AbstractDelegatingSmartContextLoader implements SmartConte * Delegates to candidate {@code SmartContextLoaders} to process the supplied * {@link ContextConfigurationAttributes}. *

Delegation is based on explicit knowledge of the implementations of the - * default loaders for {@link #getXmlLoader() XML configuration files} and - * {@link #getAnnotationConfigLoader() annotated classes}. Specifically, the - * delegation algorithm is as follows: + * default loaders for {@linkplain #getXmlLoader() XML configuration files and + * Groovy scripts} and {@linkplain #getAnnotationConfigLoader() annotated classes}. + * Specifically, the delegation algorithm is as follows: *