001: /*
002: * Copyright (c) 1998-2008 Caucho Technology -- all rights reserved
003: *
004: * This file is part of Resin(R) Open Source
005: *
006: * Each copy or derived work must preserve the copyright notice and this
007: * notice unmodified.
008: *
009: * Resin Open Source is free software; you can redistribute it and/or modify
010: * it under the terms of the GNU General Public License as published by
011: * the Free Software Foundation; either version 2 of the License, or
012: * (at your option) any later version.
013: *
014: * Resin Open Source is distributed in the hope that it will be useful,
015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
017: * of NON-INFRINGEMENT. See the GNU General Public License for more
018: * details.
019: *
020: * You should have received a copy of the GNU General Public License
021: * along with Resin Open Source; if not, write to the
022: * Free SoftwareFoundation, Inc.
023: * 59 Temple Place, Suite 330
024: * Boston, MA 02111-1307 USA
025: *
026: * @author Scott Ferguson
027: */
028:
029: package com.caucho.ejb.xa;
030:
031: import com.caucho.amber.manager.AmberConnection;
032: import com.caucho.config.ConfigException;
033: import com.caucho.ejb.EJBExceptionWrapper;
034: import com.caucho.ejb.manager.EjbContainer;
035: import com.caucho.log.Log;
036: import com.caucho.util.FreeList;
037: import com.caucho.util.L10N;
038:
039: import javax.ejb.EJBException;
040: import javax.ejb.EJBTransactionRequiredException;
041: import javax.naming.InitialContext;
042: import javax.naming.NamingException;
043: import javax.transaction.Transaction;
044: import javax.transaction.TransactionManager;
045: import javax.transaction.UserTransaction;
046: import java.util.Hashtable;
047: import java.util.logging.Level;
048: import java.util.logging.Logger;
049:
050: /**
051: * Server containing all the EJBs for a given configuration.
052: *
053: * <p>Each protocol will extend the container to override Handle creation.
054: */
055: public class EjbTransactionManager implements java.io.Serializable {
056: private static final L10N L = new L10N(EjbTransactionManager.class);
057: private static final Logger log = Log
058: .open(EjbTransactionManager.class);
059:
060: public static final int RESIN_DATABASE = 0;
061: public static final int RESIN_READ_ONLY = 1;
062: public static final int RESIN_ROW_LOCKING = 2;
063:
064: protected static final ThreadLocal<TransactionContext> _threadTransaction = new ThreadLocal<TransactionContext>();
065:
066: private FreeList<TransactionContext> _freeTransactions = new FreeList<TransactionContext>(
067: 64);
068:
069: private Hashtable<Transaction, TransactionContext> _transactionMap = new Hashtable<Transaction, TransactionContext>();
070:
071: private Hashtable<String, TransactionContext> _foreignTransactionMap = new Hashtable<String, TransactionContext>();
072:
073: private final EjbContainer _ejbContainer;
074:
075: protected final TransactionManager _transactionManager;
076: protected UserTransaction _userTransaction;
077:
078: private int _resinIsolation = -1;
079: private int _jdbcIsolation = -1;
080:
081: private long _transactionTimeout = 0;
082:
083: private boolean _isClosed;
084:
085: private boolean _isEJB3;
086:
087: /**
088: * Create a server with the given prefix name.
089: */
090: public EjbTransactionManager(EjbContainer ejbContainer)
091: throws ConfigException {
092: _ejbContainer = ejbContainer;
093:
094: UserTransaction ut = null;
095: TransactionManager tm = null;
096:
097: try {
098: InitialContext ic = new InitialContext();
099:
100: ut = (UserTransaction) ic
101: .lookup("java:comp/UserTransaction");
102:
103: tm = (TransactionManager) ic
104: .lookup("java:comp/TransactionManager");
105: } catch (NamingException e) {
106: log.log(Level.WARNING, e.toString(), e);
107: }
108:
109: _userTransaction = ut;
110: _transactionManager = tm;
111:
112: if (_transactionManager == null)
113: throw new ConfigException(L
114: .l("Can't load TransactionManager."));
115: }
116:
117: /**
118: * Returns the manager.
119: */
120: public EjbContainer getEjbContainer() {
121: return _ejbContainer;
122: }
123:
124: /**
125: * Sets EJB 3.0 for throwing expected exceptions.
126: */
127: public void setEJB3(boolean isEJB3) {
128: _isEJB3 = isEJB3;
129: }
130:
131: /**
132: * Returns true if it is EJB 3.0
133: */
134: public boolean isEJB3() {
135: return _isEJB3;
136: }
137:
138: /**
139: * Sets the Resin isolation.
140: */
141: public void setResinIsolation(int resinIsolation) {
142: _resinIsolation = resinIsolation;
143: }
144:
145: /**
146: * Sets the Resin isolation for the container.
147: */
148: public int getResinIsolation() {
149: return _resinIsolation;
150: }
151:
152: /**
153: * Sets the JDBC isolation.
154: */
155: public void setJDBCIsolation(int jdbcIsolation) {
156: _jdbcIsolation = jdbcIsolation;
157: }
158:
159: /**
160: * Gets the JDBC isolation level.
161: */
162: public int getJDBCIsolation() {
163: return _jdbcIsolation;
164: }
165:
166: /**
167: * Gets the transaction timeout
168: */
169: public long getTransactionTimeout() {
170: return _transactionTimeout;
171: }
172:
173: /**
174: * Sets the transaction timout.
175: */
176: public void setTransactionTimeout(long transactionTimeout) {
177: _transactionTimeout = transactionTimeout;
178: }
179:
180: void setUserTransaction(UserTransaction userTransaction) {
181: _userTransaction = userTransaction;
182: }
183:
184: public UserTransaction getUserTransaction() {
185: return _userTransaction;
186: }
187:
188: /**
189: * Returns a new AmberConnection.
190: */
191: public AmberConnection getAmberConnection() {
192: TransactionContext xaContext = _threadTransaction.get();
193:
194: if (xaContext != null)
195: return xaContext.getAmberConnection();
196: else
197: throw new IllegalStateException(
198: "can't get transaction outside of context");
199: }
200:
201: public void commitTransaction() throws EJBException {
202: try {
203: if (_transactionManager.getTransaction() != null)
204: _userTransaction.commit();
205: } catch (Exception e) {
206: throw EJBExceptionWrapper.create(e);
207: }
208: }
209:
210: public void rollbackTransaction() throws EJBException {
211: try {
212: _userTransaction.rollback();
213: } catch (Exception e) {
214: throw EJBExceptionWrapper.create(e);
215: }
216: }
217:
218: TransactionManager getTransactionManager() throws EJBException {
219: return _transactionManager;
220: }
221:
222: public Transaction getTransaction() throws EJBException {
223: try {
224: return _transactionManager.getTransaction();
225: } catch (Exception e) {
226: throw EJBExceptionWrapper.create(e);
227: }
228: }
229:
230: public TransactionContext getTransactionContext() {
231: return _threadTransaction.get();
232: }
233:
234: /**
235: * Returns a transaction context for the "required" transaction. If
236: * there's already an active transaction, use it. Otherwise create a
237: * new transaction.
238: *
239: * @return the transaction context for the request
240: */
241: public TransactionContext beginRequired() throws EJBException {
242: try {
243: Transaction oldTrans = _transactionManager.getTransaction();
244: TransactionContext oldCxt = _threadTransaction.get();
245:
246: // If this is within the same EJB transaction, just bump
247: // the count
248: if (oldCxt != null && oldTrans != null
249: && oldCxt.getTransaction() == oldTrans) {
250: oldCxt.pushDepth();
251: return oldCxt;
252: }
253:
254: // If there's an old transaction, see if there's a transaction context
255: // (only needed to support suspends)
256:
257: if (oldTrans != null) {
258: TransactionContext cxt = _transactionMap.get(oldTrans);
259:
260: if (cxt != null) {
261: _transactionMap.remove(oldTrans);
262:
263: _threadTransaction.set(cxt);
264: cxt.pushDepth();
265:
266: return cxt;
267: }
268: }
269:
270: // Link the new context to any old context
271: TransactionContext cxt = createTransaction();
272: cxt.setOld(oldCxt);
273:
274: _threadTransaction.set(cxt);
275: // If there was an old transaction, use it
276: if (oldTrans != null) {
277: setTransaction(cxt, oldTrans);
278: // This context is controlled by a user transaction
279: cxt.setUserTransaction(true);
280: cxt.pushDepth();
281: } else {
282: _userTransaction
283: .setTransactionTimeout((int) (_transactionTimeout / 1000L));
284: _userTransaction.begin();
285: Transaction trans = _transactionManager
286: .getTransaction();
287:
288: setTransaction(cxt, trans);
289: }
290:
291: if (_resinIsolation == RESIN_ROW_LOCKING)
292: cxt.setRowLocking(true);
293:
294: return cxt;
295: } catch (Exception e) {
296: log.log(Level.WARNING, e.toString(), e);
297:
298: throw EJBExceptionWrapper.create(e);
299: }
300: }
301:
302: /**
303: * Returns a transaction context for a single read call. The single
304: * read is like supports, but returns a null transaction context
305: * if there's no transaction.
306: *
307: * @return the transaction context for the request
308: */
309: public TransactionContext beginSingleRead() throws EJBException {
310: try {
311: TransactionContext cxt = _threadTransaction.get();
312: Transaction trans = _transactionManager.getTransaction();
313:
314: if (trans == null)
315: return null;
316:
317: // If in the same EJB transaction, return it
318: if (cxt != null && cxt.getTransaction() == trans) {
319: cxt.pushDepth();
320: return cxt;
321: }
322:
323: // Check to see if there's an old EJB transaction to handle resume()
324: TransactionContext newCxt = _transactionMap.get(trans);
325:
326: if (newCxt != null) {
327: newCxt.pushDepth();
328: return newCxt;
329: }
330:
331: // Create a new EJB context and link to any old context
332: newCxt = createTransaction();
333: newCxt.pushDepth();
334:
335: _threadTransaction.set(newCxt);
336: setTransaction(newCxt, trans);
337: // The transaction is controlled by a user transaction
338: newCxt.setUserTransaction(true);
339:
340: return newCxt;
341: } catch (Exception e) {
342: log.log(Level.WARNING, e.toString(), e);
343:
344: throw EJBExceptionWrapper.create(e);
345: }
346: }
347:
348: /**
349: * Begins a new transaction, suspending the old one if necessary.
350: *
351: * @return the old transaction context
352: */
353: public TransactionContext beginRequiresNew() throws EJBException {
354: try {
355: TransactionContext oldCxt = _threadTransaction.get();
356: Transaction oldTrans = _transactionManager.suspend();
357:
358: TransactionContext newCxt = createTransaction();
359: newCxt.setOld(oldCxt);
360: newCxt.setOldTrans(oldTrans);
361:
362: _threadTransaction.set(newCxt);
363:
364: if (_resinIsolation == RESIN_ROW_LOCKING)
365: newCxt.setRowLocking(true);
366:
367: _userTransaction
368: .setTransactionTimeout((int) (_transactionTimeout / 1000L));
369: _userTransaction.begin();
370: Transaction trans = _transactionManager.getTransaction();
371:
372: setTransaction(newCxt, trans);
373:
374: return newCxt;
375: } catch (Exception e) {
376: log.log(Level.WARNING, e.toString(), e);
377:
378: throw EJBExceptionWrapper.create(e);
379: }
380: }
381:
382: /**
383: * Begins a new transaction, suspending the old one if necessary.
384: *
385: * @return the old transaction context
386: */
387: public TransactionContext beginSupports() throws EJBException {
388: try {
389: Transaction trans = _transactionManager.getTransaction();
390: TransactionContext cxt = _threadTransaction.get();
391:
392: // Create a new EJB transaction if necessary
393: if (cxt == null || cxt.getTransaction() != trans) {
394: // check for a suspended transaction
395: if (trans != null)
396: cxt = _transactionMap.get(trans);
397: else
398: cxt = null;
399:
400: if (cxt == null) {
401: cxt = createTransactionNoDepth();
402: setTransaction(cxt, trans);
403: }
404:
405: _threadTransaction.set(cxt);
406:
407: if (trans != null) {
408: cxt.setUserTransaction(true);
409: cxt.pushDepth();
410: }
411: }
412:
413: cxt.pushDepth();
414:
415: if (_resinIsolation == RESIN_ROW_LOCKING)
416: cxt.setRowLocking(true);
417:
418: return cxt;
419: } catch (Exception e) {
420: log.log(Level.WARNING, e.toString(), e);
421:
422: throw EJBExceptionWrapper.create(e);
423: }
424: }
425:
426: /**
427: * Suspends the current transaction
428: *
429: * @return the old transaction context
430: */
431: public TransactionContext suspend() throws EJBException {
432: try {
433: TransactionContext oldCxt = _threadTransaction.get();
434: Transaction oldTrans = _transactionManager.suspend();
435:
436: if (oldTrans == null && oldCxt != null) {
437: oldCxt.pushDepth();
438: return oldCxt;
439: }
440:
441: TransactionContext cxt = createTransaction();
442: _threadTransaction.set(cxt);
443:
444: cxt.setOld(oldCxt);
445: cxt.setOldTrans(oldTrans);
446:
447: return cxt;
448: } catch (Exception e) {
449: log.log(Level.WARNING, e.toString(), e);
450:
451: throw EJBExceptionWrapper.create(e);
452: }
453: }
454:
455: /**
456: * Starts a Never transaction, i.e. there is no transaction.
457: *
458: * @return the old transaction context
459: */
460: public TransactionContext beginNever() throws EJBException {
461: try {
462: Transaction oldTrans = _transactionManager.getTransaction();
463:
464: if (oldTrans != null)
465: throw new EJBException(
466: "Transaction forbidden in 'Never' method");
467:
468: TransactionContext cxt = _threadTransaction.get();
469:
470: if (cxt == null) {
471: cxt = createTransaction();
472: _threadTransaction.set(cxt);
473: } else
474: cxt.pushDepth();
475:
476: return cxt;
477: } catch (Exception e) {
478: log.log(Level.WARNING, e.toString(), e);
479:
480: throw EJBExceptionWrapper.create(e);
481: }
482: }
483:
484: /**
485: * Begins a new transaction, the old one must exist.
486: *
487: * @return the old transaction context
488: */
489: public TransactionContext beginMandatory() throws EJBException {
490: try {
491: Transaction trans = _transactionManager.getTransaction();
492:
493: if (trans == null) {
494: // XXX: check ejb/02a0
495: // TCK ejb30/tx: ejb/0f14 vs ejb/02a0
496: if (isEJB3())
497: throw new EJBTransactionRequiredException(
498: "Transaction required in 'Mandatory' method");
499: else
500: throw new EJBException(
501: "Transaction required in 'Mandatory' method");
502: }
503:
504: return beginRequired();
505: } catch (Exception e) {
506: log.log(Level.WARNING, e.toString(), e);
507:
508: throw EJBExceptionWrapper.create(e);
509: }
510: }
511:
512: /**
513: * Resumes a suspended transaction.
514: *
515: * @return the old transaction context
516: */
517: public void resume(TransactionContext oldCxt, Transaction oldTrans,
518: Transaction completedTransaction) throws EJBException {
519: try {
520: if (completedTransaction != null)
521: _transactionMap.remove(completedTransaction);
522: _threadTransaction.set(oldCxt);
523:
524: if (oldTrans != null)
525: _transactionManager.resume(oldTrans);
526: } catch (Exception e) {
527: log.log(Level.WARNING, e.toString(), e);
528:
529: throw EJBExceptionWrapper.create(e);
530: }
531: }
532:
533: /**
534: * Binds the EJB transaction context to the TM transaction context.
535: */
536: private void setTransaction(TransactionContext cxt,
537: Transaction trans) {
538: if (cxt != null) {
539: Transaction old = cxt.getTransaction();
540:
541: if (old != null)
542: _transactionMap.remove(old);
543: } else if (trans != null)
544: _transactionMap.remove(trans);
545:
546: cxt.setTransaction(trans);
547:
548: if (trans != null)
549: _transactionMap.put(trans, cxt);
550: }
551:
552: /**
553: * Create a transaction context.
554: */
555: TransactionContext createTransaction() {
556: TransactionContext trans = _freeTransactions.allocate();
557:
558: if (trans == null)
559: trans = new TransactionContext(this );
560: trans.init(true);
561:
562: return trans;
563: }
564:
565: /**
566: * Create a transaction context.
567: */
568: TransactionContext createTransactionNoDepth() {
569: TransactionContext trans = _freeTransactions.allocate();
570:
571: if (trans == null)
572: trans = new TransactionContext(this );
573: trans.init(false);
574:
575: return trans;
576: }
577:
578: /**
579: * Creates a transaction from an external xid.
580: */
581: public TransactionContext startTransaction(String xid) {
582: try {
583: TransactionContext xa = _foreignTransactionMap.get(xid);
584:
585: if (xa != null) {
586: resume(xa, xa.getTransaction(), null);
587: return xa;
588: }
589:
590: xa = beginRequiresNew();
591:
592: _foreignTransactionMap.put(xid, xa);
593:
594: return xa;
595: } catch (Throwable e) {
596: log.log(Level.FINE, e.toString(), e);
597:
598: _foreignTransactionMap.remove(xid);
599:
600: return null;
601: }
602: }
603:
604: /**
605: * Creates a transaction from an external xid.
606: */
607: public void finishTransaction(String xid) {
608: try {
609: TransactionContext xa = _foreignTransactionMap.get(xid);
610:
611: if (xa == null) {
612: } else if (xa.isEmpty()) {
613: _foreignTransactionMap.remove(xid);
614: xa.commit();
615: } else {
616: suspend();
617: //
618: }
619: } catch (Exception e) {
620: log.log(Level.FINE, e.toString(), e);
621: }
622: }
623:
624: /**
625: * Commits a transaction from an external xid.
626: */
627: public void commitTransaction(String xid) {
628: try {
629: TransactionContext xa = _foreignTransactionMap.remove(xid);
630:
631: if (xa != null) {
632: resume(xa, xa.getTransaction(), null);
633: xa.commit();
634: }
635: } catch (Throwable e) {
636: log.log(Level.FINE, e.toString(), e);
637: }
638: }
639:
640: /**
641: * Rolls-back a transaction from an external xid.
642: */
643: public void rollbackTransaction(String xid) {
644: try {
645: TransactionContext xa = _foreignTransactionMap.remove(xid);
646:
647: if (xa != null) {
648: resume(xa, xa.getTransaction(), null);
649: xa.rollback();
650: }
651: } catch (Throwable e) {
652: log.log(Level.FINE, e.toString(), e);
653: }
654: }
655:
656: /**
657: * Free a transaction context.
658: */
659: void freeTransaction(TransactionContext trans) {
660: _freeTransactions.free(trans);
661: }
662:
663: /**
664: * Closes the container.
665: */
666: public void destroy() {
667: synchronized (this ) {
668: if (_isClosed)
669: return;
670:
671: _isClosed = true;
672: }
673:
674: _transactionMap = null;
675: }
676: }
|