2222import org .apache .commons .logging .Log ;
2323import org .apache .commons .logging .LogFactory ;
2424import org .springframework .context .ApplicationContext ;
25+ import org .springframework .context .annotation .Configuration ;
26+ import org .springframework .test .context .ContextConfiguration ;
2527import org .springframework .test .context .ContextConfigurationAttributes ;
2628import org .springframework .test .context .ContextLoader ;
2729import org .springframework .test .context .MergedContextConfiguration ;
3032import 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