AbstractContextLoader.java 13.1 KB
Newer Older
A
Arjen Poutsma 已提交
1
/*
S
Stevo Slavic 已提交
2
 * Copyright 2002-2012 the original author or authors.
A
Arjen Poutsma 已提交
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.test.context.support;

19 20 21 22 23
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;

24 25
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
26
import org.springframework.beans.BeanUtils;
A
Arjen Poutsma 已提交
27
import org.springframework.context.ApplicationContext;
28 29 30 31
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
32
import org.springframework.core.io.ClassPathResource;
A
Arjen Poutsma 已提交
33
import org.springframework.core.io.support.ResourcePatternUtils;
34
import org.springframework.test.context.ContextConfigurationAttributes;
A
Arjen Poutsma 已提交
35
import org.springframework.test.context.ContextLoader;
36
import org.springframework.test.context.MergedContextConfiguration;
37
import org.springframework.test.context.SmartContextLoader;
A
Arjen Poutsma 已提交
38 39 40 41 42 43 44
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ResourceUtils;
import org.springframework.util.StringUtils;

/**
45
 * Abstract application context loader that provides a basis for all concrete
46
 * implementations of the {@link ContextLoader} SPI. Provides a
J
Juergen Hoeller 已提交
47
 * <em>Template Method</em> based approach for {@link #processLocations processing}
48
 * resource locations.
S
Stevo Slavic 已提交
49
 *
50
 * <p>As of Spring 3.1, {@code AbstractContextLoader} also provides a basis
51
 * for all concrete implementations of the {@link SmartContextLoader} SPI. For
S
Stevo Slavic 已提交
52 53 54
 * backwards compatibility with the {@code ContextLoader} SPI,
 * {@link #processContextConfiguration(ContextConfigurationAttributes)} delegates
 * to {@link #processLocations(Class, String...)}.
A
Arjen Poutsma 已提交
55 56 57 58 59 60 61
 *
 * @author Sam Brannen
 * @author Juergen Hoeller
 * @since 2.5
 * @see #generateDefaultLocations
 * @see #modifyLocations
 */
62 63
public abstract class AbstractContextLoader implements SmartContextLoader {

64 65
	private static final Log logger = LogFactory.getLog(AbstractContextLoader.class);

66
	private static final String[] EMPTY_STRING_ARRAY = new String[0];
67 68 69
	private static final String SLASH = "/";


70 71
	// --- SmartContextLoader -----------------------------------------------

72
	/**
73
	 * For backwards compatibility with the {@link ContextLoader} SPI, the
S
Stevo Slavic 已提交
74
	 * default implementation simply delegates to {@link #processLocations(Class, String...)},
75 76 77 78 79 80 81
	 * passing it the {@link ContextConfigurationAttributes#getDeclaringClass()
	 * declaring class} and {@link ContextConfigurationAttributes#getLocations()
	 * resource locations} retrieved from the supplied
	 * {@link ContextConfigurationAttributes configuration attributes}. The
	 * processed locations are then
	 * {@link ContextConfigurationAttributes#setLocations(String[]) set} in
	 * the supplied configuration attributes.
82
	 *
83
	 * <p>Can be overridden in subclasses &mdash; for example, to process
84 85
	 * annotated classes instead of resource locations.
	 *
86
	 * @since 3.1
S
Stevo Slavic 已提交
87
	 * @see #processLocations(Class, String...)
88
	 */
89
	@Override
90
	public void processContextConfiguration(ContextConfigurationAttributes configAttributes) {
91 92 93 94
		String[] processedLocations = processLocations(configAttributes.getDeclaringClass(),
			configAttributes.getLocations());
		configAttributes.setLocations(processedLocations);
	}
A
Arjen Poutsma 已提交
95

96
	/**
97
	 * Prepare the {@link ConfigurableApplicationContext} created by this
98 99 100 101 102
	 * {@code SmartContextLoader} <i>before</i> bean definitions are read.
	 *
	 * <p>The default implementation:
	 * <ul>
	 * <li>Sets the <em>active bean definition profiles</em> from the supplied
103
	 * {@code MergedContextConfiguration} in the
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
	 * {@link org.springframework.core.env.Environment Environment} of the context.</li>
	 * <li>Determines what (if any) context initializer classes have been supplied
	 * via the {@code MergedContextConfiguration} and
	 * {@linkplain ApplicationContextInitializer#initialize invokes each} with the
	 * given application context.</li>
	 * </ul>
	 *
	 * <p>Any {@code ApplicationContextInitializers} implementing
	 * {@link org.springframework.core.Ordered Ordered} or marked with {@link
	 * org.springframework.core.annotation.Order @Order} will be sorted appropriately.
	 *
	 * @param context the newly created application context
	 * @param mergedConfig the merged context configuration
	 * @see ApplicationContextInitializer#initialize(ConfigurableApplicationContext)
	 * @see #loadContext(MergedContextConfiguration)
	 * @see ConfigurableApplicationContext#setId
	 * @since 3.2
	 */
	@SuppressWarnings("unchecked")
	protected void prepareContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) {

		context.getEnvironment().setActiveProfiles(mergedConfig.getActiveProfiles());

		Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializerClasses = mergedConfig.getContextInitializerClasses();

		if (initializerClasses.size() == 0) {
			// no ApplicationContextInitializers have been declared -> nothing to do
			return;
		}

		final List<ApplicationContextInitializer<ConfigurableApplicationContext>> initializerInstances = new ArrayList<ApplicationContextInitializer<ConfigurableApplicationContext>>();
		final Class<?> contextClass = context.getClass();

		for (Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>> initializerClass : initializerClasses) {
			Class<?> initializerContextClass = GenericTypeResolver.resolveTypeArgument(initializerClass,
				ApplicationContextInitializer.class);
			Assert.isAssignable(initializerContextClass, contextClass, String.format(
				"Could not add context initializer [%s] since its generic parameter [%s] "
						+ "is not assignable from the type of application context used by this "
						+ "context loader [%s]: ", initializerClass.getName(), initializerContextClass.getName(),
				contextClass.getName()));
			initializerInstances.add((ApplicationContextInitializer<ConfigurableApplicationContext>) BeanUtils.instantiateClass(initializerClass));
		}

		Collections.sort(initializerInstances, new AnnotationAwareOrderComparator());
		for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : initializerInstances) {
			initializer.initialize(context);
		}
	}

154 155
	// --- ContextLoader -------------------------------------------------------

A
Arjen Poutsma 已提交
156
	/**
157
	 * If the supplied {@code locations} are {@code null} or
158
	 * <em>empty</em> and {@link #isGenerateDefaultLocations()} returns
159
	 * {@code true}, default locations will be
A
Arjen Poutsma 已提交
160 161 162
	 * {@link #generateDefaultLocations(Class) generated} for the specified
	 * {@link Class class} and the configured
	 * {@link #getResourceSuffix() resource suffix}; otherwise, the supplied
163
	 * {@code locations} will be {@link #modifyLocations modified} if
J
Juergen Hoeller 已提交
164
	 * necessary and returned.
165
	 *
A
Arjen Poutsma 已提交
166 167 168
	 * @param clazz the class with which the locations are associated: to be
	 * used when generating default locations
	 * @param locations the unmodified locations to use for loading the
169
	 * application context (can be {@code null} or empty)
170
	 * @return a processed array of application context resource locations
171
	 * @since 2.5
172
	 * @see #isGenerateDefaultLocations()
S
Stevo Slavic 已提交
173 174 175 176
	 * @see #generateDefaultLocations(Class)
	 * @see #modifyLocations(Class, String...)
	 * @see org.springframework.test.context.ContextLoader#processLocations(Class, String...)
	 * @see #processContextConfiguration(ContextConfigurationAttributes)
A
Arjen Poutsma 已提交
177
	 */
178
	@Override
A
Arjen Poutsma 已提交
179
	public final String[] processLocations(Class<?> clazz, String... locations) {
180 181
		return (ObjectUtils.isEmpty(locations) && isGenerateDefaultLocations()) ? generateDefaultLocations(clazz)
				: modifyLocations(clazz, locations);
A
Arjen Poutsma 已提交
182 183 184
	}

	/**
185
	 * Generate the default classpath resource locations array based on the
A
Arjen Poutsma 已提交
186
	 * supplied class.
187
	 *
188
	 * <p>For example, if the supplied class is {@code com.example.MyTest},
A
Arjen Poutsma 已提交
189
	 * the generated locations will contain a single string with a value of
190 191
	 * &quot;classpath:/com/example/MyTest{@code &lt;suffix&gt;}&quot;,
	 * where {@code &lt;suffix&gt;} is the value of the
A
Arjen Poutsma 已提交
192
	 * {@link #getResourceSuffix() resource suffix} string.
193
	 *
194
	 * <p>As of Spring 3.1, the implementation of this method adheres to the
S
Stevo Slavic 已提交
195
	 * contract defined in the {@link SmartContextLoader} SPI. Specifically,
196 197 198
	 * this method will <em>preemptively</em> verify that the generated default
	 * location actually exists. If it does not exist, this method will log a
	 * warning and return an empty array.
199
	 *
A
Arjen Poutsma 已提交
200 201
	 * <p>Subclasses can override this method to implement a different
	 * <em>default location generation</em> strategy.
202
	 *
A
Arjen Poutsma 已提交
203 204
	 * @param clazz the class for which the default locations are to be generated
	 * @return an array of default application context resource locations
205
	 * @since 2.5
A
Arjen Poutsma 已提交
206 207 208 209 210 211
	 * @see #getResourceSuffix()
	 */
	protected String[] generateDefaultLocations(Class<?> clazz) {
		Assert.notNull(clazz, "Class must not be null");
		String suffix = getResourceSuffix();
		Assert.hasText(suffix, "Resource suffix must not be empty");
212
		String resourcePath = SLASH + ClassUtils.convertClassNameToResourcePath(clazz.getName()) + suffix;
213 214
		String prefixedResourcePath = ResourceUtils.CLASSPATH_URL_PREFIX + resourcePath;
		ClassPathResource classPathResource = new ClassPathResource(resourcePath, clazz);
215

216
		if (classPathResource.exists()) {
217
			if (logger.isInfoEnabled()) {
218 219
				logger.info(String.format("Detected default resource location \"%s\" for test class [%s].",
					prefixedResourcePath, clazz.getName()));
220
			}
221
			return new String[] { prefixedResourcePath };
222
		}
223

224
		// else
225 226 227 228 229
		if (logger.isInfoEnabled()) {
			logger.info(String.format("Could not detect default resource locations for test class [%s]: "
					+ "%s does not exist.", clazz.getName(), classPathResource));
		}
		return EMPTY_STRING_ARRAY;
A
Arjen Poutsma 已提交
230 231 232
	}

	/**
S
Sam Brannen 已提交
233
	 * Generate a modified version of the supplied locations array and return it.
234
	 *
235 236
	 * <p>A plain path &mdash; for example, &quot;context.xml&quot; &mdash; will
	 * be treated as a classpath resource that is relative to the package in which
S
Sam Brannen 已提交
237
	 * the specified class is defined. A path starting with a slash is treated
238
	 * as an absolute classpath location, for example:
A
Arjen Poutsma 已提交
239 240 241
	 * &quot;/org/springframework/whatever/foo.xml&quot;. A path which
	 * references a URL (e.g., a path prefixed with
	 * {@link ResourceUtils#CLASSPATH_URL_PREFIX classpath:},
242
	 * {@link ResourceUtils#FILE_URL_PREFIX file:}, {@code http:},
A
Arjen Poutsma 已提交
243
	 * etc.) will be added to the results unchanged.
244
	 *
A
Arjen Poutsma 已提交
245 246
	 * <p>Subclasses can override this method to implement a different
	 * <em>location modification</em> strategy.
247
	 *
A
Arjen Poutsma 已提交
248 249 250
	 * @param clazz the class with which the locations are associated
	 * @param locations the resource locations to be modified
	 * @return an array of modified application context resource locations
251
	 * @since 2.5
A
Arjen Poutsma 已提交
252 253 254 255 256
	 */
	protected String[] modifyLocations(Class<?> clazz, String... locations) {
		String[] modifiedLocations = new String[locations.length];
		for (int i = 0; i < locations.length; i++) {
			String path = locations[i];
257
			if (path.startsWith(SLASH)) {
A
Arjen Poutsma 已提交
258
				modifiedLocations[i] = ResourceUtils.CLASSPATH_URL_PREFIX + path;
259
			} else if (!ResourcePatternUtils.isUrl(path)) {
260 261
				modifiedLocations[i] = ResourceUtils.CLASSPATH_URL_PREFIX + SLASH
						+ StringUtils.cleanPath(ClassUtils.classPackageAsResourcePath(clazz) + SLASH + path);
262
			} else {
A
Arjen Poutsma 已提交
263 264 265 266 267 268 269 270
				modifiedLocations[i] = StringUtils.cleanPath(path);
			}
		}
		return modifiedLocations;
	}

	/**
	 * Determine whether or not <em>default</em> resource locations should be
271 272
	 * generated if the {@code locations} provided to
	 * {@link #processLocations(Class, String...)} are {@code null} or empty.
273
	 *
274 275
	 * <p>As of Spring 3.1, the semantics of this method have been overloaded
	 * to include detection of either default resource locations or default
S
Stevo Slavic 已提交
276
	 * configuration classes. Consequently, this method can also be used to
277
	 * determine whether or not <em>default</em> configuration classes should be
278
	 * detected if the {@code classes} present in the
279
	 * {@link ContextConfigurationAttributes configuration attributes} supplied
S
Stevo Slavic 已提交
280
	 * to {@link #processContextConfiguration(ContextConfigurationAttributes)}
281
	 * are {@code null} or empty.
282
	 *
A
Arjen Poutsma 已提交
283
	 * <p>Can be overridden by subclasses to change the default behavior.
284
	 *
285
	 * @return always {@code true} by default
286
	 * @since 2.5
A
Arjen Poutsma 已提交
287 288 289 290 291 292
	 */
	protected boolean isGenerateDefaultLocations() {
		return true;
	}

	/**
293 294 295
	 * Get the suffix to append to {@link ApplicationContext} resource locations
	 * when generating default locations.
	 *
A
Arjen Poutsma 已提交
296
	 * <p>Must be implemented by subclasses.
297
	 *
298
	 * @return the resource suffix; should not be {@code null} or empty
299
	 * @since 2.5
S
Stevo Slavic 已提交
300
	 * @see #generateDefaultLocations(Class)
A
Arjen Poutsma 已提交
301 302 303 304
	 */
	protected abstract String getResourceSuffix();

}