All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.arjuna.ats.arjuna.coordinator.BasicAction Maven / Gradle / Ivy

There is a newer version: 4.17.43.Final
Show newest version
/*
 * 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) 1998, 1999, 2000, 2001,
 *
 * Arjuna Solutions Limited,
 * Newcastle upon Tyne,
 * Tyne and Wear,
 * UK.  
 *
 * $Id: BasicAction.java 2342 2006-03-30 13:06:17Z  $
 */

package com.arjuna.ats.arjuna.coordinator;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

import com.arjuna.ats.arjuna.ObjectType;
import com.arjuna.ats.arjuna.StateManager;
import com.arjuna.ats.arjuna.common.Uid;
import com.arjuna.ats.arjuna.common.arjPropertyManager;
import com.arjuna.ats.arjuna.exceptions.ObjectStoreException;
import com.arjuna.ats.arjuna.logging.tsLogger;
import com.arjuna.ats.arjuna.objectstore.ParticipantStore;
import com.arjuna.ats.arjuna.objectstore.StoreManager;
import com.arjuna.ats.arjuna.state.InputObjectState;
import com.arjuna.ats.arjuna.state.OutputObjectState;
import com.arjuna.ats.arjuna.utils.ThreadUtil;
import com.arjuna.ats.arjuna.utils.Utility;
import com.arjuna.ats.internal.arjuna.Header;
import com.arjuna.ats.internal.arjuna.thread.ThreadActionData;

/**
 * BasicAction does most of the work of an atomic action, but does not manage
 * thread scoping. This is the responsibility of any derived classes.
 *
 * @author Mark Little ([email protected])
 * @version $Id: BasicAction.java 2342 2006-03-30 13:06:17Z  $
 * @since JTS 1.0.
 */

public class BasicAction extends StateManager
{

    public BasicAction ()
    {
        super(ObjectType.NEITHER);

        if (tsLogger.logger.isTraceEnabled()) {
            tsLogger.logger.trace("BasicAction::BasicAction()");
        }

        pendingList = null;
        preparedList = null;
        readonlyList = null;
        failedList = null;
        heuristicList = null;

        currentHierarchy = null;
        transactionStore = null;
        savedIntentionList = false;

        actionStatus = ActionStatus.CREATED;
        actionType = ActionType.NESTED;

        parentAction = null;
        recordBeingHandled = null;

        heuristicDecision = TwoPhaseOutcome.PREPARE_OK;
		_checkedAction = arjPropertyManager
				.getCoordinatorEnvironmentBean().getCheckedActionFactory()
				.getCheckedAction(get_uid(), type());

        _childThreads = null;
        _childActions = null;
    }

    /**
     * BasicAction constructor with a Uid. This constructor is for recreating an
     * BasicAction, typically during crash recovery.
     */

    public BasicAction (Uid objUid)
    {
        super(objUid, ObjectType.NEITHER);

        if (tsLogger.logger.isTraceEnabled()) {
            tsLogger.logger.trace("BasicAction::BasicAction("
                    + objUid + ")");
        }

        pendingList = null;
        preparedList = null;
        readonlyList = null;
        failedList = null;
        heuristicList = null;

        currentHierarchy = null;
        transactionStore = null;
        savedIntentionList = false;

        actionStatus = ActionStatus.CREATED;
        actionType = ActionType.NESTED;

        parentAction = null;
        recordBeingHandled = null;

        heuristicDecision = TwoPhaseOutcome.PREPARE_OK;
		_checkedAction = arjPropertyManager
				.getCoordinatorEnvironmentBean().getCheckedActionFactory()
				.getCheckedAction(get_uid(), type());

        _childThreads = null;
        _childActions = null;
    }

    /**
     * BasicAction destructor. Under normal circumstances we do very little.
     * However there exists the possibility that this action is being deleted
     * while still running (user forgot to commit/abort) - in which case we do
     * an abort for him and mark all our parents as unable to commit.
     * Additionally due to scoping we may not be the current action - but in
     * that case the current action must be one of our nested actions so by
     * applying abort to it we should end up at ourselves!
     */

    public void finalizeInternal()
    {
        if (tsLogger.logger.isTraceEnabled()) {
            tsLogger.logger.trace("BasicAction::finalize()");
        }

        if ((actionStatus == ActionStatus.RUNNING)
                || (actionStatus == ActionStatus.ABORT_ONLY)) {
            /* If current action is one of my children there's an error */

            BasicAction currentAct = BasicAction.Current();

            if ((currentAct != null) && (currentAct != this)) {
                /*
                     * Is the current action a child of this action? If so, abort
                     * until we get to the current action. This works even in a
                     * multi-threaded environment where each thread may have a
                     * different notion of current, since Current returns the thread
                     * specific current.
                     */

                if (currentAct.isAncestor(get_uid())) {
                    tsLogger.i18NLogger.warn_coordinator_BasicAction_1(get_uid());

                    while ((currentAct != this) && (currentAct != null)) {
                        tsLogger.i18NLogger.warn_coordinator_BasicAction_2(currentAct.get_uid());

                        currentAct.Abort();

                        currentAct = BasicAction.Current();
                    }
                }
            }

            BasicAction parentAct = parent();

            /* prevent commit of parents (safety) */

            while (parentAct != null) {
                parentAct.preventCommit();
                parentAct = parentAct.parent();
            }

            tsLogger.i18NLogger.warn_coordinator_BasicAction_3(get_uid());

            /* This will also kill any children */

            Abort();
        }
        else
        {
            if (actionStatus == ActionStatus.PREPARED)
                Thread.yield();
        }

        pendingList = null;
        preparedList = null;
        readonlyList = null;
        failedList = null;
        heuristicList = null;

        transactionStore = null;
        currentHierarchy = null;

        _checkedAction = null;

        if (_childThreads != null)
        {
            _childThreads.clear();
            _childThreads = null;
        }

        if (_childActions != null)
        {
            _childActions.clear();
            _childActions = null;
        }
    }

    /**
     * Return the action hierarchy for this transaction.
     */

    public final synchronized ActionHierarchy getHierarchy ()
    {
        return currentHierarchy;
    }

    /**
     * Force the only outcome for the transaction to be to rollback. Only
     * possible if this transaction has not (or is not) terminated.
     *
     * @return true if successful, false
     *         otherwise.
     */

    public final boolean preventCommit ()
    {
        if (tsLogger.logger.isTraceEnabled()) {
            tsLogger.logger.trace("BasicAction::preventCommit( " + this + ")");
        }

        boolean res = false;

        //	if (lockMutex())
        {
            /*
                * If we are active then change status. Otherwise it may be an error so check status.
                */

            if (actionStatus == ActionStatus.RUNNING)
                actionStatus = ActionStatus.ABORT_ONLY;

            /*
                * Since the reason to call this method is to make sure the transaction
                * only aborts, check the status now and if it has aborted or will abort then
                * we'll consider it a success.
                */

            res = ((actionStatus == ActionStatus.ABORT_ONLY) || (actionStatus == ActionStatus.ABORTED) || (actionStatus == ActionStatus.ABORTING));

            //	    unlockMutex();
        }

        return res;
    }

    /**
     * @return the number of threads associated with this transaction.
     */

    public final int activeThreads ()
    {
        if (_childThreads != null)
            return _childThreads.size();
        else
            return 0;
    }

    /**
     * Add a record to the atomic action. This function returns AR_ADDED if the
     * record is added. AR_REJECTED if the record cannot be added because the
     * action is past the prepare phase, and IGNORED otherwise.
     *
     * @return AddOutcome indicating outcome.
     */

    public final synchronized int add (AbstractRecord A)
    {
        int result = AddOutcome.AR_REJECTED;

        criticalStart();

        if ((actionStatus <= ActionStatus.ABORTING)
                && ((recordBeingHandled == null) || !(recordBeingHandled.equals(A))))
        {
            if (pendingList == null)
                pendingList = new RecordList();

            result = (pendingList.insert(A) ? AddOutcome.AR_ADDED
                    : AddOutcome.AR_DUPLICATE);
        }

        criticalEnd();

        return result;
    }

    /**
     * @return the depth of the current transaction hierarchy.
     */

    public final synchronized int hierarchyDepth ()
    {
        if (currentHierarchy != null)
            return currentHierarchy.depth();
        else
            return 0; /* should never happen */
    }

    /**
     * boolean function that checks whether the Uid passed as an argument is the
     * Uid for an ancestor of the current atomic action.
     *
     * @return true if the parameter represents an ancestor,
     *         false otherwise.
     */

    public final boolean isAncestor (Uid ancestor)
    {
        boolean res = false;
        
        if (get_uid().equals(ancestor)) /* actions are their own ancestors */
            res = true;
        else
        {
            if ((parentAction != null) && (actionType != ActionType.TOP_LEVEL))
                res = parentAction.isAncestor(ancestor);
        }

        return res;
    }

    /**
     * @return a reference to the parent BasicAction
     */

    public final BasicAction parent ()
    {
        if (actionType == ActionType.NESTED)
            return parentAction;
        else
            return null;
    }

    public final int typeOfAction ()
    {
        return actionType;
    }

    /**
     * @return the status of the BasicAction
     */

    public final int status ()
    {
        int s = ActionStatus.INVALID;

        //	if (tryLockMutex())
        {
            s = actionStatus;

            //	    unlockMutex();
        }

        return s;
    }

    /**
     * Set up an object store and assign it to the participantStore variable.
     *
     * @return the object store implementation to use.
     * @see com.arjuna.ats.arjuna.objectstore.ObjectStore
     */

    public ParticipantStore getStore ()
    {
        if (transactionStore == null)
        {
            transactionStore = StoreManager.getParticipantStore();
        }

        return transactionStore;
    }

    /**
     * The following function returns the Uid of the top-level atomic action. If
     * this is the top-level transaction then it is equivalent to calling
     * get_uid().
     *
     * @return the top-level transaction's Uid.
     */

    public final Uid topLevelActionUid ()
    {
        BasicAction root = this;

        while (root.parent() != null)
            root = root.parent();

        return root.get_uid();
    }

    /**
     * @return a reference to the top-level transaction. If this is the
     *         top-level transaction then a reference to itself will be
     *         returned.
     */

    public final BasicAction topLevelAction ()
    {
        BasicAction root = this;

        while (root.parent() != null)
            root = root.parent();

        return root;
    }

    /**
     * Overloaded version of activate -- sets up the store, performs read_state
     * followed by restore_state. The store root is null.
     *
     * @return true if successful, false
     *         otherwise.
     */

    public boolean activate ()
    {
        return activate(null);
    }

    /**
     * Overloaded version of activate -- sets up the store, performs read_state
     * followed by restore_state. The root of the object store to use is
     * specified in the root parameter.
     *
     * @return true if successful, false
     *         otherwise.
     */

    public boolean activate (String root)
    {
        if (tsLogger.logger.isTraceEnabled()) {
            tsLogger.logger.trace("BasicAction::activate() for action-id "
                    + get_uid());
        }

        boolean restored = false;

        // Set up store
        ParticipantStore aaStore = getStore();

        if (aaStore == null)
            return false;

        try
        {
            // Read object state

            InputObjectState oState = aaStore.read_committed(getSavingUid(), type());

            if (oState != null)
            {
                synchronized (this)
                {
                    restored = restore_state(oState, ObjectType.ANDPERSISTENT);
                }

                oState = null;
            }
            else {
                tsLogger.i18NLogger.warn_coordinator_BasicAction_5(get_uid(), type());

                restored = false;
            }

            return restored;
        }
        catch (ObjectStoreException e)
        {
            tsLogger.logger.warn(e);

            return false;
        }
    }

    /**
     * This operation deactivates a persistent object. It behaves in a similar
     * manner to the activate operation, but has an extra argument which defines
     * whether the object's state should be committed or written as a shadow.
     *
     * The root of the object store is null. It is assumed that
     * this is being called during a transaction commit.
     *
     * @return true on success, false otherwise.
     * 
     */

    public boolean deactivate ()
    {
        if (tsLogger.logger.isTraceEnabled()) {
            tsLogger.logger.trace("BasicAction::deactivate() for action-id "
                    + get_uid());
        }

        boolean deactivated = false;

        // Set up store
        ParticipantStore aaStore = getStore();

        if (aaStore == null)
            return false;

        try
        {
            // Write object state
            OutputObjectState oState = new OutputObjectState();

            if (save_state(oState, ObjectType.ANDPERSISTENT))
            {
                deactivated = aaStore.write_committed(getSavingUid(), type(), oState);

                oState = null;
            }
            else
            {
                deactivated = false;
            }

            /** If we failed to deactivate then output warning * */
            if (!deactivated) {
                tsLogger.i18NLogger.warn_coordinator_BasicAction_5a(get_uid(), type());
            }
        }
        catch (ObjectStoreException e)
        {
            tsLogger.logger.warn(e);

            deactivated = false;
        }

        return deactivated;
    }

    /**
     * Add the current thread to the list of threads associated with this
     * transaction.
     *
     * @return true if successful, false
     *         otherwise.
     */

    public final boolean addChildThread () // current thread
    {
        return addChildThread(Thread.currentThread());
    }

    /**
     * Add the specified thread to the list of threads associated with this
     * transaction.
     *
     * @return true if successful, false
     *         otherwise.
     */

    public final boolean addChildThread (Thread t)
    {
        if (tsLogger.logger.isTraceEnabled()) {
            tsLogger.logger.trace("BasicAction::addChildThread () action "+get_uid()+" adding "+t);
        }

        if (t == null)
            return false;

        boolean result = false;

        criticalStart();

        synchronized (this)
        {
            if (actionStatus <= ActionStatus.ABORTING)
            {
                if (_childThreads == null)
                    _childThreads = new Hashtable();

                _childThreads.put(ThreadUtil.getThreadId(t), t); // makes sure so we don't get
                // duplicates

                result = true;
            }
        }

        criticalEnd();

        if (tsLogger.logger.isTraceEnabled()) {
            tsLogger.logger.trace("BasicAction::addChildThread () action "+get_uid()+" adding "+t+" result = "+result);
        }

        return result;
    }

    /*
      * Can be done at any time (Is this correct?)
      */

    /**
     * Remove a child thread. The current thread is removed.
     *
     * @return true if successful, false
     *         otherwise.
     */

    public final boolean removeChildThread () // current thread
    {
        return removeChildThread(ThreadUtil.getThreadId());
    }

    /**
     * Remove the specified thread from the transaction.
     *
     * @return true if successful, false
     *         otherwise.
     */

    public final boolean removeChildThread (String threadId)
    {
        if (tsLogger.logger.isTraceEnabled()) {
            tsLogger.logger.trace("BasicAction::removeChildThread () action "+get_uid()+" removing "+threadId);
        }

        if (threadId == null)
            return false;

        boolean result = false;

        criticalStart();

        synchronized (this)
        {
            if (_childThreads != null)
            {
                _childThreads.remove(threadId);
                result = true;
            }
        }

        criticalEnd();

        if (tsLogger.logger.isTraceEnabled())
        {
            tsLogger.logger.trace("BasicAction::removeChildThread () action "+get_uid()+" removing "+threadId+" result = "+result);
        }

        return result;
    }

    /**
     * Add a new child action to the atomic action.
     *
     * @return true if successful, false
     *         otherwise.
     */

    public final boolean addChildAction (BasicAction act)
    {
        if (tsLogger.logger.isTraceEnabled()) {
            tsLogger.logger.trace("BasicAction::addAction () action "+get_uid()+" adding "+((act != null) ? act.get_uid() : Uid.nullUid()));
        }

        if (act == null)
            return false;

        boolean result = false;

        criticalStart();

        synchronized (this)
        {
            /*
                * Must be <= as we sometimes need to do processing during commit
                * phase.
                */

            if (actionStatus <= ActionStatus.ABORTING)
            {
                if (_childActions == null)
                    _childActions = new Hashtable();

                _childActions.put(act, act);
                result = true;
            }
        }

        criticalEnd();

        if (tsLogger.logger.isTraceEnabled())
        {
            tsLogger.logger.trace("BasicAction::addChildAction () action "+get_uid()+" adding "+act.get_uid()+" result = "+result);
        }

        return result;
    }

    /*
      * Can be done at any time (Is this correct?)
      */

    /**
     * Redefined version of save_state and restore_state from StateManager.
     *
     * Normal operation (no crashes):
     *
     * BasicAction.save_state is called after a successful prepare. This causes
     * and BasicAction object to be saved in the object store. This object
     * contains primarily the "intentions list" of the BasicAction. After
     * successfully completing phase 2 of the commit protocol, the BasicAction
     * object is deleted from the store.
     *
     * Failure cases:
     *
     * If a server crashes after successfully preparing, then upon recovery the
     * action must be resolved (either committed or aborted) depending upon
     * whether the co-ordinating atomic action committed or aborted. Upon server
     * recovery, the crash recovery mechanism detects ServerBasicAction objects
     * in the object store and attempts to activate the BasicAction object of
     * the co-ordinating action. If this is successful then the SAA is committed
     * else aborted.
     *
     * If, when processing phase 2 of the commit protocol, the co-ordinator
     * experiences a failure to commit from one of the records then the
     * BasicAction object is NOT deleted. It is rewritten when a new state which
     * contains a list of the records that failed during phase 2 commit. This
     * list is called the "failedList".
     *
     * The crash recovery manager will detect local BasicAction objects in
     * addition to SAA objects in the objectstore. An attempt will be made to
     * commit these actions. If the action contained a call to a now dead
     * server, this action can never be resolved and the AA object can never be
     * removed. However, if the action is purely local then after the processing
     * is complete the removed by crash recovery.
     *
     * @return true if successful, false
     *         otherwise.
     */

    public boolean save_state (OutputObjectState os, int ot)
    {
        if (tsLogger.logger.isTraceEnabled()) {
            tsLogger.logger.trace("BasicAction::save_state ()");
        }

        try
        {
            packHeader(os, new Header(get_uid(), Utility.getProcessUid()));
            
            os.packBoolean(pastFirstParticipant);
        }
        catch (IOException e)
        {
            return false;
        }

        /*
           * In a presumed abort scenario, this routine is called: a) After a
           * successful prepare - to save the intentions list. b) After a failure
           * during phase 2 of commit - to overwrite the intentions list by the
           * failedList.
           *
           * If we're using presumed nothing, then it could be called: a) Whenever
           * a participant is registered.
           */

        RecordList listToSave = null;
        boolean res = true;

        /*
           * If we have a failedList then we are re-writing a BasicAction object
           * after a failure during phase 2 commit
           */

        if ((failedList != null) && (failedList.size() > 0))
        {
            listToSave = failedList;
        }
        else
        {
            listToSave = preparedList;
        }

        AbstractRecord first = ((listToSave != null) ? listToSave.getFront()
                : null);
        AbstractRecord temp = first;
        boolean havePacked = ((listToSave == null) ? false : true);

        while ((res) && (temp != null))
        {
            listToSave.putRear(temp);

            /*
                * First check to see if we need to call save_state. If we do then
                * we must first save the record type (and enum) and then save the
                * unique identity of the record (a string). The former is needed to
                * determine what type of record we are restoring, while the latter
                * is required to re-create the actual record.
                */

            /*
                * First check to see if we need to call save_state. If we do then
                * we must first save the record type. This is used to determine
                * which type of record to create when restoring.
                */

            if (tsLogger.logger.isTraceEnabled())
            {
                tsLogger.logger.trace("BasicAction::save_state - next record to pack is a "+temp.typeIs()
                        +" record "+temp.type()+" should save it? = "+temp.doSave());
            }

            if (temp.doSave())
            {
                res = true;

                try
                {
                    if (tsLogger.logger.isTraceEnabled()) {
                        tsLogger.logger.trace("Packing a "+temp.typeIs()+" record");
                    }

                    os.packInt(temp.typeIs());
                    res = temp.save_state(os, ot);
                }
                catch (IOException e)
                {
                    res = false;
                }
            }

            temp = listToSave.getFront();

            if (temp == first)
            {
                listToSave.putFront(temp);
                temp = null;
            }
        }

        /*
           * If we only ever had a heuristic list (e.g., one-phase commit) then
           * pack a record delimiter.
           */

        if (res && (os.notempty() || !havePacked))
        {
            try
            {
                if (tsLogger.logger.isTraceEnabled()) {
                    tsLogger.logger.trace("Packing a NONE_RECORD");
                }

                os.packInt(RecordType.NONE_RECORD);
            }
            catch (IOException e)
            {
                res = false;
            }
        }

        if (res)
        {
            // Now deal with anything on the heuristic list!

            int hSize = ((heuristicList == null) ? 0 : heuristicList.size());

            try
            {
                os.packInt(hSize);
            }
            catch (IOException e)
            {
                res = false;
            }

            if (res && (hSize > 0))
            {
                first = heuristicList.getFront();
                temp = first;

                while (res && (temp != null))
                {
                    heuristicList.putRear(temp);

                    if (temp.doSave())
                    {
                        res = true;

                        try
                        {
                            if (tsLogger.logger.isTraceEnabled()) {
                                tsLogger.logger.trace("HeuristicList - packing a "+temp.typeIs()+" record");
                            }

                            os.packInt(temp.typeIs());
                            res = temp.save_state(os, ot);
                        }
                        catch (IOException e)
                        {
                            res = false;
                        }
                    }

                    temp = heuristicList.getFront();

                    if (temp == first)
                    {
                        heuristicList.putFront(temp);
                        temp = null;
                    }
                }

                if (res && os.notempty())
                {
                    try
                    {
                        if (tsLogger.logger.isTraceEnabled()) {
                            tsLogger.logger.trace("HeuristicList - packing a NONE_RECORD");
                        }

                        os.packInt(RecordType.NONE_RECORD);
                    }
                    catch (IOException e)
                    {
                        res = false;
                    }
                }
            }
        }

        if (res && os.notempty())
        {
            try
            {
                if (tsLogger.logger.isTraceEnabled()) {
                    tsLogger.logger.trace("Packing action status of "+ActionStatus.stringForm(actionStatus));
                }

                os.packInt(actionStatus);
                os.packInt(actionType); // why pack since only top-level?
                os.packInt(heuristicDecision); // can we optimize?
            }
            catch (IOException e)
            {
                res = false;
            }
        }

        return res;
    }

    /**
     * Remove a child action.
     *
     * @return true if successful, false
     *         otherwise.
     */

    public final boolean removeChildAction (BasicAction act)
    {
        if (tsLogger.logger.isTraceEnabled()) {
            tsLogger.logger.trace("BasicAction::removeChildAction () action "+get_uid()+" removing "+((act != null) ? act.get_uid() : Uid.nullUid()));
        }

        if (act == null)
            return false;

        boolean result = false;

        criticalStart();

        synchronized (this)
        {
            if (_childActions != null)
            {
                _childActions.remove(act);
                result = true;
            }
        }

        criticalEnd();

        if (tsLogger.logger.isTraceEnabled())
        {
            tsLogger.logger.trace("BasicAction::removeChildAction () action "+get_uid()+" removing "+act.get_uid()+" result = "+result);
        }

        return result;
    }

    /**
     * Add the specified CheckedAction object to this transaction.
     *
     * @see com.arjuna.ats.arjuna.coordinator.CheckedAction
     */

    protected final synchronized void setCheckedAction (CheckedAction c)
    {
        criticalStart();

        _checkedAction = c;

        criticalEnd();
    }

    /**
     * @return the Uid that the transaction's intentions list will be saved
     *         under.
     */

    public Uid getSavingUid ()
    {
        return get_uid();
    }

    /**
     * Overloads Object.toString()
     */

    public String toString ()
    {
        return new String("BasicAction: " + get_uid() + " status: "
                + ActionStatus.stringForm(actionStatus));
    }

    /**
     * This assumes the various lists are zero length when it is called.
     *
     * @return true if successful, false
     *         otherwise.
     */

    public boolean restore_state (InputObjectState os, int ot)
    {
        if (tsLogger.logger.isTraceEnabled()) {
            tsLogger.logger.trace("BasicAction::restore_state ()");
        }

        createPreparedLists();

        boolean res = true;
        int record_type = RecordType.NONE_RECORD;
        int tempActionStatus = ActionStatus.INVALID;
        int tempActionType = ActionType.TOP_LEVEL;
        int tempHeuristicDecision = TwoPhaseOutcome.PREPARE_OK;

        /*
           * Unpack the prepared list. Note: This may either be a full intentions
           * list or just the failedList, either way, restore it as the prepared
           * list.
           */

        try
        {
            Header hdr = new Header();

            unpackHeader(os, hdr);
            
            pastFirstParticipant = os.unpackBoolean();
        }
        catch (IOException e)
        {
            return false;
        }

        try
        {
            record_type = os.unpackInt();

            if (tsLogger.logger.isTraceEnabled()) {
                tsLogger.logger.trace("Unpacked a "+record_type+" record");
            }
        }
        catch (IOException e)
        {
            res = false;
        }

        while ((res) && (record_type != RecordType.NONE_RECORD))
        {
            AbstractRecord record = AbstractRecord.create(record_type);

            if (record == null) {
                tsLogger.i18NLogger.warn_coordinator_BasicAction_21(Integer.toString(record_type));

                res = false;
            }
            else
                res = (record.restore_state(os, ot) && preparedList.insert(record));

            if (res)
            {
                try
                {
                    record_type = os.unpackInt();

                    if (tsLogger.logger.isTraceEnabled()) {
                        tsLogger.logger.trace("Unpacked a "+record_type+" record");
                    }
                }
                catch (IOException e)
                {
                    res = false;
                }
            }
        }

        // Now deal with the heuristic list!

        int hSize = 0;

        if (res)
        {
            try
            {
                hSize = os.unpackInt();

                if (tsLogger.logger.isTraceEnabled()) {
                    tsLogger.logger.trace("HeuristicList - Unpacked heuristic list size of "+hSize);
                }
            }
            catch (IOException e)
            {
                res = false;
            }
        }

        if (hSize > 0)
        {
            try
            {
                record_type = os.unpackInt();

                if (tsLogger.logger.isTraceEnabled()) {
                    tsLogger.logger.trace("HeuristicList - Unpacked a "+record_type+" record");
                }
            }
            catch (IOException e)
            {
                res = false;
            }

            while ((res) && (record_type != RecordType.NONE_RECORD))
            {
                AbstractRecord record = AbstractRecord.create(record_type);

                try
                {
                    res = (record.restore_state(os, ot) && heuristicList.insert(record));

                    record_type = os.unpackInt();

                    if (tsLogger.logger.isTraceEnabled()) {
                        tsLogger.logger.trace("HeuristicList - Unpacked a "+record_type+" record");
                    }
                }
                catch (IOException e)
                {
                    res = false;
                }
                catch (final NullPointerException ex) {
                    tsLogger.i18NLogger.warn_coordinator_norecordfound(Integer.toString(record_type));

                    res = false;
                }
            }
        }

        if (res)
        {
            try
            {
                tempActionStatus = os.unpackInt();
                tempActionType = os.unpackInt();
                tempHeuristicDecision = os.unpackInt();
            }
            catch (IOException e) {
                tsLogger.i18NLogger.warn_coordinator_BasicAction_24();

                res = false;
            }
        }

        if (res)
        {
            if (tsLogger.logger.isTraceEnabled()) {
                tsLogger.logger.trace("Restored action status of "+ActionStatus.stringForm(tempActionStatus)+
                        " "+Integer.toString(tempActionStatus));

                tsLogger.logger.trace("Restored action type "+
                        ((tempActionType == ActionType.NESTED) ? "Nested" : "Top-level")+
                        " "+Integer.toString(tempActionType));

                tsLogger.logger.trace(" Restored heuristic decision of "+
                        TwoPhaseOutcome.stringForm(tempHeuristicDecision)+" "+Integer.toString(tempHeuristicDecision));
            }

            actionStatus = tempActionStatus;
            actionType = tempActionType;
            heuristicDecision = tempHeuristicDecision;
            savedIntentionList = true;
        }

        return res;
    }

    /**
     * Overloads StateManager.type()
     */

    public String type ()
    {
        return "/StateManager/BasicAction";
    }

    /**
     * @return the thread's notion of the current transaction.
     */

    public static BasicAction Current ()
    {
        return ThreadActionData.currentAction();
    }

    /**
     * If heuristic outcomes are returned, by default we will not save the state
     * once the forget method has been called on them (which happens as soon as
     * we have received all outcomes from registered resources). By specifying
     * otherwise, we will always maintain the heuristic information, which may
     * prove useful for logging and off-line resolution.
     *
     * @return true if the transaction should save its heuristic
     *         information, false otherwise.
     */

    public static boolean maintainHeuristics ()
    {
        return TxControl.maintainHeuristics;
    }

    /**
     * Overloads StateManager.destroy to prevent destroy being
     * called on a BasicAction. Could be a *very* bad idea!!
     *
     * @return false.
     * @see com.arjuna.ats.arjuna.StateManager
     */

    public boolean destroy ()
    {
        return true;
    }

    /**
     * @return the list of child transactions. Currently only their ids are
     *         given.
     * @since JTS 2.2.
     */

    public final Object[] childTransactions ()
    {
        int size = ((_childActions == null) ? 0 : _childActions.size());

        if (size > 0)
        {
            Collection c = _childActions.values();

            return c.toArray();
        }

        return null;
    }
    
    /**
     * Get any Throwable that was caught during commit processing but not directly rethrown.
     * @return a list of ThrowableS, if any
     */
    public List getDeferredThrowables() 
    {
        return deferredThrowables;
    }
    
    @Override
    public boolean equals (java.lang.Object obj)
    {
        if (obj instanceof BasicAction)
        {
            if (((BasicAction) obj).get_uid().equals(get_uid()))
                return true;
        }

        return false;
    }

    @Override
    public int hashCode()
    {
        return get_uid().hashCode();
    }

    /**
     * Forget any heuristics we may have received, and tell the resources which
     * generated them to forget too.
     *
     * @return true if heuristic information (if any) was
     *         successfully forgotten, false otherwise.
     */

    protected boolean forgetHeuristics ()
    {
        if ((heuristicList != null) && (heuristicList.size() > 0))
        {
            doForget(heuristicList);
            updateState();

            if (heuristicList.size() == 0)
                return true;
            else
                return false;
        }
        else
            return true;
    }

    /**
     * Atomic action Begin operation. Does not change the calling thread's
     * notion of the current transaction.
     *
     * @return ActionStatus indicating outcome.
     */

    protected synchronized int Begin (BasicAction parentAct)
    {
        if (tsLogger.logger.isTraceEnabled()) {
            tsLogger.logger.trace("BasicAction::Begin() for action-id "
                    + get_uid());
        }
        
        // check to see if transaction system is enabled

        if (!TxControl.isEnabled()) {
            /*
                * Prevent transaction from making forward progress.
                */

            actionStatus = ActionStatus.ABORT_ONLY;

            tsLogger.i18NLogger.warn_coordinator_notrunning();
        }
        else
        {
            if (actionStatus != ActionStatus.CREATED) {
                tsLogger.i18NLogger.warn_coordinator_BasicAction_29(get_uid(), ActionStatus.stringForm(actionStatus));
            }
            else
            {
                actionInitialise(parentAct);
                actionStatus = ActionStatus.RUNNING;

                if ((actionType != ActionType.TOP_LEVEL)
                        && ((parentAct == null) || (parentAct.status() > ActionStatus.RUNNING)))
                {
                    actionStatus = ActionStatus.ABORT_ONLY;

                    if (parentAct == null) {
                        tsLogger.i18NLogger.warn_coordinator_BasicAction_30(get_uid());
                    }
                    else
                    {
                        tsLogger.i18NLogger.warn_coordinator_BasicAction_31(get_uid(), parentAct.get_uid(), Integer.toString(parentAct.status()));
                    }
                }

                ActionManager.manager().put(this);

                if(finalizeBasicActions) {
                    finalizerObject = new BasicActionFinalizer(this);
                }

                if (TxStats.enabled())
                {
                    TxStats.getInstance().incrementTransactions();

                    if (parentAct != null)
                        TxStats.getInstance().incrementNestedTransactions();
                }
            }
        }

        return actionStatus;
    }

    /**
     * End the atomic action by committing it. This invokes the prepare()
     * operation. If this succeeds then the pendingList should be empty and the
     * records that were formally on it will have been distributed between the
     * preparedList and the readonlyList, also if the action is topLevel then
     * the intention list will have been written to the object store. Then
     * invoke phase2Commit and clean up the object store if necessary
     *
     * If prepare() fails invoke phase2Abort. In this case the pendingList may
     * still contain records but phase2Abort takes care of these. Also in this
     * case no intention list has been written.
     *
     * Does not change the calling thread's notion of the current transaction.
     *
     * Any heuristic outcomes will only be reported if the parameter is
     * true.
     *
     * @return ActionStatus indicating outcome.
     */

    protected synchronized int End (boolean reportHeuristics)
    {
        if (tsLogger.logger.isTraceEnabled()) {
            tsLogger.logger.trace("BasicAction::End() for action-id "
                    + get_uid());
        }

        /* Check for superfluous invocation */

        if ((actionStatus != ActionStatus.RUNNING)
                && (actionStatus != ActionStatus.ABORT_ONLY)) {
            switch (actionStatus) {
                case ActionStatus.CREATED:
                    tsLogger.i18NLogger.warn_coordinator_BasicAction_33(get_uid());
                    break;
                case ActionStatus.COMMITTED:
                    tsLogger.i18NLogger.warn_coordinator_BasicAction_34(get_uid());
                    break;
                default:
                    tsLogger.i18NLogger.warn_coordinator_BasicAction_35(get_uid());
                    break;
            }

            return actionStatus;
        }

        /*
           * Check we are the current action. Abort parents if not true. Check we
           * have not children (threads or actions).
           */

        if (!checkIsCurrent() || checkChildren(true)
                || (actionStatus == ActionStatus.ABORT_ONLY))
        {
            return Abort();
        }

        if (pendingList != null)
        {
            /*
                * If we only have a single item on the prepare list then we can try
                * to commit in a single phase.
                */

            if (doOnePhase())
            {
                onePhaseCommit(reportHeuristics);

                ActionManager.manager().remove(get_uid());
            }
            else
            {
                if (prepare(reportHeuristics) == TwoPhaseOutcome.PREPARE_NOTOK) {
                    tsLogger.i18NLogger.warn_coordinator_BasicAction_36(get_uid());

                    if (heuristicDecision != TwoPhaseOutcome.PREPARE_OK) {
                        tsLogger.i18NLogger.warn_coordinator_BasicAction_37(TwoPhaseOutcome.stringForm(heuristicDecision));
                    }

                    tsLogger.i18NLogger.warn_coordinator_BasicAction_38();

                    if (!reportHeuristics && TxControl.asyncCommit
                            && (parentAction == null)) {
                        TwoPhaseCommitThreadPool.submitJob(new AsyncCommit(this, false));
                    } else
                        phase2Abort(reportHeuristics); /* first phase failed */
                }
                else
                {
                    if (!reportHeuristics && TxControl.asyncCommit
                            && (parentAction == null))
                    {
                        TwoPhaseCommitThreadPool.submitJob(new AsyncCommit(this, true));
                    }
                    else
                        phase2Commit(reportHeuristics); /* first phase succeeded */
                }
            }
        }
        else
        {
            ActionManager.manager().remove(get_uid());

            actionStatus = ActionStatus.COMMITTED;

            if (TxStats.enabled())
            {
                if (heuristicDecision != TwoPhaseOutcome.HEURISTIC_ROLLBACK)
                {
                    TxStats.getInstance().incrementCommittedTransactions();
                }
            }
        }

        if (tsLogger.logger.isTraceEnabled()) {
            tsLogger.logger.tracef("BasicAction::End() result for action-id (%s) is (%s) node id: (%s)",
                    get_uid(),
                    TwoPhaseOutcome.stringForm(heuristicDecision),
                    arjPropertyManager.getCoreEnvironmentBean().getNodeIdentifier());
        }

        boolean returnCurrentStatus = false;

        if (reportHeuristics || (!reportHeuristics && !TxControl.asyncCommit))
            returnCurrentStatus = true;

        if (returnCurrentStatus)
        {
            if (reportHeuristics)
            {
                switch (heuristicDecision)
                {
                    case TwoPhaseOutcome.PREPARE_OK:
                    case TwoPhaseOutcome.FINISH_OK:
                        break;
                    case TwoPhaseOutcome.HEURISTIC_ROLLBACK:
                        return ActionStatus.H_ROLLBACK;
                    case TwoPhaseOutcome.HEURISTIC_COMMIT:
                        return ActionStatus.H_COMMIT;
                    case TwoPhaseOutcome.HEURISTIC_MIXED:
                        return ActionStatus.H_MIXED;
                    case TwoPhaseOutcome.HEURISTIC_HAZARD:
                    default:
                        return ActionStatus.H_HAZARD;
                }
            }

            /*
                * If we have a heuristic decision then we only report it
                * if required. Otherwise we return committed as per OTS rules.
                */

            switch (actionStatus)
            {
                case ActionStatus.H_COMMIT:
                case ActionStatus.H_ROLLBACK:
                case ActionStatus.H_HAZARD:
                case ActionStatus.H_MIXED:
                    if (!reportHeuristics)
                        return ActionStatus.COMMITTED;
                default:
                    return actionStatus;
            }
        }
        else
            return ActionStatus.COMMITTING; // if asynchronous then fake it.
    }

    /**
     * This is the user callable abort operation. It is invoked prior to the
     * start of two-phase commit and hence only processes records in the
     * pendingList (the other lists should be empty).
     *
     * Does not change the calling thread's notion of the current transaction.
     *
     * @return ActionStatus indicating outcome.
     */

    protected synchronized int Abort ()
    {
        if (tsLogger.logger.isTraceEnabled()) {
            tsLogger.logger.trace("BasicAction::Abort() for action-id "
                    + get_uid());
        }

        /* Check for superfluous invocation */

        if ((actionStatus != ActionStatus.RUNNING)
                && (actionStatus != ActionStatus.ABORT_ONLY)
                && (actionStatus != ActionStatus.COMMITTING)) {
            switch (actionStatus) {
                case ActionStatus.CREATED:
                    tsLogger.i18NLogger.warn_coordinator_BasicAction_39(get_uid());
                    break;
                case ActionStatus.ABORTED:
                    tsLogger.i18NLogger.warn_coordinator_BasicAction_40(get_uid());
                    break;
                default:
                    tsLogger.i18NLogger.warn_coordinator_BasicAction_41(get_uid());
                    break;
            }

            return actionStatus;
        }

        /*
           * Check we are the current action. Abort parents if not true. Some
           * implementations may want to override this.
           */

        checkIsCurrent();

        /*
           * Check we have no children (threads or actions).
           */

        checkChildren(false);

        if (pendingList != null)
        {
            actionStatus = ActionStatus.ABORTING;

            while (pendingList.size() > 0)
                doAbort(pendingList, false); // turn off heuristics reporting

            /*
                * In case we get here because an End has failed. In this case we
                * still need to tell the heuristic resources to forget their
                * decision.
                */

            forgetHeuristics();
        }

        ActionManager.manager().remove(get_uid());

        actionStatus = ActionStatus.ABORTED;

        if (TxStats.enabled())
            TxStats.getInstance().incrementAbortedTransactions();

        return actionStatus;
    }

    /**
     * Create a transaction of the specified type.
     */

    protected BasicAction (int at)
    {
        super(ObjectType.NEITHER);

        pendingList = null;
        preparedList = null;
        readonlyList = null;
        failedList = null;
        heuristicList = null;

        currentHierarchy = null;
        transactionStore = null;
        savedIntentionList = false;

        actionStatus = ActionStatus.CREATED;
        actionType = at;
        parentAction = null;
        recordBeingHandled = null;

        heuristicDecision = TwoPhaseOutcome.PREPARE_OK;
		_checkedAction = arjPropertyManager
				.getCoordinatorEnvironmentBean().getCheckedActionFactory()
				.getCheckedAction(get_uid(), type());

        _childThreads = null;
        _childActions = null;
    }

    /**
     * Recreate the specified transaction. Used for crash recovery purposes.
     */

    protected BasicAction (Uid u, int at)
    {
        super(u, ObjectType.NEITHER);

        pendingList = null;
        preparedList = null;
        readonlyList = null;
        failedList = null;
        heuristicList = null;

        currentHierarchy = null;
        transactionStore = null;
        savedIntentionList = false;

        actionStatus = ActionStatus.CREATED;
        actionType = at;
        parentAction = null;
        recordBeingHandled = null;

        heuristicDecision = TwoPhaseOutcome.PREPARE_OK;
		_checkedAction = arjPropertyManager
				.getCoordinatorEnvironmentBean().getCheckedActionFactory()
				.getCheckedAction(get_uid(), type());

        _childThreads = null;
        _childActions = null;
    }

    /**
     * Defines the start of a critical region by setting the critical flag. If
     * the signal handler is called the class variable abortAndExit is set. The
     * value of this variable is checked in the corresponding operation to end
     * the critical region.
     */

    protected final void criticalStart ()
    {
        //	_lock.lock();
    }

    /**
     * Defines the end of a critical region by resetting the critical flag. If
     * the signal handler is called the class variable abortAndExit is set. The
     * value of this variable is checked when ending the critical region.
     */

    protected final void criticalEnd ()
    {
        //	_lock.unlock();
    }

    /**
     * Cleanup phase for actions. If an action is in the PREPARED state when a
     * terminate signal is delivered (ie the coordinator node has crashed) then
     * we need to cleanup. This is essentially the same as phase2Abort but we
     * call cleanup ops rather than abort ops and let the records take care of
     * appropriate cleanup.
     *
     * The pendingList is processed because it may not be empty - since
     * prepare() stops processing the list at the first PREPARE_NOTOK result.
     *
     * The read_only list is processed to ensure that actions are aborted
     * immediately and any servers killed at that point since they need not hang
     * around. This contrasts with commit where readonlyList entries are simply
     * merged with the parent list or discarded
     */

    protected final synchronized void phase2Cleanup ()
    {
        if (tsLogger.logger.isTraceEnabled()) {
            tsLogger.logger.trace("BasicAction::phase2Cleanup() for action-id "
                    + get_uid());
        }

        criticalStart();

        actionStatus = ActionStatus.CLEANUP;

        while ((preparedList != null) && (preparedList.size() > 0))
            doCleanup(preparedList);

        while ((readonlyList != null) && (readonlyList.size() > 0))
            doCleanup(readonlyList);

        while ((pendingList != null) && (pendingList.size() > 0))
            doCleanup(pendingList);

        criticalEnd();
    }

    /**
     * Second phase of the two-phase commit protocol for committing actions.
     * This operation first invokes the doCommit operation on the preparedList.
     * This ensures that the appropriate commit operation is performed on each
     * entry which is then either deleted (top_level) or merged into the
     * parent's pendingList.
     *
     * Processing of the readonlyList is different in that if the action is
     * top_level then all records in the readonlyList are deleted without
     * further processing. If nested the records must be merged. This is an
     * optimisation to avoid unnecessary processing.
     *
     * Note that at this point the pendingList SHOULD be empty due to the prior
     * invocation of prepare().
     * 
     * @throws Error JBTM-895 tests, byteman limitation
     */

    protected synchronized final void phase2Commit (boolean reportHeuristics) throws Error
    {
        if (tsLogger.logger.isTraceEnabled()) {
            tsLogger.logger.trace("BasicAction::phase2Commit() for action-id "
                    + get_uid());
        }

        if ((pendingList != null) && (pendingList.size() > 0)) {
            int size = ((pendingList == null) ? 0 : pendingList.size());

            tsLogger.i18NLogger.warn_coordinator_BasicAction_42(get_uid(), Integer.toString(size), pendingList.toString());

            phase2Abort(reportHeuristics);
        }
        else
        {
            criticalStart();

            actionStatus = ActionStatus.COMMITTING;

            /*
                * If we get a heuristic during commit then we continue to commit
                * since we may have already told some records to commit. We could
                * optimise this if the first record raises the heuristic by
                * aborting (or going with the heuristic decision).
                */

            doCommit(preparedList, reportHeuristics); /*
													   * process the
													   * preparedList
													   */

            /*
                * Now check any heuristic decision. If we received one then we may
                * have to raise HEURISTIC_MIXED since we will have committed some
                * resources, whereas others may have aborted.
                */

            if (heuristicDecision != TwoPhaseOutcome.PREPARE_OK)
            {
                /*
                     * Heuristic decision matched the actual outcome!
                     */

                if (heuristicDecision == TwoPhaseOutcome.HEURISTIC_COMMIT)
                    heuristicDecision = TwoPhaseOutcome.FINISH_OK;
            }

            /* The readonlyList requires special attention */

            if ((readonlyList != null) && (readonlyList.size() > 0))
            {
                if (!TxControl.readonlyOptimisation)
                {
                    if (readonlyList != null)
                        doCommit(readonlyList, reportHeuristics);
                }

                // now still process the list.

                while (((recordBeingHandled = readonlyList.getFront()) != null))
                {
                    if ((actionType == ActionType.NESTED)
                            && (recordBeingHandled.propagateOnCommit()))
                    {
                        merge(recordBeingHandled);
                    }
                    else
                    {
                        recordBeingHandled = null;
                    }
                }
            }

            forgetHeuristics();

            actionStatus = ActionStatus.COMMITTED;

            updateState();

            ActionManager.manager().remove(get_uid());

            criticalEnd();

            // ok count this as a commit unless we got a heuristic rollback in which case phase2Abort
            // will have been called and will already have counted it as an abort

            if (TxStats.enabled()) {
                if (heuristicDecision != TwoPhaseOutcome.HEURISTIC_ROLLBACK) {
                    TxStats.getInstance().incrementCommittedTransactions();
                }
            }

        }
    }

    /**
     * Second phase of the two phase commit protocol for aborting actions.
     * Actions are aborted by invoking the doAbort operation on the
     * preparedList, the readonlyList, and the pendingList.
     *
     * The pendingList is processed because it may not be empty - since
     * prepare() stops processing the list at the first PREPARE_NOTOK result.
     *
     * By default, records that responsed PREPARE_READONLY will not be contacted
     * during second-phase abort, just as they are not during second-phase
     * commit. This can be overridden at runtime using the READONLY_OPTIMISATION
     * variable.
     */

    protected synchronized final void phase2Abort (boolean reportHeuristics)
    {
        if (tsLogger.logger.isTraceEnabled()) {
            tsLogger.logger.trace("BasicAction::phase2Abort() for action-id "
                    + get_uid());
        }

        criticalStart();

        actionStatus = ActionStatus.ABORTING;

        if (preparedList != null)
            doAbort(preparedList, reportHeuristics);

        if (!TxControl.readonlyOptimisation)
        {
            if (readonlyList != null)
                doAbort(readonlyList, reportHeuristics);
        }

        if (pendingList != null)
            doAbort(pendingList, reportHeuristics);

        /*
           * Check heuristic decision, and try to make it match outcome.
           */

        if (heuristicDecision != TwoPhaseOutcome.PREPARE_OK)
        {
            if (heuristicDecision == TwoPhaseOutcome.HEURISTIC_ROLLBACK)
                heuristicDecision = TwoPhaseOutcome.FINISH_OK;
        }

        forgetHeuristics();

        actionStatus = abortStatus();

        updateState(); // we may end up saving more than the heuristic list
        // here!

        ActionManager.manager().remove(get_uid());

        criticalEnd();

        /*
           * To get to this stage we had to try to commit, which means that we're
           * rolling back because of a resource problem.
           */

        if (TxStats.enabled()) {
            TxStats.getInstance().incrementResourceRollbacks();
            TxStats.getInstance().incrementAbortedTransactions();
        }
    }

    protected int async_prepare(boolean reportHeuristics) {
        int p = TwoPhaseOutcome.PREPARE_OK;
        AbstractRecord lastRec = pendingList.getRear();
        Collection> tasks = new ArrayList>();

        while (pendingList.size() != 0)
            tasks.add(TwoPhaseCommitThreadPool.submitJob(new AsyncPrepare(
                    this, reportHeuristics, pendingList.getFront())));

        // prepare the last (or only) participant on the callers thread
        if (lastRec != null)
            p = doPrepare(reportHeuristics, lastRec);

        // get the results
        for (Future task : tasks) {
            try {
                int outcome = task.get();

                if (p == TwoPhaseOutcome.PREPARE_OK)
                    p = outcome;
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }

        return p;
    }

    /**
     * Phase one of a two phase commit protocol. This function returns the
     * ouctome of the prepare operation. If all goes well it will be PREPARE_OK,
     * if not PREPARE_NOTOK. The value PREPARE_READONLY may also be returned if
     * all the records indicate that they are readonly records. Such records do
     * not take part in the second phase commit processing.
     *
     * @return TwoPhaseOutcome indicating outcome.
     */

    protected synchronized final int prepare (boolean reportHeuristics)
    {
        if (tsLogger.logger.isTraceEnabled()) {
            tsLogger.logger.trace("BasicAction::prepare () for action-id "
                    + get_uid());
        }

        boolean commitAllowed = (actionStatus != ActionStatus.ABORT_ONLY);

        actionStatus = ActionStatus.PREPARING;

        /* If we cannot commit - say the prepare failed */

        if (!commitAllowed) {
            tsLogger.i18NLogger.warn_coordinator_BasicAction_43(get_uid());

            actionStatus = ActionStatus.PREPARED;

            return TwoPhaseOutcome.PREPARE_NOTOK;
        }

        /*
           * Make sure the object store is set up for a top-level atomic action.
           */

        if (actionType == ActionType.TOP_LEVEL)
        {
            if (getStore() == null)
            {
                actionStatus = ActionStatus.ABORT_ONLY;

                return TwoPhaseOutcome.PREPARE_NOTOK;
            }
        }

        criticalStart();

        createPreparedLists();

        /*
           * Here is the start of the hard work. Walk down the pendingList
           * invoking the appropriate prepare operation. If it succeeds put the
           * record on either the preparedList or the read_only list and continue
           * until the pendingList is exhausted.
           *
           * If prepare fails on any record stop processing immediately and put
           * the offending record back on the pendingList
           */

        int p = TwoPhaseOutcome.PREPARE_OK;

        /*
           * If asynchronous prepare, then spawn a separate thread to handle each
           * entry in the intentions list. Could have some configurable option to
           * allow more limited number of threads to divide up the intentions
           * list.
           */

        if ((actionType == ActionType.TOP_LEVEL) && (TxControl.asyncPrepare))
        {
            p = async_prepare(reportHeuristics);
        }
        else
        {
            // single threaded prepare

            // createPreparedLists will have ensured list exists, but it may be empty
            if(pendingList.size() > 0) {
                p = doPrepare(reportHeuristics);
            }
        }

        if ((p != TwoPhaseOutcome.PREPARE_OK)
                && (p != TwoPhaseOutcome.PREPARE_READONLY))
        {
            if ((actionType == ActionType.NESTED)
                    && ((preparedList.size() > 0) && (p == TwoPhaseOutcome.ONE_PHASE_ERROR)))
            {
                /*
                     * For the OTS we must merge those records told to commit with
                     * the parent, as the rollback invocation must come from that
                     * since they have already been told this transaction has
                     * committed!
                     */

                AbstractRecord tmpRec = preparedList.getFront();

                while (tmpRec != null)
                {
                    merge(tmpRec);
                    tmpRec = preparedList.getFront();
                }

                if (parentAction != null)
                    parentAction.preventCommit();
                else {
                    tsLogger.i18NLogger.warn_coordinator_BasicAction_44();
                }
            }

            criticalEnd();

            return TwoPhaseOutcome.PREPARE_NOTOK;
        }

        /*
           * Now work out whether there is any state to save. Since we should be
           * single threaded once again, there is no need to protect the lists
           * with a synchronization.
           */

        /*
           * Could do this as we traverse the lists above, but would need some
           * compound class for return values.
           */

        boolean stateToSave = false;
        RecordListIterator iter = new RecordListIterator(preparedList);

        /*
           * First check the prepared list.
           */

        while (((recordBeingHandled = iter.iterate()) != null))
        {
            if (!stateToSave)
                stateToSave = recordBeingHandled.doSave();

            if (stateToSave)
                break;
        }

        iter = null;

        if (!stateToSave)
        {
            iter = new RecordListIterator(heuristicList);

            /*
                * Now check the heuristic list.
                */

            while (((recordBeingHandled = heuristicList.getFront()) != null))
            {
                if (!stateToSave)
                    stateToSave = recordBeingHandled.doSave();

                if (stateToSave)
                    break;
            }

            iter = null;
        }

        /*
           * The actual state we want to write depends upon whether or not we are
           * in charge of the transaction outcome:
           *
           * (i) if we are a root transaction, or an interposed transaction which
           * received a commit_one_phase call, then we have complete control over
           * what the transaction outcome will be. So, we will always try to
           * commit, and can set the state to committing.
           *
           * (ii) if we are an interposed transaction and it receives a complete
           * two-phase protocol, then the root is in control. So, we set the state
           * to prepared.
           *
           * (iii) nested transactions never write state, so the state is set to
           * prepared anyway.
           */

        if (actionType == ActionType.TOP_LEVEL)
            actionStatus = preparedStatus();
        else
            actionStatus = ActionStatus.PREPARED;

        /*
           * If we are here then everything went okay so save the intention list
           * in the ObjectStore in case of a node crash providing that its not
           * empty
           */

        if ((actionType == ActionType.TOP_LEVEL) && (stateToSave)
                && ((preparedList.size() > 0) || (heuristicList.size() > 0)))
        {
            /* Only do this if we have some records worth saving! */

            Uid u = getSavingUid();
            String tn = type();
            OutputObjectState state = new OutputObjectState(u, tn);

            if (!save_state(state, ObjectType.ANDPERSISTENT)) {
                tsLogger.i18NLogger.warn_coordinator_BasicAction_45(get_uid());

                criticalEnd();

                return TwoPhaseOutcome.PREPARE_NOTOK;
            }

            if (state.notempty())
            {
                try
                {
                    if (!transactionStore.write_committed(u, tn, state)) {
                        tsLogger.i18NLogger.warn_coordinator_BasicAction_46(get_uid());

                        criticalEnd();

                        return TwoPhaseOutcome.PREPARE_NOTOK;
                    }
                    else
                        savedIntentionList = true;
                }
                catch (ObjectStoreException e)
                {
                    criticalEnd();

                    return TwoPhaseOutcome.PREPARE_NOTOK;
                }
            }
        }

        criticalEnd();

        if ((preparedList.size() == 0) && (readonlyList.size() >= 0))
            return TwoPhaseOutcome.PREPARE_READONLY;
        else
            return TwoPhaseOutcome.PREPARE_OK;
    }

    /**
     * There is only one record on the intentions list. Only called from
     * synchronized methods. Don't bother about creating separate threads here!
     */

    protected void onePhaseCommit (boolean reportHeuristics)
    {
        if (tsLogger.logger.isTraceEnabled()) {
            tsLogger.logger.trace("BasicAction::onePhaseCommit() for action-id "
                    + get_uid());
        }

        /* Are we forced to abort? */

        if (actionStatus == ActionStatus.ABORT_ONLY) {
            tsLogger.i18NLogger.warn_coordinator_BasicAction_43(get_uid());

            Abort();

            return;
        }

        actionStatus = ActionStatus.COMMITTING;

        criticalStart();

        if ((heuristicList == null) && reportHeuristics)
            heuristicList = new RecordList();

        if (failedList == null)
            failedList = new RecordList();

        /*
           * Since it is one-phase, the outcome from the record is the outcome of
           * the transaction. Therefore, we don't need to save much intermediary
           * transaction state - only heuristics in the case of interposition.
           */

        boolean stateToSave = false;

        recordBeingHandled = pendingList.getFront();

        int p = ((actionType == ActionType.TOP_LEVEL) ? recordBeingHandled.topLevelOnePhaseCommit()
                : recordBeingHandled.nestedOnePhaseCommit());

        if ((p == TwoPhaseOutcome.FINISH_OK)
                || (p == TwoPhaseOutcome.PREPARE_READONLY))
        {
            if ((actionType == ActionType.NESTED)
                    && recordBeingHandled.propagateOnCommit())
            {
                merge(recordBeingHandled);
            }
            else
            {
                recordBeingHandled = null;
            }

            actionStatus = ActionStatus.COMMITTED;
        }
        else
        {
            if ((p == TwoPhaseOutcome.FINISH_ERROR) || (p == TwoPhaseOutcome.ONE_PHASE_ERROR))
            {
                /*
                     * If ONE_PHASE_ERROR then the resource has rolled back. Otherwise we
                     * don't know and will ask recovery to keep trying. We differentiate
                     * this kind of failure from a heuristic failure so that we can allow
                     * recovery to retry the commit attempt periodically.
                     */
               
                if (p == TwoPhaseOutcome.ONE_PHASE_ERROR) {
                   addDeferredThrowables(recordBeingHandled, deferredThrowables);
                }

                if (p == TwoPhaseOutcome.FINISH_ERROR)
                {
                    /*
                              * We still add to the failed list because this may not mean
                              * that the transaction has aborted.
                              */

                    if (!failedList.insert(recordBeingHandled))
                        recordBeingHandled = null;
                    else
                    {
                        addDeferredThrowables(recordBeingHandled, deferredThrowables);
                        if (!stateToSave)
                            stateToSave = recordBeingHandled.doSave();
                    }

                    /*
                              * There's been a problem and we need to retry later. Assume
                              * transaction has committed until we have further information.
                              * This also ensures that recovery will kick in periodically.
                              */

                    actionStatus = ActionStatus.COMMITTED;
                }
                else
                    actionStatus = ActionStatus.ABORTED;
            }
            else {
                /*
                         * Heuristic decision!!
                         */

                tsLogger.i18NLogger.warn_coordinator_BasicAction_47(get_uid(), TwoPhaseOutcome.stringForm(p));

                if (reportHeuristics) {
                    updateHeuristic(p, true);

                    if (!heuristicList.insert(recordBeingHandled))
                        recordBeingHandled = null;
                    else {
                        addDeferredThrowables(recordBeingHandled, deferredThrowables);
                        if (!stateToSave)
                            stateToSave = recordBeingHandled.doSave();
                    }
                }

                if (heuristicDecision == TwoPhaseOutcome.HEURISTIC_ROLLBACK) {
                    /*
                              * Signal that the action outcome is the same as the
                              * heuristic decision.
                              */

                    heuristicDecision = TwoPhaseOutcome.PREPARE_OK; // means no
                    // heuristic
                    // was
                    // raised.

                    actionStatus = ActionStatus.ABORTED;
                } else if (heuristicDecision == TwoPhaseOutcome.HEURISTIC_COMMIT) {
                    heuristicDecision = TwoPhaseOutcome.PREPARE_OK;
                    actionStatus = ActionStatus.COMMITTED;
                } else
                    actionStatus = ActionStatus.H_HAZARD; // can't really say
                // (could have
                // aborted)
            }
        }

        if (actionType == ActionType.TOP_LEVEL)
        {
            if (stateToSave && ((heuristicList.size() > 0) || (failedList.size() > 0)))
            {
                if (getStore() == null)
                {
                    tsLogger.i18NLogger.fatal_coordinator_BasicAction_48();

                    throw new com.arjuna.ats.arjuna.exceptions.FatalError(
                            tsLogger.i18NLogger.get_coordinator_BasicAction_69()
                                    + get_uid());
                }

                updateState();
            }
        }

        forgetHeuristics();

        ActionManager.manager().remove(get_uid());

        criticalEnd();

        if (TxStats.enabled()) {
            if (actionStatus == ActionStatus.ABORTED) {
                TxStats.getInstance().incrementAbortedTransactions();
            } else {
                TxStats.getInstance().incrementCommittedTransactions();
            }
        }

    }

    /**
     * @return the current heuristic decision. Each time a heuristic outcome is
     *         received, we need to merge it with any previous outcome to
     *         determine what the overall heuristic decision is (e.g., a
     *         heuristic rollback followed by a heuristic commit means the
     *         overall decision is heuristic mixed.)
     */

    protected final synchronized int getHeuristicDecision ()
    {
        return heuristicDecision;
    }

    /**
     * WARNING: use with extreme care!
     */

    protected final synchronized void setHeuristicDecision (int p)
    {
        heuristicDecision = p;
    }

    /**
     * Add the specified abstract record to the transaction. Does not do any of
     * the runtime checking of BasicAction.add, so should be used with care.
     * Currently used by crash recovery.
     */

    protected final synchronized void addRecord (AbstractRecord A)
    {
        preparedList.insert(A);
    }

    /**
     * @return the transaction's prepared status.
     *
     * @since JTS 2.0.
     */

    protected int preparedStatus ()
    {
        if (actionType == ActionType.TOP_LEVEL)
            return ActionStatus.COMMITTING;
        else
            return ActionStatus.PREPARED;
    }

    protected int abortStatus ()
    {
        return ActionStatus.ABORTED;
    }

    protected int commitStatus ()
    {
        return ActionStatus.COMMITTED;
    }


    /*
      * The single-threaded version of doPrepare. If we do not use asynchronous
      * prepare, then we don't need to lock the RecordLists - only one thread can
      * access them anyway!
      */
    private int doPrepare (boolean reportHeuristics)
    {
        /*
           * Here is the start of the hard work. Walk down the pendingList
           * invoking the appropriate prepare operation. If it succeeds put the
           * record on either the preparedList or the read_only list and continue
           * until the pendingList is exhausted.
           *
           * If prepare fails on any record stop processing immediately and put
           * the offending record back on the pendingList.
           */

        int overallTwoPhaseOutcome = TwoPhaseOutcome.PREPARE_READONLY;

        /*
        * March down the pendingList and pass the head of the list to the
        * main work routine until either we run out of elements, or one of
        * them fails.
        */
        boolean keepGoing = true;
        while(pendingList.size() > 0 && keepGoing) {
            AbstractRecord record = pendingList.getFront();
            /*
            * If a failure occurs then the record will be put back on to
            * the pending list. Otherwise it is moved to another list or
            * dropped if readonly.
            */

            int individualTwoPhaseOutcome = doPrepare(reportHeuristics, record);

            if(individualTwoPhaseOutcome != TwoPhaseOutcome.PREPARE_READONLY) {
                overallTwoPhaseOutcome = individualTwoPhaseOutcome;
            }

            keepGoing = ( individualTwoPhaseOutcome == TwoPhaseOutcome.PREPARE_OK) || ( individualTwoPhaseOutcome == TwoPhaseOutcome.PREPARE_READONLY);
        }

        return overallTwoPhaseOutcome;
    }

    /*
      * The multi-threaded version of doPrepare. Each thread was given the record
      * it should process when it was created so that if a failure occurs we can
      * put it back onto the pendingList at the right place. It also cuts down on
      * the amount of synchronisation we must do.
      */

    protected int doPrepare (boolean reportHeuristics, AbstractRecord record)
    {
        /*
           * Here is the start of the hard work. Walk down the pendingList
           * invoking the appropriate prepare operation. If it succeeds put the
           * record on either the preparedList or the read_only list and continue
           * until the pendingList is exhausted.
           *
           * If prepare fails on any record stop processing immediately and put
           * the offending record back on the pendingList.
           */

        int p = TwoPhaseOutcome.PREPARE_NOTOK;

        p = ((actionType == ActionType.TOP_LEVEL) ? record.topLevelPrepare()
                : record.nestedPrepare());

        if (tsLogger.logger.isTraceEnabled()) {
            tsLogger.logger.tracef(
                    "BasicAction::doPrepare() result for action-id (%s) on record id: (%s) is (%s) node id: (%s)",
                    get_uid(), record.order(), TwoPhaseOutcome.stringForm(p),
                    arjPropertyManager.getCoreEnvironmentBean().getNodeIdentifier());
        }

        if (p == TwoPhaseOutcome.PREPARE_OK)
        {
            record = insertRecord(preparedList, record);
        }
        else
        {
            if (p == TwoPhaseOutcome.PREPARE_READONLY)
            {
                record = insertRecord(readonlyList, record);
            }
            else
            {
                if ((p == TwoPhaseOutcome.PREPARE_NOTOK)
                        || (p == TwoPhaseOutcome.ONE_PHASE_ERROR)
                        || (!reportHeuristics))
                {
                    /*
                              * If we are a subtransaction and this is an OTS
                              * resource then we may be in trouble: we may have
                              * already told other records to commit.
                              */

                    if (actionType == ActionType.NESTED)
                    {
                        if ((preparedList.size() > 0)
                                && (p == TwoPhaseOutcome.ONE_PHASE_ERROR)) {
                            tsLogger.i18NLogger.warn_coordinator_BasicAction_49(get_uid());

                            /*
                            * Force parent to rollback. If this is not the
                            * desired result then we may need to check some
                            * environment variable (either here or in the
                            * OTS) and act accordingly. If we check in the
                            * OTS then we need to return something other
                            * than PREPARE_NOTOK.
                            */

                            /*
                            * For the OTS we must merge those records told
                            * to commit with the parent, as the rollback
                            * invocation must come from that since they
                            * have already been told this transaction has
                            * committed!
                            *
                            * However, since we may be multi-threaded
                            * (asynchronous prepare) we don't do the
                            * merging yet. Wait until all threads have
                            * terminated and then do it.
                            *
                            * Therefore, can't force parent to rollback
                            * state at present, or merge will fail.
                            */
                        }
                    }

                    /*
                              * Prepare on this record failed - we are in trouble.
                              * Add the record back onto the pendingList and return.
                              */
                    addDeferredThrowables(record, deferredThrowables);

                    record = insertRecord(pendingList, record);

                    record = null;

                    actionStatus = ActionStatus.PREPARED;

                    return p;
                }
                else {
                    /*
                    * Heuristic decision!!
                    */

                    /*
                    * Only report if request to do so.
                    */

                    tsLogger.i18NLogger.warn_coordinator_BasicAction_50(get_uid(), TwoPhaseOutcome.stringForm(p));

                    if (reportHeuristics)
                        updateHeuristic(p, false);

                    /*
                    * Don't add to the prepared list. We process heuristics
                    * separately during phase 2. The processing of records
                    * will not be in the same order as during phase 1, but
                    * does this matter for heuristic decisions? If so, then
                    * we need to modify RecordList so that records can
                    * appear on multiple lists at the same time.
                    */

                    record = insertRecord(heuristicList, record);

                    /*
                    * If we have had a heuristic decision, then attempt to
                    * make the action outcome the same. If we have a
                    * conflict, then we will abort.
                    */

                    if (heuristicDecision != TwoPhaseOutcome.HEURISTIC_COMMIT) {
                        actionStatus = ActionStatus.PREPARED;

                        return TwoPhaseOutcome.PREPARE_NOTOK;
                    } else {
                        /*
                        * Heuristic commit, which is ok since we want to
                        * commit anyway! So, ignore it (but remember the
                        * resource so we can tell it to forget later.)
                        */
                    }
                }
            }
        }

        return p;
    }

    /**
     * Walk down a record list extracting records and calling the appropriate
     * commit function. Discard or merge records as appropriate
     */

    protected int doCommit (RecordList rl, boolean reportHeuristics)
    {
        if ((rl != null) && (rl.size() > 0))
        {
            AbstractRecord rec;

            while (((rec = rl.getFront()) != null))
            {
                int outcome = doCommit(reportHeuristics, rec);

                /*
                     * Check the outcome and if we have a heuristic rollback try to
                     * rollback everything else in the list *if* we have not already
                     * committed something. That way we make the outcome for all
                     * participants the same as the first (rollback) and don't get a
                     * heuristic!
                     */

                switch (outcome)
                {
                    case TwoPhaseOutcome.FINISH_OK:
                    case TwoPhaseOutcome.HEURISTIC_COMMIT:
                        pastFirstParticipant = true;
                        break;
                    case TwoPhaseOutcome.HEURISTIC_MIXED:
                    case TwoPhaseOutcome.HEURISTIC_HAZARD:
                    default:
                        /*
                           * Do nothing and continue to commit everything else. We've
                           * got this far as errors have caused problems, but we gain
                           * nothing by now rolling back some participants. This could
                           * cause further heuristics!
                           */

                        pastFirstParticipant = true;
                        break;
                    case TwoPhaseOutcome.HEURISTIC_ROLLBACK:
                    {
                        /*
                           * A heuristic decision of commit means that we have got
                           * past the first entry in the list. So there is no going
                           * back now!
                           */

                        if (pastFirstParticipant)
                            break;
                        else
                        {
                            /*
                                * Remember the heuristic decision so we can restore it
                                * after rolling back. Otherwise we can't return the
                                * right value from commit.
                                */

                            pastFirstParticipant = true;

                            int oldDecision = heuristicDecision;

                            phase2Abort(reportHeuristics);

                            heuristicDecision = oldDecision;
                        }
                    }
                    break;
                }
            }
        }

        return TwoPhaseOutcome.FINISH_OK;
    }

    protected int doCommit (boolean reportHeuristics, AbstractRecord record)
    {
        if (tsLogger.logger.isTraceEnabled()) {
            tsLogger.logger.trace("BasicAction::doCommit ("
                    + record + ")");
        }

        /*
           * To get heuristics right, as soon as we manage to commit the first
           * record we set the heuristic to HEURISTIC_COMMIT. Then, if any other
           * heuristics are raised we can manage the final outcome correctly.
           */

        int ok = TwoPhaseOutcome.FINISH_ERROR;

        recordBeingHandled = record;

        if (recordBeingHandled != null)
        {
            if (actionType == ActionType.TOP_LEVEL)
            {
                if ((ok = recordBeingHandled.topLevelCommit()) == TwoPhaseOutcome.FINISH_OK)
                {
                    /*
                          * Record successfully committed, we can delete it now.
                          */

                    recordBeingHandled = null;

                    updateHeuristic(TwoPhaseOutcome.FINISH_OK, true); // must
                    // remember
                    // that
                    // something
                    // has
                    // committed
                }
                else
                {
                    if (tsLogger.logger.isTraceEnabled()) {
                        tsLogger.logger.trace("BasicAction.doCommit for "+get_uid()+" received "+
                                TwoPhaseOutcome.stringForm(ok)+" from "+RecordType.typeToClass(recordBeingHandled.typeIs()));
                    }

                    if ((reportHeuristics)
                            && ((ok == TwoPhaseOutcome.HEURISTIC_ROLLBACK)
                            || (ok == TwoPhaseOutcome.HEURISTIC_COMMIT)
                            || (ok == TwoPhaseOutcome.HEURISTIC_MIXED) || (ok == TwoPhaseOutcome.HEURISTIC_HAZARD)))
                    {
                        updateHeuristic(ok, true);
                        heuristicList.insert(recordBeingHandled);
                        addDeferredThrowables(recordBeingHandled, deferredThrowables);
                    }
                    else
                    {
                        if (ok == TwoPhaseOutcome.NOT_PREPARED)
                        {
                            /*
                                    * If this is the first resource then rollback,
                                    * otherwise promote to HEURISTIC_HAZARD, but don't
                                    * add to heuristicList.
                                    */

                            updateHeuristic(TwoPhaseOutcome.HEURISTIC_HAZARD, true);
                        }
                        else
                        {
                            /*
                                    * The commit failed. Add this record to the failed
                                    * list to indicate this. Covers statuses like FAILED_ERROR.
                                    */


                            if ((ok == TwoPhaseOutcome.HEURISTIC_ROLLBACK)
                                    || (ok == TwoPhaseOutcome.HEURISTIC_COMMIT)
                                    || (ok == TwoPhaseOutcome.HEURISTIC_MIXED) || (ok == TwoPhaseOutcome.HEURISTIC_HAZARD))
                            {
                                updateHeuristic(ok, true);                               
                            }
                            
                            failedList.insert(recordBeingHandled);
                            addDeferredThrowables(recordBeingHandled, deferredThrowables);
                        }
                    }
                }
            }
            else
            {
                /*
                     * Thankfully nested actions cannot raise heuristics!
                     */

                ok = recordBeingHandled.nestedCommit();

                if (recordBeingHandled.propagateOnCommit())
                {
                    merge(recordBeingHandled);
                }
                else
                {
                    recordBeingHandled = null;
                }
            }

            if (ok != TwoPhaseOutcome.FINISH_OK)
            {
                /* Preserve error messages */
            }

            if (tsLogger.logger.isTraceEnabled()) {
                tsLogger.logger.tracef(
                        "BasicAction::doCommit() result for action-id (%s) on record id: (%s) is (%s) node id: (%s)",
                        get_uid(), record.order(), TwoPhaseOutcome.stringForm(ok),
                        arjPropertyManager.getCoreEnvironmentBean().getNodeIdentifier());
            }

        }

        return ok;
    }

    /*
      * Walk down a record list extracting records and calling the appropriate
      * abort function. Discard records when done.
      */

    protected int doAbort (RecordList list_toprocess, boolean reportHeuristics)
    {
        if ((list_toprocess != null) && (list_toprocess.size() > 0))
        {
            while ((recordBeingHandled = list_toprocess.getFront()) != null)
            {
                doAbort(reportHeuristics, recordBeingHandled);
            }
        }

        return TwoPhaseOutcome.FINISH_OK;
    }

    protected int doAbort (boolean reportHeuristics, AbstractRecord record)
    {
        if (tsLogger.logger.isTraceEnabled()) {
            tsLogger.logger.trace("BasicAction::doAbort ("
                    + record + ")");
        }

        int ok = TwoPhaseOutcome.FINISH_OK;

        recordBeingHandled = record;

        if (recordBeingHandled != null)
        {
            if (actionType == ActionType.TOP_LEVEL)
                ok = recordBeingHandled.topLevelAbort();
            else
                ok = recordBeingHandled.nestedAbort();

            if ((actionType != ActionType.TOP_LEVEL)
                    && (recordBeingHandled.propagateOnAbort()))
            {
                merge(recordBeingHandled);
            }
            else
            {
                if (ok == TwoPhaseOutcome.FINISH_OK)
                {
                    updateHeuristic(TwoPhaseOutcome.FINISH_OK, false); // remember
                    // that
                    // something
                    // aborted
                    // ok
                }
                else
                {
                    if ((reportHeuristics)
                            && ((ok == TwoPhaseOutcome.HEURISTIC_ROLLBACK)
                            || (ok == TwoPhaseOutcome.HEURISTIC_COMMIT)
                            || (ok == TwoPhaseOutcome.HEURISTIC_MIXED) || (ok == TwoPhaseOutcome.HEURISTIC_HAZARD))) {
                        if (actionType == ActionType.TOP_LEVEL)
                            tsLogger.i18NLogger.warn_coordinator_BasicAction_52(get_uid(), TwoPhaseOutcome.stringForm(ok));
                        else
                            tsLogger.i18NLogger.warn_coordinator_BasicAction_53(get_uid(), TwoPhaseOutcome.stringForm(ok));

                        updateHeuristic(ok, false);
                        heuristicList.insert(recordBeingHandled);
                        addDeferredThrowables(recordBeingHandled, deferredThrowables);
                    }
                    else
                    {
                        if (ok != TwoPhaseOutcome.FINISH_OK) {
                            if (actionType == ActionType.TOP_LEVEL)
                                tsLogger.i18NLogger.warn_coordinator_BasicAction_54(get_uid(),
                                        TwoPhaseOutcome.stringForm(ok),
                                        RecordType.typeToClass(recordBeingHandled.typeIs()).getCanonicalName());
                            else
                                tsLogger.i18NLogger.warn_coordinator_BasicAction_55(get_uid(),
                                        TwoPhaseOutcome.stringForm(ok),
                                        RecordType.typeToClass(recordBeingHandled.typeIs()).getCanonicalName());
                        }
                    }
                }

                /*
                     * Don't need a canDelete as in the C++ version since Java's
                     * garbage collection will deal with things for us.
                     */

                recordBeingHandled = null;
            }

            if (tsLogger.logger.isTraceEnabled()) {
                tsLogger.logger.tracef(
                        "BasicAction::doAbort() result for action-id (%s) on record id: (%s) is (%s) node id: (%s)",
                        get_uid(), record.order(), TwoPhaseOutcome.stringForm(ok),
                        arjPropertyManager.getCoreEnvironmentBean().getNodeIdentifier());
            }

        }

        return ok;
    }

    protected AbstractRecord insertRecord (RecordList reclist, AbstractRecord record)
    {
        boolean lock = TxControl.asyncPrepare;

        if (lock)
        {
            synchronized (reclist)
            {
                if (!reclist.insert(record))
                    record = null;
            }
        }
        else
        {
            if (!reclist.insert(record))
                record = null;
        }

        return record;
    }

    /**
     * Do we want to check that a transaction can only be terminated by a thread
     * that has it as its current transaction? The base class has this check
     * enabled (i.e., we check), but some implementations may wish to override
     * this.
     *
     * @return false to disable checking.
     */

    protected boolean checkForCurrent ()
    {
        return false;
    }

    /*
      * If we get a single heuristic then we will always rollback during prepare.
      *
      * Getting a heuristic during commit is slightly different, since some
      * resources may have already committed, changing the type of heuristic we
      * may need to throw. However, once we get to commit we know that it will be
      * the final outcome. So, as soon as a single resource commits successfully,
      * we can take it as a HEURISTIC_COMMIT. We will forget a HEURISTIC_COMMIT
      * outcome at the end anyway.
      */

    protected final synchronized void updateHeuristic (int p, boolean commit)
    {
        /*
           * Some resource has prepared/committed ok, so we need to remember this
           * in case we get a future heuristic.
           */

        if (p == TwoPhaseOutcome.FINISH_OK)
        {
            // only count the first heuristic.

            if (TxStats.enabled())
                TxStats.getInstance().incrementHeuristics();

            if (commit)
            {
                if (heuristicDecision == TwoPhaseOutcome.PREPARE_OK)
                    p = TwoPhaseOutcome.HEURISTIC_COMMIT;

                if (heuristicDecision == TwoPhaseOutcome.HEURISTIC_ROLLBACK)
                    heuristicDecision = TwoPhaseOutcome.HEURISTIC_MIXED;
            }
            else
            {
                if (heuristicDecision == TwoPhaseOutcome.PREPARE_OK)
                    p = TwoPhaseOutcome.HEURISTIC_ROLLBACK;

                if (heuristicDecision == TwoPhaseOutcome.HEURISTIC_COMMIT)
                    heuristicDecision = TwoPhaseOutcome.HEURISTIC_MIXED;
            }

            // leave HAZARD and MIXED alone
        }

        /*
           * Is this the first heuristic? Always give HEURISTIC_MIXED priority,
           * but if we have no heuristic and we get a HEURISTIC_HAZARD then go
           * with that until something better comes along!
           */

        /*
           * Have we already been given a conflicting heuristic? If so, raise the
           * decision to the next heuristic level.
           */

        switch (heuristicDecision)
        {
            case TwoPhaseOutcome.PREPARE_OK:
                if ((p != TwoPhaseOutcome.PREPARE_OK)
                        && (p != TwoPhaseOutcome.FINISH_OK)) // first heuristic
                    // outcome.
                    heuristicDecision = p;
                break;
            case TwoPhaseOutcome.HEURISTIC_COMMIT:
                if ((p == TwoPhaseOutcome.HEURISTIC_ROLLBACK)
                        || (p == TwoPhaseOutcome.HEURISTIC_MIXED))
                    heuristicDecision = TwoPhaseOutcome.HEURISTIC_MIXED;
                else
                {
                    if (p == TwoPhaseOutcome.HEURISTIC_HAZARD)
                        heuristicDecision = TwoPhaseOutcome.HEURISTIC_HAZARD;
                }
                break;
            case TwoPhaseOutcome.HEURISTIC_ROLLBACK:
                if ((p == TwoPhaseOutcome.HEURISTIC_COMMIT)
                        || (p == TwoPhaseOutcome.HEURISTIC_MIXED))
                    heuristicDecision = TwoPhaseOutcome.HEURISTIC_MIXED;
                else
                {
                    if (p == TwoPhaseOutcome.HEURISTIC_HAZARD)
                        heuristicDecision = TwoPhaseOutcome.HEURISTIC_HAZARD;
                }
                break;
            case TwoPhaseOutcome.HEURISTIC_HAZARD:
                if (p == TwoPhaseOutcome.HEURISTIC_MIXED)
                    heuristicDecision = TwoPhaseOutcome.HEURISTIC_MIXED;
                break;
            case TwoPhaseOutcome.HEURISTIC_MIXED:
                break;
            default:
                heuristicDecision = p; // anything!
                break;
        }
    }

    protected void updateState ()
    {
        if (tsLogger.logger.isTraceEnabled()) {
            tsLogger.logger.trace("BasicAction::updateState() for action-id "
                    + get_uid());
        }

        /*
           * If the action is topLevel then prepare() will have written the
           * intention_list to the object_store. If any of the phase2Commit
           * processing failed then records will exist on the failedList. If this
           * is the case then we need to re-write the BasicAction record in the
           * object store. If the failed list is empty we can simply delete the
           * BasicAction record.
           */

        if (actionType == ActionType.TOP_LEVEL)
        {
            /*
                * make sure the object store is set up for a top-level atomic
                * action.
                */

            getStore();

            /*
                * If we have failures then rewrite the intentions list. Otherwise,
                * delete the log entry. Depending upon how we get here the intentions
                * list will either be in the preparedList or the failedList. Fortunately
                * save_state will figure out which one to use.
                */

            if (((failedList != null) && (failedList.size() > 0))
                    || ((heuristicList != null) && (heuristicList.size() > 0))
                    || ((preparedList != null) && (preparedList.size() > 0)))
            {
                /*
                     * Re-write the BasicAction record with the failed list
                     */

                Uid u = getSavingUid();
                String tn = type();
                OutputObjectState state = new OutputObjectState(u, tn);

                if (!save_state(state, ObjectType.ANDPERSISTENT)) {
                    tsLogger.i18NLogger.warn_coordinator_BasicAction_64();

                    // what else?
                }

                if (state.notempty())
                {
                    try
                    {
                        if (!transactionStore.write_committed(u, tn, state)) {
                            tsLogger.i18NLogger.warn_coordinator_BasicAction_65();
                        }
                    }
                    catch (ObjectStoreException e)
                    {
                        tsLogger.logger.warn(e);
                    }
                }
            }
            else
            {
                try
                {
                    if (savedIntentionList)
                    {
                        if (transactionStore.remove_committed(getSavingUid(), type()))
                        {
                            savedIntentionList = false;
                        }
                    }
                }
                catch (ObjectStoreException e) {
                    tsLogger.i18NLogger.warn_coordinator_BasicAction_70(e);
                }
            }
        }
    }

    /*
      * This is only meant as an instance cut of the children, so don't lock the
      * entire transaction. Thus, the list may change before we return.
      */

    private final void createPreparedLists ()
    {
        if (preparedList == null)
            preparedList = new RecordList();

        if (readonlyList == null)
            readonlyList = new RecordList();

        if (failedList == null)
            failedList = new RecordList();

        if (heuristicList == null)
            heuristicList = new RecordList();

        if (pendingList == null)
            pendingList = new RecordList();
    }

    /**
     * Check to see if this transaction is the one that is current for this
     * thread. If it isn't, then we mark this transaction as rollback only.
     *
     * @return true if the transaction is current,
     *         false otherwise.
     */

    private final boolean checkIsCurrent ()
    {
        boolean isCurrent = true;

        if (checkForCurrent())
        {
            BasicAction currentAct = BasicAction.Current();

            /* Ensure I am the currently active action */

            if ((currentAct != null) && (currentAct != this)) {
                tsLogger.i18NLogger.warn_coordinator_BasicAction_56(currentAct.get_uid(), get_uid());

                isCurrent = false;

                if (currentAct.isAncestor(get_uid())) {
                    /* current action is one of my children */

                    BasicAction parentAct = parent();

                    /* prevent commit of my parents (ensures safety) */

                    while (parentAct != null) {
                        parentAct.preventCommit();
                        parentAct = parentAct.parent();
                    }
                }
            }

            currentAct = null;
        }

        return isCurrent;
    }

    private final boolean checkChildren (boolean isCommit)
    {
        boolean problem = false;

        /*
           * If we have child threads then by default we just print a warning and
           * continue. The other threads will eventually find out the outcome.
           */

        if ((_childThreads != null) && (_childThreads.size() > 0))
        {
            if ((_childThreads.size() != 1)
                    || ((_childThreads.size() == 1) && (!_childThreads.contains(Thread.currentThread())))) {
                /*
                     * More than one thread or the one thread is not the current
                     * thread
                     */

                if (isCommit) {
                    tsLogger.i18NLogger.warn_coordinator_BasicAction_57(get_uid());
                } else {
                    tsLogger.i18NLogger.warn_coordinator_BasicAction_58(get_uid());
                }

                if (_checkedAction != null)
                    _checkedAction.check(isCommit, get_uid(), _childThreads);

                removeAllChildThreads();
            }
        }

        /* Ensure I have no child actions */

        if ((_childActions != null) && (_childActions.size() > 0))
        {
            problem = true;

            Enumeration iter = _childActions.elements();
            BasicAction child = null;
            boolean printError = true;

            /*
                * We may have already aborted our children, e.g., because of an
                * out-of-sequence commit, so we check here to reduce the number of
                * error messages!
                *
                * We can't just remove the children when we are finished with them
                * because BasicAction is not responsible for action tracking.
                */

            while (iter.hasMoreElements())
            {
                child = iter.nextElement();

                if (child.status() != ActionStatus.ABORTED) {
                    if (printError) {
                        if (isCommit) {
                            tsLogger.i18NLogger.warn_coordinator_BasicAction_59(get_uid());
                        } else {
                            tsLogger.i18NLogger.warn_coordinator_BasicAction_60(get_uid());
                        }

                        printError = false;
                    }

                    tsLogger.i18NLogger.warn_coordinator_BasicAction_61(child.get_uid());

                    child.Abort();
                    child = null;
                }
            }

            iter = null;

            if (isCommit) {
                tsLogger.i18NLogger.warn_coordinator_BasicAction_62(((child != null ? child.get_uid().toString() : "null")));
            }
        }

        return problem;
    }

    /*
      * Just in case we are deleted/terminated with threads still registered. We
      * must make sure those threads don't try to remove themselves from this
      * action later. So we unregister them ourselves now.
      *
      * This is only called by End/Abort and so all child actions will have been
      * previously terminated as well.
      */

    private final void removeAllChildThreads ()
    {
        /*
           * Do not remove the current thread as it is committing/aborting!
           */

        criticalStart();

        if ((_childThreads != null) && (_childThreads.size() != 0))
        {
            Thread currentThread = Thread.currentThread();

            /*
                * Iterate through all registered threads and tell them to ignore
                * the action pointer, i.e., they are now no longer within this
                * action.
                */

            Enumeration iter = _childThreads.elements();
            Thread t = null;

            while (iter.hasMoreElements())
            {
                t = iter.nextElement();

                if (tsLogger.logger.isTraceEnabled()) {
                    tsLogger.logger.trace("BasicAction::removeAllChildThreads () action "+get_uid()+" removing "+t);
                }

                if (t != currentThread)
                    ThreadActionData.purgeAction(this, t);
            }
        }

        criticalEnd();
    }

    /**
     * actionInitialise determines whether the BasicAction is a nested,
     * top-level, or a top-level nested atomic action
     */

    private final void actionInitialise (BasicAction parent)
    {
        if (tsLogger.logger.isTraceEnabled()) {
            tsLogger.logger.trace("BasicAction::actionInitialise() for action-id "
                    + get_uid());
        }

        criticalStart();

        if (parent != null) /* ie not top_level */
        {
            if (tsLogger.logger.isTraceEnabled()) {
                tsLogger.logger.trace("Action "+get_uid()+" with parent status "+parent.actionStatus);
            }

            currentHierarchy = new ActionHierarchy(parent.getHierarchy());
        }
        else
        {
            currentHierarchy = new ActionHierarchy(
                    ActionHierarchy.DEFAULT_HIERARCHY_DEPTH);

            /*
                * This is a top-level atomic action so set the signal handler block
                * a number of signals.
                */
        }

        currentHierarchy.add(get_uid(), actionType);

        switch (actionType)
        {
            case ActionType.TOP_LEVEL:
                if (parent != null)
                {
                    /*
                      * do not want to print warning all the time as this is what
                      * nested top-level actions are used for.
                      */

                    if (tsLogger.logger.isTraceEnabled()) {
                        tsLogger.logger.trace("Running Top Level Action "+get_uid()+" from within " +
                                "nested action ("+parent.get_uid()+")");
                    }
                }
                break;
            case ActionType.NESTED:
                if (parent == null)
                    actionType = ActionType.TOP_LEVEL;
                break;
        }

        parentAction = parent;

        criticalEnd();
    }

    private final void doForget (RecordList list_toprocess)
    {
        if (tsLogger.logger.isTraceEnabled()) {
            tsLogger.logger.trace("BasicAction::doForget ("
                    + list_toprocess + ")");
        }

        /*
           * If the user has selected to maintain all heuristic information, then
           * we never explicitly tell resources to forget. We assume that the user
           * (or some management tool) will do this, and simply save as much
           * information as we can into the action state to allow them to do so.
           *
           * However, if we had a resource that returned a heuristic outcome and
           * we managed to make the outcome of this transaction the same as that
           * outcome, we removed the heuristic. So, we need to tell the resource
           * regardless, or it'll never be able to tidy up.
           */

        boolean force = (boolean) (heuristicDecision == TwoPhaseOutcome.FINISH_OK);

        if (!TxControl.maintainHeuristics || force)
        {
            if (list_toprocess.size() > 0)
            {
                RecordList tmpList = new RecordList();

                while (((recordBeingHandled = list_toprocess.getFront())) != null)
                {
                    /*
                          * Remember for later if we cannot tell it to forget.
                          */

                    if (recordBeingHandled.forgetHeuristic())
                        recordBeingHandled = null;
                    else
                        tmpList.putFront(recordBeingHandled);
                }

                /*
                     * Now put those resources we couldn't tell to forget back on
                     * the heuristic list.
                     */

                if (tmpList.size() > 0)
                {
                    while ((recordBeingHandled = tmpList.getFront()) != null)
                        list_toprocess.putFront(recordBeingHandled);
                }
            }
        }
    }

    /*
      * Walk down a record list extracting records and calling the appropriate
      * cleanup function. Discard records when done. NOTE: We only need to do
      * cleanup at top level since cleanup at nested level would be subsumed when
      * the parent action is forced to abort
      *
      * Ignore heuristics. Who can we report them to?
      *
      * This routine is called by phase2Cleanup, which gets called only in
      * exceptional circumstances. By default we leave cleaning up the various
      * lists until the action instance goes out of scope.
      */

    private final void doCleanup (RecordList list_toprocess)
    {
        if (tsLogger.logger.isTraceEnabled()) {
            tsLogger.logger.trace("BasicAction::doCleanup ("
                    + list_toprocess + ")");
        }

        if (list_toprocess.size() > 0)
        {
            int ok = TwoPhaseOutcome.FINISH_OK;

            while (((recordBeingHandled = list_toprocess.getFront()) != null))
            {
                if (actionType == ActionType.TOP_LEVEL)
                    ok = recordBeingHandled.topLevelCleanup();
                else
                    ok = recordBeingHandled.nestedCleanup();

                if ((actionType != ActionType.TOP_LEVEL)
                        && (recordBeingHandled.propagateOnAbort()))
                {
                    merge(recordBeingHandled);
                }
                else
                {
                    if (ok != TwoPhaseOutcome.FINISH_OK)
                    {
                        /* Preserve error messages */
                    }

                    recordBeingHandled = null;
                }
            }
        }
    }

    private final synchronized boolean doOnePhase ()
    {
        if (TxControl.onePhase)
        {
            return (((pendingList == null) || (pendingList.size() == 1)) ? true
                    : false);
        }
        else
            return false;
    }

    /*
      * Operation to merge a record into those held by the parent BasicAction.
      * This is accomplished by invoking the add operation of the parent
      * BasicAction. If the add operation does not return AR_ADDED, the record is
      * deleted
      */

    private final synchronized void merge (AbstractRecord A)
    {
        int as;

        if ((as = parentAction.add(A)) != AddOutcome.AR_ADDED)
        {
            A = null;

            if (as == AddOutcome.AR_REJECTED)
                tsLogger.i18NLogger.warn_coordinator_BasicAction_68();
        }
    }
    
    /* Adds the deferred throwables of the given record to the given list of throwables. */
    
    private void addDeferredThrowables(AbstractRecord record, List throwables) 
    {
        if (record instanceof ExceptionDeferrer)
            ((ExceptionDeferrer) record).getDeferredThrowables(throwables);
        else if (record.value() instanceof ExceptionDeferrer)
            ((ExceptionDeferrer) record.value()).getDeferredThrowables(throwables);
    }

    /* These (genuine) lists hold the abstract records */

    protected RecordList pendingList;
    protected RecordList preparedList;
    protected RecordList readonlyList;
    protected RecordList failedList;
    protected RecordList heuristicList;
    protected boolean savedIntentionList;

    private ActionHierarchy currentHierarchy;
    private ParticipantStore transactionStore;  // a ParticipantStore is also a TxLog

    //    private boolean savedIntentionList;

    /* Atomic action status variables */

    private int actionStatus;
    private int actionType;
    private BasicAction parentAction;
    private AbstractRecord recordBeingHandled;
    private int heuristicDecision;
    private CheckedAction _checkedAction; // control what happens if threads active when terminating.
    private boolean pastFirstParticipant;  // remember where we are (were) in committing during recovery

    /*
      * We need to keep track of the number of threads associated with each
      * action. Since we can't override the basic thread methods, we have to
      * provide an explicit means of registering threads with an action.
      */

    private Hashtable _childThreads;
    private Hashtable _childActions;

    private BasicActionFinalizer finalizerObject;
    private static final boolean finalizeBasicActions = arjPropertyManager.getCoordinatorEnvironmentBean().isFinalizeBasicActions();

    //    private Mutex _lock = new Mutex(); // TODO

    private List deferredThrowables = new ArrayList();
}

class BasicActionFinalizer
{
    private final BasicAction basicAction;

    BasicActionFinalizer(BasicAction basicAction)
    {
        this.basicAction = basicAction;
    }

    @Override
    protected void finalize() throws Throwable
    {
        basicAction.finalizeInternal();
    }
}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy