001/* 002 * Copyright 2002-2016 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; 018 019import java.io.Serializable; 020import java.util.Arrays; 021import java.util.Collections; 022import java.util.LinkedHashSet; 023import java.util.Set; 024 025import org.springframework.context.ApplicationContext; 026import org.springframework.context.ApplicationContextInitializer; 027import org.springframework.context.ConfigurableApplicationContext; 028import org.springframework.core.style.ToStringCreator; 029import org.springframework.util.Assert; 030import org.springframework.util.ObjectUtils; 031import org.springframework.util.StringUtils; 032 033/** 034 * {@code MergedContextConfiguration} encapsulates the <em>merged</em> 035 * context configuration declared on a test class and all of its superclasses 036 * via {@link ContextConfiguration @ContextConfiguration}, 037 * {@link ActiveProfiles @ActiveProfiles}, and 038 * {@link TestPropertySource @TestPropertySource}. 039 * 040 * <p>Merged context resource locations, annotated classes, active profiles, 041 * property resource locations, and in-lined properties represent all declared 042 * values in the test class hierarchy taking into consideration the semantics 043 * of the {@link ContextConfiguration#inheritLocations}, 044 * {@link ActiveProfiles#inheritProfiles}, 045 * {@link TestPropertySource#inheritLocations}, and 046 * {@link TestPropertySource#inheritProperties} flags. 047 * 048 * <p>A {@link SmartContextLoader} uses {@code MergedContextConfiguration} 049 * to load an {@link org.springframework.context.ApplicationContext ApplicationContext}. 050 * 051 * <p>{@code MergedContextConfiguration} is also used by the 052 * {@link org.springframework.test.context.cache.ContextCache ContextCache} 053 * as the key for caching an 054 * {@link org.springframework.context.ApplicationContext ApplicationContext} 055 * that was loaded using properties of this {@code MergedContextConfiguration}. 056 * 057 * @author Sam Brannen 058 * @author Phillip Webb 059 * @since 3.1 060 * @see ContextConfiguration 061 * @see ContextHierarchy 062 * @see ActiveProfiles 063 * @see TestPropertySource 064 * @see ContextConfigurationAttributes 065 * @see SmartContextLoader#loadContext(MergedContextConfiguration) 066 */ 067public class MergedContextConfiguration implements Serializable { 068 069 private static final long serialVersionUID = -3290560718464957422L; 070 071 private static final String[] EMPTY_STRING_ARRAY = new String[0]; 072 073 private static final Class<?>[] EMPTY_CLASS_ARRAY = new Class<?>[0]; 074 075 private static final Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> EMPTY_INITIALIZER_CLASSES = 076 Collections.<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> emptySet(); 077 078 private static final Set<ContextCustomizer> EMPTY_CONTEXT_CUSTOMIZERS = Collections.<ContextCustomizer> emptySet(); 079 080 081 private final Class<?> testClass; 082 083 private final String[] locations; 084 085 private final Class<?>[] classes; 086 087 private final Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> contextInitializerClasses; 088 089 private final String[] activeProfiles; 090 091 private final String[] propertySourceLocations; 092 093 private final String[] propertySourceProperties; 094 095 private final Set<ContextCustomizer> contextCustomizers; 096 097 private final ContextLoader contextLoader; 098 099 private final CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate; 100 101 private final MergedContextConfiguration parent; 102 103 104 private static String[] processStrings(String[] array) { 105 return (array != null ? array : EMPTY_STRING_ARRAY); 106 } 107 108 private static Class<?>[] processClasses(Class<?>[] classes) { 109 return (classes != null ? classes : EMPTY_CLASS_ARRAY); 110 } 111 112 private static Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> processContextInitializerClasses( 113 Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> contextInitializerClasses) { 114 115 return (contextInitializerClasses != null ? 116 Collections.unmodifiableSet(contextInitializerClasses) : EMPTY_INITIALIZER_CLASSES); 117 } 118 119 private static Set<ContextCustomizer> processContextCustomizers(Set<ContextCustomizer> contextCustomizers) { 120 return (contextCustomizers != null ? 121 Collections.unmodifiableSet(contextCustomizers) : EMPTY_CONTEXT_CUSTOMIZERS); 122 } 123 124 private static String[] processActiveProfiles(String[] activeProfiles) { 125 if (activeProfiles == null) { 126 return EMPTY_STRING_ARRAY; 127 } 128 129 // Active profiles must be unique 130 Set<String> profilesSet = new LinkedHashSet<String>(Arrays.asList(activeProfiles)); 131 return StringUtils.toStringArray(profilesSet); 132 } 133 134 /** 135 * Generate a null-safe {@link String} representation of the supplied 136 * {@link ContextLoader} based solely on the fully qualified name of the 137 * loader or "null" if the supplied loaded is {@code null}. 138 */ 139 protected static String nullSafeToString(ContextLoader contextLoader) { 140 return (contextLoader != null ? contextLoader.getClass().getName() : "null"); 141 } 142 143 144 /** 145 * Create a new {@code MergedContextConfiguration} instance for the 146 * supplied parameters. 147 * <p>Delegates to 148 * {@link #MergedContextConfiguration(Class, String[], Class[], Set, String[], String[], String[], ContextLoader, CacheAwareContextLoaderDelegate, MergedContextConfiguration)}. 149 * @param testClass the test class for which the configuration was merged 150 * @param locations the merged context resource locations 151 * @param classes the merged annotated classes 152 * @param activeProfiles the merged active bean definition profiles 153 * @param contextLoader the resolved {@code ContextLoader} 154 */ 155 public MergedContextConfiguration(Class<?> testClass, String[] locations, Class<?>[] classes, 156 String[] activeProfiles, ContextLoader contextLoader) { 157 158 this(testClass, locations, classes, null, activeProfiles, contextLoader); 159 } 160 161 /** 162 * Create a new {@code MergedContextConfiguration} instance for the 163 * supplied parameters. 164 * <p>Delegates to 165 * {@link #MergedContextConfiguration(Class, String[], Class[], Set, String[], String[], String[], ContextLoader, CacheAwareContextLoaderDelegate, MergedContextConfiguration)}. 166 * @param testClass the test class for which the configuration was merged 167 * @param locations the merged context resource locations 168 * @param classes the merged annotated classes 169 * @param contextInitializerClasses the merged context initializer classes 170 * @param activeProfiles the merged active bean definition profiles 171 * @param contextLoader the resolved {@code ContextLoader} 172 * @see #MergedContextConfiguration(Class, String[], Class[], Set, String[], ContextLoader, CacheAwareContextLoaderDelegate, MergedContextConfiguration) 173 */ 174 public MergedContextConfiguration(Class<?> testClass, String[] locations, Class<?>[] classes, 175 Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> contextInitializerClasses, 176 String[] activeProfiles, ContextLoader contextLoader) { 177 178 this(testClass, locations, classes, contextInitializerClasses, activeProfiles, contextLoader, null, null); 179 } 180 181 /** 182 * Create a new {@code MergedContextConfiguration} instance for the 183 * supplied parameters. 184 * <p>Delegates to 185 * {@link #MergedContextConfiguration(Class, String[], Class[], Set, String[], String[], String[], ContextLoader, CacheAwareContextLoaderDelegate, MergedContextConfiguration)}. 186 * @param testClass the test class for which the configuration was merged 187 * @param locations the merged context resource locations 188 * @param classes the merged annotated classes 189 * @param contextInitializerClasses the merged context initializer classes 190 * @param activeProfiles the merged active bean definition profiles 191 * @param contextLoader the resolved {@code ContextLoader} 192 * @param cacheAwareContextLoaderDelegate a cache-aware context loader 193 * delegate with which to retrieve the parent context 194 * @param parent the parent configuration or {@code null} if there is no parent 195 * @since 3.2.2 196 */ 197 public MergedContextConfiguration(Class<?> testClass, String[] locations, Class<?>[] classes, 198 Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> contextInitializerClasses, 199 String[] activeProfiles, ContextLoader contextLoader, 200 CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, MergedContextConfiguration parent) { 201 202 this(testClass, locations, classes, contextInitializerClasses, activeProfiles, null, null, contextLoader, 203 cacheAwareContextLoaderDelegate, parent); 204 } 205 206 /** 207 * Create a new {@code MergedContextConfiguration} instance by copying 208 * all fields from the supplied {@code MergedContextConfiguration}. 209 * @since 4.1 210 */ 211 public MergedContextConfiguration(MergedContextConfiguration mergedConfig) { 212 this(mergedConfig.testClass, mergedConfig.locations, mergedConfig.classes, 213 mergedConfig.contextInitializerClasses, mergedConfig.activeProfiles, mergedConfig.propertySourceLocations, 214 mergedConfig.propertySourceProperties, mergedConfig.contextCustomizers, 215 mergedConfig.contextLoader, mergedConfig.cacheAwareContextLoaderDelegate, mergedConfig.parent); 216 } 217 218 /** 219 * Create a new {@code MergedContextConfiguration} instance for the 220 * supplied parameters. 221 * <p>If a {@code null} value is supplied for {@code locations}, 222 * {@code classes}, {@code activeProfiles}, {@code propertySourceLocations}, 223 * or {@code propertySourceProperties} an empty array will be stored instead. 224 * If a {@code null} value is supplied for the 225 * {@code contextInitializerClasses} an empty set will be stored instead. 226 * Furthermore, active profiles will be sorted, and duplicate profiles 227 * will be removed. 228 * @param testClass the test class for which the configuration was merged 229 * @param locations the merged context resource locations 230 * @param classes the merged annotated classes 231 * @param contextInitializerClasses the merged context initializer classes 232 * @param activeProfiles the merged active bean definition profiles 233 * @param propertySourceLocations the merged {@code PropertySource} locations 234 * @param propertySourceProperties the merged {@code PropertySource} properties 235 * @param contextLoader the resolved {@code ContextLoader} 236 * @param cacheAwareContextLoaderDelegate a cache-aware context loader 237 * delegate with which to retrieve the parent context 238 * @param parent the parent configuration or {@code null} if there is no parent 239 * @since 4.1 240 */ 241 public MergedContextConfiguration(Class<?> testClass, String[] locations, Class<?>[] classes, 242 Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> contextInitializerClasses, 243 String[] activeProfiles, String[] propertySourceLocations, String[] propertySourceProperties, 244 ContextLoader contextLoader, CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, 245 MergedContextConfiguration parent) { 246 this(testClass, locations, classes, contextInitializerClasses, activeProfiles, 247 propertySourceLocations, propertySourceProperties, 248 EMPTY_CONTEXT_CUSTOMIZERS, contextLoader, 249 cacheAwareContextLoaderDelegate, parent); 250 } 251 252 /** 253 * Create a new {@code MergedContextConfiguration} instance for the 254 * supplied parameters. 255 * <p>If a {@code null} value is supplied for {@code locations}, 256 * {@code classes}, {@code activeProfiles}, {@code propertySourceLocations}, 257 * or {@code propertySourceProperties} an empty array will be stored instead. 258 * If a {@code null} value is supplied for {@code contextInitializerClasses} 259 * or {@code contextCustomizers}, an empty set will be stored instead. 260 * Furthermore, active profiles will be sorted, and duplicate profiles 261 * will be removed. 262 * @param testClass the test class for which the configuration was merged 263 * @param locations the merged context resource locations 264 * @param classes the merged annotated classes 265 * @param contextInitializerClasses the merged context initializer classes 266 * @param activeProfiles the merged active bean definition profiles 267 * @param propertySourceLocations the merged {@code PropertySource} locations 268 * @param propertySourceProperties the merged {@code PropertySource} properties 269 * @param contextCustomizers the context customizers 270 * @param contextLoader the resolved {@code ContextLoader} 271 * @param cacheAwareContextLoaderDelegate a cache-aware context loader 272 * delegate with which to retrieve the parent context 273 * @param parent the parent configuration or {@code null} if there is no parent 274 * @since 4.3 275 */ 276 public MergedContextConfiguration(Class<?> testClass, String[] locations, Class<?>[] classes, 277 Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> contextInitializerClasses, 278 String[] activeProfiles, String[] propertySourceLocations, String[] propertySourceProperties, 279 Set<ContextCustomizer> contextCustomizers, ContextLoader contextLoader, 280 CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, MergedContextConfiguration parent) { 281 282 this.testClass = testClass; 283 this.locations = processStrings(locations); 284 this.classes = processClasses(classes); 285 this.contextInitializerClasses = processContextInitializerClasses(contextInitializerClasses); 286 this.activeProfiles = processActiveProfiles(activeProfiles); 287 this.propertySourceLocations = processStrings(propertySourceLocations); 288 this.propertySourceProperties = processStrings(propertySourceProperties); 289 this.contextCustomizers = processContextCustomizers(contextCustomizers); 290 this.contextLoader = contextLoader; 291 this.cacheAwareContextLoaderDelegate = cacheAwareContextLoaderDelegate; 292 this.parent = parent; 293 } 294 295 296 /** 297 * Get the {@linkplain Class test class} associated with this 298 * {@code MergedContextConfiguration}. 299 */ 300 public Class<?> getTestClass() { 301 return this.testClass; 302 } 303 304 /** 305 * Get the merged resource locations for {@code ApplicationContext} 306 * configuration files for the {@linkplain #getTestClass() test class}. 307 * <p>Context resource locations typically represent XML configuration 308 * files or Groovy scripts. 309 */ 310 public String[] getLocations() { 311 return this.locations; 312 } 313 314 /** 315 * Get the merged annotated classes for the {@linkplain #getTestClass() test class}. 316 */ 317 public Class<?>[] getClasses() { 318 return this.classes; 319 } 320 321 /** 322 * Determine if this {@code MergedContextConfiguration} instance has 323 * path-based context resource locations. 324 * @return {@code true} if the {@link #getLocations() locations} array is not empty 325 * @since 4.0.4 326 * @see #hasResources() 327 * @see #hasClasses() 328 */ 329 public boolean hasLocations() { 330 return !ObjectUtils.isEmpty(getLocations()); 331 } 332 333 /** 334 * Determine if this {@code MergedContextConfiguration} instance has 335 * class-based resources. 336 * @return {@code true} if the {@link #getClasses() classes} array is not empty 337 * @since 4.0.4 338 * @see #hasResources() 339 * @see #hasLocations() 340 */ 341 public boolean hasClasses() { 342 return !ObjectUtils.isEmpty(getClasses()); 343 } 344 345 /** 346 * Determine if this {@code MergedContextConfiguration} instance has 347 * either path-based context resource locations or class-based resources. 348 * @return {@code true} if either the {@link #getLocations() locations} 349 * or the {@link #getClasses() classes} array is not empty 350 * @since 4.0.4 351 * @see #hasLocations() 352 * @see #hasClasses() 353 */ 354 public boolean hasResources() { 355 return (hasLocations() || hasClasses()); 356 } 357 358 /** 359 * Get the merged {@code ApplicationContextInitializer} classes for the 360 * {@linkplain #getTestClass() test class}. 361 */ 362 public Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> getContextInitializerClasses() { 363 return this.contextInitializerClasses; 364 } 365 366 /** 367 * Get the merged active bean definition profiles for the 368 * {@linkplain #getTestClass() test class}. 369 * @see ActiveProfiles 370 */ 371 public String[] getActiveProfiles() { 372 return this.activeProfiles; 373 } 374 375 /** 376 * Get the merged resource locations for test {@code PropertySources} 377 * for the {@linkplain #getTestClass() test class}. 378 * @see TestPropertySource#locations 379 * @see java.util.Properties 380 */ 381 public String[] getPropertySourceLocations() { 382 return this.propertySourceLocations; 383 } 384 385 /** 386 * Get the merged test {@code PropertySource} properties for the 387 * {@linkplain #getTestClass() test class}. 388 * <p>Properties will be loaded into the {@code Environment}'s set of 389 * {@code PropertySources}. 390 * @see TestPropertySource#properties 391 * @see java.util.Properties 392 */ 393 public String[] getPropertySourceProperties() { 394 return this.propertySourceProperties; 395 } 396 397 /** 398 * Get the merged {@link ContextCustomizer ContextCustomizers} that will be applied 399 * when the application context is loaded. 400 */ 401 public Set<ContextCustomizer> getContextCustomizers() { 402 return this.contextCustomizers; 403 } 404 405 /** 406 * Get the resolved {@link ContextLoader} for the {@linkplain #getTestClass() test class}. 407 */ 408 public ContextLoader getContextLoader() { 409 return this.contextLoader; 410 } 411 412 /** 413 * Get the {@link MergedContextConfiguration} for the parent application context 414 * in a context hierarchy. 415 * @return the parent configuration or {@code null} if there is no parent 416 * @see #getParentApplicationContext() 417 * @since 3.2.2 418 */ 419 public MergedContextConfiguration getParent() { 420 return this.parent; 421 } 422 423 /** 424 * Get the parent {@link ApplicationContext} for the context defined by this 425 * {@code MergedContextConfiguration} from the context cache. 426 * <p>If the parent context has not yet been loaded, it will be loaded, stored 427 * in the cache, and then returned. 428 * @return the parent {@code ApplicationContext} or {@code null} if there is no parent 429 * @see #getParent() 430 * @since 3.2.2 431 */ 432 public ApplicationContext getParentApplicationContext() { 433 if (this.parent == null) { 434 return null; 435 } 436 Assert.state(this.cacheAwareContextLoaderDelegate != null, 437 "Cannot retrieve a parent application context without access to the CacheAwareContextLoaderDelegate"); 438 return this.cacheAwareContextLoaderDelegate.loadContext(this.parent); 439 } 440 441 442 /** 443 * Determine if the supplied object is equal to this {@code MergedContextConfiguration} 444 * instance by comparing both object's {@linkplain #getLocations() locations}, 445 * {@linkplain #getClasses() annotated classes}, 446 * {@linkplain #getContextInitializerClasses() context initializer classes}, 447 * {@linkplain #getActiveProfiles() active profiles}, 448 * {@linkplain #getPropertySourceLocations() property source locations}, 449 * {@linkplain #getPropertySourceProperties() property source properties}, 450 * {@linkplain #getParent() parents}, and the fully qualified names of their 451 * {@link #getContextLoader() ContextLoaders}. 452 */ 453 @Override 454 public boolean equals(Object other) { 455 if (this == other) { 456 return true; 457 } 458 if (other == null || other.getClass() != getClass()) { 459 return false; 460 } 461 462 MergedContextConfiguration otherConfig = (MergedContextConfiguration) other; 463 if (!Arrays.equals(this.locations, otherConfig.locations)) { 464 return false; 465 } 466 if (!Arrays.equals(this.classes, otherConfig.classes)) { 467 return false; 468 } 469 if (!this.contextInitializerClasses.equals(otherConfig.contextInitializerClasses)) { 470 return false; 471 } 472 if (!Arrays.equals(this.activeProfiles, otherConfig.activeProfiles)) { 473 return false; 474 } 475 if (!Arrays.equals(this.propertySourceLocations, otherConfig.propertySourceLocations)) { 476 return false; 477 } 478 if (!Arrays.equals(this.propertySourceProperties, otherConfig.propertySourceProperties)) { 479 return false; 480 } 481 if (!this.contextCustomizers.equals(otherConfig.contextCustomizers)) { 482 return false; 483 } 484 485 if (this.parent == null) { 486 if (otherConfig.parent != null) { 487 return false; 488 } 489 } 490 else if (!this.parent.equals(otherConfig.parent)) { 491 return false; 492 } 493 494 if (!nullSafeToString(this.contextLoader).equals(nullSafeToString(otherConfig.contextLoader))) { 495 return false; 496 } 497 498 return true; 499 } 500 501 /** 502 * Generate a unique hash code for all properties of this 503 * {@code MergedContextConfiguration} excluding the 504 * {@linkplain #getTestClass() test class}. 505 */ 506 @Override 507 public int hashCode() { 508 int result = Arrays.hashCode(this.locations); 509 result = 31 * result + Arrays.hashCode(this.classes); 510 result = 31 * result + this.contextInitializerClasses.hashCode(); 511 result = 31 * result + Arrays.hashCode(this.activeProfiles); 512 result = 31 * result + Arrays.hashCode(this.propertySourceLocations); 513 result = 31 * result + Arrays.hashCode(this.propertySourceProperties); 514 result = 31 * result + this.contextCustomizers.hashCode(); 515 result = 31 * result + (this.parent != null ? this.parent.hashCode() : 0); 516 result = 31 * result + nullSafeToString(this.contextLoader).hashCode(); 517 return result; 518 } 519 520 /** 521 * Provide a String representation of the {@linkplain #getTestClass() test class}, 522 * {@linkplain #getLocations() locations}, {@linkplain #getClasses() annotated classes}, 523 * {@linkplain #getContextInitializerClasses() context initializer classes}, 524 * {@linkplain #getActiveProfiles() active profiles}, 525 * {@linkplain #getPropertySourceLocations() property source locations}, 526 * {@linkplain #getPropertySourceProperties() property source properties}, 527 * {@linkplain #getContextCustomizers() context customizers}, 528 * the name of the {@link #getContextLoader() ContextLoader}, and the 529 * {@linkplain #getParent() parent configuration}. 530 */ 531 @Override 532 public String toString() { 533 return new ToStringCreator(this) 534 .append("testClass", this.testClass) 535 .append("locations", ObjectUtils.nullSafeToString(this.locations)) 536 .append("classes", ObjectUtils.nullSafeToString(this.classes)) 537 .append("contextInitializerClasses", ObjectUtils.nullSafeToString(this.contextInitializerClasses)) 538 .append("activeProfiles", ObjectUtils.nullSafeToString(this.activeProfiles)) 539 .append("propertySourceLocations", ObjectUtils.nullSafeToString(this.propertySourceLocations)) 540 .append("propertySourceProperties", ObjectUtils.nullSafeToString(this.propertySourceProperties)) 541 .append("contextCustomizers", this.contextCustomizers) 542 .append("contextLoader", nullSafeToString(this.contextLoader)) 543 .append("parent", this.parent) 544 .toString(); 545 } 546 547}