001: //$Id: JTATransaction.java 9601 2006-03-11 18:17:43Z epbernard $
002: package org.hibernate.transaction;
003:
004: import javax.naming.InitialContext;
005: import javax.naming.NamingException;
006: import javax.transaction.Status;
007: import javax.transaction.Synchronization;
008: import javax.transaction.SystemException;
009: import javax.transaction.TransactionManager;
010: import javax.transaction.UserTransaction;
011:
012: import org.apache.commons.logging.Log;
013: import org.apache.commons.logging.LogFactory;
014: import org.hibernate.AssertionFailure;
015: import org.hibernate.HibernateException;
016: import org.hibernate.Transaction;
017: import org.hibernate.TransactionException;
018: import org.hibernate.jdbc.JDBCContext;
019: import org.hibernate.util.JTAHelper;
020:
021: /**
022: * Implements a basic transaction strategy for JTA transactions. Instances check to
023: * see if there is an existing JTA transaction. If none exists, a new transaction
024: * is started. If one exists, all work is done in the existing context. The
025: * following properties are used to locate the underlying <tt>UserTransaction</tt>:
026: * <br><br>
027: * <table>
028: * <tr><td><tt>hibernate.jndi.url</tt></td><td>JNDI initial context URL</td></tr>
029: * <tr><td><tt>hibernate.jndi.class</tt></td><td>JNDI provider class</td></tr>
030: * <tr><td><tt>jta.UserTransaction</tt></td><td>JNDI name</td></tr>
031: * </table>
032: * @author Gavin King
033: */
034: public class JTATransaction implements Transaction {
035:
036: private static final Log log = LogFactory
037: .getLog(JTATransaction.class);
038:
039: private final JDBCContext jdbcContext;
040: private final TransactionFactory.Context transactionContext;
041:
042: private UserTransaction ut;
043: private boolean newTransaction;
044: private boolean begun;
045: private boolean commitFailed;
046: private boolean commitSucceeded;
047: private boolean callback;
048:
049: public JTATransaction(InitialContext context, String utName,
050: JDBCContext jdbcContext,
051: TransactionFactory.Context transactionContext) {
052: this .jdbcContext = jdbcContext;
053: this .transactionContext = transactionContext;
054:
055: log.debug("Looking for UserTransaction under: " + utName);
056:
057: try {
058: ut = (UserTransaction) context.lookup(utName);
059: } catch (NamingException ne) {
060: log.error("Could not find UserTransaction in JNDI", ne);
061: throw new TransactionException(
062: "Could not find UserTransaction in JNDI: ", ne);
063: }
064: if (ut == null) {
065: throw new AssertionFailure(
066: "A naming service lookup returned null");
067: }
068:
069: log.debug("Obtained UserTransaction");
070: }
071:
072: public void begin() throws HibernateException {
073: if (begun) {
074: return;
075: }
076: if (commitFailed) {
077: throw new TransactionException(
078: "cannot re-start transaction after failed commit");
079: }
080:
081: log.debug("begin");
082:
083: try {
084: newTransaction = ut.getStatus() == Status.STATUS_NO_TRANSACTION;
085: if (newTransaction) {
086: ut.begin();
087: log.debug("Began a new JTA transaction");
088: }
089: } catch (Exception e) {
090: log.error("JTA transaction begin failed", e);
091: throw new TransactionException(
092: "JTA transaction begin failed", e);
093: }
094:
095: /*if (newTransaction) {
096: // don't need a synchronization since we are committing
097: // or rolling back the transaction ourselves - assuming
098: // that we do no work in beforeTransactionCompletion()
099: synchronization = false;
100: }*/
101:
102: boolean synchronization = jdbcContext
103: .registerSynchronizationIfPossible();
104:
105: if (!newTransaction && !synchronization) {
106: log
107: .warn("You should set hibernate.transaction.manager_lookup_class if cache is enabled");
108: }
109:
110: if (!synchronization) {
111: //if we could not register a synchronization,
112: //do the before/after completion callbacks
113: //ourself (but we need to let jdbcContext
114: //know that this is what we are going to
115: //do, so it doesn't keep trying to register
116: //synchronizations)
117: callback = jdbcContext.registerCallbackIfNecessary();
118: }
119:
120: begun = true;
121: commitSucceeded = false;
122:
123: jdbcContext.afterTransactionBegin(this );
124: }
125:
126: public void commit() throws HibernateException {
127: if (!begun) {
128: throw new TransactionException(
129: "Transaction not successfully started");
130: }
131:
132: log.debug("commit");
133:
134: boolean flush = !transactionContext.isFlushModeNever()
135: && (callback || !transactionContext
136: .isFlushBeforeCompletionEnabled());
137:
138: if (flush) {
139: transactionContext.managedFlush(); //if an exception occurs during flush, user must call rollback()
140: }
141:
142: if (callback && newTransaction) {
143: jdbcContext.beforeTransactionCompletion(this );
144: }
145:
146: closeIfRequired();
147:
148: if (newTransaction) {
149: try {
150: ut.commit();
151: commitSucceeded = true;
152: log.debug("Committed JTA UserTransaction");
153: } catch (Exception e) {
154: commitFailed = true; // so the transaction is already rolled back, by JTA spec
155: log.error("JTA commit failed", e);
156: throw new TransactionException("JTA commit failed: ", e);
157: } finally {
158: afterCommitRollback();
159: }
160: } else {
161: // this one only really needed for badly-behaved applications!
162: // (if the TransactionManager has a Sychronization registered,
163: // its a noop)
164: // (actually we do need it for downgrading locks)
165: afterCommitRollback();
166: }
167:
168: }
169:
170: public void rollback() throws HibernateException {
171: if (!begun && !commitFailed) {
172: throw new TransactionException(
173: "Transaction not successfully started");
174: }
175:
176: log.debug("rollback");
177:
178: /*if (!synchronization && newTransaction && !commitFailed) {
179: jdbcContext.beforeTransactionCompletion(this);
180: }*/
181:
182: try {
183: closeIfRequired();
184: } catch (Exception e) {
185: log.error("could not close session during rollback", e);
186: //swallow it, and continue to roll back JTA transaction
187: }
188:
189: try {
190: if (newTransaction) {
191: if (!commitFailed) {
192: ut.rollback();
193: log.debug("Rolled back JTA UserTransaction");
194: }
195: } else {
196: ut.setRollbackOnly();
197: log.debug("set JTA UserTransaction to rollback only");
198: }
199: } catch (Exception e) {
200: log.error("JTA rollback failed", e);
201: throw new TransactionException("JTA rollback failed", e);
202: } finally {
203: afterCommitRollback();
204: }
205: }
206:
207: private static final int NULL = Integer.MIN_VALUE;
208:
209: private void afterCommitRollback() throws TransactionException {
210:
211: begun = false;
212:
213: if (callback) { // this method is a noop if there is a Synchronization!
214:
215: if (!newTransaction) {
216: log
217: .warn("You should set hibernate.transaction.manager_lookup_class if cache is enabled");
218: }
219: int status = NULL;
220: try {
221: status = ut.getStatus();
222: } catch (Exception e) {
223: log
224: .error(
225: "Could not determine transaction status after commit",
226: e);
227: throw new TransactionException(
228: "Could not determine transaction status after commit",
229: e);
230: } finally {
231: /*if (status!=Status.STATUS_COMMITTED && status!=Status.STATUS_ROLLEDBACK) {
232: log.warn("Transaction not complete - you should set hibernate.transaction.manager_lookup_class if cache is enabled");
233: //throw exception??
234: }*/
235: jdbcContext.afterTransactionCompletion(
236: status == Status.STATUS_COMMITTED, this );
237: }
238:
239: }
240: }
241:
242: public boolean wasRolledBack() throws TransactionException {
243:
244: //if (!begun) return false;
245: //if (commitFailed) return true;
246:
247: final int status;
248: try {
249: status = ut.getStatus();
250: } catch (SystemException se) {
251: log.error("Could not determine transaction status", se);
252: throw new TransactionException(
253: "Could not determine transaction status", se);
254: }
255: if (status == Status.STATUS_UNKNOWN) {
256: throw new TransactionException(
257: "Could not determine transaction status");
258: } else {
259: return JTAHelper.isRollback(status);
260: }
261: }
262:
263: public boolean wasCommitted() throws TransactionException {
264:
265: //if (!begun || commitFailed) return false;
266:
267: final int status;
268: try {
269: status = ut.getStatus();
270: } catch (SystemException se) {
271: log.error("Could not determine transaction status", se);
272: throw new TransactionException(
273: "Could not determine transaction status: ", se);
274: }
275: if (status == Status.STATUS_UNKNOWN) {
276: throw new TransactionException(
277: "Could not determine transaction status");
278: } else {
279: return status == Status.STATUS_COMMITTED;
280: }
281: }
282:
283: public boolean isActive() throws TransactionException {
284:
285: if (!begun || commitFailed || commitSucceeded)
286: return false;
287:
288: final int status;
289: try {
290: status = ut.getStatus();
291: } catch (SystemException se) {
292: log.error("Could not determine transaction status", se);
293: throw new TransactionException(
294: "Could not determine transaction status: ", se);
295: }
296: if (status == Status.STATUS_UNKNOWN) {
297: throw new TransactionException(
298: "Could not determine transaction status");
299: } else {
300: return status == Status.STATUS_ACTIVE;
301: }
302: }
303:
304: public void registerSynchronization(Synchronization sync)
305: throws HibernateException {
306: if (getTransactionManager() == null) {
307: throw new IllegalStateException(
308: "JTA TransactionManager not available");
309: } else {
310: try {
311: getTransactionManager().getTransaction()
312: .registerSynchronization(sync);
313: } catch (Exception e) {
314: throw new TransactionException(
315: "could not register synchronization", e);
316: }
317: }
318: }
319:
320: private TransactionManager getTransactionManager() {
321: return transactionContext.getFactory().getTransactionManager();
322: }
323:
324: private void closeIfRequired() throws HibernateException {
325: boolean close = callback
326: && transactionContext.shouldAutoClose()
327: && !transactionContext.isClosed();
328: if (close) {
329: transactionContext.managedClose();
330: }
331: }
332:
333: public void setTimeout(int seconds) {
334: try {
335: ut.setTransactionTimeout(seconds);
336: } catch (SystemException se) {
337: throw new TransactionException(
338: "could not set transaction timeout", se);
339: }
340: }
341:
342: protected UserTransaction getUserTransaction() {
343: return ut;
344: }
345: }
|