001: /*
002: * Copyright 2002-2007 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: * http://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:
017: package org.springframework.test;
018:
019: import org.springframework.beans.factory.support.DefaultListableBeanFactory;
020: import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
021: import org.springframework.context.ConfigurableApplicationContext;
022: import org.springframework.context.support.GenericApplicationContext;
023: import org.springframework.util.ClassUtils;
024: import org.springframework.util.ResourceUtils;
025: import org.springframework.util.StringUtils;
026:
027: /**
028: * Abstract JUnit test class that holds and exposes a single Spring
029: * {@link org.springframework.context.ApplicationContext ApplicationContext}.
030: *
031: * <p>This class will cache contexts based on a <i>context key</i>: normally the
032: * config locations String array describing the Spring resource descriptors making
033: * up the context. Unless the {@link #setDirty()} method is called by a test, the
034: * context will not be reloaded, even across different subclasses of this test.
035: * This is particularly beneficial if your context is slow to construct, for example
036: * if you are using Hibernate and the time taken to load the mappings is an issue.
037: *
038: * <p>For such standard usage, simply override the {@link #getConfigLocations()}
039: * method and provide the desired config files.
040: *
041: * <p>If you don't want to load a standard context from an array of config locations,
042: * you can override the {@link #contextKey()} method. In conjunction with this you
043: * typically need to override the {@link #loadContext(Object)} method, which by
044: * default loads the locations specified in the {@link #getConfigLocations()} method.
045: *
046: * <p><b>WARNING:</b> When doing integration tests from within Eclipse, only use
047: * classpath resource URLs. Else, you may see misleading failures when changing
048: * context locations.
049: *
050: * @author Juergen Hoeller
051: * @author Rod Johnson
052: * @since 2.0
053: * @see #getConfigLocations()
054: * @see #contextKey()
055: * @see #loadContext(Object)
056: * @see #getApplicationContext()
057: */
058: public abstract class AbstractSingleSpringContextTests extends
059: AbstractSpringContextTests {
060:
061: /** Application context this test will run against */
062: protected ConfigurableApplicationContext applicationContext;
063:
064: private int loadCount = 0;
065:
066: /**
067: * Default constructor for AbstractDependencyInjectionSpringContextTests.
068: */
069: public AbstractSingleSpringContextTests() {
070: }
071:
072: /**
073: * Constructor for AbstractDependencyInjectionSpringContextTests with a JUnit name.
074: * @param name the name of this text fixture
075: */
076: public AbstractSingleSpringContextTests(String name) {
077: super (name);
078: }
079:
080: /**
081: * This implementation is final.
082: * Override <code>onSetUp</code> for custom behavior.
083: * @see #onSetUp()
084: */
085: protected final void setUp() throws Exception {
086: this .applicationContext = getContext(contextKey());
087: prepareTestInstance();
088: onSetUp();
089: }
090:
091: /**
092: * Prepare this test instance, for example populating its fields.
093: * The context has already been loaded at the time of this callback.
094: * <p>The default implementation does nothing.
095: * @throws Exception in case of preparation failure
096: */
097: protected void prepareTestInstance() throws Exception {
098: }
099:
100: /**
101: * Subclasses can override this method in place of the
102: * <code>setUp()</code> method, which is final in this class.
103: * This implementation does nothing.
104: * @throws Exception simply let any exception propagate
105: */
106: protected void onSetUp() throws Exception {
107: }
108:
109: /**
110: * Called to say that the "applicationContext" instance variable is dirty and
111: * should be reloaded. We need to do this if a test has modified the context
112: * (for example, by replacing a bean definition).
113: */
114: protected void setDirty() {
115: setDirty(contextKey());
116: }
117:
118: /**
119: * This implementation is final.
120: * Override <code>onTearDown</code> for custom behavior.
121: * @see #onTearDown()
122: */
123: protected final void tearDown() throws Exception {
124: onTearDown();
125: }
126:
127: /**
128: * Subclasses can override this to add custom behavior on teardown.
129: * @throws Exception simply let any exception propagate
130: */
131: protected void onTearDown() throws Exception {
132: }
133:
134: /**
135: * Return a key for this context. Default is the config location array
136: * as determined by {@link #getConfigLocations()}.
137: * <p>If you override this method, you will typically have to override
138: * {@link #loadContext(Object)} as well, being able to handle the key type
139: * that this method returns.
140: * @return the context key
141: * @see #getConfigLocations()
142: */
143: protected Object contextKey() {
144: return getConfigLocations();
145: }
146:
147: /**
148: * This implementation assumes a key of type String array and loads
149: * a context from the given locations.
150: * <p>If you override {@link #contextKey()}, you will typically have to
151: * override this method as well, being able to handle the key type
152: * that <code>contextKey()</code> returns.
153: * @see #getConfigLocations()
154: */
155: protected ConfigurableApplicationContext loadContext(Object key)
156: throws Exception {
157: return loadContextLocations((String[]) key);
158: }
159:
160: /**
161: * Load a Spring ApplicationContext from the given config locations.
162: * <p>The default implementation creates a standard
163: * {@link #createApplicationContext GenericApplicationContext},
164: * allowing for customizing the internal bean factory through
165: * {@link #customizeBeanFactory}.
166: * @param locations the config locations (as Spring resource locations,
167: * e.g. full classpath locations or any kind of URL)
168: * @return the corresponding ApplicationContext instance (potentially cached)
169: * @throws Exception if context loading failed
170: * @see #createApplicationContext
171: * @see #customizeBeanFactory
172: */
173: protected ConfigurableApplicationContext loadContextLocations(
174: String[] locations) throws Exception {
175: ++this .loadCount;
176: if (logger.isInfoEnabled()) {
177: logger.info("Loading context for locations: "
178: + StringUtils
179: .arrayToCommaDelimitedString(locations));
180: }
181: return createApplicationContext(locations);
182: }
183:
184: /**
185: * Create a Spring ApplicationContext for use by this test.
186: * <p>The default implementation creates a standard GenericApplicationContext
187: * instance, populates it from the specified config locations through a
188: * {@link org.springframework.beans.factory.xml.XmlBeanDefinitionReader},
189: * and calls {@link #customizeBeanFactory} to allow for customizing the
190: * context's DefaultListableBeanFactory.
191: * @param locations the config locations (as Spring resource locations,
192: * e.g. full classpath locations or any kind of URL)
193: * @return the GenericApplicationContext instance
194: * @see #loadContextLocations
195: * @see #customizeBeanFactory
196: */
197: protected ConfigurableApplicationContext createApplicationContext(
198: String[] locations) {
199: GenericApplicationContext context = new GenericApplicationContext();
200: customizeBeanFactory(context.getDefaultListableBeanFactory());
201: new XmlBeanDefinitionReader(context)
202: .loadBeanDefinitions(locations);
203: context.refresh();
204: return context;
205: }
206:
207: /**
208: * Customize the internal bean factory of the ApplicationContext used by this test.
209: * <p>The default implementation is empty. Can be overridden in subclasses
210: * to customize DefaultListableBeanFactory's standard settings.
211: * @param beanFactory the newly created bean factory for this context
212: * @see #loadContextLocations
213: * @see #createApplicationContext
214: * @see org.springframework.beans.factory.support.DefaultListableBeanFactory#setAllowBeanDefinitionOverriding
215: * @see org.springframework.beans.factory.support.DefaultListableBeanFactory#setAllowEagerClassLoading
216: * @see org.springframework.beans.factory.support.DefaultListableBeanFactory#setAllowCircularReferences
217: * @see org.springframework.beans.factory.support.DefaultListableBeanFactory#setAllowRawInjectionDespiteWrapping
218: */
219: protected void customizeBeanFactory(
220: DefaultListableBeanFactory beanFactory) {
221: }
222:
223: /**
224: * Subclasses can override this method to return the locations of their
225: * config files, unless they override {@link #contextKey()} and
226: * {@link #loadContext(Object)} instead.
227: * <p>A plain path will be treated as class path location, e.g.:
228: * "org/springframework/whatever/foo.xml". Note however that you may prefix path
229: * locations with standard Spring resource prefixes. Therefore, a config location
230: * path prefixed with "classpath:" with behave the same as a plain path, but a
231: * config location such as "file:/some/path/path/location/appContext.xml" will
232: * be treated as a filesystem location.
233: * <p>The default implementation builds config locations for the config paths
234: * specified through {@link #getConfigPaths()}.
235: * @return an array of config locations
236: * @see #getConfigPaths()
237: * @see org.springframework.core.io.ResourceLoader#getResource(String)
238: */
239: protected String[] getConfigLocations() {
240: String[] paths = getConfigPaths();
241: String[] locations = new String[paths.length];
242: for (int i = 0; i < paths.length; i++) {
243: String path = paths[i];
244: if (path.startsWith("/")) {
245: locations[i] = ResourceUtils.CLASSPATH_URL_PREFIX
246: + path;
247: } else {
248: locations[i] = ResourceUtils.CLASSPATH_URL_PREFIX
249: + StringUtils.cleanPath(ClassUtils
250: .classPackageAsResourcePath(getClass())
251: + "/" + path);
252: }
253: }
254: return locations;
255: }
256:
257: /**
258: * Subclasses can override this method to return paths to their
259: * config files, relative to the concrete test class.
260: * <p>A plain path, e.g. "context.xml", will be loaded as classpath resource
261: * from the same package that the concrete test class is defined in.
262: * A path starting with a slash is treated as fully qualified class path
263: * location, e.g.: "/org/springframework/whatever/foo.xml".
264: * <p>The default implementation builds an array for the config path
265: * specified through {@link #getConfigPath()}.
266: * @return an array of config locations
267: * @see #getConfigPath()
268: * @see java.lang.Class#getResource(String)
269: */
270: protected String[] getConfigPaths() {
271: String path = getConfigPath();
272: return (path != null ? new String[] { path } : new String[0]);
273: }
274:
275: /**
276: * Subclasses can override this method to return a single path to a
277: * config file, relative to the concrete test class.
278: * <p>A plain path, e.g. "context.xml", will be loaded as classpath resource
279: * from the same package that the concrete test class is defined in.
280: * A path starting with a slash is treated as fully qualified class path
281: * location, e.g.: "/org/springframework/whatever/foo.xml".
282: * <p>The default implementation simply returns <code>null</code>.
283: * @return an array of config locations
284: * @see #getConfigPath()
285: * @see java.lang.Class#getResource(String)
286: */
287: protected String getConfigPath() {
288: return null;
289: }
290:
291: /**
292: * Return the ApplicationContext that this base class manages.
293: */
294: public final ConfigurableApplicationContext getApplicationContext() {
295: return this .applicationContext;
296: }
297:
298: /**
299: * Return the current number of context load attempts.
300: */
301: public final int getLoadCount() {
302: return this.loadCount;
303: }
304:
305: }
|