/* * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved * * This file is part of Resin(R) Open Source * * Each copy or derived work must preserve the copyright notice and this * notice unmodified. * * Resin Open Source is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Resin Open Source is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty * of NON-INFRINGEMENT. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with Resin Open Source; if not, write to the * * Free Software Foundation, Inc. * 59 Temple Place, Suite 330 * Boston, MA 02111-1307 USA * * @author Scott Ferguson */ package com.caucho.transaction; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import javax.resource.spi.LocalTransaction; import javax.transaction.HeuristicMixedException; import javax.transaction.HeuristicRollbackException; import javax.transaction.NotSupportedException; import javax.transaction.RollbackException; import javax.transaction.Status; import javax.transaction.Synchronization; import javax.transaction.SystemException; import javax.transaction.Transaction; import javax.transaction.xa.XAException; import javax.transaction.xa.XAResource; import javax.transaction.xa.Xid; import com.caucho.transaction.xalog.AbstractXALogStream; import com.caucho.util.Alarm; import com.caucho.util.AlarmListener; import com.caucho.util.CharBuffer; import com.caucho.util.L10N; /** * Implementation of the transaction. Transactions are normally associated with * a single thread. */ public class TransactionImpl implements Transaction, AlarmListener { private static final Logger log = Logger.getLogger(TransactionImpl.class .getName()); private static final L10N L = new L10N(TransactionImpl.class); // private final static long EXTRA_TIMEOUT = 60000; private final static long EXTRA_TIMEOUT = 0; private final static long MAX_TIMEOUT = 24 * 3600 * 1000L; // flag when the resource is active (between getConnection() and close()) private final static int RES_ACTIVE = 0x1; // flag when the resource shares another resource manager private final static int RES_SHARED_RM = 0x2; // flag when the resource is suspended private final static int RES_SUSPENDED = 0x4; // flag when the resource wants to commit private final static int RES_COMMIT = 0x8; private TransactionManagerImpl _transactionManager; private UserTransactionImpl _userTransaction; private UserTransactionSuspendState _suspendState; // The transaction id for the resource private XidImpl _xid; /** Transaction resources **/ // TODO Should these be re-factored to lists? /** * How many resources are available in the transaction. */ private int _resourceCount; /** * The current resources in the transaction */ private XAResource [] _resources; /** * The first resource can use a local transaction. */ private LocalTransaction _localTransaction; /** * The xids for the resources. */ private XidImpl [] _resourceXids; /** * Whether the resources are active (between begin/end) or not. */ private int [] _resourceStates; /** * Transaction resources that a client API such * as JPA may store and retrieve by key. */ private Map<Object, Object> _mappedResources; private long _timeout = 0; /** Transaction synchronization **/ /** * Synchronizations interposed for container resources like the JPA provider. */ private ArrayList<Synchronization> _interposedSynchronizations; /** * Transaction synchronization list for resources like stateful EJBs. */ private ArrayList<Synchronization> _synchronizations; /** Transaction status **/ // State of the transaction private int _status; // True if the transaction is suspended. private boolean _isSuspended; // True if the transaction was closed. private boolean _isDead; private Throwable _rollbackException; private AbstractXALogStream _xaLog; private HashMap<String, Object> _attributes; private long _beginTime; private Alarm _alarm; /** * Creates a new transaction. * * @param manager * the owning transaction manager */ TransactionImpl(TransactionManagerImpl manager) { _transactionManager = manager; _timeout = _transactionManager.getTimeout(); _status = Status.STATUS_NO_TRANSACTION; _alarm = new Alarm("xa-timeout", this, ClassLoader.getSystemClassLoader()); } public static TransactionImpl getCurrent() { return TransactionManagerImpl.getLocal().getCurrent(); } /** * Sets the user transaction. */ public void setUserTransaction(UserTransactionImpl ut) { _userTransaction = ut; } public Xid getXid() { return _xid; } /** * Returns true if the transaction has any associated resources. */ boolean hasResources() { return _resourceCount > 0; } public boolean isActive() { return _status == Status.STATUS_ACTIVE; } /** * Returns true if the transaction is currently suspended. */ boolean isSuspended() { return _isSuspended; } /** * Returns true if the transaction is dead, i.e. failed for some reason. */ boolean isDead() { return _isDead; } /** * Puts a resource into a map of resources and adds the resource to the * transaction. * * @param key * User defined key for the Resource. * @param resource * The Resource to enlist in the transaction. * @throws RollbackException * If a roll-back occurs. * @throws SystemException * If an unexpected problem occurs. */ public void putResource(Object key, XAResource resource) throws RollbackException, SystemException { enlistResource(resource); if (_mappedResources == null) { _mappedResources = new HashMap<Object, Object>(); } _mappedResources.put(key, resource); } public void putResource(Object key, Object resource) { if (_mappedResources == null) { _mappedResources = new HashMap<Object, Object>(); } _mappedResources.put(key, resource); } /** * Gets a Resource from the underlying map. * * @param key * User defined key for the resource. * @return The Resource mapped to the key. */ public Object getResource(Object key) { Map<Object, Object> mappedResources = _mappedResources; if (mappedResources != null) return mappedResources.get(key); else return null; } /** * Enlists a resource with the current transaction. Example resources are * database or JMS connections. * * @return true if successful */ @Override public boolean enlistResource(XAResource resource) throws RollbackException, SystemException { if (resource == null) { throw new IllegalArgumentException(L.l("Resource must not be null in enlistResource")); } if (_isSuspended) { throw new IllegalStateException(L.l( "Can't enlist resource {0} because the transaction is suspended.", resource)); } if (_status == Status.STATUS_ACTIVE) { // normal } else { // validate the status if (_status != Status.STATUS_MARKED_ROLLBACK) { } else if (_rollbackException != null) { throw RollbackExceptionWrapper.create(_rollbackException); } else throw new RollbackException( L.l("Can't enlist resource {0} because the transaction is marked rollback-only.", resource)); if (_status == Status.STATUS_NO_TRANSACTION) throw new IllegalStateException(L.l( "Can't enlist resource {0} because the transaction is not active", resource)); throw new IllegalStateException( L.l("Can't enlist resource {0} because the transaction is not in an active state. state='{1}'", resource, xaState(_status))); } // creates enough space in the arrays for the resource if (_resources == null) { _resources = new XAResource[16]; _resourceXids = new XidImpl[16]; _resourceStates = new int[16]; } else if (_resources.length <= _resourceCount) { int oldLength = _resources.length; int newLength = 2 * oldLength; XAResource [] resources = new XAResource[newLength]; XidImpl [] resourceXids = new XidImpl[newLength]; int [] resourceStates = new int[newLength]; System.arraycopy(_resources, 0, resources, 0, oldLength); System.arraycopy(_resourceStates, 0, resourceStates, 0, oldLength); System.arraycopy(_resourceXids, 0, resourceXids, 0, oldLength); _resources = resources; _resourceStates = resourceStates; _resourceXids = resourceXids; } int flags = XAResource.TMNOFLAGS; // Active transaction will call the XAResource.start() method // to let the resource manager know that the resource is managed. // // If the resource uses the same resource manager as one of the // current resources, issue a TMJOIN message. XidImpl xid = _xid; boolean hasNewResource = true; for (int i = 0; i < _resourceCount; i++) { if (_resources[i] != resource) { } else if ((_resourceStates[i] & RES_ACTIVE) != 0) { IllegalStateException exn; exn = new IllegalStateException( L.l("Can't enlist same resource {0} twice. " + "Delist is required before calling enlist with an old resource.", resource)); setRollbackOnly(exn); throw exn; } try { if (_resources[i].isSameRM(resource)) { flags = XAResource.TMJOIN; xid = _resourceXids[i]; if ((_resourceStates[i] & RES_ACTIVE) == 0) { _resources[i] = resource; _resourceStates[i] |= RES_ACTIVE; hasNewResource = false; } break; } } catch (Exception e) { log.log(Level.FINE, e.toString(), e); } } if (_resourceCount > 0 && flags != XAResource.TMJOIN) xid = new XidImpl(_xid, _resourceCount + 1); try { if (_timeout > 0) resource.setTransactionTimeout((int) (_timeout / 1000L)); if (log.isLoggable(Level.FINER)) { if (flags == XAResource.TMJOIN) log.finer(this + " join-XA " + resource); else log.finer(this + " start-XA " + resource); } resource.start(xid, flags); } catch (XAException e) { setRollbackOnly(e); String message = L.l("Failed to enlist resource {0} in transaction because of exception:\n{1}", resource, e); log.log(Level.SEVERE, message, e); throw new SystemException(message); } if (hasNewResource) { _resources[_resourceCount] = resource; _resourceStates[_resourceCount] = RES_ACTIVE; if (flags == XAResource.TMJOIN) _resourceStates[_resourceCount] |= RES_SHARED_RM; _resourceXids[_resourceCount] = xid; _resourceCount++; } return true; } /** * De-lists a resource from the current transaction * * @param resource * the resource to delist * @param flag * XXX: ??? * * @return true if successful */ @Override public boolean delistResource(XAResource resource, int flag) throws SystemException { if (_isSuspended) throw new IllegalStateException(L.l("transaction is suspended")); if (_resourceCount == 0) return true; int index; for (index = _resourceCount - 1; index >= 0; index--) { if (_resources[index].equals(resource)) break; } if (index < 0) return false; // If there is no current transaction, // remove it from the resource list entirely. if (_status == Status.STATUS_NO_TRANSACTION) { for (; index + 1 < _resourceCount; index++) { _resources[index] = _resources[index + 1]; _resourceStates[index] = _resourceStates[index + 1]; _resourceXids[index] = _resourceXids[index + 1]; } _resourceCount--; return true; } if (_status == Status.STATUS_MARKED_ROLLBACK) flag = XAResource.TMFAIL; _resourceStates[index] &= ~RES_ACTIVE; try { resource.end(_resourceXids[index], flag); } catch (XAException e) { setRollbackOnly(e); String message = L.l("Failed to delist resource due to: {0}", e); log.log(Level.SEVERE, message, e); throw new SystemException(message); } return true; } /** * Returns the current number of resources. */ public int getEnlistedResourceCount() { return _resourceCount; } /** * Return true if the transaction has no resources. */ public boolean isEmpty() { if (_isDead) return true; else if (_resourceCount > 0) return false; // XXX: ejb/3692 because TransactionContext adds itself else if (_synchronizations != null && _synchronizations.size() > 1) return false; // TODO Should the interposed transactions be added here as // well? else return true; } /** * Returns true if the local transaction optimization would be allowed. */ public boolean allowLocalTransactionOptimization() { // XXX: can also check if all are non-local return _resourceCount == 0; } /** * sets the timeout for the transaction */ public void setTransactionTimeout(int seconds) throws SystemException { if (seconds == 0) _timeout = _transactionManager.getTimeout(); else if (seconds < 0) _timeout = MAX_TIMEOUT; else { _timeout = 1000L * (long) seconds; } if (_status == Status.STATUS_ACTIVE && _timeout > 0) { _alarm.queue(_timeout + EXTRA_TIMEOUT); } else { _alarm.dequeue(); } } /** * sets the timeout for the transaction */ public int getTransactionTimeout() throws SystemException { if (_timeout < 0) return -1; else return (int) (_timeout / 1000L); } /** * Adds an attribute. */ public void setAttribute(String var, Object value) { if (_attributes == null) _attributes = new HashMap<String, Object>(); _attributes.put(var, value); } /** * Gets an attribute. */ public Object getAttribute(String var) { if (_attributes != null) return _attributes.get(var); else return null; } /** * Registers synchronization interposed by container resources such as the JPA * persistence provider. * * @param synchronization * Interposed synchronization. */ public void registerInterposedSynchronization(Synchronization synchronization) { if (_interposedSynchronizations == null) { _interposedSynchronizations = new ArrayList<Synchronization>(); } _interposedSynchronizations.add(synchronization); } /** * Register a synchronization callback */ @Override public void registerSynchronization(Synchronization synchronization) { if (_synchronizations == null) _synchronizations = new ArrayList<Synchronization>(); _synchronizations.add(synchronization); if (log.isLoggable(Level.FINER)) log.finer(this + " registerSync " + synchronization); } /** * Returns the status of this transaction */ @Override public int getStatus() { return _status; } /** * Start a transaction. */ void begin() throws SystemException, NotSupportedException { if (_status != Status.STATUS_NO_TRANSACTION) { int status = _status; // env/0691 /* try { rollback(); } catch (Throwable e) { log.log(Level.WARNING, e.toString(), e); } */ throw new NotSupportedException( L.l("{0} nested transactions are not supported. " + "The previous transaction for this thread did not commit() or rollback(). " + "Check that every UserTransaction.begin() has its commit() or rollback() in a finally block.\nStatus was {1}.", this, xaState(status))); } if (_isDead) throw new IllegalStateException(L.l("{0} error trying to use dead transaction.")); _status = Status.STATUS_ACTIVE; _beginTime = Alarm.getCurrentTime(); _rollbackException = null; if (_xid == null) _xid = _transactionManager.createXID(); if (log.isLoggable(Level.FINE)) log.fine(this + " begin"); if (_timeout > 0) { _alarm.queue(_timeout + EXTRA_TIMEOUT); } } /** * Suspend the transaction. The timeouts are stopped. */ void suspend() throws SystemException { if (_isSuspended) throw new IllegalStateException(L.l("can't suspend already-suspended transaction")); // _alarm.dequeue(); _isSuspended = true; for (int i = _resourceCount - 1; i >= 0; i--) { if ((_resourceStates[i] & (RES_ACTIVE | RES_SUSPENDED)) == RES_ACTIVE) { try { XAResource resource = _resources[i]; resource.end(_resourceXids[i], XAResource.TMSUSPEND); } catch (Exception e) { setRollbackOnly(e); } } } if (_userTransaction != null) _suspendState = _userTransaction.userSuspend(); if (log.isLoggable(Level.FINE)) log.fine(this + " suspended"); } /** * Resume the transaction and requeue the timeout. */ void resume() throws SystemException { if (!_isSuspended) throw new IllegalStateException(L .l("can't resume non-suspended transaction")); if (_timeout > 0) _alarm.queue(_timeout + EXTRA_TIMEOUT); for (int i = _resourceCount - 1; i >= 0; i--) { if ((_resourceStates[i] & (RES_ACTIVE | RES_SUSPENDED)) == RES_ACTIVE) { try { XAResource resource = _resources[i]; resource.start(_resourceXids[i], XAResource.TMRESUME); } catch (Exception e) { setRollbackOnly(e); } } } if (_userTransaction != null) _userTransaction.userResume(_suspendState); _isSuspended = false; if (log.isLoggable(Level.FINE)) log.fine(this + " resume"); } /** * Force any completion to be a roll-back. */ @Override public void setRollbackOnly() throws SystemException { if (_status != Status.STATUS_ACTIVE && _status != Status.STATUS_MARKED_ROLLBACK) { throw new IllegalStateException( L.l("Can't set rollback-only because the transaction is not active, state={0}.", xaState(_status))); } _status = Status.STATUS_MARKED_ROLLBACK; _alarm.dequeue(); _timeout = 0; if (log.isLoggable(Level.FINE)) log.fine(this + " rollback-only"); if (_rollbackException == null) { _rollbackException = new RollbackException(L.l("Transaction marked rollback-only")); _rollbackException.fillInStackTrace(); } } /** * Force any completion to be a rollback. */ public void setRollbackOnly(Throwable exn) { if (_status != Status.STATUS_ACTIVE && _status != Status.STATUS_MARKED_ROLLBACK) { throw new IllegalStateException( L.l("Can't set rollback-only because the transaction is not active, state={0}.", xaState(_status))); } _status = Status.STATUS_MARKED_ROLLBACK; if (_rollbackException == null) { _rollbackException = exn; } if (log.isLoggable(Level.FINER)) { log.log(Level.FINER, this + " rollback-only: " + exn.toString(), exn); } else if (log.isLoggable(Level.FINE)) { log.fine(this + " rollback-only: " + exn.toString()); } } public boolean isRollbackOnly() { return _status == Status.STATUS_MARKED_ROLLBACK; } /** * Commit the transaction. */ public void commit() throws RollbackException, HeuristicMixedException, HeuristicRollbackException, SystemException { _alarm.dequeue(); Exception heuristicExn = null; try { switch (_status) { case Status.STATUS_ACTIVE: if (log.isLoggable(Level.FINE)) log.fine(this + " commit (active)"); break; case Status.STATUS_MARKED_ROLLBACK: if (log.isLoggable(Level.FINE)) log.fine(this + " commit (marked rollback)"); break; case Status.STATUS_NO_TRANSACTION: if (log.isLoggable(Level.FINE)) log.fine(this + " commit (no transaction)"); throw new IllegalStateException( L.l("Can't commit outside of a transaction. Either the UserTransaction.begin() is missing or the transaction has already been committed or rolled back.")); default: if (log.isLoggable(Level.FINE)) log.fine(this + " commit (unknown: " + _status + ")"); rollbackInt(); throw new IllegalStateException(L.l( "Can't commit because the transaction state is {0}", String .valueOf(_status))); } try { callBeforeCompletion(); } catch (RollbackException e) { rollbackInt(); throw e; } catch (Throwable e) { setRollbackOnly(e); rollbackInt(); RollbackException newException = new RollbackException(e.toString()); newException.initCause(e); throw newException; } if (_status == Status.STATUS_MARKED_ROLLBACK) { rollbackInt(); if (_rollbackException != null) throw new RollbackExceptionWrapper( L.l("Transaction can't commit because it has been marked rolled back\n {0}", _rollbackException), _rollbackException); else throw new RollbackException( L.l("Transaction can't commit because it has been marked rolled back.")); } if (_resourceCount > 0) { _status = Status.STATUS_PREPARING; AbstractXALogStream xaLog = _transactionManager.getXALogStream(); boolean hasPrepare = false; boolean allowSinglePhase = false; for (int i = _resourceCount - 1; i >= 0; i--) { XAResource resource = (XAResource) _resources[i]; if (i == 0 && (xaLog == null || !hasPrepare)) { // server/1601 _resourceStates[0] |= RES_COMMIT; allowSinglePhase = true; break; } if ((_resourceStates[i] & RES_SHARED_RM) == 0) { try { int prepare = resource.prepare(_resourceXids[i]); if (prepare == XAResource.XA_RDONLY) { } else if (prepare == XAResource.XA_OK) { hasPrepare = true; _resourceStates[i] |= RES_COMMIT; } else { log.finer(this + " unexpected prepare result " + prepare); rollbackInt(); } } catch (XAException e) { _transactionManager.addCommitResourceFail(); heuristicExn = heuristicException(heuristicExn, e); rollbackInt(); throw new RollbackExceptionWrapper(L.l("all commits rolled back"), heuristicExn); } } } if (hasPrepare && xaLog != null) { _xaLog = xaLog; xaLog.writeTMCommit(_xid); } _status = Status.STATUS_COMMITTING; Exception exn = commitResources(allowSinglePhase); if (heuristicExn == null) heuristicExn = exn; } if (heuristicExn != null && log.isLoggable(Level.FINE)) log.fine(this + " " + heuristicExn); _status = Status.STATUS_ROLLEDBACK; if (heuristicExn == null) _status = Status.STATUS_COMMITTED; else if (heuristicExn instanceof RollbackException) { throw (RollbackException) heuristicExn; } else if (heuristicExn instanceof HeuristicMixedException) { throw (HeuristicMixedException) heuristicExn; } else if (heuristicExn instanceof HeuristicRollbackException) { throw (HeuristicRollbackException) heuristicExn; } else if (heuristicExn instanceof SystemException) { throw (SystemException) heuristicExn; } else { throw RollbackExceptionWrapper.create(heuristicExn); } } finally { callAfterCompletion(); } } private Exception commitResources(boolean allowSinglePhase) { Exception heuristicExn = null; if (allowSinglePhase) { try { XAResource resource = (XAResource) _resources[0]; if ((_resourceStates[0] & RES_COMMIT) != 0) resource.commit(_xid, true); if (_timeout > 0) resource.setTransactionTimeout(0); } catch (XAException e) { log.log(Level.FINE, e.toString(), e); heuristicExn = heuristicException(heuristicExn, e); _transactionManager.addCommitResourceFail(); } } for (int i = 0; i < _resourceCount; i++) { XAResource resource = (XAResource) _resources[i]; if (i == 0 && allowSinglePhase) continue; if ((_resourceStates[i] & RES_SHARED_RM) != 0) continue; if ((_resourceStates[i] & RES_COMMIT) == 0) continue; if (heuristicExn == null) { try { resource.commit(_resourceXids[i], false); if (_timeout > 0) resource.setTransactionTimeout(0); } catch (XAException e) { _transactionManager.addCommitResourceFail(); heuristicExn = e; log.log(Level.FINE, e.toString(), e); } } else { try { resource.rollback(_resourceXids[i]); if (_timeout > 0) resource.setTransactionTimeout(0); } catch (XAException e) { log.log(Level.FINE, e.toString(), e); } } } return heuristicExn; } /** * Rollback the transaction. */ @Override public void rollback() { _alarm.dequeue(); try { callBeforeCompletion(); } catch (Throwable e) { setRollbackOnly(e); } try { switch (_status) { case Status.STATUS_ACTIVE: case Status.STATUS_MARKED_ROLLBACK: // fall through to normal completion break; case Status.STATUS_NO_TRANSACTION: throw new IllegalStateException( L.l("Can't rollback outside of a transaction. Either the UserTransaction.begin() is missing or the transaction has already been committed or rolled back.")); default: rollbackInt(); throw new IllegalStateException(L.l("Can't rollback in state: {0}", String.valueOf(_status))); } _status = Status.STATUS_MARKED_ROLLBACK; rollbackInt(); } finally { callAfterCompletion(); } } /** * Calculates the heuristic exception based on the resource manager's * exception. */ private Exception heuristicException(Exception oldException, XAException xaException) { switch (xaException.errorCode) { case XAException.XA_HEURHAZ: case XAException.XA_HEURCOM: return oldException; case XAException.XA_HEURRB: if (oldException instanceof HeuristicMixedException) return oldException; else if (oldException instanceof HeuristicRollbackException) return oldException; else if (oldException instanceof RollbackException) return oldException; else return new HeuristicRollbackException(getXAMessage(xaException)); default: if (oldException instanceof SystemException) return oldException; else return new SystemExceptionWrapper(getXAMessage(xaException), xaException); } } /** * Rollback the transaction. */ private void rollbackInt() { _status = Status.STATUS_ROLLING_BACK; if (log.isLoggable(Level.FINE)) log.fine(this + " rollback"); for (int i = 0; i < _resourceCount; i++) { XAResource resource = (XAResource) _resources[i]; if ((_resourceStates[i] & RES_SHARED_RM) != 0) continue; try { resource.rollback(_resourceXids[i]); if (_timeout > 0) resource.setTransactionTimeout(0); } catch (Throwable e) { log.log(Level.FINE, e.toString(), e); } } _status = Status.STATUS_ROLLEDBACK; } /** * Call all the Synchronization listeners before the commit()/rollback() * starts. */ private void callBeforeCompletion() throws RollbackException { _alarm.dequeue(); if (_interposedSynchronizations != null) { // env/06a2 for (int i = 0; i < _interposedSynchronizations.size(); i++) { Synchronization sync = _interposedSynchronizations.get(i); try { sync.beforeCompletion(); } catch (RuntimeException e) { setRollbackOnly(e); RollbackException newException = new RollbackException(e.toString()); newException.initCause(e); throw newException; } catch (Throwable e) { log.log(Level.FINE, e.toString(), e); } } } // server/16h2 if (_synchronizations != null) { // env/06a2 for (int i = 0; i < _synchronizations.size(); i++) { //for (int i = _synchronizations.size() - 1; i >= 0; i--) { Synchronization sync = _synchronizations.get(i); if (log.isLoggable(Level.FINEST)) log.finest(this + " beforeCompletion " + sync); try { sync.beforeCompletion(); } catch (RuntimeException e) { setRollbackOnly(e); RollbackException newException = new RollbackException(e.toString()); newException.initCause(e); throw newException; } catch (Throwable e) { log.log(Level.FINE, e.toString(), e); } } } // tell the resources everything's done for (int i = _resourceCount - 1; i >= 0; i--) { XAResource resource = _resources[i]; int flag; if (_status == Status.STATUS_MARKED_ROLLBACK) flag = XAResource.TMFAIL; else flag = XAResource.TMSUCCESS; try { if ((_resourceStates[i] & RES_ACTIVE) != 0) resource.end(_resourceXids[i], flag); } catch (Throwable e) { log.log(Level.FINE, e.toString(), e); setRollbackOnly(e); } } } /** * Call all the Synchronization listeners after the commit()/rollback() * complete. */ private void callAfterCompletion() { ArrayList<Synchronization> interposedSynchronizations = _interposedSynchronizations; _interposedSynchronizations = null; ArrayList<Synchronization> synchronizations = _synchronizations; _synchronizations = null; _userTransaction = null; // env/0660 _timeout = _transactionManager.getTimeout(); XidImpl xid = _xid; _xid = null; int status = _status; _status = Status.STATUS_NO_TRANSACTION; _rollbackException = null; // remove the resources which have officially delisted for (int i = _resourceCount - 1; i >= 0; i--) _resources[i] = null; _resourceCount = 0; _mappedResources = null; AbstractXALogStream xaLog = _xaLog; _xaLog = null; if (xaLog != null) { try { xaLog.writeTMFinish(xid); } catch (Throwable e) { log.log(Level.FINER, e.toString(), e); } } int length = (interposedSynchronizations == null ? 0 : interposedSynchronizations.size()); for (int i = length - 1; i >= 0; i--) { Synchronization sync = (Synchronization) interposedSynchronizations.get(i); try { if (log.isLoggable(Level.FINEST)) log.finest(this + " afterCompletion " + sync); sync.afterCompletion(status); } catch (Throwable e) { log.log(Level.FINE, e.toString(), e); } } length = synchronizations == null ? 0 : synchronizations.size(); // for (int i = length - 1; i >= 0; i--) { for (int i = 0; i < length; i++) { Synchronization sync = (Synchronization) synchronizations.get(i); try { if (log.isLoggable(Level.FINEST)) log.finest(this + " afterCompletion " + sync); sync.afterCompletion(status); } catch (Throwable e) { log.log(Level.FINE, e.toString(), e); } } if (_attributes != null) _attributes.clear(); if (status == Status.STATUS_COMMITTED) _transactionManager.endCommitTime(_beginTime); else _transactionManager.endRollbackTime(_beginTime); } @Override public void handleAlarm(Alarm alarm) { try { String msg = L.l("{0}: timed out after {1} seconds.", this, String.valueOf(getTransactionTimeout())); log.warning(msg); RollbackException exn = new RollbackException(msg); setRollbackOnly(exn); // should not close at this point because there could be following // statements that also need to be rolled back // server/16a7 // close(); } catch (Throwable e) { log.log(Level.FINE, e.toString(), e); } } /** * Close the transaction, rolling back everything and removing all enlisted * resources. */ public void close() { _isDead = true; _alarm.dequeue(); try { if (_status != Status.STATUS_NO_TRANSACTION) rollback(); } catch (Throwable e) { log.log(Level.FINE, e.toString(), e); } if (_synchronizations != null) _synchronizations.clear(); if (_interposedSynchronizations != null) _interposedSynchronizations.clear(); for (int i = _resourceCount - 1; i >= 0; i--) _resources[i] = null; _resourceCount = 0; Map<Object,Object> mappedResources = _mappedResources; _mappedResources = null; if (mappedResources != null) mappedResources.clear(); _xid = null; _timeout = _transactionManager.getTimeout(); } /** * Printable version of the transaction. */ public String toString() { if (_xid == null) return "Transaction[]"; CharBuffer cb = new CharBuffer(); cb.append("Transaction["); byte [] branch = _xid.getBranchQualifier(); addByte(cb, branch[0]); cb.append(":"); byte [] global = _xid.getGlobalTransactionId(); for (int i = 24; i < 28; i++) addByte(cb, global[i]); cb.append("]"); return cb.toString(); } /** * Adds hex for debug */ private void addByte(CharBuffer cb, int b) { int h = (b / 16) & 0xf; int l = b & 0xf; if (h >= 10) cb.append((char) ('a' + h - 10)); else cb.append((char) ('0' + h)); if (l >= 10) cb.append((char) ('a' + l - 10)); else cb.append((char) ('0' + l)); } /** * Converts XA error code to a message. */ private static String getXAMessage(XAException xaException) { if (xaException.getMessage() != null && !xaException.getMessage().equals("")) return xaException.getMessage(); else return (xaName(xaException.errorCode) + ": " + xaMessage(xaException.errorCode)); } /** * Converts XA state code to string. */ private static String xaState(int xaState) { switch (xaState) { case Status.STATUS_ACTIVE: return "ACTIVE"; case Status.STATUS_MARKED_ROLLBACK: return "MARKED-ROLLBACK"; case Status.STATUS_PREPARED: return "PREPARED"; case Status.STATUS_COMMITTED: return "COMITTED"; case Status.STATUS_ROLLEDBACK: return "ROLLEDBACK"; case Status.STATUS_PREPARING: return "PREPARING"; case Status.STATUS_COMMITTING: return "COMMITTING"; case Status.STATUS_ROLLING_BACK: return "ROLLING_BACK"; case Status.STATUS_NO_TRANSACTION: return "NO_TRANSACTION"; default: return "XA-STATE(" + xaState + ")"; } } /** * Converts XA error code to string. */ private static String xaName(int xaCode) { switch (xaCode) { // rollback codes case XAException.XA_RBROLLBACK: return "XA_RBROLLBACK"; case XAException.XA_RBCOMMFAIL: return "XA_RBCOMMFAIL"; case XAException.XA_RBDEADLOCK: return "XA_RBDEADLOCK"; case XAException.XA_RBINTEGRITY: return "XA_RBINTEGRITY"; case XAException.XA_RBOTHER: return "XA_RBOTHER"; case XAException.XA_RBPROTO: return "XA_RBPROTO"; case XAException.XA_RBTIMEOUT: return "XA_RBTIMEOUT"; case XAException.XA_RBTRANSIENT: return "XA_RBTRANSIENT"; // suspension code case XAException.XA_NOMIGRATE: return "XA_NOMIGRATE"; // heuristic completion codes case XAException.XA_HEURHAZ: return "XA_HEURHAZ"; case XAException.XA_HEURCOM: return "XA_HEURCOM"; case XAException.XA_HEURRB: return "XA_HEURRB"; case XAException.XA_HEURMIX: return "XA_HEURMIX"; case XAException.XA_RDONLY: return "XA_RDONLY"; // errors case XAException.XAER_RMERR: return "XA_RMERR"; case XAException.XAER_NOTA: return "XA_NOTA"; case XAException.XAER_INVAL: return "XA_INVAL"; case XAException.XAER_PROTO: return "XA_PROTO"; case XAException.XAER_RMFAIL: return "XA_RMFAIL"; case XAException.XAER_DUPID: return "XA_DUPID"; case XAException.XAER_OUTSIDE: return "XA_OUTSIDE"; default: return "XA(" + xaCode + ")"; } } /** * Converts XA error code to a message. */ private static String xaMessage(int xaCode) { switch (xaCode) { // rollback codes case XAException.XA_RBROLLBACK: case XAException.XA_RBOTHER: return L.l("Resource rolled back for an unspecified reason."); case XAException.XA_RBCOMMFAIL: return L.l("Resource rolled back because of a communication failure."); case XAException.XA_RBDEADLOCK: return L.l("Resource rolled back because of a deadlock."); case XAException.XA_RBINTEGRITY: return L.l("Resource rolled back because of an integrity check failure."); case XAException.XA_RBPROTO: return L .l("Resource rolled back because of a protocol error in the resource manager."); case XAException.XA_RBTIMEOUT: return L.l("Resource rolled back because of a timeout."); case XAException.XA_RBTRANSIENT: return L.l("Resource rolled back because of transient error."); // suspension code case XAException.XA_NOMIGRATE: return L.l("Resumption must occur where the suspension occurred."); // heuristic completion codes case XAException.XA_HEURHAZ: return L.l("Resource may have been heuristically completed."); case XAException.XA_HEURCOM: return L.l("Resource has been heuristically committed."); case XAException.XA_HEURRB: return L.l("Resource has been heuristically rolled back."); case XAException.XA_HEURMIX: return L.l("Resource has been heuristically committed and rolled back."); case XAException.XA_RDONLY: return L .l("Resource was read-only and has been heuristically committed."); // errors case XAException.XAER_RMERR: return L.l("Resource manager error."); case XAException.XAER_NOTA: return L.l("The XID (transaction identifier) was invalid."); case XAException.XAER_INVAL: return L.l("Invalid arguments were given."); case XAException.XAER_PROTO: return L.l("Method called in an invalid context."); case XAException.XAER_RMFAIL: return L.l("Resource manager is unavailable."); case XAException.XAER_DUPID: return L.l("Duplicate XID (transaction identifier)."); case XAException.XAER_OUTSIDE: return L.l("Resource manager called outside of transaction."); default: return ""; } } }