Open Source Repository

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



org/springframework/test/annotation/AbstractAnnotationAwareTransactionalTests.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.annotation;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Map;

import javax.sql.DataSource;

import junit.framework.AssertionFailedError;

import org.springframework.context.ApplicationContext;
import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
import org.springframework.test.AbstractTransactionalDataSourceSpringContextTests;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.annotation.AnnotationTransactionAttributeSource;
import org.springframework.transaction.interceptor.TransactionAttributeSource;
import org.springframework.util.Assert;

/**
 <p>
 * Java 5 specific subclass of
 {@link AbstractTransactionalDataSourceSpringContextTests}, exposing a
 {@link SimpleJdbcTemplate} and obeying annotations for transaction control.
 </p>
 <p>
 * For example, test methods can be annotated with the regular Spring
 {@link org.springframework.transaction.annotation.Transactional @Transactional}
 * annotation (e.g., to force execution in a read-only transaction) or with the
 {@link NotTransactional @NotTransactional} annotation to prevent any
 * transaction being created at all. In addition, individual test methods can be
 * annotated with {@link Rollback @Rollback} to override the
 {@link #isDefaultRollback() default rollback} settings.
 </p>
 <p>
 * The following list constitutes all annotations currently supported by
 * AbstractAnnotationAwareTransactionalTests:
 </p>
 <ul>
 <li>{@link DirtiesContext @DirtiesContext}</li>
 <li>{@link ProfileValueSourceConfiguration @ProfileValueSourceConfiguration}</li>
 <li>{@link IfProfileValue @IfProfileValue}</li>
 <li>{@link ExpectedException @ExpectedException}</li>
 <li>{@link Timed @Timed}</li>
 <li>{@link Repeat @Repeat}</li>
 <li>{@link org.springframework.transaction.annotation.Transactional @Transactional}</li>
 <li>{@link NotTransactional @NotTransactional}</li>
 <li>{@link Rollback @Rollback}</li>
 </ul>
 *
 @author Rod Johnson
 @author Sam Brannen
 @author Juergen Hoeller
 @since 2.0
 @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 AbstractAnnotationAwareTransactionalTests extends
    AbstractTransactionalDataSourceSpringContextTests {

  protected SimpleJdbcTemplate simpleJdbcTemplate;

  private final TransactionAttributeSource transactionAttributeSource = new AnnotationTransactionAttributeSource();

  /**
   {@link ProfileValueSource} available to subclasses but primarily intended
   * for use in {@link #isDisabledInThisEnvironment(Method)}.
   <p>Set to {@link SystemProfileValueSource} by default for backwards
   * compatibility; however, the value may be changed in the
   {@link #AbstractAnnotationAwareTransactionalTests(String)} constructor.
   */
  protected ProfileValueSource profileValueSource = SystemProfileValueSource.getInstance();


  /**
   * Default constructor for AbstractAnnotationAwareTransactionalTests, which
   * delegates to {@link #AbstractAnnotationAwareTransactionalTests(String)}.
   */
  public AbstractAnnotationAwareTransactionalTests() {
    this(null);
  }

  /**
   * Constructs a new AbstractAnnotationAwareTransactionalTests instance with
   * the specified JUnit <code>name</code> and retrieves the configured (or
   * default) {@link ProfileValueSource}.
   @param name the name of the current test
   @see ProfileValueUtils#retrieveProfileValueSource(Class)
   */
  public AbstractAnnotationAwareTransactionalTests(String name) {
    super(name);
    this.profileValueSource = ProfileValueUtils.retrieveProfileValueSource(getClass());
  }


  @Override
  public void setDataSource(DataSource dataSource) {
    super.setDataSource(dataSource);
    // JdbcTemplate will be identically configured
    this.simpleJdbcTemplate = new SimpleJdbcTemplate(this.jdbcTemplate);
  }

  /**
   * Search for a unique {@link ProfileValueSource} in the supplied
   {@link ApplicationContext}. If found, the
   <code>profileValueSource</code> for this test will be set to the unique
   {@link ProfileValueSource}.
   @param applicationContext the ApplicationContext in which to search for
   * the ProfileValueSource; may not be <code>null</code>
   @deprecated Use {@link ProfileValueSourceConfiguration @ProfileValueSourceConfiguration} instead.
   */
  @Deprecated
  protected void findUniqueProfileValueSourceFromContext(ApplicationContext applicationContext) {
    Assert.notNull(applicationContext, "Can not search for a ProfileValueSource in a null ApplicationContext.");
    ProfileValueSource uniqueProfileValueSource = null;
    Map<?, ?> beans = applicationContext.getBeansOfType(ProfileValueSource.class);
    if (beans.size() == 1) {
      uniqueProfileValueSource = (ProfileValueSourcebeans.values().iterator().next();
    }
    if (uniqueProfileValueSource != null) {
      this.profileValueSource = uniqueProfileValueSource;
    }
  }

  /**
   * Overridden to populate transaction definition from annotations.
   */
  @Override
  public void runBare() throws Throwable {
    // getName will return the name of the method being run.
    if (isDisabledInThisEnvironment(getName())) {
      // Let superclass log that we didn't run the test.
      super.runBare();
      return;
    }

    final Method testMethod = getTestMethod();

    if (isDisabledInThisEnvironment(testMethod)) {
      recordDisabled();
      this.logger.info("**** " + getClass().getName() "." + getName() " disabled in this environment: "
          "Total disabled tests=" + getDisabledTestCount());
      return;
    }

    TransactionDefinition explicitTransactionDefinition =
        this.transactionAttributeSource.getTransactionAttribute(testMethod, getClass());
    if (explicitTransactionDefinition != null) {
      this.logger.info("Custom transaction definition [" + explicitTransactionDefinition + "] for test method ["
          + getName() "].");
      setTransactionDefinition(explicitTransactionDefinition);
    }
    else if (testMethod.isAnnotationPresent(NotTransactional.class)) {
      // Don't have any transaction...
      preventTransaction();
    }

    // Let JUnit handle execution. We're just changing the state of the test class first.
    runTestTimed(new TestExecutionCallback() {
      public void run() throws Throwable {
        try {
          AbstractAnnotationAwareTransactionalTests.super.runBare();
        }
        finally {
          // Mark the context to be blown away if the test was
          // annotated to result in setDirty being invoked
          // automatically.
          if (testMethod.isAnnotationPresent(DirtiesContext.class)) {
            AbstractAnnotationAwareTransactionalTests.this.setDirty();
          }
        }
      }
    }, testMethod);
  }

  /**
   * Determine if the test for the supplied <code>testMethod</code> should
   * run in the current environment.
   <p>The default implementation is based on
   {@link IfProfileValue @IfProfileValue} semantics.
   @param testMethod the test method
   @return <code>true</code> if the test is <em>disabled</em> in the current environment
   @see ProfileValueUtils#isTestEnabledInThisEnvironment
   */
  protected boolean isDisabledInThisEnvironment(Method testMethod) {
    return !ProfileValueUtils.isTestEnabledInThisEnvironment(this.profileValueSource, testMethod, getClass());
  }

  /**
   * Get the current test method.
   */
  protected Method getTestMethod() {
    assertNotNull("TestCase.getName() cannot be null", getName());
    Method testMethod = null;
    try {
      // Use same algorithm as JUnit itself to retrieve the test method
      // about to be executed (the method name is returned by getName). It
      // has to be public so we can retrieve it.
      testMethod = getClass().getMethod(getName()(Class[]) null);
    }
    catch (NoSuchMethodException ex) {
      fail("Method '" + getName() "' not found");
    }
    if (!Modifier.isPublic(testMethod.getModifiers())) {
      fail("Method '" + getName() "' should be public");
    }
    return testMethod;
  }

  /**
   * Determine whether or not to rollback transactions for the current test
   * by taking into consideration the
   {@link #isDefaultRollback() default rollback} flag and a possible
   * method-level override via the {@link Rollback @Rollback} annotation.
   @return the <em>rollback</em> flag for the current test
   */
  @Override
  protected boolean isRollback() {
    boolean rollback = isDefaultRollback();
    Rollback rollbackAnnotation = getTestMethod().getAnnotation(Rollback.class);
    if (rollbackAnnotation != null) {
      boolean rollbackOverride = rollbackAnnotation.value();
      if (this.logger.isDebugEnabled()) {
        this.logger.debug("Method-level @Rollback(" + rollbackOverride + ") overrides default rollback ["
            + rollback + "] for test [" + getName() "].");
      }
      rollback = rollbackOverride;
    }
    else {
      if (this.logger.isDebugEnabled()) {
        this.logger.debug("No method-level @Rollback override: using default rollback [" + rollback
            "] for test [" + getName() "].");
      }
    }
    return rollback;
  }

  private void runTestTimed(TestExecutionCallback tec, Method testMethodthrows Throwable {
    Timed timed = testMethod.getAnnotation(Timed.class);
    if (timed == null) {
      runTest(tec, testMethod);
    }
    else {
      long startTime = System.currentTimeMillis();
      try {
        runTest(tec, testMethod);
      }
      finally {
        long elapsed = System.currentTimeMillis() - startTime;
        if (elapsed > timed.millis()) {
          fail("Took " + elapsed + " ms; limit was " + timed.millis());
        }
      }
    }
  }

  private void runTest(TestExecutionCallback tec, Method testMethodthrows Throwable {
    ExpectedException expectedExceptionAnnotation = testMethod.getAnnotation(ExpectedException.class);
    boolean exceptionIsExpected = (expectedExceptionAnnotation != null && expectedExceptionAnnotation.value() != null);
    Class<? extends Throwable> expectedException = (exceptionIsExpected ? expectedExceptionAnnotation.value() null);

    Repeat repeat = testMethod.getAnnotation(Repeat.class);
    int runs = ((repeat != null&& (repeat.value() 1)) ? repeat.value() 1;

    for (int i = 0; i < runs; i++) {
      try {
        if (runs > && this.logger != null && this.logger.isInfoEnabled()) {
          this.logger.info("Repetition " (i + 1" of test " + testMethod.getName());
        }
        tec.run();
        if (exceptionIsExpected) {
          fail("Expected exception: " + expectedException.getName());
        }
      }
      catch (Throwable t) {
        if (!exceptionIsExpected) {
          throw t;
        }
        if (!expectedException.isAssignableFrom(t.getClass())) {
          // Wrap the unexpected throwable with an explicit message.
          AssertionFailedError assertionError = new AssertionFailedError("Unexpected exception, expected<" +
              expectedException.getName() "> but was<" + t.getClass().getName() ">");
          assertionError.initCause(t);
          throw assertionError;
        }
      }
    }
  }


  private static interface TestExecutionCallback {

    void run() throws Throwable;
  }

}