AbstractContextLoader.java 13.2 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 51
 * <p>As of Spring 3.1, <code>AbstractContextLoader</code> also provides a basis
 * 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
	public void processContextConfiguration(ContextConfigurationAttributes configAttributes) {
90 91 92 93
		String[] processedLocations = processLocations(configAttributes.getDeclaringClass(),
			configAttributes.getLocations());
		configAttributes.setLocations(processedLocations);
	}
A
Arjen Poutsma 已提交
94

95 96 97 98 99 100 101 102 103 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
	/**
	 * Prepare the {@link ConfigurableApplicationContext} created by this 
	 * {@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
	 * <code>MergedContextConfiguration</code> in the
	 * {@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);
		}
	}

153 154
	// --- ContextLoader -------------------------------------------------------

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

	/**
183
	 * Generate the default classpath resource locations array based on the
A
Arjen Poutsma 已提交
184
	 * supplied class.
185
	 *
A
Arjen Poutsma 已提交
186 187 188 189 190
	 * <p>For example, if the supplied class is <code>com.example.MyTest</code>,
	 * the generated locations will contain a single string with a value of
	 * &quot;classpath:/com/example/MyTest<code>&lt;suffix&gt;</code>&quot;,
	 * where <code>&lt;suffix&gt;</code> is the value of the
	 * {@link #getResourceSuffix() resource suffix} string.
191
	 *
192
	 * <p>As of Spring 3.1, the implementation of this method adheres to the
S
Stevo Slavic 已提交
193
	 * contract defined in the {@link SmartContextLoader} SPI. Specifically,
194 195 196
	 * 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.
197
	 *
A
Arjen Poutsma 已提交
198 199
	 * <p>Subclasses can override this method to implement a different
	 * <em>default location generation</em> strategy.
200
	 *
A
Arjen Poutsma 已提交
201 202
	 * @param clazz the class for which the default locations are to be generated
	 * @return an array of default application context resource locations
203
	 * @since 2.5
A
Arjen Poutsma 已提交
204 205 206 207 208 209
	 * @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");
210
		String resourcePath = SLASH + ClassUtils.convertClassNameToResourcePath(clazz.getName()) + suffix;
211 212
		String prefixedResourcePath = ResourceUtils.CLASSPATH_URL_PREFIX + resourcePath;
		ClassPathResource classPathResource = new ClassPathResource(resourcePath, clazz);
213

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

222
		// else
223 224 225 226 227
		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 已提交
228 229 230
	}

	/**
S
Sam Brannen 已提交
231
	 * Generate a modified version of the supplied locations array and return it.
232
	 *
233 234
	 * <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 已提交
235
	 * the specified class is defined. A path starting with a slash is treated
236
	 * as an absolute classpath location, for example:
A
Arjen Poutsma 已提交
237 238 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:},
	 * {@link ResourceUtils#FILE_URL_PREFIX file:}, <code>http:</code>,
	 * etc.) will be added to the results unchanged.
242
	 *
A
Arjen Poutsma 已提交
243 244
	 * <p>Subclasses can override this method to implement a different
	 * <em>location modification</em> strategy.
245
	 *
A
Arjen Poutsma 已提交
246 247 248
	 * @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
249
	 * @since 2.5
A
Arjen Poutsma 已提交
250 251 252 253 254
	 */
	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];
255
			if (path.startsWith(SLASH)) {
A
Arjen Poutsma 已提交
256
				modifiedLocations[i] = ResourceUtils.CLASSPATH_URL_PREFIX + path;
257
			} else if (!ResourcePatternUtils.isUrl(path)) {
258 259
				modifiedLocations[i] = ResourceUtils.CLASSPATH_URL_PREFIX + SLASH
						+ StringUtils.cleanPath(ClassUtils.classPackageAsResourcePath(clazz) + SLASH + path);
260
			} else {
A
Arjen Poutsma 已提交
261 262 263 264 265 266 267 268 269
				modifiedLocations[i] = StringUtils.cleanPath(path);
			}
		}
		return modifiedLocations;
	}

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

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

}