001/*
002 * Copyright 2002-2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.test.context.support;
018
019import java.util.ArrayList;
020import java.util.Arrays;
021import java.util.Collection;
022import java.util.Collections;
023import java.util.LinkedHashSet;
024import java.util.List;
025import java.util.Map;
026import java.util.Set;
027
028import org.apache.commons.logging.Log;
029import org.apache.commons.logging.LogFactory;
030
031import org.springframework.beans.BeanInstantiationException;
032import org.springframework.beans.BeanUtils;
033import org.springframework.core.annotation.AnnotationAwareOrderComparator;
034import org.springframework.core.annotation.AnnotationUtils;
035import org.springframework.core.io.support.SpringFactoriesLoader;
036import org.springframework.test.context.BootstrapContext;
037import org.springframework.test.context.CacheAwareContextLoaderDelegate;
038import org.springframework.test.context.ContextConfiguration;
039import org.springframework.test.context.ContextConfigurationAttributes;
040import org.springframework.test.context.ContextCustomizer;
041import org.springframework.test.context.ContextCustomizerFactory;
042import org.springframework.test.context.ContextHierarchy;
043import org.springframework.test.context.ContextLoader;
044import org.springframework.test.context.MergedContextConfiguration;
045import org.springframework.test.context.SmartContextLoader;
046import org.springframework.test.context.TestContext;
047import org.springframework.test.context.TestContextBootstrapper;
048import org.springframework.test.context.TestExecutionListener;
049import org.springframework.test.context.TestExecutionListeners;
050import org.springframework.test.context.TestExecutionListeners.MergeMode;
051import org.springframework.test.util.MetaAnnotationUtils;
052import org.springframework.test.util.MetaAnnotationUtils.AnnotationDescriptor;
053import org.springframework.util.Assert;
054import org.springframework.util.ClassUtils;
055import org.springframework.util.StringUtils;
056
057/**
058 * Abstract implementation of the {@link TestContextBootstrapper} interface which
059 * provides most of the behavior required by a bootstrapper.
060 *
061 * <p>Concrete subclasses typically will only need to provide implementations for
062 * the following methods:
063 * <ul>
064 * <li>{@link #getDefaultContextLoaderClass}
065 * <li>{@link #processMergedContextConfiguration}
066 * </ul>
067 *
068 * <p>To plug in custom
069 * {@link org.springframework.test.context.cache.ContextCache ContextCache}
070 * support, override {@link #getCacheAwareContextLoaderDelegate()}.
071 *
072 * @author Sam Brannen
073 * @author Juergen Hoeller
074 * @author Phillip Webb
075 * @since 4.1
076 */
077public abstract class AbstractTestContextBootstrapper implements TestContextBootstrapper {
078
079        private final Log logger = LogFactory.getLog(getClass());
080
081        private BootstrapContext bootstrapContext;
082
083
084        @Override
085        public void setBootstrapContext(BootstrapContext bootstrapContext) {
086                this.bootstrapContext = bootstrapContext;
087        }
088
089        @Override
090        public BootstrapContext getBootstrapContext() {
091                return this.bootstrapContext;
092        }
093
094        /**
095         * Build a new {@link DefaultTestContext} using the {@linkplain Class test class}
096         * in the {@link BootstrapContext} associated with this bootstrapper and
097         * by delegating to {@link #buildMergedContextConfiguration()} and
098         * {@link #getCacheAwareContextLoaderDelegate()}.
099         * <p>Concrete subclasses may choose to override this method to return a
100         * custom {@link TestContext} implementation.
101         * @since 4.2
102         */
103        @Override
104        public TestContext buildTestContext() {
105                return new DefaultTestContext(getBootstrapContext().getTestClass(), buildMergedContextConfiguration(),
106                                getCacheAwareContextLoaderDelegate());
107        }
108
109        @Override
110        public final List<TestExecutionListener> getTestExecutionListeners() {
111                Class<?> clazz = getBootstrapContext().getTestClass();
112                Class<TestExecutionListeners> annotationType = TestExecutionListeners.class;
113                List<Class<? extends TestExecutionListener>> classesList = new ArrayList<Class<? extends TestExecutionListener>>();
114                boolean usingDefaults = false;
115
116                AnnotationDescriptor<TestExecutionListeners> descriptor =
117                                MetaAnnotationUtils.findAnnotationDescriptor(clazz, annotationType);
118
119                // Use defaults?
120                if (descriptor == null) {
121                        if (logger.isDebugEnabled()) {
122                                logger.debug(String.format("@TestExecutionListeners is not present for class [%s]: using defaults.",
123                                                clazz.getName()));
124                        }
125                        usingDefaults = true;
126                        classesList.addAll(getDefaultTestExecutionListenerClasses());
127                }
128                else {
129                        // Traverse the class hierarchy...
130                        while (descriptor != null) {
131                                Class<?> declaringClass = descriptor.getDeclaringClass();
132                                TestExecutionListeners testExecutionListeners = descriptor.synthesizeAnnotation();
133                                if (logger.isTraceEnabled()) {
134                                        logger.trace(String.format("Retrieved @TestExecutionListeners [%s] for declaring class [%s].",
135                                                        testExecutionListeners, declaringClass.getName()));
136                                }
137
138                                boolean inheritListeners = testExecutionListeners.inheritListeners();
139                                AnnotationDescriptor<TestExecutionListeners> superDescriptor =
140                                                MetaAnnotationUtils.findAnnotationDescriptor(
141                                                                descriptor.getRootDeclaringClass().getSuperclass(), annotationType);
142
143                                // If there are no listeners to inherit, we might need to merge the
144                                // locally declared listeners with the defaults.
145                                if ((!inheritListeners || superDescriptor == null) &&
146                                                testExecutionListeners.mergeMode() == MergeMode.MERGE_WITH_DEFAULTS) {
147                                        if (logger.isDebugEnabled()) {
148                                                logger.debug(String.format("Merging default listeners with listeners configured via " +
149                                                                "@TestExecutionListeners for class [%s].", descriptor.getRootDeclaringClass().getName()));
150                                        }
151                                        usingDefaults = true;
152                                        classesList.addAll(getDefaultTestExecutionListenerClasses());
153                                }
154
155                                classesList.addAll(0, Arrays.asList(testExecutionListeners.listeners()));
156
157                                descriptor = (inheritListeners ? superDescriptor : null);
158                        }
159                }
160
161                Collection<Class<? extends TestExecutionListener>> classesToUse = classesList;
162                // Remove possible duplicates if we loaded default listeners.
163                if (usingDefaults) {
164                        classesToUse = new LinkedHashSet<Class<? extends TestExecutionListener>>(classesList);
165                }
166
167                List<TestExecutionListener> listeners = instantiateListeners(classesToUse);
168                // Sort by Ordered/@Order if we loaded default listeners.
169                if (usingDefaults) {
170                        AnnotationAwareOrderComparator.sort(listeners);
171                }
172
173                if (logger.isInfoEnabled()) {
174                        logger.info("Using TestExecutionListeners: " + listeners);
175                }
176                return listeners;
177        }
178
179        private List<TestExecutionListener> instantiateListeners(Collection<Class<? extends TestExecutionListener>> classesList) {
180                List<TestExecutionListener> listeners = new ArrayList<TestExecutionListener>(classesList.size());
181                for (Class<? extends TestExecutionListener> listenerClass : classesList) {
182                        NoClassDefFoundError ncdfe = null;
183                        try {
184                                listeners.add(BeanUtils.instantiateClass(listenerClass));
185                        }
186                        catch (NoClassDefFoundError err) {
187                                ncdfe = err;
188                        }
189                        catch (BeanInstantiationException ex) {
190                                if (!(ex.getCause() instanceof NoClassDefFoundError)) {
191                                        throw ex;
192                                }
193                                ncdfe = (NoClassDefFoundError) ex.getCause();
194                        }
195                        if (ncdfe != null) {
196                                if (logger.isDebugEnabled()) {
197                                        logger.debug(String.format("Could not instantiate TestExecutionListener [%s]. " +
198                                                        "Specify custom listener classes or make the default listener classes " +
199                                                        "(and their required dependencies) available. Offending class: [%s]",
200                                                        listenerClass.getName(), ncdfe.getMessage()));
201                                }
202                        }
203                }
204                return listeners;
205        }
206
207        /**
208         * Get the default {@link TestExecutionListener} classes for this bootstrapper.
209         * <p>This method is invoked by {@link #getTestExecutionListeners()} and
210         * delegates to {@link #getDefaultTestExecutionListenerClassNames()} to
211         * retrieve the class names.
212         * <p>If a particular class cannot be loaded, a {@code DEBUG} message will
213         * be logged, but the associated exception will not be rethrown.
214         */
215        @SuppressWarnings("unchecked")
216        protected Set<Class<? extends TestExecutionListener>> getDefaultTestExecutionListenerClasses() {
217                Set<Class<? extends TestExecutionListener>> defaultListenerClasses = new LinkedHashSet<Class<? extends TestExecutionListener>>();
218                ClassLoader cl = getClass().getClassLoader();
219                for (String className : getDefaultTestExecutionListenerClassNames()) {
220                        try {
221                                defaultListenerClasses.add((Class<? extends TestExecutionListener>) ClassUtils.forName(className, cl));
222                        }
223                        catch (Throwable ex) {
224                                if (logger.isDebugEnabled()) {
225                                        logger.debug("Could not load default TestExecutionListener class [" + className +
226                                                        "]. Specify custom listener classes or make the default listener classes available.", ex);
227                                }
228                        }
229                }
230                return defaultListenerClasses;
231        }
232
233        /**
234         * Get the names of the default {@link TestExecutionListener} classes for
235         * this bootstrapper.
236         * <p>The default implementation looks up all
237         * {@code org.springframework.test.context.TestExecutionListener} entries
238         * configured in all {@code META-INF/spring.factories} files on the classpath.
239         * <p>This method is invoked by {@link #getDefaultTestExecutionListenerClasses()}.
240         * @return an <em>unmodifiable</em> list of names of default {@code TestExecutionListener}
241         * classes
242         * @see SpringFactoriesLoader#loadFactoryNames
243         */
244        protected List<String> getDefaultTestExecutionListenerClassNames() {
245                List<String> classNames =
246                                SpringFactoriesLoader.loadFactoryNames(TestExecutionListener.class, getClass().getClassLoader());
247                if (logger.isInfoEnabled()) {
248                        logger.info(String.format("Loaded default TestExecutionListener class names from location [%s]: %s",
249                                        SpringFactoriesLoader.FACTORIES_RESOURCE_LOCATION, classNames));
250                }
251                return Collections.unmodifiableList(classNames);
252        }
253
254        /**
255         * {@inheritDoc}
256         */
257        @SuppressWarnings("unchecked")
258        @Override
259        public final MergedContextConfiguration buildMergedContextConfiguration() {
260                Class<?> testClass = getBootstrapContext().getTestClass();
261                CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate = getCacheAwareContextLoaderDelegate();
262
263                if (MetaAnnotationUtils.findAnnotationDescriptorForTypes(
264                                testClass, ContextConfiguration.class, ContextHierarchy.class) == null) {
265                        return buildDefaultMergedContextConfiguration(testClass, cacheAwareContextLoaderDelegate);
266                }
267
268                if (AnnotationUtils.findAnnotation(testClass, ContextHierarchy.class) != null) {
269                        Map<String, List<ContextConfigurationAttributes>> hierarchyMap =
270                                        ContextLoaderUtils.buildContextHierarchyMap(testClass);
271                        MergedContextConfiguration parentConfig = null;
272                        MergedContextConfiguration mergedConfig = null;
273
274                        for (List<ContextConfigurationAttributes> list : hierarchyMap.values()) {
275                                List<ContextConfigurationAttributes> reversedList = new ArrayList<ContextConfigurationAttributes>(list);
276                                Collections.reverse(reversedList);
277
278                                // Don't use the supplied testClass; instead ensure that we are
279                                // building the MCC for the actual test class that declared the
280                                // configuration for the current level in the context hierarchy.
281                                Assert.notEmpty(reversedList, "ContextConfigurationAttributes list must not be empty");
282                                Class<?> declaringClass = reversedList.get(0).getDeclaringClass();
283
284                                mergedConfig = buildMergedContextConfiguration(
285                                                declaringClass, reversedList, parentConfig, cacheAwareContextLoaderDelegate, true);
286                                parentConfig = mergedConfig;
287                        }
288
289                        // Return the last level in the context hierarchy
290                        return mergedConfig;
291                }
292                else {
293                        return buildMergedContextConfiguration(testClass,
294                                        ContextLoaderUtils.resolveContextConfigurationAttributes(testClass),
295                                        null, cacheAwareContextLoaderDelegate, true);
296                }
297        }
298
299        private MergedContextConfiguration buildDefaultMergedContextConfiguration(Class<?> testClass,
300                        CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate) {
301
302                List<ContextConfigurationAttributes> defaultConfigAttributesList =
303                                Collections.singletonList(new ContextConfigurationAttributes(testClass));
304
305                ContextLoader contextLoader = resolveContextLoader(testClass, defaultConfigAttributesList);
306                if (logger.isInfoEnabled()) {
307                        logger.info(String.format(
308                                        "Neither @ContextConfiguration nor @ContextHierarchy found for test class [%s], using %s",
309                                        testClass.getName(), contextLoader.getClass().getSimpleName()));
310                }
311                return buildMergedContextConfiguration(testClass, defaultConfigAttributesList, null,
312                                cacheAwareContextLoaderDelegate, false);
313        }
314
315        /**
316         * Build the {@link MergedContextConfiguration merged context configuration}
317         * for the supplied {@link Class testClass}, context configuration attributes,
318         * and parent context configuration.
319         * @param testClass the test class for which the {@code MergedContextConfiguration}
320         * should be built (must not be {@code null})
321         * @param configAttributesList the list of context configuration attributes for the
322         * specified test class, ordered <em>bottom-up</em> (i.e., as if we were
323         * traversing up the class hierarchy); never {@code null} or empty
324         * @param parentConfig the merged context configuration for the parent application
325         * context in a context hierarchy, or {@code null} if there is no parent
326         * @param cacheAwareContextLoaderDelegate the cache-aware context loader delegate to
327         * be passed to the {@code MergedContextConfiguration} constructor
328         * @param requireLocationsClassesOrInitializers whether locations, classes, or
329         * initializers are required; typically {@code true} but may be set to {@code false}
330         * if the configured loader supports empty configuration
331         * @return the merged context configuration
332         * @see #resolveContextLoader
333         * @see ContextLoaderUtils#resolveContextConfigurationAttributes
334         * @see SmartContextLoader#processContextConfiguration
335         * @see ContextLoader#processLocations
336         * @see ActiveProfilesUtils#resolveActiveProfiles
337         * @see ApplicationContextInitializerUtils#resolveInitializerClasses
338         * @see MergedContextConfiguration
339         */
340        private MergedContextConfiguration buildMergedContextConfiguration(Class<?> testClass,
341                        List<ContextConfigurationAttributes> configAttributesList, MergedContextConfiguration parentConfig,
342                        CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate,
343                        boolean requireLocationsClassesOrInitializers) {
344
345                Assert.notEmpty(configAttributesList, "ContextConfigurationAttributes list must not be null or empty");
346
347                ContextLoader contextLoader = resolveContextLoader(testClass, configAttributesList);
348                List<String> locations = new ArrayList<String>();
349                List<Class<?>> classes = new ArrayList<Class<?>>();
350                List<Class<?>> initializers = new ArrayList<Class<?>>();
351
352                for (ContextConfigurationAttributes configAttributes : configAttributesList) {
353                        if (logger.isTraceEnabled()) {
354                                logger.trace(String.format("Processing locations and classes for context configuration attributes %s",
355                                                configAttributes));
356                        }
357                        if (contextLoader instanceof SmartContextLoader) {
358                                SmartContextLoader smartContextLoader = (SmartContextLoader) contextLoader;
359                                smartContextLoader.processContextConfiguration(configAttributes);
360                                locations.addAll(0, Arrays.asList(configAttributes.getLocations()));
361                                classes.addAll(0, Arrays.asList(configAttributes.getClasses()));
362                        }
363                        else {
364                                String[] processedLocations = contextLoader.processLocations(
365                                                configAttributes.getDeclaringClass(), configAttributes.getLocations());
366                                locations.addAll(0, Arrays.asList(processedLocations));
367                                // Legacy ContextLoaders don't know how to process classes
368                        }
369                        initializers.addAll(0, Arrays.asList(configAttributes.getInitializers()));
370                        if (!configAttributes.isInheritLocations()) {
371                                break;
372                        }
373                }
374
375                Set<ContextCustomizer> contextCustomizers = getContextCustomizers(testClass,
376                                Collections.unmodifiableList(configAttributesList));
377
378                if (requireLocationsClassesOrInitializers &&
379                                areAllEmpty(locations, classes, initializers, contextCustomizers)) {
380                        throw new IllegalStateException(String.format(
381                                        "%s was unable to detect defaults, and no ApplicationContextInitializers " +
382                                        "or ContextCustomizers were declared for context configuration attributes %s",
383                                        contextLoader.getClass().getSimpleName(), configAttributesList));
384                }
385
386                MergedTestPropertySources mergedTestPropertySources =
387                                TestPropertySourceUtils.buildMergedTestPropertySources(testClass);
388                MergedContextConfiguration mergedConfig = new MergedContextConfiguration(testClass,
389                                StringUtils.toStringArray(locations),
390                                ClassUtils.toClassArray(classes),
391                                ApplicationContextInitializerUtils.resolveInitializerClasses(configAttributesList),
392                                ActiveProfilesUtils.resolveActiveProfiles(testClass),
393                                mergedTestPropertySources.getLocations(),
394                                mergedTestPropertySources.getProperties(),
395                                contextCustomizers, contextLoader, cacheAwareContextLoaderDelegate, parentConfig);
396
397                return processMergedContextConfiguration(mergedConfig);
398        }
399
400        private Set<ContextCustomizer> getContextCustomizers(Class<?> testClass,
401                        List<ContextConfigurationAttributes> configAttributes) {
402
403                List<ContextCustomizerFactory> factories = getContextCustomizerFactories();
404                Set<ContextCustomizer> customizers = new LinkedHashSet<ContextCustomizer>(factories.size());
405                for (ContextCustomizerFactory factory : factories) {
406                        ContextCustomizer customizer = factory.createContextCustomizer(testClass, configAttributes);
407                        if (customizer != null) {
408                                customizers.add(customizer);
409                        }
410                }
411                return customizers;
412        }
413
414        /**
415         * Get the {@link ContextCustomizerFactory} instances for this bootstrapper.
416         * <p>The default implementation uses the {@link SpringFactoriesLoader} mechanism
417         * for loading factories configured in all {@code META-INF/spring.factories}
418         * files on the classpath.
419         * @since 4.3
420         * @see SpringFactoriesLoader#loadFactories
421         */
422        protected List<ContextCustomizerFactory> getContextCustomizerFactories() {
423                return SpringFactoriesLoader.loadFactories(ContextCustomizerFactory.class, getClass().getClassLoader());
424        }
425
426        /**
427         * Resolve the {@link ContextLoader} {@linkplain Class class} to use for the
428         * supplied list of {@link ContextConfigurationAttributes} and then instantiate
429         * and return that {@code ContextLoader}.
430         * <p>If the user has not explicitly declared which loader to use, the value
431         * returned from {@link #getDefaultContextLoaderClass} will be used as the
432         * default context loader class. For details on the class resolution process,
433         * see {@link #resolveExplicitContextLoaderClass} and
434         * {@link #getDefaultContextLoaderClass}.
435         * @param testClass the test class for which the {@code ContextLoader} should be
436         * resolved; must not be {@code null}
437         * @param configAttributesList the list of configuration attributes to process; must
438         * not be {@code null}; must be ordered <em>bottom-up</em>
439         * (i.e., as if we were traversing up the class hierarchy)
440         * @return the resolved {@code ContextLoader} for the supplied {@code testClass}
441         * (never {@code null})
442         * @throws IllegalStateException if {@link #getDefaultContextLoaderClass(Class)}
443         * returns {@code null}
444         */
445        protected ContextLoader resolveContextLoader(Class<?> testClass,
446                        List<ContextConfigurationAttributes> configAttributesList) {
447
448                Assert.notNull(testClass, "Class must not be null");
449                Assert.notNull(configAttributesList, "ContextConfigurationAttributes list must not be null");
450
451                Class<? extends ContextLoader> contextLoaderClass = resolveExplicitContextLoaderClass(configAttributesList);
452                if (contextLoaderClass == null) {
453                        contextLoaderClass = getDefaultContextLoaderClass(testClass);
454                        if (contextLoaderClass == null) {
455                                throw new IllegalStateException("getDefaultContextLoaderClass() must not return null");
456                        }
457                }
458                if (logger.isTraceEnabled()) {
459                        logger.trace(String.format("Using ContextLoader class [%s] for test class [%s]",
460                                        contextLoaderClass.getName(), testClass.getName()));
461                }
462                return BeanUtils.instantiateClass(contextLoaderClass, ContextLoader.class);
463        }
464
465        /**
466         * Resolve the {@link ContextLoader} {@linkplain Class class} to use for the supplied
467         * list of {@link ContextConfigurationAttributes}.
468         * <p>Beginning with the first level in the context configuration attributes hierarchy:
469         * <ol>
470         * <li>If the {@link ContextConfigurationAttributes#getContextLoaderClass()
471         * contextLoaderClass} property of {@link ContextConfigurationAttributes} is
472         * configured with an explicit class, that class will be returned.</li>
473         * <li>If an explicit {@code ContextLoader} class is not specified at the current
474         * level in the hierarchy, traverse to the next level in the hierarchy and return to
475         * step #1.</li>
476         * </ol>
477         * @param configAttributesList the list of configuration attributes to process;
478         * must not be {@code null}; must be ordered <em>bottom-up</em>
479         * (i.e., as if we were traversing up the class hierarchy)
480         * @return the {@code ContextLoader} class to use for the supplied configuration
481         * attributes, or {@code null} if no explicit loader is found
482         * @throws IllegalArgumentException if supplied configuration attributes are
483         * {@code null} or <em>empty</em>
484         */
485        protected Class<? extends ContextLoader> resolveExplicitContextLoaderClass(
486                        List<ContextConfigurationAttributes> configAttributesList) {
487
488                Assert.notNull(configAttributesList, "ContextConfigurationAttributes list must not be null");
489
490                for (ContextConfigurationAttributes configAttributes : configAttributesList) {
491                        if (logger.isTraceEnabled()) {
492                                logger.trace(String.format("Resolving ContextLoader for context configuration attributes %s",
493                                                configAttributes));
494                        }
495                        Class<? extends ContextLoader> contextLoaderClass = configAttributes.getContextLoaderClass();
496                        if (ContextLoader.class != contextLoaderClass) {
497                                if (logger.isDebugEnabled()) {
498                                        logger.debug(String.format(
499                                                        "Found explicit ContextLoader class [%s] for context configuration attributes %s",
500                                                        contextLoaderClass.getName(), configAttributes));
501                                }
502                                return contextLoaderClass;
503                        }
504                }
505                return null;
506        }
507
508        /**
509         * Get the {@link CacheAwareContextLoaderDelegate} to use for transparent
510         * interaction with the {@code ContextCache}.
511         * <p>The default implementation simply delegates to
512         * {@code getBootstrapContext().getCacheAwareContextLoaderDelegate()}.
513         * <p>Concrete subclasses may choose to override this method to return a custom
514         * {@code CacheAwareContextLoaderDelegate} implementation with custom
515         * {@link org.springframework.test.context.cache.ContextCache ContextCache} support.
516         * @return the context loader delegate (never {@code null})
517         */
518        protected CacheAwareContextLoaderDelegate getCacheAwareContextLoaderDelegate() {
519                return getBootstrapContext().getCacheAwareContextLoaderDelegate();
520        }
521
522        /**
523         * Determine the default {@link ContextLoader} {@linkplain Class class}
524         * to use for the supplied test class.
525         * <p>The class returned by this method will only be used if a {@code ContextLoader}
526         * class has not been explicitly declared via {@link ContextConfiguration#loader}.
527         * @param testClass the test class for which to retrieve the default
528         * {@code ContextLoader} class
529         * @return the default {@code ContextLoader} class for the supplied test class
530         * (never {@code null})
531         */
532        protected abstract Class<? extends ContextLoader> getDefaultContextLoaderClass(Class<?> testClass);
533
534        /**
535         * Process the supplied, newly instantiated {@link MergedContextConfiguration} instance.
536         * <p>The returned {@link MergedContextConfiguration} instance may be a wrapper
537         * around or a replacement for the original.
538         * <p>The default implementation simply returns the supplied instance unmodified.
539         * <p>Concrete subclasses may choose to return a specialized subclass of
540         * {@link MergedContextConfiguration} based on properties in the supplied instance.
541         * @param mergedConfig the {@code MergedContextConfiguration} to process; never {@code null}
542         * @return a fully initialized {@code MergedContextConfiguration}; never {@code null}
543         */
544        protected MergedContextConfiguration processMergedContextConfiguration(MergedContextConfiguration mergedConfig) {
545                return mergedConfig;
546        }
547
548
549        private static boolean areAllEmpty(Collection<?>... collections) {
550                for (Collection<?> collection : collections) {
551                        if (!collection.isEmpty()) {
552                                return false;
553                        }
554                }
555                return true;
556        }
557
558}