/* * 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: TransactionFactoryImple.java 2342 2006-03-30 13:06:17Z $ */ package com.arjuna.ats.internal.jts.orbspecific; import java.util.Enumeration; import org.omg.CORBA.BAD_OPERATION; import org.omg.CORBA.BAD_PARAM; import org.omg.CORBA.CompletionStatus; import org.omg.CORBA.NO_MEMORY; import org.omg.CORBA.SystemException; import org.omg.CORBA.UNKNOWN; import org.omg.CosTransactions.Control; import org.omg.CosTransactions.Coordinator; import org.omg.CosTransactions.Inactive; import org.omg.CosTransactions.NoTransaction; import org.omg.CosTransactions.PropagationContext; import org.omg.CosTransactions.Status; import org.omg.CosTransactions.Terminator; import org.omg.CosTransactions.TransactionFactory; import org.omg.CosTransactions.otid_t; import com.arjuna.ArjunaOTS.GlobalTransactionInfo; import com.arjuna.ArjunaOTS.TransactionInfo; import com.arjuna.ArjunaOTS.UidCoordinator; import com.arjuna.ats.arjuna.common.Uid; import com.arjuna.ats.arjuna.coordinator.ActionManager; import com.arjuna.ats.arjuna.coordinator.ActionStatus; import com.arjuna.ats.arjuna.coordinator.BasicAction; import com.arjuna.ats.arjuna.coordinator.TransactionReaper; import com.arjuna.ats.arjuna.coordinator.TxControl; import com.arjuna.ats.arjuna.exceptions.ObjectStoreException; import com.arjuna.ats.arjuna.objectstore.RecoveryStore; import com.arjuna.ats.arjuna.objectstore.StateStatus; import com.arjuna.ats.arjuna.objectstore.StoreManager; import com.arjuna.ats.arjuna.state.InputObjectState; import com.arjuna.ats.internal.arjuna.common.UidHelper; import com.arjuna.ats.internal.jts.ControlWrapper; import com.arjuna.ats.internal.jts.ORBManager; import com.arjuna.ats.internal.jts.interposition.FactoryList; import com.arjuna.ats.internal.jts.interposition.ServerFactory; import com.arjuna.ats.internal.jts.orbspecific.coordinator.ArjunaTransactionImple; import com.arjuna.ats.internal.jts.orbspecific.interposition.ServerControl; import com.arjuna.ats.internal.jts.recovery.transactions.AssumedCompleteHeuristicTransaction; import com.arjuna.ats.internal.jts.utils.Helper; import com.arjuna.ats.internal.jts.utils.TxStoreLog; import com.arjuna.ats.jts.logging.jtsLogger; import com.arjuna.ats.jts.utils.Utility; /** * An implementation of ArjunaOTS::ArjunaFactory. * * Problem: garbage collection! If a user keeps a reference to a Control, say, * then we will delete the implementation object when the action terminates. * However, the user's reference is still valid, only the thing it points to is * no longer there. In the remote case this is ok as the Orb will raise an * exception. In the local case, however, the program is likely to crash when it * tries to dereference freed memory! There's nothing we can do about this * (unless we decide never to garbage collect!) apart from warn against using * Control, Coordinator, and Terminator explicitly - if you go via Current then * everything's ok. * * @author Mark Little (mark@arjuna.com) * @version $Id: TransactionFactoryImple.java 2342 2006-03-30 13:06:17Z $ * @since JTS 1.0. */ public class TransactionFactoryImple extends com.arjuna.ArjunaOTS.ArjunaFactoryPOA { public TransactionFactoryImple () { if (jtsLogger.logger.isTraceEnabled()) { jtsLogger.logger.trace("TransactionFactoryImple::TransactionFactoryImple ()"); } _factoryRef = getReference(); } public TransactionFactoryImple (String name) { if (jtsLogger.logger.isTraceEnabled()) { jtsLogger.logger.trace("TransactionFactoryImple::TransactionFactoryImple ( " + name + " )"); } _factoryRef = getReference(); } public final synchronized TransactionFactory getReference () { if (_factoryRef == null) { ORBManager.getPOA().objectIsReady(this); _factoryRef = org.omg.CosTransactions.TransactionFactoryHelper.narrow(ORBManager.getPOA().corbaReference(this)); } return _factoryRef; } /** * Assume that a value of 0 at the client means the same at the server! */ public Control create (int time_out) throws SystemException { if (jtsLogger.logger.isTraceEnabled()) { jtsLogger.logger.trace("TransactionFactoryImple::create ( " + time_out + " )"); } ControlImple tranControl = createLocal(time_out); return tranControl.getControl(); } /** * This creates a local instance of a transaction control, but does not * register it with the ORB. Either call its getControl method directly, or * use the create method of the factory. */ public ControlImple createLocal (int time_out) throws SystemException { if (jtsLogger.logger.isTraceEnabled()) { jtsLogger.logger.trace("TransactionFactoryImple::createLocal ( " + time_out + " )"); } try { ControlImple tranControl = new ControlImple((Control) null, (ArjunaTransactionImple) null); int theTimeout = time_out; if (theTimeout == 0) theTimeout = TxControl.getDefaultTimeout(); if (theTimeout > 0) { /* * Currently we do not remove controls from the list once they * have terminated. We should to save time and space! */ TransactionReaper reaper = TransactionReaper.transactionReaper(); reaper.insert(new ControlWrapper((ControlImple) tranControl), theTimeout); } return tranControl; } catch (OutOfMemoryError e) { /* * Rather than try again after running gc simply return and let the * user deal with it. May help with memory! */ System.gc(); throw new NO_MEMORY(0, CompletionStatus.COMPLETED_NO); } } /** * In Arjuna we can do low-cost nested aborts at clients which do not * involve telling servers. The server finds out the next time a call is * made when it checks the hierarchy. */ public ControlImple recreateLocal (PropagationContext ctx) throws SystemException { if (jtsLogger.logger.isTraceEnabled()) { jtsLogger.logger.trace("TransactionFactoryImple::recreateLocal ()"); } if (ctx.current.coord == null) // nothing to use!! return null; /* * Now take the propagation context and create a local proxy for it * which matches. * * We maintain a proxy for each top-level action. If we already have a * proxy for this action, then pass it the context and let it figure out * what the hierarchy should be - we may already have created it, or * have most of it. */ /* * For each type of transaction we know about, we maintain a creator * function. This allows us to remain implementation neutral, while at * the same time retaining flexibility: to support a new transaction * type simple register a new creator function. */ return creators.recreateLocal(ctx, ctx.current.otid.formatID); } public Control recreate (PropagationContext ctx) throws SystemException { if (jtsLogger.logger.isTraceEnabled()) { jtsLogger.logger.trace("TransactionFactoryImple::recreate ()"); } return recreateLocal(ctx).getControl(); } /** * Non-idl methods, but we put them here because they are related to the * work the factory does. */ public static Control create_subtransaction (Control control, ArjunaTransactionImple parent) { if (jtsLogger.logger.isTraceEnabled()) { jtsLogger.logger.trace("TransactionFactoryImple::create_subtransaction ( " + control + ", " + ((parent != null) ? parent.get_uid() : Uid.nullUid()) + " )"); } try { ControlImple subTranControl = new ControlImple(control, parent); return subTranControl.getControl(); } catch (OutOfMemoryError e) { System.gc(); throw new NO_MEMORY(0, CompletionStatus.COMPLETED_NO); } } public static Control createProxy (Coordinator coordinator, Terminator terminator) { return TransactionFactoryImple.createProxy(coordinator, terminator, null); } public static Control createProxy (Coordinator coordinator, Terminator terminator, Control parentControl) { if (jtsLogger.logger.isTraceEnabled()) { jtsLogger.logger.trace("TransactionFactoryImple::createProxy ( " + coordinator + ", " + terminator + ", " + parentControl + " )"); } /* * Different from the C++ version in that we can cache proxy * implementations and reuse them, since we rely upon the Java garbage * collection facility to remove them when all outstanding references * have gone. */ /* * If not an Arjuna UidCoordinator then we may have a memory leak since * there is no way to remove this control. */ UidCoordinator uidCoord = Helper.getUidCoordinator(coordinator); Uid theUid = null; if (uidCoord != null) { try { theUid = Helper.getUid(uidCoord); /* * allServerControls contains only the proxy implementations. */ if (ServerControl.allServerControls != null) { synchronized (ServerControl.allServerControls) { ControlImple c = (ControlImple) ServerControl.allServerControls.get(theUid); if (c != null) return c.getControl(); } } } catch (Exception e) { /* * Not a JBoss transaction, so allocate any Uid. */ theUid = new Uid(); } uidCoord = null; } else theUid = new Uid(); ControlImple proxy = new ControlImple(coordinator, terminator, parentControl, theUid); return proxy.getControl(); } public static Control createPropagatedControl (Coordinator coord) { if (jtsLogger.logger.isTraceEnabled()) { jtsLogger.logger.trace("TransactionFactoryImple::createPropagatedControl ( " + coord + " )"); } ControlImple proxyControl = new ControlImple(coord, null); return proxyControl.getControl(); } /** * Now methods to return the identities of the currently running * transactions, and those which have terminated but left entries in the * object store. * * WARNING: these methods should be used sparingly since they *must* lock * the transaction database while examining it, and this will prevent any * new transactions from being created/started. * * @param the * type of transaction (active, unresolved) to get data on. * @since JTS 2.1. */ public org.omg.CosTransactions.otid_t[] numberOfTransactions (com.arjuna.ArjunaOTS.TransactionType t) throws Inactive, NoTransaction, SystemException { switch (t.value()) { case com.arjuna.ArjunaOTS.TransactionType._TransactionTypeActive: return activeTransactions(); case com.arjuna.ArjunaOTS.TransactionType._TransactionTypeUnresolved: return unresolvedTransactions(); default: throw new BAD_OPERATION(); } } /** * @return the list of child transactions. */ public org.omg.CosTransactions.otid_t[] getChildTransactions (otid_t parent) throws Inactive, NoTransaction, SystemException { Uid u = Utility.otidToUid(parent); org.omg.CosTransactions.otid_t[] ctx = null; if (u == null) throw new BAD_PARAM( "otid_t " + jtsLogger.i18NLogger.get_orbspecific_otiderror()); else { BasicAction act = ActionManager.manager().get(u); if (act == null) throw new NoTransaction(); else { if (act.status() == ActionStatus.RUNNING) { Object[] children = act.childTransactions(); int size = ((children == null) ? 0 : children.length); if (size > 0) { ctx = new org.omg.CosTransactions.otid_t[size]; for (int i = 0; i < size; i++) { ctx[i] = Utility.uidToOtid((Uid) children[i]); } } } else throw new Inactive(); } } return ctx; } /** * @return the status of a transaction when all we have is its unique name. * The transaction must be in the local list. * @since JTS 2.1.2. */ public org.omg.CosTransactions.Status getCurrentStatus (otid_t txid) throws SystemException { Uid u = Utility.otidToUid(txid); if (u == null) throw new BAD_PARAM( "otid_t " + jtsLogger.i18NLogger.get_orbspecific_otiderror()); else return getCurrentStatus(u); } /** * @return the status of a transaction when all we have is its unique name. * The transaction must be in the local list. * @since JTS 2.1. */ public org.omg.CosTransactions.Status getCurrentStatus (Uid u) throws SystemException { if (!u.valid()) throw new BAD_PARAM(); else { try { ControlImple ctx = null; synchronized (ControlImple.allControls) { ctx = (ControlImple) ControlImple.allControls.get(u); } if (ctx != null) return ctx.getImplHandle().get_status(); else { /* * If there is a persistent representation for this * transaction, then return that status. Otherwise check * whether this is a server transaction. */ org.omg.CosTransactions.Status s = getOSStatus(u); if ((s == org.omg.CosTransactions.Status.StatusUnknown) || (s == org.omg.CosTransactions.Status.StatusNoTransaction)) { return ServerFactory.getCurrentStatus(u); // check it's // not a // server // transaction } else return s; } } catch (Exception e) { jtsLogger.i18NLogger.warn_orbspecific_tficaught("TransactionFactoryImple.getCurrentStatus", u, e); return Status.StatusUnknown; } } } /** * @return the status of a transaction when all we have is its unique name. * If the transaction is not in the local list then we look in the * ObjectStore. * @since JTS 2.1.2. */ public org.omg.CosTransactions.Status getStatus (otid_t txid) throws NoTransaction, SystemException { Uid u = Utility.otidToUid(txid); if (u == null) throw new BAD_PARAM( "otid_t " + jtsLogger.i18NLogger.get_orbspecific_otiderror()); else return getStatus(u); } /** * @return the status of a transaction when all we have is its unique name. * If the transaction is not in the local list then we look in the * ObjectStore. * @since JTS 2.1. */ public org.omg.CosTransactions.Status getStatus (Uid u) throws NoTransaction, SystemException { org.omg.CosTransactions.Status s = org.omg.CosTransactions.Status.StatusUnknown; try { s = getCurrentStatus(u); } catch (SystemException e2) { throw e2; } catch (Exception e3) { jtsLogger.i18NLogger.warn_orbspecific_tficaught("TransactionFactoryImple.getStatus", u, e3); return Status.StatusUnknown; } /* * If status is unknown, then transaction is cannot be active (even if * it is a server transaction). So, check the object store. */ if ((s == org.omg.CosTransactions.Status.StatusUnknown) || (s == org.omg.CosTransactions.Status.StatusNoTransaction)) { return getOSStatus(u); } else return s; } /** * @return the status of the transaction as recorded in the object store. * @since JTS 2.1.1. */ public org.omg.CosTransactions.Status getOSStatus (Uid u) throws NoTransaction, SystemException { org.omg.CosTransactions.Status s = org.omg.CosTransactions.Status.StatusUnknown; if (!u.valid()) throw new BAD_PARAM(); else { // if here then it is not active, so look in the object store RecoveryStore recoveryStore = StoreManager.getRecoveryStore(); try { /* * Do we need to search server transactions too? Possibly not, * since an interposed coordinator can never always say with * certainty what the status is of the root coordinator. */ int status = recoveryStore.currentState(u, ArjunaTransactionImple.typeName()); switch (status) { case StateStatus.OS_UNKNOWN: final Status heuristicStatus = getHeuristicStatus(u, recoveryStore); if (org.omg.CosTransactions.Status.StatusNoTransaction.equals(heuristicStatus) || org.omg.CosTransactions.Status.StatusUnknown.equals(heuristicStatus)) { // means no state present, so check if server transaction return ServerFactory.getOSStatus(u); } return heuristicStatus; case StateStatus.OS_COMMITTED: return org.omg.CosTransactions.Status.StatusCommitted; case StateStatus.OS_UNCOMMITTED: return org.omg.CosTransactions.Status.StatusPrepared; case StateStatus.OS_HIDDEN: case StateStatus.OS_COMMITTED_HIDDEN: case StateStatus.OS_UNCOMMITTED_HIDDEN: return org.omg.CosTransactions.Status.StatusPrepared; default: return ServerFactory.getStatus(u); } } catch (Exception e) { jtsLogger.i18NLogger.warn_orbspecific_tficaught("TransactionFactoryImple.getStatus", u, e); return Status.StatusUnknown; } } } /* * @return information on the transactions known by this object. * * @since JTS 2.1. */ public GlobalTransactionInfo getGlobalInfo () throws SystemException { GlobalTransactionInfo info = new GlobalTransactionInfo(); info.totalNumberOfTransactions = (int)com.arjuna.ats.arjuna.coordinator.TxStats.getInstance().getNumberOfTransactions(); info.numberOfCommittedTransactions = (int)com.arjuna.ats.arjuna.coordinator.TxStats.getInstance().getNumberOfCommittedTransactions(); info.numberOfAbortedTransactions = (int)com.arjuna.ats.arjuna.coordinator.TxStats.getInstance().getNumberOfAbortedTransactions(); if (info.totalNumberOfTransactions > 0) info.averageLifetime = (float) (TransactionReaper.transactionLifetime() / info.totalNumberOfTransactions); else info.averageLifetime = (float) 0.0; info.numberOfHeuristics = (int)com.arjuna.ats.arjuna.coordinator.TxStats.getInstance().getNumberOfHeuristics(); TransactionReaper reaper = TransactionReaper.transactionReaper(); if (reaper.checkingPeriod() == Long.MAX_VALUE) info.reaperTimeout = 0; else info.reaperTimeout = (int) reaper.checkingPeriod(); info.defaultTimeout = TxControl.getDefaultTimeout(); return info; } /** * @return information on a specific transaction. * @since JTS 2.1.2. */ public TransactionInfo getTransactionInfo (otid_t txid) throws org.omg.CosTransactions.NoTransaction, SystemException { Uid u = Utility.otidToUid(txid); if (u == null) throw new BAD_PARAM( "otid_t " + jtsLogger.i18NLogger.get_orbspecific_otiderror()); else return getTransactionInfo(u); } /** * @return information on a specific transaction. * @since JTS 2.1.2. */ public TransactionInfo getTransactionInfo (Uid u) throws org.omg.CosTransactions.NoTransaction, SystemException { if (!u.valid()) throw new BAD_PARAM( jtsLogger.i18NLogger.get_orbspecific_invaliduid()+ " " + u); else { try { synchronized (ControlImple.allControls) { ControlImple ctx = (ControlImple) ControlImple.allControls.get(u); if (ctx != null) { TransactionInfo info = new TransactionInfo(); info.currentDepth = ctx.getImplHandle().getHierarchy().depth(); TransactionReaper reaper = TransactionReaper.transactionReaper(); info.timeout = reaper.getTimeout(ctx); info.numberOfThreads = ctx.getImplHandle().activeThreads(); return info; } else throw new NoTransaction(); } } catch (NoTransaction ex) { throw ex; } catch (Exception e) { e.printStackTrace(); throw new UNKNOWN(); } } } /** * @return the transaction Control. * @since JTS 2.1.2. */ public org.omg.CosTransactions.Control getTransaction (otid_t txid) throws org.omg.CosTransactions.NoTransaction, SystemException { Uid u = Utility.otidToUid(txid); if (u == null) throw new BAD_PARAM( "otid_t " + jtsLogger.i18NLogger.get_orbspecific_otiderror()); else return getTransaction(u); } /** * @return the transaction Control. * @since JTS 2.1.2. */ public org.omg.CosTransactions.Control getTransaction (Uid u) throws org.omg.CosTransactions.NoTransaction, SystemException { if (!u.valid()) throw new BAD_PARAM(); else { try { synchronized (ControlImple.allControls) { ControlImple ctx = (ControlImple) ControlImple.allControls.get(u); if (ctx != null) return ctx.getControl(); else throw new NoTransaction(); } } catch (NoTransaction ex) { throw ex; } catch (Exception e) { e.printStackTrace(); throw new UNKNOWN(); } } } private final org.omg.CosTransactions.otid_t[] activeTransactions () throws Inactive, NoTransaction, SystemException { if (jtsLogger.logger.isTraceEnabled()) { jtsLogger.logger.trace("TransactionFactoryImple::activeTransactions ()"); } if (ControlImple.allControls == null) throw new Inactive(); else { synchronized (ControlImple.allControls) { if (ControlImple.allControls.size() == 0) throw new NoTransaction(); else { org.omg.CosTransactions.otid_t[] ids = new org.omg.CosTransactions.otid_t[ControlImple.allControls.size()]; Enumeration iter = ControlImple.allControls.elements(); int i = 0; while (iter.hasMoreElements()) { ControlImple cont = (ControlImple) iter.nextElement(); if (cont != null) { ids[i] = Utility.uidToOtid(cont.get_uid().stringForm()); i++; } } return ids; } } } } private final org.omg.CosTransactions.otid_t[] unresolvedTransactions () throws Inactive, NoTransaction, SystemException { if (jtsLogger.logger.isTraceEnabled()) { jtsLogger.logger.trace("TransactionFactoryImple::terminatedTransactions ()"); } InputObjectState uids = new InputObjectState(); if (!TxStoreLog.getTransactions(uids, StateStatus.OS_COMMITTED_HIDDEN)) { throw new NoTransaction(); } else { Uid theUid = null; int count = 0; boolean finished = false; while (!finished) { try { theUid = UidHelper.unpackFrom(uids); if (theUid.equals(Uid.nullUid())) finished = true; else count++; } catch (Exception e) { finished = true; } } org.omg.CosTransactions.otid_t[] ids = new org.omg.CosTransactions.otid_t[count]; uids.reread(); for (int i = 0; i < count; i++) { try { theUid = UidHelper.unpackFrom(uids); ids[i] = Utility.uidToOtid(theUid.stringForm()); } catch (Exception e) { } } return ids; } } private org.omg.CosTransactions.Status getHeuristicStatus(final Uid uid, final RecoveryStore recoveryStore) throws ObjectStoreException { final int status = recoveryStore.currentState(uid, AssumedCompleteHeuristicTransaction.typeName()); switch (status) { case StateStatus.OS_UNKNOWN: return org.omg.CosTransactions.Status.StatusNoTransaction; case StateStatus.OS_COMMITTED: return org.omg.CosTransactions.Status.StatusCommitted; case StateStatus.OS_UNCOMMITTED: return org.omg.CosTransactions.Status.StatusPrepared; case StateStatus.OS_HIDDEN: case StateStatus.OS_COMMITTED_HIDDEN: case StateStatus.OS_UNCOMMITTED_HIDDEN: return org.omg.CosTransactions.Status.StatusPrepared; default: return org.omg.CosTransactions.Status.StatusUnknown; } } private TransactionFactory _factoryRef; private static FactoryList creators = new FactoryList(); }