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.amber.entity.AmberEntityHome;
033: import com.caucho.amber.entity.Entity;
034: import com.caucho.ejb.EJBExceptionWrapper;
035: import com.caucho.ejb.entity.EntityServer;
036: import com.caucho.ejb.entity.QEntity;
037: import com.caucho.log.Log;
038: import com.caucho.transaction.TransactionImpl;
039: import com.caucho.util.Alarm;
040: import com.caucho.util.L10N;
041:
042: import javax.ejb.EJBException;
043: import javax.ejb.SessionSynchronization;
044: import javax.transaction.Status;
045: import javax.transaction.Synchronization;
046: import javax.transaction.Transaction;
047: import javax.transaction.UserTransaction;
048: import java.rmi.RemoteException;
049: import java.util.logging.Level;
050: import java.util.logging.Logger;
051:
052: /**
053: * Handles entity beans for a single transaction.
054: */
055: public class TransactionContext implements Synchronization {
056: static final L10N L = new L10N(TransactionContext.class);
057: protected static final Logger log = Log
058: .open(TransactionContext.class);
059:
060: public static final TransactionContext NULL_TRANSACTION = new TransactionContext(
061: null);
062:
063: private EjbTransactionManager _container;
064: private AmberConnection _amberConn;
065:
066: private UserTransaction _userTransaction;
067: private Transaction _transaction;
068:
069: // the previous TransactionContext when this one completes
070: TransactionContext _old;
071: // the previous Transaction when this one completes
072: Transaction _oldTrans;
073:
074: private long _startTime;
075:
076: private TransactionObject[] _objects = new TransactionObject[16];
077: private int _objectTop;
078:
079: private SessionSynchronization[] _sessions = new SessionSynchronization[16];
080: private int _sessionTop;
081:
082: private boolean _isRowLocking;
083: private boolean _rollbackOnly;
084:
085: private boolean _isUserTransaction;
086: private int _depth;
087: private boolean _isAlive;
088: private boolean _isCommitting;
089:
090: TransactionContext(EjbTransactionManager container) {
091: _container = container;
092:
093: if (container != null)
094: _userTransaction = container.getUserTransaction();
095: }
096:
097: void init(boolean pushDepth) {
098: if (_isAlive) {
099: log
100: .warning(L
101: .l(
102: "Transaction {0} nested start. This is an internal Resin error, please report it as a bug at bugs@caucho.com.",
103: this ));
104:
105: throw new IllegalStateException(L
106: .l("nested transaction start"));
107: }
108:
109: _transaction = null;
110: _old = null;
111: _oldTrans = null;
112: _objectTop = 0;
113: _sessionTop = 0;
114: _rollbackOnly = false;
115: _depth = pushDepth ? 1 : 0;
116: _isUserTransaction = false;
117: _isAlive = true;
118: _isRowLocking = false;
119: _startTime = Alarm.getCurrentTime();
120: }
121:
122: public void setTransaction(Transaction transaction) {
123: if (_transaction != null && _transaction != transaction)
124: throw new IllegalStateException(
125: "can't set transaction twice.");
126: _transaction = transaction;
127:
128: if (transaction != null) {
129: try {
130: transaction.registerSynchronization(this );
131: } catch (Exception e) {
132: throw new EJBExceptionWrapper(e);
133: }
134: }
135: }
136:
137: public Transaction getTransaction() {
138: return _transaction;
139: }
140:
141: /**
142: * Returns true for a read-only transaction.
143: */
144: public boolean isReadOnly() {
145: return _transaction == null;
146: }
147:
148: /**
149: * Returns true for a row-locking transaction.
150: */
151: public boolean isRowLocking() {
152: return _transaction != null && _isRowLocking;
153: }
154:
155: /**
156: * Set true for a row-locking transaction.
157: */
158: public void setRowLocking(boolean isRowLocking) {
159: _isRowLocking = isRowLocking;
160: }
161:
162: TransactionContext getOld() {
163: return _old;
164: }
165:
166: void setOld(TransactionContext old) {
167: _old = old;
168: }
169:
170: Transaction getOldTrans() {
171: return _oldTrans;
172: }
173:
174: void setOldTrans(Transaction old) {
175: if (old == _transaction && old != null)
176: throw new IllegalStateException();
177:
178: _oldTrans = old;
179: }
180:
181: public void pushDepth() {
182: if (_depth == 0) {
183: _isAlive = true;
184: }
185:
186: _depth++;
187: }
188:
189: public void setUserTransaction(boolean isUserTransaction) {
190: if (_depth == 0)
191: _isAlive = true;
192:
193: _isUserTransaction = isUserTransaction;
194: }
195:
196: /**
197: * Returns true if the transaction must rollback.
198: */
199: public boolean getRollbackOnly() {
200: return _rollbackOnly;
201: }
202:
203: /**
204: * Forces the transaction to rollback.
205: */
206: public void setRollbackOnly() {
207: _rollbackOnly = true;
208:
209: if (_transaction != null) {
210: try {
211: _transaction.setRollbackOnly();
212: } catch (Exception e) {
213: throw new EJBExceptionWrapper(e);
214: }
215: }
216: }
217:
218: /**
219: * Forces the transaction to rollback.
220: */
221: public RuntimeException setRollbackOnly(Throwable exn) {
222: _rollbackOnly = true;
223:
224: if (log.isLoggable(Level.FINER))
225: log.log(Level.FINER, "rollback only: " + exn, exn);
226:
227: if (_transaction != null) {
228: try {
229: if (_transaction instanceof TransactionImpl)
230: ((TransactionImpl) _transaction)
231: .setRollbackOnly(exn);
232: else
233: _transaction.setRollbackOnly();
234: } catch (Exception e) {
235: return EJBExceptionWrapper.createRuntime(exn);
236: }
237: }
238:
239: return EJBExceptionWrapper.createRuntime(exn);
240: }
241:
242: /**
243: * Add a new persistent object to the transaction context.
244: *
245: * @param object the new persistent object to be managed
246: */
247: public void addObject(TransactionObject object) {
248: if (_objects.length <= _objectTop + 1) {
249: TransactionObject[] newObjects;
250: newObjects = new TransactionObject[_objects.length * 2];
251: for (int i = 0; i < _objectTop; i++)
252: newObjects[i] = _objects[i];
253: _objects = newObjects;
254: }
255:
256: _objects[_objectTop++] = object;
257: }
258:
259: /**
260: * Remove a transaction object from the transaction context.
261: *
262: * @param object the transaction object to be removed
263: */
264: public void removeObject(TransactionObject object) {
265: for (int i = 0; i < _objectTop; i++) {
266: if (_objects[i] == object) {
267: for (int j = i; j + 1 < _objectTop; j++)
268: _objects[j] = _objects[j + 1];
269: i--;
270: _objectTop--;
271: }
272: }
273: }
274:
275: /**
276: * Returns the matching object.
277: */
278: public QEntity getEntity(EntityServer server, Object primaryKey) {
279: for (int i = _objectTop - 1; i >= 0; i--) {
280: TransactionObject obj = _objects[i];
281:
282: if (obj instanceof QEntity) {
283: QEntity entity = (QEntity) obj;
284:
285: if (entity._caucho_isMatch(server, primaryKey)) {
286: return entity;
287: }
288: }
289: }
290:
291: return null;
292: }
293:
294: public Entity getAmberEntity(EntityServer server, Object key) {
295: QEntity entity = getEntity(server, key);
296:
297: if (entity != null)
298: return (Entity) entity;
299:
300: AmberEntityHome entityHome = server.getAmberEntityHome();
301: Class cl = entityHome.getRootType().getInstanceClass();
302:
303: return getAmberConnection().getEntity(cl, key);
304: }
305:
306: public void addAmberEntity(EntityServer server, Entity entity) {
307: try {
308: AmberEntityHome entityHome = server.getAmberEntityHome();
309:
310: entity.__caucho_makePersistent(getAmberConnection(),
311: entityHome.getEntityType());
312:
313: addObject((TransactionObject) entity);
314: } catch (RuntimeException e) {
315: throw e;
316: } catch (Exception e) {
317: throw new RuntimeException(e);
318: }
319: }
320:
321: /**
322: * Returns the amber connection.
323: */
324: public AmberConnection getAmberConnection() {
325: if (_amberConn == null) {
326: _amberConn = _container.getEjbContainer()
327: .createEjbPersistenceUnit().getThreadConnection(
328: false);
329: }
330:
331: try {
332: _amberConn.setXA(_transaction != null);
333: } catch (Exception e) {
334: log.log(Level.WARNING, e.toString(), e);
335: }
336:
337: return _amberConn;
338: }
339:
340: /**
341: * Add a session to the transaction context.
342: *
343: * @param session the new session to be managed
344: */
345: public void addSession(SessionSynchronization session)
346: throws EJBException {
347: for (int i = _sessionTop - 1; i >= 0; i--)
348: if (_sessions[i] == session)
349: return;
350:
351: if (_sessionTop + 1 >= _sessions.length) {
352: SessionSynchronization[] newSessions;
353: newSessions = new SessionSynchronization[_sessions.length * 2];
354:
355: for (int i = 0; i < _sessionTop; i++)
356: newSessions[i] = _sessions[i];
357: _sessions = newSessions;
358: }
359:
360: _sessions[_sessionTop++] = session;
361: try {
362: session.afterBegin();
363: } catch (RemoteException e) {
364: EJBException exn = new EJBException(e.toString());
365: exn.initCause(e);
366: throw exn;
367: }
368: }
369:
370: /**
371: * Synchronizes the objects in the context.
372: */
373: public void sync() throws EJBException {
374: try {
375: for (int i = _objectTop - 1; i >= 0; i--) {
376: _objects[i]._caucho_sync();
377: }
378: } catch (Exception e) {
379: throw setRollbackOnly(e);
380: }
381: }
382:
383: /**
384: * Returns true if the transaction isn't used.
385: */
386: public boolean isEmpty() {
387: if (!_isAlive)
388: return true;
389: else if (_transaction == null)
390: return true;
391: else if (!(_transaction instanceof TransactionImpl))
392: return false;
393: else
394: return ((TransactionImpl) _transaction).isEmpty();
395: }
396:
397: /**
398: * Commit the transaction
399: */
400: public void commit() throws EJBException {
401: if (_isCommitting || --_depth > 0)
402: return;
403:
404: boolean hasCompletion = false;
405: try {
406: _isCommitting = true;
407:
408: if (!_isAlive) {
409: log.warning(L.l("Transaction has died"));
410: } else if (_transaction == null) {
411: hasCompletion = true;
412: try {
413: beforeCompletion();
414: } finally {
415: afterCompletion(_rollbackOnly ? Status.STATUS_ROLLEDBACK
416: : Status.STATUS_COMMITTED);
417: }
418: } else if (_rollbackOnly
419: || _transaction.getStatus() == Status.STATUS_MARKED_ROLLBACK) {
420: hasCompletion = true;
421: _userTransaction.rollback();
422: } else if (_transaction.getStatus() != Status.STATUS_NO_TRANSACTION) {
423: hasCompletion = true;
424: _userTransaction.commit();
425: }
426: } catch (Exception e) {
427: throw EJBExceptionWrapper.createRuntime(e);
428: } finally {
429: _isCommitting = false;
430:
431: if (!hasCompletion)
432: afterCompletion(Status.STATUS_ROLLEDBACK);
433: }
434: }
435:
436: /**
437: * Rollback the transaction (help?)
438: */
439: public void rollback() throws EJBException {
440: if (_isCommitting || --_depth > 0)
441: return;
442:
443: try {
444: _isCommitting = true;
445:
446: if (!_rollbackOnly)
447: setRollbackOnly();
448:
449: if (_transaction == null && _isAlive) {
450: try {
451: beforeCompletion();
452: } finally {
453: afterCompletion(Status.STATUS_ROLLEDBACK);
454: }
455: } else
456: _userTransaction.rollback();
457: } catch (EJBException e) {
458: throw e;
459: } catch (Exception e) {
460: throw new EJBExceptionWrapper(e);
461: } finally {
462: _isCommitting = false;
463:
464: if (_depth <= 0 && _transaction != null)
465: afterCompletion(Status.STATUS_ROLLEDBACK);
466: }
467: }
468:
469: /**
470: * Called by the transaction before the transaction completes.
471: */
472: public void beforeCompletion() {
473: try {
474: if (_transaction != null
475: && _transaction.getStatus() == Status.STATUS_MARKED_ROLLBACK)
476: _rollbackOnly = true;
477:
478: for (int i = _sessionTop - 1; i >= 0; i--)
479: _sessions[i].beforeCompletion();
480:
481: for (int i = _objectTop - 1; i >= 0; i--) {
482: _objects[i]._caucho_beforeCompletion(!_rollbackOnly);
483: }
484:
485: // ejb/0600: com.caucho.transaction.TransactionImpl
486: // will call _amberConn.beforeCompletion() which
487: // already calls beforeCommit().
488: //
489: // if (_amberConn != null)
490: // _amberConn.beforeCommit();
491: } catch (Throwable e) {
492: throw setRollbackOnly(e);
493: }
494: }
495:
496: public boolean isDead() {
497: return !_isAlive;
498: }
499:
500: /**
501: * After the transaction completes, do any extra work.
502: */
503: public void afterCompletion(int status) {
504: /*
505: if (! _isUserTransaction && _depth > 0) {
506: return;
507: }
508: */
509:
510: if (!_isAlive) {
511: IllegalStateException e = new IllegalStateException(
512: "after completion called for dead transaction.");
513: log.log(Level.WARNING, e.toString(), e);
514: return;
515: }
516:
517: boolean wasCommitted = status == Status.STATUS_COMMITTED;
518: int sessionTop = _sessionTop;
519: int objectTop = _objectTop;
520: TransactionContext old = _old;
521: Transaction transaction = _transaction;
522: Transaction oldTrans = _oldTrans;
523:
524: _sessionTop = 0;
525: _objectTop = 0;
526: _old = null;
527: if (oldTrans == transaction)
528: oldTrans = null;
529: _oldTrans = null;
530: _rollbackOnly = false;
531:
532: Throwable exn = null;
533:
534: try {
535: AmberConnection amberConn = _amberConn;
536: _amberConn = null;
537: if (amberConn != null) {
538: amberConn.afterCommit(wasCommitted);
539: amberConn.freeConnection();
540: }
541: } catch (Throwable e) {
542: log.log(Level.WARNING, e.toString(), e);
543: }
544:
545: for (int i = sessionTop - 1; i >= 0; i--) {
546: try {
547: _sessions[i].afterCompletion(wasCommitted);
548: _sessions[i] = null;
549: } catch (Throwable e) {
550: exn = e;
551: log.log(Level.WARNING, e.toString(), e);
552: }
553: }
554:
555: for (int i = objectTop - 1; i >= 0; i--) {
556: try {
557: _objects[i]._caucho_afterCompletion(wasCommitted);
558: _objects[i] = null;
559: } catch (Throwable e) {
560: exn = e;
561: log.log(Level.WARNING, e.toString(), e);
562: }
563: }
564:
565: _transaction = null;
566: _isAlive = false;
567:
568: if (_depth == 0 || _isUserTransaction && _depth == 1) {
569: _container.resume(old, oldTrans, transaction);
570: _container.freeTransaction(this);
571: }
572:
573: if (exn != null)
574: throw EJBExceptionWrapper.createRuntime(exn);
575: }
576: }
|