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.annotation;
018:
019: import java.lang.reflect.Method;
020: import java.util.Map;
021:
022: import javax.sql.DataSource;
023:
024: import org.apache.commons.logging.Log;
025:
026: import org.springframework.context.ApplicationContext;
027: import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
028: import org.springframework.test.AbstractTransactionalDataSourceSpringContextTests;
029: import org.springframework.transaction.TransactionDefinition;
030: import org.springframework.transaction.annotation.AnnotationTransactionAttributeSource;
031: import org.springframework.transaction.interceptor.TransactionAttributeSource;
032:
033: /**
034: * Java 5 specific subclass of
035: * {@link AbstractTransactionalDataSourceSpringContextTests}, exposing a
036: * {@link SimpleJdbcTemplate} and obeying annotations for transaction control.
037: *
038: * <p>For example, test methods can be annotated with the regular Spring
039: * {@link org.springframework.transaction.annotation.Transactional} annotation
040: * (for example, to force execution in a read-only transaction) or with the
041: * {@link NotTransactional} annotation to prevent any transaction being created
042: * at all.
043: *
044: * @author Rod Johnson
045: * @since 2.0
046: */
047: public abstract class AbstractAnnotationAwareTransactionalTests extends
048: AbstractTransactionalDataSourceSpringContextTests {
049:
050: protected SimpleJdbcTemplate simpleJdbcTemplate;
051:
052: private TransactionAttributeSource transactionAttributeSource = new AnnotationTransactionAttributeSource();
053:
054: protected ProfileValueSource profileValueSource = SystemProfileValueSource
055: .getInstance();
056:
057: @Override
058: public void setDataSource(DataSource dataSource) {
059: super .setDataSource(dataSource);
060: // JdbcTemplate will be identically configured
061: this .simpleJdbcTemplate = new SimpleJdbcTemplate(
062: this .jdbcTemplate);
063: }
064:
065: // TODO code to try to load (and cache!) ProfileValueSource
066: // from a given URL? It's easy enough to do, of course.
067: protected void findUniqueProfileValueSourceFromContext(
068: ApplicationContext ac) {
069: Map beans = ac.getBeansOfType(ProfileValueSource.class);
070: if (beans.size() == 1) {
071: this .profileValueSource = (ProfileValueSource) beans
072: .values().iterator().next();
073: }
074: }
075:
076: /**
077: * Overridden to populate transaction definition from annotations.
078: */
079: @Override
080: public void runBare() throws Throwable {
081: // getName will return the name of the method being run.
082: if (isDisabledInThisEnvironment(getName())) {
083: // Let superclass log that we didn't run the test.
084: super .runBare();
085: return;
086: }
087:
088: // Use same algorithm as JUnit itself to retrieve the test method
089: // about to be executed (the method name is returned by getName).
090: // It has to be public so we can retrieve it
091: final Method testMethod = getClass().getMethod(getName(),
092: (Class[]) null);
093:
094: if (isDisabledInThisEnvironment(testMethod)) {
095: logger.info("**** " + getClass().getName() + "."
096: + getName() + " disabled in this environment: "
097: + "Total disabled tests=" + getDisabledTestCount());
098: recordDisabled();
099: return;
100: }
101:
102: TransactionDefinition explicitTransactionDefinition = this .transactionAttributeSource
103: .getTransactionAttribute(testMethod, getClass());
104: if (explicitTransactionDefinition != null) {
105: logger.info("Custom transaction definition ["
106: + explicitTransactionDefinition
107: + " for test method " + getName());
108: setTransactionDefinition(explicitTransactionDefinition);
109: } else if (testMethod
110: .isAnnotationPresent(NotTransactional.class)) {
111: // Don't have any transaction...
112: preventTransaction();
113: }
114:
115: // Let JUnit handle execution. We're just changing the state of the
116: // test class first.
117: runTestTimed(new TestExecutionCallback() {
118: public void run() throws Throwable {
119: try {
120: AbstractAnnotationAwareTransactionalTests.super
121: .runBare();
122: } finally {
123: // Mark the context to be blown
124: // away if the test was annotated to result
125: // in setDirty being invoked automatically
126: if (testMethod
127: .isAnnotationPresent(DirtiesContext.class)) {
128: AbstractAnnotationAwareTransactionalTests.this
129: .setDirty();
130: }
131: }
132: }
133: }, testMethod, logger);
134: }
135:
136: protected boolean isDisabledInThisEnvironment(Method testMethod) {
137: IfProfileValue inProfile = testMethod
138: .getAnnotation(IfProfileValue.class);
139: if (inProfile == null) {
140: inProfile = getClass().getAnnotation(IfProfileValue.class);
141: }
142:
143: if (inProfile != null) {
144: // May be true
145: return !this .profileValueSource.get(inProfile.name())
146: .equals(inProfile.value());
147: } else {
148: return false;
149: }
150:
151: // TODO IfNotProfileValue
152: }
153:
154: private static void runTestTimed(TestExecutionCallback tec,
155: Method testMethod, Log logger) throws Throwable {
156: Timed timed = testMethod.getAnnotation(Timed.class);
157:
158: if (timed == null) {
159: runTest(tec, testMethod, logger);
160: } else {
161: long startTime = System.currentTimeMillis();
162: try {
163: runTest(tec, testMethod, logger);
164: } finally {
165: long elapsed = System.currentTimeMillis() - startTime;
166: if (elapsed > timed.millis()) {
167: fail("Took " + elapsed + " ms; limit was "
168: + timed.millis());
169: }
170: }
171: }
172: }
173:
174: private static void runTest(TestExecutionCallback tec,
175: Method testMethod, Log logger) throws Throwable {
176: ExpectedException ee = testMethod
177: .getAnnotation(ExpectedException.class);
178: Repeat repeat = testMethod.getAnnotation(Repeat.class);
179:
180: int runs = (repeat != null) ? repeat.value() : 1;
181:
182: for (int i = 0; i < runs; i++) {
183: try {
184: if (i > 0 && logger != null) {
185: logger.info("Repetition " + i + " of test "
186: + testMethod.getName());
187: }
188: tec.run();
189: if (ee != null) {
190: fail("Expected throwable of class " + ee.value());
191: }
192: } catch (Throwable t) {
193: if (ee == null) {
194: throw t;
195: }
196: if (ee.value().isAssignableFrom(t.getClass())) {
197: // Ok
198: } else {
199: //fail("Expected throwable of class " + ee.value() + "; got " + t);
200: // Throw the unexpected problem throwable
201: throw t;
202: }
203: }
204: }
205: }
206:
207: private static interface TestExecutionCallback {
208:
209: void run() throws Throwable;
210: }
211:
212: }
|