001: /*
002: * $Id: TransactionUtil.java,v 1.4 2003/12/04 21:54:20 ajzeneski Exp $
003: *
004: * Copyright (c) 2001, 2002 The Open For Business Project - www.ofbiz.org
005: *
006: * Permission is hereby granted, free of charge, to any person obtaining a
007: * copy of this software and associated documentation files (the "Software"),
008: * to deal in the Software without restriction, including without limitation
009: * the rights to use, copy, modify, merge, publish, distribute, sublicense,
010: * and/or sell copies of the Software, and to permit persons to whom the
011: * Software is furnished to do so, subject to the following conditions:
012: *
013: * The above copyright notice and this permission notice shall be included
014: * in all copies or substantial portions of the Software.
015: *
016: * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
017: * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
018: * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
019: * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
020: * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT
021: * OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
022: * THE USE OR OTHER DEALINGS IN THE SOFTWARE.
023: */
024: package org.ofbiz.entity.transaction;
025:
026: import java.sql.Connection;
027: import java.sql.SQLException;
028: import java.sql.Timestamp;
029:
030: import javax.sql.XAConnection;
031: import javax.transaction.*;
032: import javax.transaction.HeuristicMixedException;
033: import javax.transaction.HeuristicRollbackException;
034: import javax.transaction.NotSupportedException;
035: import javax.transaction.RollbackException;
036: import javax.transaction.Status;
037: import javax.transaction.SystemException;
038: import javax.transaction.Transaction;
039: import javax.transaction.TransactionManager;
040: import javax.transaction.UserTransaction;
041: import javax.transaction.xa.XAResource;
042:
043: import org.ofbiz.base.util.Debug;
044: import org.ofbiz.base.util.UtilDateTime;
045:
046: /**
047: * <p>Transaction Utility to help with some common transaction tasks
048: * <p>Provides a wrapper around the transaction objects to allow for changes in underlying implementations in the future.
049: *
050: * @author <a href="mailto:jonesde@ofbiz.org">David E. Jones</a>
051: * @version $Revision: 1.4 $
052: * @since 2.0
053: */
054: public class TransactionUtil implements Status {
055: // Debug module name
056: public static final String module = TransactionUtil.class.getName();
057:
058: /** Begins a transaction in the current thread IF transactions are available; only
059: * tries if the current transaction status is ACTIVE, if not active it returns false.
060: * If and on only if it begins a transaction it will return true. In other words, if
061: * a transaction is already in place it will return false and do nothing.
062: */
063: public static boolean begin() throws GenericTransactionException {
064: return begin(0);
065: }
066:
067: /** Begins a transaction in the current thread IF transactions are available; only
068: * tries if the current transaction status is ACTIVE, if not active it returns false.
069: * If and on only if it begins a transaction it will return true. In other words, if
070: * a transaction is already in place it will return false and do nothing.
071: */
072: public static boolean begin(int timeout)
073: throws GenericTransactionException {
074: UserTransaction ut = TransactionFactory.getUserTransaction();
075: if (ut != null) {
076: try {
077: int currentStatus = ut.getStatus();
078: Debug
079: .logVerbose(
080: "[TransactionUtil.begin] current status : "
081: + getTransactionStateString(currentStatus),
082: module);
083: if (currentStatus == Status.STATUS_ACTIVE) {
084: Debug
085: .logVerbose(
086: "[TransactionUtil.begin] active transaction in place, so no transaction begun",
087: module);
088: return false;
089: } else if (currentStatus == Status.STATUS_MARKED_ROLLBACK) {
090: Debug
091: .logVerbose(
092: "[TransactionUtil.begin] active transaction marked for rollback in place, so no transaction begun",
093: module);
094: throw new GenericTransactionException(
095: "The current transaction is marked for rollback, should stop immediately.");
096: //return false;
097: }
098:
099: // set the timeout for THIS transaction
100: if (timeout > 0) {
101: ut.setTransactionTimeout(timeout);
102: Debug.logVerbose(
103: "[TransactionUtil.begin] set transaction timeout to : "
104: + timeout + " seconds", module);
105: }
106:
107: // begin the transaction
108: ut.begin();
109: Debug.logVerbose(
110: "[TransactionUtil.begin] transaction begun",
111: module);
112:
113: // reset the transaction stamps, just in case...
114: clearTransactionStamps();
115: // initialize the start stamp
116: getTransactionStartStamp();
117:
118: return true;
119: } catch (NotSupportedException e) {
120: //This is Java 1.4 only, but useful for certain debuggins: Throwable t = e.getCause() == null ? e : e.getCause();
121: throw new GenericTransactionException(
122: "Not Supported error, could not begin transaction (probably a nesting problem)",
123: e);
124: } catch (SystemException e) {
125: //This is Java 1.4 only, but useful for certain debuggins: Throwable t = e.getCause() == null ? e : e.getCause();
126: throw new GenericTransactionException(
127: "System error, could not begin transaction", e);
128: }
129: } else {
130: Debug
131: .logInfo(
132: "[TransactionUtil.begin] no user transaction, so no transaction begun",
133: module);
134: return false;
135: }
136: }
137:
138: /** Gets the status of the transaction in the current thread IF
139: * transactions are available, otherwise returns STATUS_NO_TRANSACTION */
140: public static int getStatus() throws GenericTransactionException {
141: UserTransaction ut = TransactionFactory.getUserTransaction();
142: if (ut != null) {
143: try {
144: return ut.getStatus();
145: } catch (SystemException e) {
146: throw new GenericTransactionException(
147: "System error, could not get status", e);
148: }
149: } else {
150: return STATUS_NO_TRANSACTION;
151: }
152: }
153:
154: /** Commits the transaction in the current thread IF transactions are available
155: * AND if beganTransaction is true
156: */
157: public static void commit(boolean beganTransaction)
158: throws GenericTransactionException {
159: if (beganTransaction) {
160: TransactionUtil.commit();
161: }
162: }
163:
164: /** Commits the transaction in the current thread IF transactions are available */
165: public static void commit() throws GenericTransactionException {
166: UserTransaction ut = TransactionFactory.getUserTransaction();
167:
168: if (ut != null) {
169: try {
170: int status = ut.getStatus();
171: Debug.logVerbose(
172: "[TransactionUtil.commit] current status : "
173: + getTransactionStateString(status),
174: module);
175:
176: if (status != STATUS_NO_TRANSACTION) {
177: ut.commit();
178: Debug
179: .logVerbose(
180: "[TransactionUtil.commit] transaction committed",
181: module);
182:
183: // clear out the stamps to keep it clean
184: clearTransactionStamps();
185: } else {
186: Debug
187: .logInfo(
188: "[TransactionUtil.commit] Not committing transaction, status is STATUS_NO_TRANSACTION",
189: module);
190: }
191: } catch (RollbackException e) {
192: if (Debug.infoOn())
193: Thread.dumpStack();
194: //This is Java 1.4 only, but useful for certain debuggins: Throwable t = e.getCause() == null ? e : e.getCause();
195: throw new GenericTransactionException(
196: "Roll back error, could not commit transaction, was rolled back instead",
197: e);
198: } catch (HeuristicMixedException e) {
199: //This is Java 1.4 only, but useful for certain debuggins: Throwable t = e.getCause() == null ? e : e.getCause();
200: throw new GenericTransactionException(
201: "Could not commit transaction, HeuristicMixed exception",
202: e);
203: } catch (HeuristicRollbackException e) {
204: //This is Java 1.4 only, but useful for certain debuggins: Throwable t = e.getCause() == null ? e : e.getCause();
205: throw new GenericTransactionException(
206: "Could not commit transaction, HeuristicRollback exception",
207: e);
208: } catch (SystemException e) {
209: //This is Java 1.4 only, but useful for certain debuggins: Throwable t = e.getCause() == null ? e : e.getCause();
210: throw new GenericTransactionException(
211: "System error, could not commit transaction", e);
212: }
213: } else {
214: Debug
215: .logInfo(
216: "[TransactionUtil.commit] UserTransaction is null, not commiting",
217: module);
218: }
219: }
220:
221: /** Rolls back transaction in the current thread IF transactions are available
222: * AND if beganTransaction is true; if beganTransaction is not true,
223: * setRollbackOnly is called to insure that the transaction will be rolled back
224: */
225: public static void rollback(boolean beganTransaction)
226: throws GenericTransactionException {
227: if (beganTransaction) {
228: TransactionUtil.rollback();
229: } else {
230: TransactionUtil.setRollbackOnly();
231: }
232: }
233:
234: /** Rolls back transaction in the current thread IF transactions are available */
235: public static void rollback() throws GenericTransactionException {
236: UserTransaction ut = TransactionFactory.getUserTransaction();
237:
238: if (ut != null) {
239: try {
240: int status = ut.getStatus();
241: Debug.logVerbose(
242: "[TransactionUtil.rollback] current status : "
243: + getTransactionStateString(status),
244: module);
245:
246: if (status != STATUS_NO_TRANSACTION) {
247: //if (Debug.infoOn()) Thread.dumpStack();
248: if (Debug.infoOn()) {
249: Exception newE = new Exception("Stack Trace");
250: Debug.logError(newE,
251: "[TransactionUtil.rollback]", module);
252: }
253: ut.rollback();
254: Debug
255: .logInfo(
256: "[TransactionUtil.rollback] transaction rolled back",
257: module);
258:
259: // clear out the stamps to keep it clean
260: clearTransactionStamps();
261: } else {
262: Debug
263: .logInfo(
264: "[TransactionUtil.rollback] transaction not rolled back, status is STATUS_NO_TRANSACTION",
265: module);
266: }
267: } catch (SystemException e) {
268: //This is Java 1.4 only, but useful for certain debuggins: Throwable t = e.getCause() == null ? e : e.getCause();
269: throw new GenericTransactionException(
270: "System error, could not roll back transaction",
271: e);
272: }
273: } else {
274: Debug
275: .logInfo(
276: "[TransactionUtil.rollback] No UserTransaction, transaction not rolled back",
277: module);
278: }
279: }
280:
281: /** Makes a roll back the only possible outcome of the transaction in the current thread IF transactions are available */
282: public static void setRollbackOnly()
283: throws GenericTransactionException {
284: UserTransaction ut = TransactionFactory.getUserTransaction();
285: if (ut != null) {
286: try {
287: int status = ut.getStatus();
288: Debug.logVerbose(
289: "[TransactionUtil.setRollbackOnly] current code : "
290: + getTransactionStateString(status),
291: module);
292:
293: if (status != STATUS_NO_TRANSACTION) {
294: if (Debug.infoOn())
295: Thread.dumpStack();
296: ut.setRollbackOnly();
297: Debug
298: .logInfo(
299: "[TransactionUtil.setRollbackOnly] transaction roll back only set",
300: module);
301: } else {
302: Debug
303: .logInfo(
304: "[TransactionUtil.setRollbackOnly] transaction roll back only not set, status is STATUS_NO_TRANSACTION",
305: module);
306: }
307: } catch (SystemException e) {
308: //This is Java 1.4 only, but useful for certain debuggins: Throwable t = e.getCause() == null ? e : e.getCause();
309: throw new GenericTransactionException(
310: "System error, could not set roll back only on transaction",
311: e);
312: }
313: } else {
314: Debug
315: .logInfo(
316: "[TransactionUtil.setRollbackOnly] No UserTransaction, transaction roll back only not set",
317: module);
318: }
319: }
320:
321: /** Sets the timeout of the transaction in the current thread IF transactions are available */
322: public static void setTransactionTimeout(int seconds)
323: throws GenericTransactionException {
324: UserTransaction ut = TransactionFactory.getUserTransaction();
325: if (ut != null) {
326: try {
327: ut.setTransactionTimeout(seconds);
328: } catch (SystemException e) {
329: throw new GenericTransactionException(
330: "System error, could not set transaction timeout",
331: e);
332: }
333: }
334: }
335:
336: /** Enlists the given XAConnection and if a transaction is active in the current thread, returns a plain JDBC Connection */
337: public static Connection enlistConnection(XAConnection xacon)
338: throws GenericTransactionException {
339: if (xacon == null) {
340: return null;
341: }
342: try {
343: XAResource resource = xacon.getXAResource();
344: TransactionUtil.enlistResource(resource);
345: return xacon.getConnection();
346: } catch (SQLException e) {
347: throw new GenericTransactionException(
348: "SQL error, could not enlist connection in transaction even though transactions are available",
349: e);
350: }
351: }
352:
353: public static void enlistResource(XAResource resource)
354: throws GenericTransactionException {
355: if (resource == null) {
356: return;
357: }
358:
359: try {
360: TransactionManager tm = TransactionFactory
361: .getTransactionManager();
362: if (tm != null && tm.getStatus() == STATUS_ACTIVE) {
363: Transaction tx = tm.getTransaction();
364: if (tx != null) {
365: tx.enlistResource(resource);
366: }
367: }
368: } catch (RollbackException e) {
369: //This is Java 1.4 only, but useful for certain debuggins: Throwable t = e.getCause() == null ? e : e.getCause();
370: throw new GenericTransactionException(
371: "Roll Back error, could not enlist resource in transaction even though transactions are available, current transaction rolled back",
372: e);
373: } catch (SystemException e) {
374: //This is Java 1.4 only, but useful for certain debuggins: Throwable t = e.getCause() == null ? e : e.getCause();
375: throw new GenericTransactionException(
376: "System error, could not enlist resource in transaction even though transactions are available",
377: e);
378: }
379: }
380:
381: public static String getTransactionStateString(int state) {
382: switch (state) {
383: case Status.STATUS_ACTIVE:
384: return "Transaction Active (" + state + ")";
385: case Status.STATUS_COMMITTED:
386: return "Transaction Committed (" + state + ")";
387: case Status.STATUS_COMMITTING:
388: return "Transaction Committing (" + state + ")";
389: case Status.STATUS_MARKED_ROLLBACK:
390: return "Transaction Marked Rollback (" + state + ")";
391: case Status.STATUS_NO_TRANSACTION:
392: return "No Transaction (" + state + ")";
393: case Status.STATUS_PREPARED:
394: return "Transaction Prepared (" + state + ")";
395: case Status.STATUS_PREPARING:
396: return "Transaction Preparing (" + state + ")";
397: case Status.STATUS_ROLLEDBACK:
398: return "Transaction Rolledback (" + state + ")";
399: case Status.STATUS_ROLLING_BACK:
400: return "Transaction Rolling Back (" + state + ")";
401: case Status.STATUS_UNKNOWN:
402: return "Transaction Status Unknown (" + state + ")";
403: default:
404: return "Not a valid state code (" + state + ")";
405: }
406: }
407:
408: public static void registerSynchronization(Synchronization sync)
409: throws GenericTransactionException {
410: if (sync == null) {
411: return;
412: }
413:
414: try {
415: TransactionManager tm = TransactionFactory
416: .getTransactionManager();
417: if (tm != null && tm.getStatus() == STATUS_ACTIVE) {
418: Transaction tx = tm.getTransaction();
419: if (tx != null) {
420: tx.registerSynchronization(sync);
421: }
422: }
423: } catch (RollbackException e) {
424: //This is Java 1.4 only, but useful for certain debuggins: Throwable t = e.getCause() == null ? e : e.getCause();
425: throw new GenericTransactionException(
426: "Roll Back error, could not register synchronization in transaction even though transactions are available, current transaction rolled back",
427: e);
428: } catch (SystemException e) {
429: //This is Java 1.4 only, but useful for certain debuggins: Throwable t = e.getCause() == null ? e : e.getCause();
430: throw new GenericTransactionException(
431: "System error, could not register synchronization in transaction even though transactions are available",
432: e);
433: }
434: }
435:
436: private static ThreadLocal transactionStartStamp = new ThreadLocal();
437: private static ThreadLocal transactionLastNowStamp = new ThreadLocal();
438:
439: public static Timestamp getTransactionStartStamp() {
440: Timestamp curStamp = (Timestamp) transactionStartStamp.get();
441: if (curStamp == null) {
442: curStamp = UtilDateTime.nowTimestamp();
443: transactionStartStamp.set(curStamp);
444:
445: // we know this is the first time set for this transaction, so make sure the StampClearSync is registered
446: try {
447: registerSynchronization(new StampClearSync());
448: } catch (GenericTransactionException e) {
449: Debug
450: .logError(
451: e,
452: "Error registering StampClearSync synchronization, stamps will still be reset if begin/commit/rollback are call through TransactionUtil, but not if otherwise",
453: module);
454: }
455: }
456: return curStamp;
457: }
458:
459: public static Timestamp getTransactionUniqueNowStamp() {
460: Timestamp lastNowStamp = (Timestamp) transactionLastNowStamp
461: .get();
462: Timestamp nowTimestamp = UtilDateTime.nowTimestamp();
463:
464: // check for an overlap with the lastNowStamp, or if the lastNowStamp is in the future because of incrementing to make each stamp unique
465: if (lastNowStamp != null
466: && (lastNowStamp.equals(nowTimestamp) || lastNowStamp
467: .after(nowTimestamp))) {
468: nowTimestamp = new Timestamp(lastNowStamp.getTime() + 1);
469: }
470:
471: transactionLastNowStamp.set(nowTimestamp);
472: return nowTimestamp;
473: }
474:
475: protected static void clearTransactionStamps() {
476: transactionStartStamp.set(null);
477: transactionLastNowStamp.set(null);
478: }
479:
480: public static class StampClearSync implements Synchronization {
481: public void afterCompletion(int status) {
482: TransactionUtil.clearTransactionStamps();
483: }
484:
485: public void beforeCompletion() {
486: }
487: }
488: }
|