Skip to content

Commit 466c3b4

Browse files
committed
[SPR-8541] Documented DelegatingSmartContextLoader.
1 parent 0a88d4b commit 466c3b4

File tree

2 files changed

+95
-30
lines changed

2 files changed

+95
-30
lines changed

org.springframework.test/src/main/java/org/springframework/test/context/SmartContextLoader.java

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@
2222
* <p>Strategy interface for loading an {@link ApplicationContext application context}
2323
* for an integration test managed by the Spring TestContext Framework.
2424
*
25-
* <p>The {@code SmartContextLoader} SPI supersedes the {@link ContextLoader}
26-
* SPI introduced in Spring 2.5: a {@code SmartContextLoader} can process both
27-
* resource locations and configuration classes. Furthermore, a {@code SmartContextLoader}
28-
* can set active bean definition profiles in the context that it loads (see
29-
* {@link MergedContextConfiguration#getActiveProfiles()} and
30-
* {@link #loadContext(MergedContextConfiguration)}).
25+
* <p>The {@code SmartContextLoader} SPI supersedes the {@link ContextLoader} SPI
26+
* introduced in Spring 2.5: a {@code SmartContextLoader} can choose to process
27+
* either resource locations or configuration classes. Furthermore, a
28+
* {@code SmartContextLoader} can set active bean definition profiles in the
29+
* context that it loads (see {@link MergedContextConfiguration#getActiveProfiles()}
30+
* and {@link #loadContext(MergedContextConfiguration)}).
3131
*
3232
* <p>Clients of a {@code SmartContextLoader} should call
3333
* {@link #processContextConfiguration(ContextConfigurationAttributes)
@@ -80,7 +80,6 @@ public interface SmartContextLoader extends ContextLoader {
8080
* <code>locations</code> or <code>classes</code> property empty signals that
8181
* this {@code SmartContextLoader} was not able to generate or detect defaults.
8282
* @param configAttributes the context configuration attributes to process
83-
* @see #generatesDefaults()
8483
*/
8584
void processContextConfiguration(ContextConfigurationAttributes configAttributes);
8685

org.springframework.test/src/main/java/org/springframework/test/context/support/DelegatingSmartContextLoader.java

Lines changed: 89 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
import org.apache.commons.logging.Log;
2323
import org.apache.commons.logging.LogFactory;
2424
import org.springframework.context.ApplicationContext;
25+
import org.springframework.context.annotation.Configuration;
26+
import org.springframework.test.context.ContextConfiguration;
2527
import org.springframework.test.context.ContextConfigurationAttributes;
2628
import org.springframework.test.context.ContextLoader;
2729
import org.springframework.test.context.MergedContextConfiguration;
@@ -30,7 +32,23 @@
3032
import org.springframework.util.ObjectUtils;
3133

3234
/**
33-
* TODO Document DelegatingSmartContextLoader.
35+
* {@code DelegatingSmartContextLoader} is an implementation of the {@link SmartContextLoader}
36+
* SPI that delegates to a set of <em>candidate</em> SmartContextLoaders (i.e.,
37+
* {@link GenericXmlContextLoader} and {@link AnnotationConfigContextLoader}) to
38+
* determine which context loader is appropriate for a given test classÕs configuration.
39+
* Each candidate is given a chance to {@link #processContextConfiguration process} the
40+
* {@link ContextConfigurationAttributes} for each class in the test class hierarchy that
41+
* is annotated with {@link ContextConfiguration @ContextConfiguration}, and the candidate
42+
* that supports the merged, processed configuration will be used to actually
43+
* {@link #loadContext load} the context.
44+
*
45+
* <p>Placing an empty {@code @ContextConfiguration} annotation on
46+
* a test class signals that default resource locations (i.e., XML configuration files)
47+
* or default {@link Configuration configuration classes} should be detected. Furthermore,
48+
* if a specific {@link ContextLoader} or {@link SmartContextLoader} is not explicitly
49+
* declared via {@code @ContextConfiguration}, {@code DelegatingSmartContextLoader} will
50+
* be used as the default loader, thus providing automatic support for either XML
51+
* configuration files or configuration classes, but not both simultaneously.
3452
*
3553
* @author Sam Brannen
3654
* @since 3.1
@@ -43,7 +61,7 @@ public class DelegatingSmartContextLoader implements SmartContextLoader {
4361
private static final Log logger = LogFactory.getLog(DelegatingSmartContextLoader.class);
4462

4563
private final SmartContextLoader xmlLoader = new GenericXmlContextLoader();
46-
private final SmartContextLoader annotationLoader = new AnnotationConfigContextLoader();
64+
private final SmartContextLoader annotationConfigLoader = new AnnotationConfigContextLoader();
4765

4866

4967
// --- SmartContextLoader --------------------------------------------------
@@ -70,24 +88,53 @@ private static boolean supports(SmartContextLoader loader, MergedContextConfigur
7088
}
7189

7290
/**
73-
* TODO Document processContextConfiguration() implementation.
91+
* Delegates to candidate {@code SmartContextLoaders} to process the supplied
92+
* {@link ContextConfigurationAttributes}.
93+
*
94+
* <p>Delegation is based on explicit knowledge of the implementations of
95+
* {@link GenericXmlContextLoader} and {@link AnnotationConfigContextLoader}.
96+
* Specifically, the delegation algorithm is as follows:
97+
*
98+
* <ul>
99+
* <li>If the resource locations or configuration classes in the supplied
100+
* {@code ContextConfigurationAttributes} are not empty, the appropriate
101+
* candidate loader will be allowed to process the configuration <em>as is</em>,
102+
* without any checks for detection of defaults.</li>
103+
* <li>Otherwise, {@code GenericXmlContextLoader} will be allowed to process
104+
* the configuration in order to detect default resource locations. If
105+
* {@code GenericXmlContextLoader} detects default resource locations,
106+
* an {@code info} message will be logged.</li>
107+
* <li>Subsequently, {@code AnnotationConfigContextLoader} will be allowed to
108+
* process the configuration in order to detect default configuration classes.
109+
* If {@code AnnotationConfigContextLoader} detects default configuration
110+
* classes, an {@code info} message will be logged.</li>
111+
* </ul>
112+
*
113+
* @param configAttributes the context configuration attributes to process
114+
* @throws IllegalArgumentException if the supplied configuration attributes are
115+
* <code>null</code>, or if the supplied configuration attributes include both
116+
* resource locations and configuration classes
117+
* @throws IllegalStateException if {@code GenericXmlContextLoader} detects default
118+
* configuration classes; if {@code AnnotationConfigContextLoader} detects default
119+
* resource locations; if neither candidate loader detects defaults for the supplied
120+
* context configuration; or if both candidate loaders detect defaults for the
121+
* supplied context configuration
74122
*/
75123
public void processContextConfiguration(final ContextConfigurationAttributes configAttributes) {
76124

77-
if (configAttributes.hasLocations() && configAttributes.hasClasses()) {
78-
throw new IllegalStateException(String.format(
79-
"Cannot process locations AND configuration classes for context "
80-
+ "configuration %s; configure one or the other, but not both.", configAttributes));
81-
}
125+
Assert.notNull(configAttributes, "configAttributes must not be null");
126+
Assert.isTrue(configAttributes.hasLocations() && configAttributes.hasClasses(), String.format(
127+
"Cannot process locations AND configuration classes for context "
128+
+ "configuration %s; configure one or the other, but not both.", configAttributes));
82129

83130
// If the original locations or classes were not empty, there's no
84-
// need to bother with default detection checks; just let the respective
85-
// loader process the configuration.
131+
// need to bother with default detection checks; just let the
132+
// appropriate loader process the configuration.
86133
if (configAttributes.hasLocations()) {
87134
delegateProcessing(xmlLoader, configAttributes);
88135
}
89136
else if (configAttributes.hasClasses()) {
90-
delegateProcessing(annotationLoader, configAttributes);
137+
delegateProcessing(annotationConfigLoader, configAttributes);
91138
}
92139
else {
93140
// Else attempt to detect defaults...
@@ -109,28 +156,28 @@ else if (configAttributes.hasClasses()) {
109156
name(xmlLoader), configAttributes));
110157
}
111158

112-
// Now let the annotation loader process the configuration.
113-
delegateProcessing(annotationLoader, configAttributes);
159+
// Now let the annotation config loader process the configuration.
160+
delegateProcessing(annotationConfigLoader, configAttributes);
114161

115162
if (configAttributes.hasClasses()) {
116163
if (logger.isInfoEnabled()) {
117164
logger.info(String.format(
118165
"%s detected default configuration classes for context configuration %s.",
119-
name(annotationLoader), configAttributes));
166+
name(annotationConfigLoader), configAttributes));
120167
}
121168
}
122169

123170
if (!xmlLoaderDetectedDefaults && configAttributes.hasLocations()) {
124171
throw new IllegalStateException(String.format(
125172
"%s should NOT have detected default locations for context configuration %s.",
126-
name(annotationLoader), configAttributes));
173+
name(annotationConfigLoader), configAttributes));
127174
}
128175

129176
// If neither loader detected defaults, throw an exception.
130177
if (!configAttributes.hasResources()) {
131178
throw new IllegalStateException(String.format(
132179
"Neither %s nor %s was able to detect defaults for context configuration %s.", name(xmlLoader),
133-
name(annotationLoader), configAttributes));
180+
name(annotationConfigLoader), configAttributes));
134181
}
135182

136183
if (configAttributes.hasLocations() && configAttributes.hasClasses()) {
@@ -145,16 +192,35 @@ else if (configAttributes.hasClasses()) {
145192
}
146193

147194
/**
148-
* TODO Document loadContext(MergedContextConfiguration) implementation.
195+
* Delegates to an appropriate candidate {@code SmartContextLoader} to load
196+
* an {@link ApplicationContext}.
197+
*
198+
* <p>Delegation is based on explicit knowledge of the implementations of
199+
* {@link GenericXmlContextLoader} and {@link AnnotationConfigContextLoader}.
200+
* Specifically, the delegation algorithm is as follows:
201+
*
202+
* <ul>
203+
* <li>If the resource locations in the supplied {@code MergedContextConfiguration}
204+
* are not empty and the configuration classes are empty,
205+
* {@code GenericXmlContextLoader} will load the {@code ApplicationContext}.</li>
206+
* <li>If the configuration classes in the supplied {@code MergedContextConfiguration}
207+
* are not empty and the resource locations are empty,
208+
* {@code AnnotationConfigContextLoader} will load the {@code ApplicationContext}.</li>
209+
* </ul>
210+
*
211+
* @param mergedConfig the merged context configuration to use to load the application context
212+
* @throws IllegalArgumentException if the supplied merged configuration is <code>null</code>
213+
* @throws IllegalStateException if neither candidate loader is capable of loading an
214+
* {@code ApplicationContext} from the supplied merged context configuration
149215
*/
150216
public ApplicationContext loadContext(MergedContextConfiguration mergedConfig) throws Exception {
151217
Assert.notNull(mergedConfig, "mergedConfig must not be null");
152218

153-
List<SmartContextLoader> candidates = Arrays.asList(xmlLoader, annotationLoader);
219+
List<SmartContextLoader> candidates = Arrays.asList(xmlLoader, annotationConfigLoader);
154220

155-
// Determine if each loader can load a context from the mergedConfig. If
156-
// it can, let it; otherwise, keep iterating.
157221
for (SmartContextLoader loader : candidates) {
222+
// Determine if each loader can load a context from the
223+
// mergedConfig. If it can, let it; otherwise, keep iterating.
158224
if (supports(loader, mergedConfig)) {
159225
if (logger.isDebugEnabled()) {
160226
logger.debug(String.format("Delegating to %s to load context from %s.", name(loader), mergedConfig));
@@ -165,7 +231,7 @@ public ApplicationContext loadContext(MergedContextConfiguration mergedConfig) t
165231

166232
throw new IllegalStateException(String.format(
167233
"Neither %s nor %s was able to load an ApplicationContext from %s.", name(xmlLoader),
168-
name(annotationLoader), mergedConfig));
234+
name(annotationConfigLoader), mergedConfig));
169235
}
170236

171237
// --- ContextLoader -------------------------------------------------------
@@ -177,7 +243,7 @@ public ApplicationContext loadContext(MergedContextConfiguration mergedConfig) t
177243
* @throws UnsupportedOperationException
178244
*/
179245
public String[] processLocations(Class<?> clazz, String... locations) {
180-
throw new UnsupportedOperationException("DelegatingSmartContextLoader does not support the ContextLoader API. "
246+
throw new UnsupportedOperationException("DelegatingSmartContextLoader does not support the ContextLoader SPI. "
181247
+ "Call processContextConfiguration(ContextConfigurationAttributes) instead.");
182248
}
183249

@@ -188,7 +254,7 @@ public String[] processLocations(Class<?> clazz, String... locations) {
188254
* @throws UnsupportedOperationException
189255
*/
190256
public ApplicationContext loadContext(String... locations) throws Exception {
191-
throw new UnsupportedOperationException("DelegatingSmartContextLoader does not support the ContextLoader API. "
257+
throw new UnsupportedOperationException("DelegatingSmartContextLoader does not support the ContextLoader SPI. "
192258
+ "Call loadContext(MergedContextConfiguration) instead.");
193259
}
194260

0 commit comments

Comments
 (0)