[SPR-8541] Documented DelegatingSmartContextLoader.

This commit is contained in:
Sam Brannen 2011-08-20 17:41:29 +00:00
parent 0a88d4bae1
commit 466c3b4b2a
2 changed files with 95 additions and 30 deletions

View File

@ -22,12 +22,12 @@ import org.springframework.context.ApplicationContext;
* <p>Strategy interface for loading an {@link ApplicationContext application context}
* for an integration test managed by the Spring TestContext Framework.
*
* <p>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)}).
* <p>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)}).
*
* <p>Clients of a {@code SmartContextLoader} should call
* {@link #processContextConfiguration(ContextConfigurationAttributes)
@ -80,7 +80,6 @@ public interface SmartContextLoader extends ContextLoader {
* <code>locations</code> or <code>classes</code> 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);

View File

@ -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 <em>candidate</em> 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.
*
* <p>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}.
*
* <p>Delegation is based on explicit knowledge of the implementations of
* {@link GenericXmlContextLoader} and {@link AnnotationConfigContextLoader}.
* Specifically, the delegation algorithm is as follows:
*
* <ul>
* <li>If the resource locations or configuration classes in the supplied
* {@code ContextConfigurationAttributes} are not empty, the appropriate
* candidate loader will be allowed to process the configuration <em>as is</em>,
* without any checks for detection of defaults.</li>
* <li>Otherwise, {@code GenericXmlContextLoader} will be allowed to process
* the configuration in order to detect default resource locations. If
* {@code GenericXmlContextLoader} detects default resource locations,
* an {@code info} message will be logged.</li>
* <li>Subsequently, {@code AnnotationConfigContextLoader} will be allowed to
* process the configuration in order to detect default configuration classes.
* If {@code AnnotationConfigContextLoader} detects default configuration
* classes, an {@code info} message will be logged.</li>
* </ul>
*
* @param configAttributes the context configuration attributes to process
* @throws IllegalArgumentException if the supplied configuration attributes are
* <code>null</code>, 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(
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}.
*
* <p>Delegation is based on explicit knowledge of the implementations of
* {@link GenericXmlContextLoader} and {@link AnnotationConfigContextLoader}.
* Specifically, the delegation algorithm is as follows:
*
* <ul>
* <li>If the resource locations in the supplied {@code MergedContextConfiguration}
* are not empty and the configuration classes are empty,
* {@code GenericXmlContextLoader} will load the {@code ApplicationContext}.</li>
* <li>If the configuration classes in the supplied {@code MergedContextConfiguration}
* are not empty and the resource locations are empty,
* {@code AnnotationConfigContextLoader} will load the {@code ApplicationContext}.</li>
* </ul>
*
* @param mergedConfig the merged context configuration to use to load the application context
* @throws IllegalArgumentException if the supplied merged configuration is <code>null</code>
* @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<SmartContextLoader> candidates = Arrays.asList(xmlLoader, annotationLoader);
List<SmartContextLoader> 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.");
}