diff --git a/org.springframework.test/src/main/java/org/springframework/test/context/SmartContextLoader.java b/org.springframework.test/src/main/java/org/springframework/test/context/SmartContextLoader.java index 422bd88bb01..624031dbed8 100644 --- a/org.springframework.test/src/main/java/org/springframework/test/context/SmartContextLoader.java +++ b/org.springframework.test/src/main/java/org/springframework/test/context/SmartContextLoader.java @@ -22,12 +22,12 @@ import org.springframework.context.ApplicationContext; *

Strategy interface for loading an {@link ApplicationContext application context} * for an integration test managed by the Spring TestContext Framework. * - *

The {@code SmartContextLoader} SPI supersedes the {@link ContextLoader} - * SPI introduced in Spring 2.5: a {@code SmartContextLoader} can process both - * resource locations and configuration classes. Furthermore, a {@code SmartContextLoader} - * can set active bean definition profiles in the context that it loads (see - * {@link MergedContextConfiguration#getActiveProfiles()} and - * {@link #loadContext(MergedContextConfiguration)}). + *

The {@code SmartContextLoader} SPI supersedes the {@link ContextLoader} SPI + * introduced in Spring 2.5: a {@code SmartContextLoader} can choose to process + * either resource locations or configuration classes. Furthermore, a + * {@code SmartContextLoader} can set active bean definition profiles in the + * context that it loads (see {@link MergedContextConfiguration#getActiveProfiles()} + * and {@link #loadContext(MergedContextConfiguration)}). * *

Clients of a {@code SmartContextLoader} should call * {@link #processContextConfiguration(ContextConfigurationAttributes) @@ -80,7 +80,6 @@ public interface SmartContextLoader extends ContextLoader { * locations or classes property empty signals that * this {@code SmartContextLoader} was not able to generate or detect defaults. * @param configAttributes the context configuration attributes to process - * @see #generatesDefaults() */ void processContextConfiguration(ContextConfigurationAttributes configAttributes); diff --git a/org.springframework.test/src/main/java/org/springframework/test/context/support/DelegatingSmartContextLoader.java b/org.springframework.test/src/main/java/org/springframework/test/context/support/DelegatingSmartContextLoader.java index 2915cf96a0e..8f95dac2c95 100644 --- a/org.springframework.test/src/main/java/org/springframework/test/context/support/DelegatingSmartContextLoader.java +++ b/org.springframework.test/src/main/java/org/springframework/test/context/support/DelegatingSmartContextLoader.java @@ -22,6 +22,8 @@ import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.ContextConfigurationAttributes; import org.springframework.test.context.ContextLoader; import org.springframework.test.context.MergedContextConfiguration; @@ -30,7 +32,23 @@ import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; /** - * TODO Document DelegatingSmartContextLoader. + * {@code DelegatingSmartContextLoader} is an implementation of the {@link SmartContextLoader} + * SPI that delegates to a set of candidate SmartContextLoaders (i.e., + * {@link GenericXmlContextLoader} and {@link AnnotationConfigContextLoader}) 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 + * {@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. + * + *

Placing an empty {@code @ContextConfiguration} annotation on + * a test class signals that default resource locations (i.e., XML configuration files) + * or default {@link Configuration configuration classes} should be detected. Furthermore, + * if a specific {@link ContextLoader} or {@link SmartContextLoader} is not explicitly + * declared via {@code @ContextConfiguration}, {@code DelegatingSmartContextLoader} will + * be used as the default loader, thus providing automatic support for either XML + * configuration files or configuration classes, but not both simultaneously. * * @author Sam Brannen * @since 3.1 @@ -43,7 +61,7 @@ public class DelegatingSmartContextLoader implements SmartContextLoader { private static final Log logger = LogFactory.getLog(DelegatingSmartContextLoader.class); private final SmartContextLoader xmlLoader = new GenericXmlContextLoader(); - private final SmartContextLoader annotationLoader = new AnnotationConfigContextLoader(); + private final SmartContextLoader annotationConfigLoader = new AnnotationConfigContextLoader(); // --- SmartContextLoader -------------------------------------------------- @@ -70,24 +88,53 @@ public class DelegatingSmartContextLoader implements SmartContextLoader { } /** - * TODO Document processContextConfiguration() implementation. + * Delegates to candidate {@code SmartContextLoaders} to process the supplied + * {@link ContextConfigurationAttributes}. + * + *

Delegation is based on explicit knowledge of the implementations of + * {@link GenericXmlContextLoader} and {@link AnnotationConfigContextLoader}. + * Specifically, the delegation algorithm is as follows: + * + *

+ * + * @param configAttributes the context configuration attributes to process + * @throws IllegalArgumentException if the supplied configuration attributes are + * null, or if the supplied configuration attributes include both + * resource locations and configuration classes + * @throws IllegalStateException if {@code GenericXmlContextLoader} detects default + * configuration classes; if {@code AnnotationConfigContextLoader} detects default + * resource locations; if neither candidate loader detects defaults for the supplied + * context configuration; or if both candidate loaders detect defaults for the + * supplied context configuration */ public void processContextConfiguration(final ContextConfigurationAttributes configAttributes) { - if (configAttributes.hasLocations() && configAttributes.hasClasses()) { - throw new IllegalStateException(String.format( - "Cannot process locations AND configuration classes for context " - + "configuration %s; configure one or the other, but not both.", configAttributes)); - } + Assert.notNull(configAttributes, "configAttributes must not be null"); + Assert.isTrue(configAttributes.hasLocations() && configAttributes.hasClasses(), String.format( + "Cannot process locations AND configuration classes for context " + + "configuration %s; configure one or the other, but not both.", configAttributes)); // If the original locations or classes were not empty, there's no - // need to bother with default detection checks; just let the respective - // loader process the configuration. + // need to bother with default detection checks; just let the + // appropriate loader process the configuration. if (configAttributes.hasLocations()) { delegateProcessing(xmlLoader, configAttributes); } else if (configAttributes.hasClasses()) { - delegateProcessing(annotationLoader, configAttributes); + delegateProcessing(annotationConfigLoader, configAttributes); } else { // Else attempt to detect defaults... @@ -109,28 +156,28 @@ public class DelegatingSmartContextLoader implements SmartContextLoader { name(xmlLoader), configAttributes)); } - // Now let the annotation loader process the configuration. - delegateProcessing(annotationLoader, configAttributes); + // Now let the annotation config loader process the configuration. + delegateProcessing(annotationConfigLoader, configAttributes); if (configAttributes.hasClasses()) { if (logger.isInfoEnabled()) { logger.info(String.format( "%s detected default configuration classes for context configuration %s.", - name(annotationLoader), configAttributes)); + name(annotationConfigLoader), configAttributes)); } } if (!xmlLoaderDetectedDefaults && configAttributes.hasLocations()) { throw new IllegalStateException(String.format( "%s should NOT have detected default locations for context configuration %s.", - name(annotationLoader), configAttributes)); + name(annotationConfigLoader), configAttributes)); } // If neither loader detected defaults, throw an exception. if (!configAttributes.hasResources()) { throw new IllegalStateException(String.format( "Neither %s nor %s was able to detect defaults for context configuration %s.", name(xmlLoader), - name(annotationLoader), configAttributes)); + name(annotationConfigLoader), configAttributes)); } if (configAttributes.hasLocations() && configAttributes.hasClasses()) { @@ -145,16 +192,35 @@ public class DelegatingSmartContextLoader implements SmartContextLoader { } /** - * TODO Document loadContext(MergedContextConfiguration) implementation. + * Delegates to an appropriate candidate {@code SmartContextLoader} to load + * an {@link ApplicationContext}. + * + *

Delegation is based on explicit knowledge of the implementations of + * {@link GenericXmlContextLoader} and {@link AnnotationConfigContextLoader}. + * Specifically, the delegation algorithm is as follows: + * + *

+ * + * @param mergedConfig the merged context configuration to use to load the application context + * @throws IllegalArgumentException if the supplied merged configuration is null + * @throws IllegalStateException if neither candidate loader is capable of loading an + * {@code ApplicationContext} from the supplied merged context configuration */ public ApplicationContext loadContext(MergedContextConfiguration mergedConfig) throws Exception { Assert.notNull(mergedConfig, "mergedConfig must not be null"); - List candidates = Arrays.asList(xmlLoader, annotationLoader); + List candidates = Arrays.asList(xmlLoader, annotationConfigLoader); - // Determine if each loader can load a context from the mergedConfig. If - // it can, let it; otherwise, keep iterating. for (SmartContextLoader loader : candidates) { + // Determine if each loader can load a context from the + // mergedConfig. If it can, let it; otherwise, keep iterating. if (supports(loader, mergedConfig)) { if (logger.isDebugEnabled()) { logger.debug(String.format("Delegating to %s to load context from %s.", name(loader), mergedConfig)); @@ -165,7 +231,7 @@ public class DelegatingSmartContextLoader implements SmartContextLoader { throw new IllegalStateException(String.format( "Neither %s nor %s was able to load an ApplicationContext from %s.", name(xmlLoader), - name(annotationLoader), mergedConfig)); + name(annotationConfigLoader), mergedConfig)); } // --- ContextLoader ------------------------------------------------------- @@ -177,7 +243,7 @@ public class DelegatingSmartContextLoader implements SmartContextLoader { * @throws UnsupportedOperationException */ public String[] processLocations(Class clazz, String... locations) { - throw new UnsupportedOperationException("DelegatingSmartContextLoader does not support the ContextLoader API. " + throw new UnsupportedOperationException("DelegatingSmartContextLoader does not support the ContextLoader SPI. " + "Call processContextConfiguration(ContextConfigurationAttributes) instead."); } @@ -188,7 +254,7 @@ public class DelegatingSmartContextLoader implements SmartContextLoader { * @throws UnsupportedOperationException */ public ApplicationContext loadContext(String... locations) throws Exception { - throw new UnsupportedOperationException("DelegatingSmartContextLoader does not support the ContextLoader API. " + throw new UnsupportedOperationException("DelegatingSmartContextLoader does not support the ContextLoader SPI. " + "Call loadContext(MergedContextConfiguration) instead."); }