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}