001/* 002 * Copyright 2002-2017 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.util.Arrays; 020 021import org.apache.commons.logging.Log; 022import org.apache.commons.logging.LogFactory; 023 024import org.springframework.context.ApplicationContextInitializer; 025import org.springframework.core.annotation.AnnotationAttributes; 026import org.springframework.core.style.ToStringCreator; 027import org.springframework.lang.Nullable; 028import org.springframework.util.Assert; 029import org.springframework.util.ObjectUtils; 030import org.springframework.util.StringUtils; 031 032/** 033 * {@code ContextConfigurationAttributes} encapsulates the context configuration 034 * attributes declared via {@link ContextConfiguration @ContextConfiguration}. 035 * 036 * @author Sam Brannen 037 * @author Phillip Webb 038 * @since 3.1 039 * @see ContextConfiguration 040 * @see SmartContextLoader#processContextConfiguration(ContextConfigurationAttributes) 041 * @see MergedContextConfiguration 042 */ 043public class ContextConfigurationAttributes { 044 045 private static final String[] EMPTY_LOCATIONS = new String[0]; 046 047 private static final Class<?>[] EMPTY_CLASSES = new Class<?>[0]; 048 049 050 private static final Log logger = LogFactory.getLog(ContextConfigurationAttributes.class); 051 052 private final Class<?> declaringClass; 053 054 private Class<?>[] classes = new Class<?>[0]; 055 056 private String[] locations = new String[0]; 057 058 private final boolean inheritLocations; 059 060 private final Class<? extends ApplicationContextInitializer<?>>[] initializers; 061 062 private final boolean inheritInitializers; 063 064 @Nullable 065 private final String name; 066 067 private final Class<? extends ContextLoader> contextLoaderClass; 068 069 070 /** 071 * Construct a new {@link ContextConfigurationAttributes} instance with default values. 072 * @param declaringClass the test class that declared {@code @ContextConfiguration}, 073 * either explicitly or implicitly 074 * @since 4.3 075 */ 076 @SuppressWarnings("unchecked") 077 public ContextConfigurationAttributes(Class<?> declaringClass) { 078 this(declaringClass, EMPTY_LOCATIONS, EMPTY_CLASSES, false, (Class[]) EMPTY_CLASSES, true, ContextLoader.class); 079 } 080 081 /** 082 * Construct a new {@link ContextConfigurationAttributes} instance for the 083 * supplied {@link ContextConfiguration @ContextConfiguration} annotation and 084 * the {@linkplain Class test class} that declared it. 085 * @param declaringClass the test class that declared {@code @ContextConfiguration} 086 * @param contextConfiguration the annotation from which to retrieve the attributes 087 */ 088 public ContextConfigurationAttributes(Class<?> declaringClass, ContextConfiguration contextConfiguration) { 089 this(declaringClass, contextConfiguration.locations(), contextConfiguration.classes(), 090 contextConfiguration.inheritLocations(), contextConfiguration.initializers(), 091 contextConfiguration.inheritInitializers(), contextConfiguration.name(), contextConfiguration.loader()); 092 } 093 094 /** 095 * Construct a new {@link ContextConfigurationAttributes} instance for the 096 * supplied {@link AnnotationAttributes} (parsed from a 097 * {@link ContextConfiguration @ContextConfiguration} annotation) and 098 * the {@linkplain Class test class} that declared them. 099 * @param declaringClass the test class that declared {@code @ContextConfiguration} 100 * @param annAttrs the annotation attributes from which to retrieve the attributes 101 */ 102 @SuppressWarnings("unchecked") 103 public ContextConfigurationAttributes(Class<?> declaringClass, AnnotationAttributes annAttrs) { 104 this(declaringClass, annAttrs.getStringArray("locations"), annAttrs.getClassArray("classes"), 105 annAttrs.getBoolean("inheritLocations"), 106 (Class<? extends ApplicationContextInitializer<?>>[]) annAttrs.getClassArray("initializers"), 107 annAttrs.getBoolean("inheritInitializers"), annAttrs.getString("name"), annAttrs.getClass("loader")); 108 } 109 110 /** 111 * Construct a new {@link ContextConfigurationAttributes} instance for the 112 * {@linkplain Class test class} that declared the 113 * {@link ContextConfiguration @ContextConfiguration} annotation and its 114 * corresponding attributes. 115 * @param declaringClass the test class that declared {@code @ContextConfiguration} 116 * @param locations the resource locations declared via {@code @ContextConfiguration} 117 * @param classes the annotated classes declared via {@code @ContextConfiguration} 118 * @param inheritLocations the {@code inheritLocations} flag declared via {@code @ContextConfiguration} 119 * @param initializers the context initializers declared via {@code @ContextConfiguration} 120 * @param inheritInitializers the {@code inheritInitializers} flag declared via {@code @ContextConfiguration} 121 * @param contextLoaderClass the {@code ContextLoader} class declared via {@code @ContextConfiguration} 122 * @throws IllegalArgumentException if the {@code declaringClass} or {@code contextLoaderClass} is 123 * {@code null} 124 */ 125 public ContextConfigurationAttributes( 126 Class<?> declaringClass, String[] locations, Class<?>[] classes, boolean inheritLocations, 127 Class<? extends ApplicationContextInitializer<?>>[] initializers, 128 boolean inheritInitializers, Class<? extends ContextLoader> contextLoaderClass) { 129 130 this(declaringClass, locations, classes, inheritLocations, initializers, inheritInitializers, null, 131 contextLoaderClass); 132 } 133 134 /** 135 * Construct a new {@link ContextConfigurationAttributes} instance for the 136 * {@linkplain Class test class} that declared the 137 * {@link ContextConfiguration @ContextConfiguration} annotation and its 138 * corresponding attributes. 139 * @param declaringClass the test class that declared {@code @ContextConfiguration} 140 * @param locations the resource locations declared via {@code @ContextConfiguration} 141 * @param classes the annotated classes declared via {@code @ContextConfiguration} 142 * @param inheritLocations the {@code inheritLocations} flag declared via {@code @ContextConfiguration} 143 * @param initializers the context initializers declared via {@code @ContextConfiguration} 144 * @param inheritInitializers the {@code inheritInitializers} flag declared via {@code @ContextConfiguration} 145 * @param name the name of level in the context hierarchy, or {@code null} if not applicable 146 * @param contextLoaderClass the {@code ContextLoader} class declared via {@code @ContextConfiguration} 147 * @throws IllegalArgumentException if the {@code declaringClass} or {@code contextLoaderClass} is 148 * {@code null} 149 */ 150 public ContextConfigurationAttributes( 151 Class<?> declaringClass, String[] locations, Class<?>[] classes, boolean inheritLocations, 152 Class<? extends ApplicationContextInitializer<?>>[] initializers, 153 boolean inheritInitializers, @Nullable String name, Class<? extends ContextLoader> contextLoaderClass) { 154 155 Assert.notNull(declaringClass, "'declaringClass' must not be null"); 156 Assert.notNull(contextLoaderClass, "'contextLoaderClass' must not be null"); 157 158 if (!ObjectUtils.isEmpty(locations) && !ObjectUtils.isEmpty(classes) && logger.isDebugEnabled()) { 159 logger.debug(String.format( 160 "Test class [%s] has been configured with @ContextConfiguration's 'locations' (or 'value') %s " + 161 "and 'classes' %s attributes. Most SmartContextLoader implementations support " + 162 "only one declaration of resources per @ContextConfiguration annotation.", 163 declaringClass.getName(), ObjectUtils.nullSafeToString(locations), 164 ObjectUtils.nullSafeToString(classes))); 165 } 166 167 this.declaringClass = declaringClass; 168 this.locations = locations; 169 this.classes = classes; 170 this.inheritLocations = inheritLocations; 171 this.initializers = initializers; 172 this.inheritInitializers = inheritInitializers; 173 this.name = (StringUtils.hasText(name) ? name : null); 174 this.contextLoaderClass = contextLoaderClass; 175 } 176 177 178 /** 179 * Get the {@linkplain Class class} that declared the 180 * {@link ContextConfiguration @ContextConfiguration} annotation, either explicitly 181 * or implicitly. 182 * @return the declaring class (never {@code null}) 183 */ 184 public Class<?> getDeclaringClass() { 185 return this.declaringClass; 186 } 187 188 /** 189 * Set the <em>processed</em> annotated classes, effectively overriding the 190 * original value declared via {@link ContextConfiguration @ContextConfiguration}. 191 * @see #getClasses() 192 */ 193 public void setClasses(Class<?>... classes) { 194 this.classes = classes; 195 } 196 197 /** 198 * Get the annotated classes that were declared via 199 * {@link ContextConfiguration @ContextConfiguration}. 200 * <p>Note: this is a mutable property. The returned value may therefore 201 * represent a <em>processed</em> value that does not match the original value 202 * declared via {@link ContextConfiguration @ContextConfiguration}. 203 * @return the annotated classes (potentially {<em>empty</em>) 204 * @see ContextConfiguration#classes 205 * @see #setClasses(Class[]) 206 */ 207 public Class<?>[] getClasses() { 208 return this.classes; 209 } 210 211 /** 212 * Determine if this {@code ContextConfigurationAttributes} instance has 213 * class-based resources. 214 * @return {@code true} if the {@link #getClasses() classes} array is not empty 215 * @see #hasResources() 216 * @see #hasLocations() 217 */ 218 public boolean hasClasses() { 219 return (getClasses().length > 0); 220 } 221 222 /** 223 * Set the <em>processed</em> resource locations, effectively overriding the 224 * original value declared via {@link ContextConfiguration @ContextConfiguration}. 225 * @see #getLocations() 226 */ 227 public void setLocations(String... locations) { 228 this.locations = locations; 229 } 230 231 /** 232 * Get the resource locations that were declared via 233 * {@link ContextConfiguration @ContextConfiguration}. 234 * <p>Note: this is a mutable property. The returned value may therefore 235 * represent a <em>processed</em> value that does not match the original value 236 * declared via {@link ContextConfiguration @ContextConfiguration}. 237 * @return the resource locations (potentially <em>empty</em>) 238 * @see ContextConfiguration#value 239 * @see ContextConfiguration#locations 240 * @see #setLocations 241 */ 242 public String[] getLocations() { 243 return this.locations; 244 } 245 246 /** 247 * Determine if this {@code ContextConfigurationAttributes} instance has 248 * path-based resource locations. 249 * @return {@code true} if the {@link #getLocations() locations} array is not empty 250 * @see #hasResources() 251 * @see #hasClasses() 252 */ 253 public boolean hasLocations() { 254 return (getLocations().length > 0); 255 } 256 257 /** 258 * Determine if this {@code ContextConfigurationAttributes} instance has 259 * either path-based resource locations or class-based resources. 260 * @return {@code true} if either the {@link #getLocations() locations} 261 * or the {@link #getClasses() classes} array is not empty 262 * @see #hasLocations() 263 * @see #hasClasses() 264 */ 265 public boolean hasResources() { 266 return (hasLocations() || hasClasses()); 267 } 268 269 /** 270 * Get the {@code inheritLocations} flag that was declared via 271 * {@link ContextConfiguration @ContextConfiguration}. 272 * @return the {@code inheritLocations} flag 273 * @see ContextConfiguration#inheritLocations 274 */ 275 public boolean isInheritLocations() { 276 return this.inheritLocations; 277 } 278 279 /** 280 * Get the {@code ApplicationContextInitializer} classes that were declared via 281 * {@link ContextConfiguration @ContextConfiguration}. 282 * @return the {@code ApplicationContextInitializer} classes 283 * @since 3.2 284 */ 285 public Class<? extends ApplicationContextInitializer<?>>[] getInitializers() { 286 return this.initializers; 287 } 288 289 /** 290 * Get the {@code inheritInitializers} flag that was declared via 291 * {@link ContextConfiguration @ContextConfiguration}. 292 * @return the {@code inheritInitializers} flag 293 * @since 3.2 294 */ 295 public boolean isInheritInitializers() { 296 return this.inheritInitializers; 297 } 298 299 /** 300 * Get the name of the context hierarchy level that was declared via 301 * {@link ContextConfiguration @ContextConfiguration}. 302 * @return the name of the context hierarchy level or {@code null} if not applicable 303 * @see ContextConfiguration#name() 304 * @since 3.2.2 305 */ 306 @Nullable 307 public String getName() { 308 return this.name; 309 } 310 311 /** 312 * Get the {@code ContextLoader} class that was declared via 313 * {@link ContextConfiguration @ContextConfiguration}. 314 * @return the {@code ContextLoader} class 315 * @see ContextConfiguration#loader 316 */ 317 public Class<? extends ContextLoader> getContextLoaderClass() { 318 return this.contextLoaderClass; 319 } 320 321 322 /** 323 * Determine if the supplied object is equal to this 324 * {@code ContextConfigurationAttributes} instance by comparing both object's 325 * {@linkplain #getDeclaringClass() declaring class}, 326 * {@linkplain #getLocations() locations}, 327 * {@linkplain #getClasses() annotated classes}, 328 * {@linkplain #isInheritLocations() inheritLocations flag}, 329 * {@linkplain #getInitializers() context initializer classes}, 330 * {@linkplain #isInheritInitializers() inheritInitializers flag}, and the 331 * {@link #getContextLoaderClass() ContextLoader class}. 332 */ 333 @Override 334 public boolean equals(@Nullable Object other) { 335 if (this == other) { 336 return true; 337 } 338 if (!(other instanceof ContextConfigurationAttributes)) { 339 return false; 340 } 341 ContextConfigurationAttributes otherAttr = (ContextConfigurationAttributes) other; 342 return (ObjectUtils.nullSafeEquals(this.declaringClass, otherAttr.declaringClass) && 343 Arrays.equals(this.classes, otherAttr.classes)) && 344 Arrays.equals(this.locations, otherAttr.locations) && 345 this.inheritLocations == otherAttr.inheritLocations && 346 Arrays.equals(this.initializers, otherAttr.initializers) && 347 this.inheritInitializers == otherAttr.inheritInitializers && 348 ObjectUtils.nullSafeEquals(this.name, otherAttr.name) && 349 ObjectUtils.nullSafeEquals(this.contextLoaderClass, otherAttr.contextLoaderClass); 350 } 351 352 /** 353 * Generate a unique hash code for all properties of this 354 * {@code ContextConfigurationAttributes} instance excluding the 355 * {@linkplain #getName() name}. 356 */ 357 @Override 358 public int hashCode() { 359 int result = this.declaringClass.hashCode(); 360 result = 31 * result + Arrays.hashCode(this.classes); 361 result = 31 * result + Arrays.hashCode(this.locations); 362 result = 31 * result + Arrays.hashCode(this.initializers); 363 return result; 364 } 365 366 /** 367 * Provide a String representation of the context configuration attributes 368 * and declaring class. 369 */ 370 @Override 371 public String toString() { 372 return new ToStringCreator(this) 373 .append("declaringClass", this.declaringClass.getName()) 374 .append("classes", ObjectUtils.nullSafeToString(this.classes)) 375 .append("locations", ObjectUtils.nullSafeToString(this.locations)) 376 .append("inheritLocations", this.inheritLocations) 377 .append("initializers", ObjectUtils.nullSafeToString(this.initializers)) 378 .append("inheritInitializers", this.inheritInitializers) 379 .append("name", this.name) 380 .append("contextLoaderClass", this.contextLoaderClass.getName()) 381 .toString(); 382 } 383 384}