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