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.transaction.PlatformTransactionManager;
020: import org.springframework.transaction.TransactionDefinition;
021: import org.springframework.transaction.TransactionException;
022: import org.springframework.transaction.TransactionStatus;
023: import org.springframework.transaction.support.DefaultTransactionDefinition;
024:
025: /**
026: * Convenient base class for tests that should occur in a transaction, but normally
027: * will roll the transaction back on the completion of each test.
028: *
029: * <p>This is useful in a range of circumstances, allowing the following benefits:
030: * <ul>
031: * <li>Ability to delete or insert any data in the database, without affecting other tests
032: * <li>Providing a transactional context for any code requiring a transaction
033: * <li>Ability to write anything to the database without any need to clean up.
034: * </ul>
035: *
036: * <p>This class is typically very fast, compared to traditional setup/teardown scripts.
037: *
038: * <p>If data should be left in the database, call the {@link #setComplete()}
039: * method in each test. The {@link #setDefaultRollback "defaultRollback"} property,
040: * which defaults to "true", determines whether transactions will complete by default.
041: *
042: * <p>It is even possible to end the transaction early; for example, to verify lazy
043: * loading behavior of an O/R mapping tool. (This is a valuable away to avoid
044: * unexpected errors when testing a web UI, for example.) Simply call the
045: * {@link #endTransaction()} method. Execution will then occur without a
046: * transactional context.
047: *
048: * <p>The {@link #startNewTransaction()} method may be called after a call to
049: * {@link #endTransaction()} if you wish to create a new transaction, quite
050: * independent of the old transaction. The new transaction's default fate will be to
051: * roll back, unless {@link #setComplete()} is called again during the scope of the
052: * new transaction. Any number of transactions may be created and ended in this way.
053: * The final transaction will automatically be rolled back when the test case is
054: * torn down.
055: *
056: * <p>Transactional behavior requires a single bean in the context implementing the
057: * {@link org.springframework.transaction.PlatformTransactionManager} interface.
058: * This will be set by the superclass's Dependency Injection mechanism.
059: * If using the superclass's Field Injection mechanism, the implementation should
060: * be named "transactionManager". This mechanism allows the use of the
061: * {@link AbstractDependencyInjectionSpringContextTests} superclass even
062: * when there is more than one transaction manager in the context.
063: *
064: * <p><b>This base class can also be used without transaction management, if no
065: * PlatformTransactionManager bean is found in the context provided.</b>
066: * Be careful about using this mode, as it allows the potential to permanently modify
067: * data. This mode is available only if dependency checking is turned off in the
068: * {@link AbstractDependencyInjectionSpringContextTests} superclass. The non-transactional
069: * capability is provided to enable use of the same subclass in different environments.
070: *
071: * @author Rod Johnson
072: * @author Juergen Hoeller
073: * @since 1.1.1
074: */
075: public abstract class AbstractTransactionalSpringContextTests extends
076: AbstractDependencyInjectionSpringContextTests {
077:
078: /** The transaction manager to use */
079: protected PlatformTransactionManager transactionManager;
080:
081: /** Should we roll back by default? */
082: private boolean defaultRollback = true;
083:
084: /** Should we commit the current transaction? */
085: private boolean complete = false;
086:
087: /** Number of transactions started */
088: private int transactionsStarted = 0;
089:
090: /**
091: * Transaction definition used by this test class: by default, a plain
092: * DefaultTransactionDefinition. Subclasses can change this to cause different behavior.
093: */
094: protected TransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
095:
096: /**
097: * TransactionStatus for this test. Typical subclasses won't need to use it.
098: */
099: protected TransactionStatus transactionStatus;
100:
101: /**
102: * Default constructor for AbstractTransactionalSpringContextTests.
103: */
104: public AbstractTransactionalSpringContextTests() {
105: }
106:
107: /**
108: * Constructor for AbstractTransactionalSpringContextTests with a JUnit name.
109: */
110: public AbstractTransactionalSpringContextTests(String name) {
111: super (name);
112: }
113:
114: /**
115: * Specify the transaction manager to use. No transaction management will be available
116: * if this is not set. Populated through dependency injection by the superclass.
117: * <p>This mode works only if dependency checking is turned off in the
118: * {@link AbstractDependencyInjectionSpringContextTests} superclass.
119: */
120: public void setTransactionManager(
121: PlatformTransactionManager transactionManager) {
122: this .transactionManager = transactionManager;
123: }
124:
125: /**
126: * Subclasses can set this value in their constructor to change
127: * default, which is always to roll the transaction back.
128: */
129: public void setDefaultRollback(boolean defaultRollback) {
130: this .defaultRollback = defaultRollback;
131: }
132:
133: /**
134: * Call this method in an overridden {@link #runBare()} method to
135: * prevent transactional execution.
136: */
137: protected void preventTransaction() {
138: this .transactionDefinition = null;
139: }
140:
141: /**
142: * Call this method in an overridden {@link #runBare()} method to
143: * override the transaction attributes that will be used, so that
144: * {@link #setUp()} and {@link #tearDown()} behavior is modified.
145: * @param customDefinition the custom transaction definition
146: */
147: protected void setTransactionDefinition(
148: TransactionDefinition customDefinition) {
149: this .transactionDefinition = customDefinition;
150: }
151:
152: /**
153: * This implementation creates a transaction before test execution.
154: * <p>Override {@link #onSetUpBeforeTransaction()} and/or
155: * {@link #onSetUpInTransaction()} to add custom set-up behavior
156: * for transactional execution. Alternatively, override this method
157: * for general set-up behavior, calling <code>super.onSetUp()</code>
158: * as part of your method implementation.
159: * @throws Exception simply let any exception propagate
160: * @see #onTearDown()
161: */
162: protected void onSetUp() throws Exception {
163: this .complete = !this .defaultRollback;
164:
165: if (this .transactionManager == null) {
166: logger
167: .info("No transaction manager set: test will NOT run within a transaction");
168: } else if (this .transactionDefinition == null) {
169: logger
170: .info("No transaction definition set: test will NOT run within a transaction");
171: } else {
172: onSetUpBeforeTransaction();
173: startNewTransaction();
174: try {
175: onSetUpInTransaction();
176: } catch (Exception ex) {
177: endTransaction();
178: throw ex;
179: }
180: }
181: }
182:
183: /**
184: * Subclasses can override this method to perform any setup operations,
185: * such as populating a database table, <i>before</i> the transaction
186: * created by this class. Only invoked if there <i>is</i> a transaction:
187: * that is, if {@link #preventTransaction()} has not been invoked in
188: * an overridden {@link #runTest()} method.
189: * @throws Exception simply let any exception propagate
190: */
191: protected void onSetUpBeforeTransaction() throws Exception {
192: }
193:
194: /**
195: * Subclasses can override this method to perform any setup operations,
196: * such as populating a database table, <i>within</i> the transaction
197: * created by this class.
198: * <p><b>NB:</b> Not called if there is no transaction management, due to no
199: * transaction manager being provided in the context.
200: * <p>If any {@link Throwable} is thrown, the transaction that has been started
201: * prior to the execution of this method will be {@link #endTransaction() ended}
202: * (or rather an attempt will be made to {@link #endTransaction() end it gracefully});
203: * The offending {@link Throwable} will then be rethrown.
204: * @throws Exception simply let any exception propagate
205: */
206: protected void onSetUpInTransaction() throws Exception {
207: }
208:
209: /**
210: * This implementation ends the transaction after test execution.
211: * <p>Override {@link #onTearDownInTransaction()} and/or
212: * {@link #onTearDownAfterTransaction()} to add custom tear-down behavior
213: * for transactional execution. Alternatively, override this method for
214: * general tear-down behavior, calling <code>super.onTearDown()</code>
215: * as part of your method implementation.
216: * <p>Note that {@link #onTearDownInTransaction()} will only be called
217: * if a transaction is still active at the time of the test shutdown.
218: * In particular, it will <i>not</i> be called if the transaction has
219: * been completed with an explicit {@link #endTransaction()} call before.
220: * @throws Exception simply let any exception propagate
221: * @see #onSetUp()
222: */
223: protected void onTearDown() throws Exception {
224: // Call onTearDownInTransaction and end transaction if the transaction is still active.
225: if (this .transactionStatus != null
226: && !this .transactionStatus.isCompleted()) {
227: try {
228: onTearDownInTransaction();
229: } finally {
230: endTransaction();
231: }
232: }
233: // Call onTearDownAfterTransaction if there was at least one transaction,
234: // even if it has been completed early through an endTransaction() call.
235: if (this .transactionsStarted > 0) {
236: onTearDownAfterTransaction();
237: }
238: }
239:
240: /**
241: * Subclasses can override this method to run invariant tests here.
242: * The transaction is <i>still active</i> at this point, so any changes
243: * made in the transaction will still be visible. However, there is no need
244: * to clean up the database, as a rollback will follow automatically.
245: * <p><b>NB:</b> Not called if there is no actual transaction, for example
246: * due to no transaction manager being provided in the application context.
247: * @throws Exception simply let any exception propagate
248: */
249: protected void onTearDownInTransaction() throws Exception {
250: }
251:
252: /**
253: * Subclasses can override this method to perform cleanup after a transaction
254: * here. At this point, the transaction is <i>not active anymore</i>.
255: * @throws Exception simply let any exception propagate
256: */
257: protected void onTearDownAfterTransaction() throws Exception {
258: }
259:
260: /**
261: * Cause the transaction to commit for this test method,
262: * even if default is set to rollback.
263: * @throws IllegalStateException if the operation cannot be set to
264: * complete as no transaction manager was provided
265: */
266: protected void setComplete() {
267: if (this .transactionManager == null) {
268: throw new IllegalStateException(
269: "No transaction manager set");
270: }
271: this .complete = true;
272: }
273:
274: /**
275: * Immediately force a commit or rollback of the transaction,
276: * according to the complete flag.
277: * <p>Can be used to explicitly let the transaction end early,
278: * for example to check whether lazy associations of persistent objects
279: * work outside of a transaction (that is, have been initialized properly).
280: * @see #setComplete()
281: */
282: protected void endTransaction() {
283: if (this .transactionStatus != null) {
284: try {
285: if (!this .complete) {
286: this .transactionManager
287: .rollback(this .transactionStatus);
288: logger
289: .info("Rolled back transaction after test execution");
290: } else {
291: this .transactionManager
292: .commit(this .transactionStatus);
293: logger
294: .info("Committed transaction after test execution");
295: }
296: } finally {
297: this .transactionStatus = null;
298: }
299: }
300: }
301:
302: /**
303: * Start a new transaction. Only call this method if {@link #endTransaction()}
304: * has been called. {@link #setComplete()} can be used again in the new transaction.
305: * The fate of the new transaction, by default, will be the usual rollback.
306: * @throws TransactionException if starting the transaction failed
307: */
308: protected void startNewTransaction() throws TransactionException {
309: if (this .transactionStatus != null) {
310: throw new IllegalStateException(
311: "Cannot start new transaction without ending existing transaction: "
312: + "Invoke endTransaction() before startNewTransaction()");
313: }
314: if (this .transactionManager == null) {
315: throw new IllegalStateException(
316: "No transaction manager set");
317: }
318:
319: this .transactionStatus = this .transactionManager
320: .getTransaction(this .transactionDefinition);
321: ++this .transactionsStarted;
322: this .complete = !this .defaultRollback;
323:
324: if (logger.isInfoEnabled()) {
325: logger.info("Began transaction ("
326: + this .transactionsStarted
327: + "): transaction manager ["
328: + this .transactionManager
329: + "]; default rollback = " + this.defaultRollback);
330: }
331: }
332:
333: }
|