Open Source Repository

Home /spring/spring-test-3.0.5 | Repository Home



org/springframework/test/AbstractSingleSpringContextTests.java
/*
 * 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()
 @deprecated as of Spring 3.0, in favor of using the listener-based test context framework
 * ({@link org.springframework.test.context.junit38.AbstractJUnit38SpringContextTests})
 */
@Deprecated
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 keythrows 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[] locationsthrows 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;
  }

}