/* * Copyright 2002-2008 the original author or authors. * * 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; import org.springframework.beans.factory.support.BeanDefinitionReader; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.support.GenericApplicationContext; import org.springframework.util.ClassUtils; import org.springframework.util.ResourceUtils; import org.springframework.util.StringUtils; /** * <p> * Abstract JUnit 3.8 test class that holds and exposes a single Spring * {@link org.springframework.context.ApplicationContext ApplicationContext}. * </p> * <p> * This class will cache contexts based on a <i>context key</i>: normally the * config locations String array describing the Spring resource descriptors * making up the context. Unless the {@link #setDirty()} method is called by a * test, the context will not be reloaded, even across different subclasses of * this test. This is particularly beneficial if your context is slow to * construct, for example if you are using Hibernate and the time taken to load * the mappings is an issue. * </p> * <p> * For such standard usage, simply override the {@link #getConfigLocations()} * method and provide the desired config files. For alternative configuration * options, see {@link #getConfigPath()} and {@link #getConfigPaths()}. * </p> * <p> * If you don't want to load a standard context from an array of config * locations, you can override the {@link #contextKey()} method. In conjunction * with this you typically need to override the {@link #loadContext(Object)} * method, which by default loads the locations specified in the * {@link #getConfigLocations()} method. * </p> * <p> * <b>WARNING:</b> When doing integration tests from within Eclipse, only use * classpath resource URLs. Else, you may see misleading failures when changing * context locations. * </p> * * @author Juergen Hoeller * @author Rod Johnson * @author Sam Brannen * @since 2.0 * @see #getConfigLocations() * @see #contextKey() * @see #loadContext(Object) * @see #getApplicationContext() */ public abstract class AbstractSingleSpringContextTests extends AbstractSpringContextTests { /** Application context this test will run against */ protected ConfigurableApplicationContext applicationContext; private int loadCount = 0; /** * Default constructor for AbstractSingleSpringContextTests. */ public AbstractSingleSpringContextTests() { } /** * Constructor for AbstractSingleSpringContextTests with a JUnit name. * @param name the name of this text fixture */ public AbstractSingleSpringContextTests(String name) { super(name); } /** * This implementation is final. Override <code>onSetUp</code> for custom behavior. * @see #onSetUp() */ protected final void setUp() throws Exception { // lazy load, in case getApplicationContext() has not yet been called. if (this.applicationContext == null) { this.applicationContext = getContext(contextKey()); } prepareTestInstance(); onSetUp(); } /** * Prepare this test instance, for example populating its fields. * The context has already been loaded at the time of this callback. * <p>The default implementation does nothing. * @throws Exception in case of preparation failure */ protected void prepareTestInstance() throws Exception { } /** * Subclasses can override this method in place of the <code>setUp()</code> * method, which is final in this class. * <p>The default implementation does nothing. * @throws Exception simply let any exception propagate */ protected void onSetUp() throws Exception { } /** * Called to say that the "applicationContext" instance variable is dirty * and should be reloaded. We need to do this if a test has modified the * context (for example, by replacing a bean definition). */ protected void setDirty() { setDirty(contextKey()); } /** * This implementation is final. Override <code>onTearDown</code> for * custom behavior. * @see #onTearDown() */ protected final void tearDown() throws Exception { onTearDown(); } /** * Subclasses can override this to add custom behavior on teardown. * @throws Exception simply let any exception propagate */ protected void onTearDown() throws Exception { } /** * Return a key for this context. Default is the config location array as * determined by {@link #getConfigLocations()}. * <p>If you override this method, you will typically have to override * {@link #loadContext(Object)} as well, being able to handle the key type * that this method returns. * @return the context key * @see #getConfigLocations() */ protected Object contextKey() { return getConfigLocations(); } /** * This implementation assumes a key of type String array and loads a * context from the given locations. * <p>If you override {@link #contextKey()}, you will typically have to * override this method as well, being able to handle the key type that * <code>contextKey()</code> returns. * @see #getConfigLocations() */ protected ConfigurableApplicationContext loadContext(Object key) throws Exception { return loadContextLocations((String[]) key); } /** * Load a Spring ApplicationContext from the given config locations. * <p>The default implementation creates a standard * {@link #createApplicationContext GenericApplicationContext}, allowing * for customizing the internal bean factory through * {@link #customizeBeanFactory}. * @param locations the config locations (as Spring resource locations, * e.g. full classpath locations or any kind of URL) * @return the corresponding ApplicationContext instance (potentially cached) * @throws Exception if context loading failed * @see #createApplicationContext(String[]) */ protected ConfigurableApplicationContext loadContextLocations(String[] locations) throws Exception { ++this.loadCount; if (this.logger.isInfoEnabled()) { this.logger.info("Loading context for locations: " + StringUtils.arrayToCommaDelimitedString(locations)); } return createApplicationContext(locations); } /** * Create a Spring {@link ConfigurableApplicationContext} for use by this test. * <p>The default implementation creates a standard {@link GenericApplicationContext} * instance, calls the {@link #prepareApplicationContext} prepareApplicationContext} * method and the {@link #customizeBeanFactory customizeBeanFactory} method to allow * for customizing the context and its DefaultListableBeanFactory, populates the * context from the specified config <code>locations</code> through the configured * {@link #createBeanDefinitionReader(GenericApplicationContext) BeanDefinitionReader}, * and finally {@link ConfigurableApplicationContext#refresh() refreshes} the context. * @param locations the config locations (as Spring resource locations, * e.g. full classpath locations or any kind of URL) * @return the GenericApplicationContext instance * @see #loadContextLocations(String[]) * @see #customizeBeanFactory(DefaultListableBeanFactory) * @see #createBeanDefinitionReader(GenericApplicationContext) */ protected ConfigurableApplicationContext createApplicationContext(String[] locations) { GenericApplicationContext context = new GenericApplicationContext(); prepareApplicationContext(context); customizeBeanFactory(context.getDefaultListableBeanFactory()); createBeanDefinitionReader(context).loadBeanDefinitions(locations); context.refresh(); return context; } /** * Prepare the GenericApplicationContext used by this test. * Called before bean definitions are read. * <p>The default implementation is empty. Can be overridden in subclasses to * customize GenericApplicationContext's standard settings. * @param context the context for which the BeanDefinitionReader should be created * @see #createApplicationContext * @see org.springframework.context.support.GenericApplicationContext#setResourceLoader * @see org.springframework.context.support.GenericApplicationContext#setId */ protected void prepareApplicationContext(GenericApplicationContext context) { } /** * Customize the internal bean factory of the ApplicationContext used by * this test. Called before bean definitions are read. * <p>The default implementation is empty. Can be overridden in subclasses to * customize DefaultListableBeanFactory's standard settings. * @param beanFactory the newly created bean factory for this context * @see #loadContextLocations * @see #createApplicationContext * @see org.springframework.beans.factory.support.DefaultListableBeanFactory#setAllowBeanDefinitionOverriding * @see org.springframework.beans.factory.support.DefaultListableBeanFactory#setAllowEagerClassLoading * @see org.springframework.beans.factory.support.DefaultListableBeanFactory#setAllowCircularReferences * @see org.springframework.beans.factory.support.DefaultListableBeanFactory#setAllowRawInjectionDespiteWrapping */ protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) { } /** * Factory method for creating new {@link BeanDefinitionReader}s for * loading bean definitions into the supplied * {@link GenericApplicationContext context}. * <p>The default implementation creates a new {@link XmlBeanDefinitionReader}. * Can be overridden in subclasses to provide a different * BeanDefinitionReader implementation. * @param context the context for which the BeanDefinitionReader should be created * @return a BeanDefinitionReader for the supplied context * @see #createApplicationContext(String[]) * @see BeanDefinitionReader * @see XmlBeanDefinitionReader */ protected BeanDefinitionReader createBeanDefinitionReader(GenericApplicationContext context) { return new XmlBeanDefinitionReader(context); } /** * Subclasses can override this method to return the locations of their * config files, unless they override {@link #contextKey()} and * {@link #loadContext(Object)} instead. * <p>A plain path will be treated as class path location, e.g.: * "org/springframework/whatever/foo.xml". Note however that you may prefix * path locations with standard Spring resource prefixes. Therefore, a * config location path prefixed with "classpath:" with behave the same as a * plain path, but a config location such as * "file:/some/path/path/location/appContext.xml" will be treated as a * filesystem location. * <p>The default implementation builds config locations for the config paths * specified through {@link #getConfigPaths()}. * @return an array of config locations * @see #getConfigPaths() * @see org.springframework.core.io.ResourceLoader#getResource(String) */ protected String[] getConfigLocations() { String[] paths = getConfigPaths(); String[] locations = new String[paths.length]; for (int i = 0; i < paths.length; i++) { String path = paths[i]; if (path.startsWith("/")) { locations[i] = ResourceUtils.CLASSPATH_URL_PREFIX + path; } else { locations[i] = ResourceUtils.CLASSPATH_URL_PREFIX + StringUtils.cleanPath(ClassUtils.classPackageAsResourcePath(getClass()) + "/" + path); } } return locations; } /** * Subclasses can override this method to return paths to their config * files, relative to the concrete test class. * <p>A plain path, e.g. "context.xml", will be loaded as classpath resource * from the same package that the concrete test class is defined in. A path * starting with a slash is treated as fully qualified class path location, * e.g.: "/org/springframework/whatever/foo.xml". * <p>The default implementation builds an array for the config path specified * through {@link #getConfigPath()}. * @return an array of config locations * @see #getConfigPath() * @see java.lang.Class#getResource(String) */ protected String[] getConfigPaths() { String path = getConfigPath(); return (path != null ? new String[] { path } : new String[0]); } /** * Subclasses can override this method to return a single path to a config * file, relative to the concrete test class. * <p>A plain path, e.g. "context.xml", will be loaded as classpath resource * from the same package that the concrete test class is defined in. A path * starting with a slash is treated as fully qualified class path location, * e.g.: "/org/springframework/whatever/foo.xml". * <p>The default implementation simply returns <code>null</code>. * @return an array of config locations * @see #getConfigPath() * @see java.lang.Class#getResource(String) */ protected String getConfigPath() { return null; } /** * Return the ApplicationContext that this base class manages; may be * <code>null</code>. */ public final ConfigurableApplicationContext getApplicationContext() { // lazy load, in case setUp() has not yet been called. if (this.applicationContext == null) { try { this.applicationContext = getContext(contextKey()); } catch (Exception e) { // log and continue... if (this.logger.isDebugEnabled()) { this.logger.debug("Caught exception while retrieving the ApplicationContext for test [" + getClass().getName() + "." + getName() + "].", e); } } } return this.applicationContext; } /** * Return the current number of context load attempts. */ public final int getLoadCount() { return this.loadCount; } }