/* * JBoss, Home of Professional Open Source * Copyright 2006, Red Hat Middleware LLC, and individual contributors * as indicated by the @author tags. * See the copyright.txt in the distribution for a full listing * of individual contributors. * This copyrighted material is made available to anyone wishing to use, * modify, copy, or redistribute it subject to the terms and conditions * of the GNU Lesser General Public License, v. 2.1. * This program is distributed in the hope that it will be useful, but WITHOUT A * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. * You should have received a copy of the GNU Lesser General Public License, * v.2.1 along with this distribution; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * * (C) 2005-2006, * @author JBoss Inc. */ /* * Copyright (C) 2001, 2002, * * Hewlett-Packard Arjuna Labs, * Newcastle upon Tyne, * Tyne and Wear, * UK. * * $Id: AtomicTransaction.java 2342 2006-03-30 13:06:17Z $ */ package com.arjuna.ats.jts.extensions; import org.omg.CORBA.BAD_OPERATION; import org.omg.CORBA.CompletionStatus; import org.omg.CORBA.INVALID_TRANSACTION; import org.omg.CORBA.SystemException; import org.omg.CORBA.TRANSACTION_ROLLEDBACK; import org.omg.CORBA.UNKNOWN; import org.omg.CORBA.WrongTransaction; import org.omg.CosTransactions.Control; import org.omg.CosTransactions.HeuristicHazard; import org.omg.CosTransactions.HeuristicMixed; import org.omg.CosTransactions.Inactive; import org.omg.CosTransactions.InvalidControl; import org.omg.CosTransactions.NoTransaction; import org.omg.CosTransactions.NotSubtransaction; import org.omg.CosTransactions.PropagationContext; import org.omg.CosTransactions.RecoveryCoordinator; import org.omg.CosTransactions.Resource; import org.omg.CosTransactions.Status; import org.omg.CosTransactions.SubtransactionAwareResource; import org.omg.CosTransactions.SubtransactionsUnavailable; import org.omg.CosTransactions.Synchronization; import org.omg.CosTransactions.SynchronizationUnavailable; import org.omg.CosTransactions.Unavailable; import com.arjuna.ats.arjuna.common.Uid; import com.arjuna.ats.internal.jts.ControlWrapper; import com.arjuna.ats.internal.jts.OTSImpleManager; import com.arjuna.ats.internal.jts.orbspecific.CurrentImple; import com.arjuna.ats.jts.exceptions.ExceptionCodes; import com.arjuna.ats.jts.logging.jtsLogger; import com.arjuna.ats.jts.utils.Utility; /** * Similar to CosTransactions::Current. However, this class does transaction * scoping, so if an instance is garbage collected and the transaction is still * running, it will be rolled back automatically. It also adds some convenience * routines which Current does not do. * * @author Mark Little (mark_little@hp.com) * @version $Id: AtomicTransaction.java 2342 2006-03-30 13:06:17Z $ * @since JTS 1.0. */ public class AtomicTransaction { /** * The types of transactions which can be created. */ public static final int TOP_LEVEL = 0; public static final int NESTED = 1; /** * Create a new transaction. It is not running at this stage. */ public AtomicTransaction () { if (jtsLogger.logger.isTraceEnabled()) { jtsLogger.logger.trace("AtomicTransaction::AtomicTransaction ()"); } _theAction = null; _theStatus = Status.StatusNoTransaction; _timeout = get_timeout(); } public void finalize () { if (jtsLogger.logger.isTraceEnabled()) { jtsLogger.logger.trace("AtomicTransaction.finalize ()"); } if (_theAction != null) { if (getStatus() == Status.StatusActive) { jtsLogger.i18NLogger.warn_extensions_atscope("AtomicTransaction.finalize", get_uid()); String name = null; try { name = get_transaction_name(); } catch (SystemException ex) { jtsLogger.i18NLogger.warn_extensions_namefail(ex); } try { rollback(); // tidies up for us. } catch (NoTransaction e) { jtsLogger.i18NLogger.warn_extensions_abortfailnoexist(name); } catch (Exception e) { jtsLogger.i18NLogger.warn_extensions_abortfail(name); } } } } /** * @return the transaction name. Should only be used for debugging purposes. */ public String get_transaction_name () throws SystemException { if (jtsLogger.logger.isTraceEnabled()) { jtsLogger.logger.trace("AtomicTransaction::get_transaction_name ()"); } if (_theAction != null) { try { return _theAction.get_transaction_name(); } catch (SystemException e) { jtsLogger.i18NLogger.warn_extensions_atunavailable("AtomicTransaction.get_transaction_name"); throw e; } } else throw new UNKNOWN(ExceptionCodes.UNKNOWN_EXCEPTION, CompletionStatus.COMPLETED_NO); } /** * Start the transaction. * * @exception org.omg.CosTransactions.SubtransactionsUnavailable * if subtransactions have been disabled, and the invoking * thread already has a transaction associated with it. * * @exception org.omg.CORBA.INVALID_TRANSACTION * if the transaction has already begun or has completed. */ public void begin () throws SubtransactionsUnavailable, SystemException { if (jtsLogger.logger.isTraceEnabled()) { jtsLogger.logger.trace("AtomicTransaction::begin ()"); } // already begun? CurrentImple current = OTSImpleManager.current(); synchronized (_theStatus) { if (_theAction != null) { throw new INVALID_TRANSACTION(ExceptionCodes.ALREADY_BEGUN, CompletionStatus.COMPLETED_NO); } current.begin(); _theAction = current.getControlWrapper(); } _theStatus = current.get_status(); if (jtsLogger.logger.isTraceEnabled()) { jtsLogger.logger.trace("AtomicTransaction::begin create " + _theAction); } } /* * Commit the transaction. If the current transaction associated with the * thread is not this transaction, then this transaction is rolled back and * leave current alone. * * @param report_heuristics indicates whether heuristic reporting is * desired. * * @exception org.omg.CosTransactions.NoTransaction if the transaction has * already been terminated. * * @exception org.omg.CORBA.TRANSACTION_ROLLEDBACK if the transaction rolls * back. * * @exception org.omg.CosTransactions.HeuristicMixed if some of the * transaction participants committed, while some rolled back. * * @exception org.omg.CosTransactions.HeuristicHazard if some of the * transaction participants committed, some rolled back, and the outcome of * others is indeterminate. * * @exception org.omg.CORBA.WRONG_TRANSACTION if the current transaction is * not this transaction. */ public void commit (boolean report_heuristics) throws NoTransaction, HeuristicMixed, HeuristicHazard, WrongTransaction, SystemException { if (jtsLogger.logger.isTraceEnabled()) { jtsLogger.logger.trace("AtomicTransaction::commit ( " + report_heuristics + " ) for " + _theAction); } /* * We shouldn't need to synchronize for the entire duration of the * commit since we actually use Current to do the transaction * termination and only look at _theAction to check that Current's * notion of the transaction is the same. If we only synchronize when we * do the check then it is still possible for multiple threads to get * past this point and try to commit the transaction. However, then * Current will do the checking for us. */ synchronized (_theStatus) { if (_theAction == null) { throw new NoTransaction(); } } if (!validTransaction()) { throw new WrongTransaction(); } /* * OK to use current since we have just guaranteed that the transaction * is the same as current. Use current rather than saved control since * it will do thread tracking for us. */ CurrentImple current = OTSImpleManager.current(); /* * Release our handle first, since current is about to destroy the * action control. */ try { current.commit(report_heuristics); _theStatus = Status.StatusCommitted; } catch (NoTransaction e) { _theStatus = Status.StatusNoTransaction; throw e; } catch (HeuristicMixed e) { _theStatus = getStatus(); throw e; } catch (HeuristicHazard e) { _theStatus = getStatus(); throw e; } catch (TRANSACTION_ROLLEDBACK e) { _theStatus = Status.StatusRolledBack; throw e; } catch (SystemException e) { _theStatus = getStatus(); throw e; } } /* * Rollback the transaction. If the current transaction associated with the * thread is not this transaction, then this transaction is rolled back and * leave current alone. * * @exception org.omg.CosTransactions.NoTransaction if the transaction has * already been terminated. * * @exception org.omg.CORBA.WRONG_TRANSACTION if the current transaction is * not this transaction. */ public void rollback () throws NoTransaction, WrongTransaction, SystemException { if (jtsLogger.logger.isTraceEnabled()) { jtsLogger.logger.trace("AtomicTransaction::rollback for " + _theAction); } /* * We shouldn't need to synchronize for the entire duration of the * rollback since we actually use Current to do the transaction * termination and only look at _theAction to check that Current's * notion of the transaction is the same. If we only synchronize when we * do the check then it is still possible for multiple threads to get * past this point and try to rollback the transaction. However, then * Current will do the checking for us. */ synchronized (_theStatus) { if (_theAction == null) { throw new NoTransaction(); } } if (!validTransaction()) { throw new WrongTransaction(); } /* * OK to use current since we have just guaranteed that the transaction * is the same as current. Use current rather than saved control since * it will do thread tracking for us. */ CurrentImple current = OTSImpleManager.current(); try { current.rollback(); _theStatus = Status.StatusRolledBack; } catch (NoTransaction e) { _theStatus = Status.StatusNoTransaction; throw e; } catch (TRANSACTION_ROLLEDBACK e) { _theStatus = Status.StatusRolledBack; } catch (SystemException e) { _theStatus = getStatus(); throw e; } } /** * Set the transaction timeout. This is the same as calling * org.omg.CosTransactions.Current.set_timeout(). */ public void set_timeout (int seconds) throws SystemException { if (jtsLogger.logger.isTraceEnabled()) { jtsLogger.logger.trace("AtomicTransaction::set_timeout ( " + seconds + " )"); } CurrentImple current = OTSImpleManager.current(); if (current != null) current.set_timeout(seconds); else throw new UNKNOWN(); } /** * @return the timeout associated with transactions created by the current * thread. */ public int get_timeout () throws SystemException { CurrentImple current = OTSImpleManager.current(); if (current != null) { int val = current.get_timeout(); if (jtsLogger.logger.isTraceEnabled()) { jtsLogger.logger.trace("AtomicTransaction::get_timeout returning " + val); } return val; } else throw new UNKNOWN(); } /** * @return the timeout associated with this transaction. */ public int getTimeout () throws SystemException { return _timeout; } /** * @return the propagation context for this transaction. * * @since JTS 2.1. * * @exception org.omg.CosTransactions.Inactive * if the current transaction is no longer in the active * phase. */ public PropagationContext get_txcontext () throws Inactive, SystemException { if (_theAction == null) { throw new Inactive(); } else { try { return _theAction.get_coordinator().get_txcontext(); } catch (NullPointerException ex) { throw new Inactive(); } catch (Exception e) { throw new BAD_OPERATION(e.toString()); } } } /** * Register the specified resource with this transaction. * * @return the org.omg.CosTransactions.RecoveryCoordinator reference that * can be used to later query the outcome of the transaction. * * @exception org.omg.CosTransactions.Inactive * if the current transaction is no longer in the active * phase. */ public RecoveryCoordinator registerResource (Resource r) throws Inactive, SystemException { if (jtsLogger.logger.isTraceEnabled()) { jtsLogger.logger.trace("AtomicTransaction::registerResource ( " + r + " )"); } RecoveryCoordinator rc = null; synchronized (_theStatus) { if (_theAction == null) { throw new Inactive(); } } return _theAction.register_resource(r); } /** * Register the specified subtransaction aware resource with this * transaction. This transaction must be a subtransaction. * * @exception org.omg.CosTransactions.Inactive * if this transaction is no longer in the active phase. * * @exception org.omg.CosTransactions.NotSubtransaction * if this transaction is not a subtransaction. */ public void registerSubtranAware (SubtransactionAwareResource r) throws Inactive, NotSubtransaction, SystemException { if (jtsLogger.logger.isTraceEnabled()) { jtsLogger.logger.trace("AtomicTransaction::registerSubtranAware ( " + r + " )"); } synchronized (_theStatus) { if (_theAction == null) { throw new Inactive(); } } _theAction.register_subtran_aware(r); } /** * Register the specified synchronization with this transaction. This * transaction must be a top-level transaction. * * @exception org.omg.CosTransactions.Inactive * if this transaction is no longer in the active phase. * * @exception org.omg.CosTransactions.SynchronizationUnavailable * if this transaction it not a top-level transaction. */ public void registerSynchronization (Synchronization sync) throws Inactive, SynchronizationUnavailable, SystemException { if (jtsLogger.logger.isTraceEnabled()) { jtsLogger.logger.trace("AtomicTransaction::registerSynchronization ( " + sync + " )"); } synchronized (_theStatus) { if (_theAction == null) { throw new Inactive(); } } _theAction.register_synchronization(sync); } /* * Should probably remove ability to get control as this allows a user to * commit/abort outside the control of the AtomicTransaction. */ /** * @return the org.omg.CosTransactions.Control reference to this * transaction. */ public Control control () throws NoTransaction, SystemException { if (_theAction == null) { throw new NoTransaction(); } else { try { return _theAction.get_control(); } catch (Unavailable ex) { throw new NoTransaction(); } } } public boolean equals (Object obj) { if (obj == null) return false; if (obj == this) return true; if (obj instanceof AtomicTransaction) { /* * If we can't get either coordinator to compare, then assume * transactions are different. */ try { AtomicTransaction tx = (AtomicTransaction) obj; ControlWrapper txControl = tx._theAction; if ((_theAction == null) && (txControl == null)) { return true; } else if (_theAction == null) { return false; } else { return _theAction.equals(txControl); } } catch (Exception e) { } } return false; } /** * Suspend this transaction from the current thread. This transaction must * be active on the calling thread for this method to succeed. * * @exception org.omg.CosTransactions.NoTransaction * if there is no current transaction. * * @exception org.omg.CORBA.WrongTransaction * if the transaction associated with the current thread is * different from this thread. */ public void suspend () throws NoTransaction, WrongTransaction, SystemException { if (jtsLogger.logger.isTraceEnabled()) { jtsLogger.logger.trace("AtomicTransaction::suspend called for " + _theAction); } synchronized (_theStatus) { if (_theAction == null) throw new NoTransaction(); } if (!validTransaction()) { throw new WrongTransaction(); } synchronized (_theStatus) { _theAction = OTSImpleManager.current().suspendWrapper(); /* * Make sure to set the status in case we get called again. */ if (_theAction == null) _theStatus = org.omg.CosTransactions.Status.StatusNoTransaction; } } /** * Resume this transaction. * * @exception org.omg.CosTransactions.InvalidControl * if this transaction is invalid. */ public void resume () throws InvalidControl, SystemException { if (jtsLogger.logger.isTraceEnabled()) { jtsLogger.logger.trace("AtomicTransaction::resume called for " + _theAction); } synchronized (_theStatus) { if (_theAction == null) throw new InvalidControl(); } OTSImpleManager.current().resumeWrapper(_theAction); } /** * @return the status of this transaction. */ public org.omg.CosTransactions.Status get_status () throws SystemException { _theStatus = getStatus(); if (jtsLogger.logger.isTraceEnabled()) { jtsLogger.logger.trace("AtomicTransaction::get_status called for " + _theAction + " returning " + Utility.stringStatus(_theStatus)); } return _theStatus; } /** * Allow action commit to be supressed. Although the OTS would require an InactiveException * if the transaction is not active, we ignore that via this route. */ public void rollbackOnly () throws SystemException, NoTransaction { if (jtsLogger.logger.isTraceEnabled()) { jtsLogger.logger.trace("AtomicTransaction::rollbackOnly called for " + _theAction); } synchronized (_theStatus) { if (_theAction == null) throw new NoTransaction(); } _theAction.preventCommit(); } public int hashCode () { try { return _theAction.hash_transaction(); } catch (Exception e) { return -1; } } public Uid get_uid () { if (_theAction != null) return _theAction.get_uid(); else return Uid.nullUid(); } /** * If this transaction current? Assume we have checked that we are actually * a transaction! * * If not valid then abort this transaction here. Leave current alone. */ protected final boolean validTransaction () { if (jtsLogger.logger.isTraceEnabled()) { jtsLogger.logger.trace("AtomicTransaction::validTransaction called for " + _theAction); } /* * If we get here then _theAction is not null. */ CurrentImple current = OTSImpleManager.current(); boolean valid = false; try { ControlWrapper currentTransaction = current.getControlWrapper(); if (currentTransaction == null) { jtsLogger.i18NLogger.warn_extensions_atnovalidtx("AtomicTransaction.validTransaction"); return false; } valid = _theAction.equals(currentTransaction); if (!valid) { String transactionName = get_transaction_name(); String currentTransactionName = currentTransaction.get_transaction_name(); jtsLogger.i18NLogger.warn_extensions_atoutofseq("AtomicTransaction", transactionName); jtsLogger.i18NLogger.warn_extensions_atwillabort(currentTransactionName); try { _theAction.rollback(); } catch (Exception ex) { jtsLogger.i18NLogger.warn_extensions_atcannotabort("AtomicTransaction", transactionName); } } } catch (Exception e) { jtsLogger.i18NLogger.warn_extensions_atgenerror("AtomicTransaction", e ); } return valid; } protected AtomicTransaction (ControlWrapper tx) { if (jtsLogger.logger.isTraceEnabled()) { jtsLogger.logger.trace("AtomicTransaction::AtomicTransaction ()"); } _theAction = tx; _theStatus = getStatus(); /* * Once a transaction is created there is no way to get its timeout. So, * we use the timeout associated with the current thread, since that is * most likely to be the right value. */ _timeout = get_timeout(); } protected final org.omg.CosTransactions.Status getStatus () { if (_theStatus != null) { switch (_theStatus.value()) { case Status._StatusRolledBack: case Status._StatusCommitted: case Status._StatusNoTransaction: return _theStatus; default: break; } } /* * It shouldn't be possible for _theAction to be null and for the status * to be unset. If it is something went wrong!! */ org.omg.CosTransactions.Status stat = org.omg.CosTransactions.Status.StatusUnknown; if (_theAction != null) { stat = _theAction.get_status(); } return stat; } protected ControlWrapper _theAction; protected org.omg.CosTransactions.Status _theStatus; protected int _timeout; }