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}