Package com.arjuna.ats.arjuna.coordinator

Source Code of com.arjuna.ats.arjuna.coordinator.BasicAction

/*
* 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 com.arjuna.ats.arjuna.logging.tsLogger;
import com.arjuna.ats.arjuna.logging.FacilityCode;

import com.arjuna.ats.arjuna.*;
import com.arjuna.ats.arjuna.common.*;
import com.arjuna.ats.arjuna.state.*;
import com.arjuna.ats.arjuna.objectstore.ObjectStore;
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.coordinator.CheckedActionFactoryImple;
import com.arjuna.ats.internal.arjuna.thread.*;
import java.util.*;

import java.io.IOException;

import com.arjuna.common.util.logging.*;

import com.arjuna.ats.arjuna.exceptions.FatalError;
import com.arjuna.ats.arjuna.exceptions.ObjectStoreException;

/**
* 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 (mark@arjuna.com)
* @version $Id: BasicAction.java 2342 2006-03-30 13:06:17Z  $
* @since JTS 1.0.
*
*
*
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_1
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_1] - Action nesting
*          error - deletion of action id {0} invoked while child actions active
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_2
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_2] - Aborting child
*          {0}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_3
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_3] - Destructor of
*          still running action id {0} invoked - Aborting
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_5
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_5] - Activate of
*          atomic action with id {0} and type {1} unexpectedly failed
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_6
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_6] -
*          BasicAction::addChildThread () action {0} adding {1}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_7
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_7] -
*          BasicAction::addChildThread () action {0} adding {1} result = {2}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_8
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_8] -
*          BasicAction::removeChildThread () action {0} removing {1}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_9
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_9] - \
*          BasicAction::removeChildThread () action {0} removing {1} result =
*          {2}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_10
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_10] -
*          BasicAction::addAction () action {0} adding {1}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_11
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_11] -
*          BasicAction::addChildAction () action {0} adding {1} result = {2}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_12
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_12] -
*          BasicAction::removeChildAction () action {0} removing {1}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_13
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_13] -
*          BasicAction::removeChildAction () action {0} removing {1} result =
*          {2}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_14
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_14] -
*          BasicAction::save_state - next record to pack is a \n {0} record
*          ({1}) should save it? = {2}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_15
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_15] - Packing a {0}
*          record
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_16
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_16] - Packing a
*          NONE_RECORD
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_17
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_17] - HeuristicList -
*          packing a {0} record
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_18
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_18] - HeuristicList -
*          packing a NONE_RECORD
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_19
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_19] - Packing action
*          status of {0}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_20
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_20] - Unpacked a {0}
*          record
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_21
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_21] -
*          BasicAction.restore_state - could not recover {0}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_22
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_22] - HeuristicList -
*          Unpacked heuristic list size of {0}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_23
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_23] - HeuristicList -
*          Unpacked a {0} record
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_24
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_24] -
*          BasicAction.restore_state - error unpacking action status.
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_25
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_25] - Restored action
*          status of {0} {1}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_26
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_26] - Restored action
*          type {0} {1}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_27
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_27] - Restored
*          heuristic decision of {0} {1}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_28
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_28] -
*          BasicAction.destroy called on {0}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_29
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_29] -
*          BasicAction.Begin of action {0} ignored - incorrect invocation
*          sequence {1}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_30
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_30] -
*          BasicAction.Begin of action {0} ignored - no parent and set as
*          nested action!
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_31
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_31] -
*          BasicAction.Begin of action {0} ignored - parent action {1} is not
*          running: {2}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_32
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_32] - The Arjuna
*          licence does not permit any further transactions to be committed!
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_33
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_33] - End called on
*          non-running atomic action {0}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_34
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_34] - End called on
*          already committed atomic action {0}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_35
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_35] - End called
*          illegally on atomic action {0}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_36
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_36] -
*          BasicAction.End() - prepare phase of action-id {0} failed.
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_37
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_37] - Received
*          heuristic: {0} .
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_38
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_38] - Action Aborting
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_39
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_39] - Abort called on
*          non-running atomic action {0}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_40
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_40] - Abort called on
*          already aborted atomic action {0}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_41
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_41] - Abort called
*          illegaly on atomic action {0}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_42
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_42] - BasicAction {0} -
*          non-empty ( {1} ) pendingList {2}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_43
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_43] - Transaction {0}
*          marked as rollback only. Will abort.
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_44
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_44] - Cannot force
*          parent to rollback - no handle!
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_45
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_45] -
*          BasicAction::prepare - creating intentions list failed for {0}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_46
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_46] -
*          BasicAction::prepare - intentions list write failed for {0}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_47
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_47] - One-phase
*          commit of action {0} received heuristic decision: {1}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_48
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_48] -
*          BasicAction.onePhaseCommit failed - no object store for atomic
*          action state!
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_49
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_49] - Prepare phase
*          of nested action {0} received inconsistent outcomes.
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_50
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_50] - Prepare phase
*          of action {0} received heuristic decision: {1}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_51
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_51] -
*          BasicAction.doCommit for {0} received {1} from {2}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_52
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_52] - Top-level abort
*          of action {0} received heuristic decision: {1}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_53
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_53] - Nested abort of
*          action {0} received heuristic decision: {1}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_54
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_54] - Top-level abort
*          of action {0} received {1} from {2}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_55
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_55] - Nested abort of
*          action {0} received {1} from {2}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_56
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_56] -
*          BasicAction.checkIsCurrent {0} - terminating non-current
*          transaction: {1}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_57
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_57] - Commit of
*          action id {0} invoked while multiple threads active within it.
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_58
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_58] - Abort of action
*          id {0} invoked while multiple threads active within it.
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_59
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_59] - Commit of
*          action id {0} invoked while child actions active
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_60
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_60] - Abort of action
*          id {0} invoked while child actions active
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_61
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_61] - \tAborting
*          child: {0}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_62
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_62] - \tNow aborting
*          self: {0}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_63
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_63] -
*          BasicAction::removeAllChildThreads () action {0} removing {1}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_64
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_64] -
*          BasicAction.updateState - Could not create ObjectState for
*          failedList
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_65
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_65] - BasicAction.End -
*          Could not write failed list
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_66
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_66] - Action {0} with
*          parent status {1}
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_67
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_67] - Running Top
*          Level Action {0} from within nested action ({1})
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_68
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_68] - (Internal)
*          BasicAction.merge - record rejected
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_69
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_69] - No object store
*          for:
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_70
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_70] - Could not
*          remove intentions list:
* @message com.arjuna.ats.arjuna.coordinator.BasicAction_71
*          [com.arjuna.ats.arjuna.coordinator.BasicAction_71] - Deactivation of
*          atomic action with id {0} and type {1} unexpectedly failed
* @message com.arjuna.ats.arjuna.coordinator.checkedactionfactory
*          [com.arjuna.ats.arjuna.coordinator.checkedactionfactory] - Failed to resolve
*          CheckedActionFactory class {0}
* @message com.arjuna.ats.arjuna.coordinator.cafactoryerror
*          [com.arjuna.ats.arjuna.coordinator.cafactoryerror] - Cannot continue due to
*          CheckedActionFactory resolution problem with
* @message com.arjuna.ats.arjuna.coordinator.notrunning
*          [com.arjuna.ats.arjuna.coordinator.notrunning] - Cannot begin new transaction as TM is disabled. Marking as rollback-only.
*/

public class BasicAction extends StateManager
{

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

    if (tsLogger.arjLogger.isDebugEnabled())
    {
      tsLogger.arjLogger.debug(DebugLevel.CONSTRUCTORS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "BasicAction::BasicAction()");
    }

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

    currentHierarchy = null;
    currentStore = null;
    savedIntentionList = false;

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

    parentAction = null;
    recordBeingHandled = null;

    heuristicDecision = TwoPhaseOutcome.PREPARE_OK;
    _checkedAction = _checkedActionFactory.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.arjLogger.isDebugEnabled())
    {
      tsLogger.arjLogger.debug(DebugLevel.CONSTRUCTORS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "BasicAction::BasicAction("
          + objUid + ")");
    }

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

    currentHierarchy = null;
    currentStore = null;
    savedIntentionList = false;

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

    parentAction = null;
    recordBeingHandled = null;

    heuristicDecision = TwoPhaseOutcome.PREPARE_OK;
    _checkedAction = _checkedActionFactory.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 finalize ()
  {
    if (tsLogger.arjLogger.isDebugEnabled())
    {
      tsLogger.arjLogger.debug(DebugLevel.DESTRUCTORS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "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()))
        {
          if (tsLogger.arjLoggerI18N.isWarnEnabled())
          {
            tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_1", new Object[]
            { get_uid() });
          }

          while ((currentAct != this) && (currentAct != null))
          {
            if (tsLogger.arjLoggerI18N.isWarnEnabled())
            {
              tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_2", new Object[]
              { currentAct.get_uid() });
            }

            currentAct.Abort();

            currentAct = BasicAction.Current();
          }
        }
      }

      BasicAction parentAct = parent();

      /* prevent commit of parents (safety) */

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

      if (tsLogger.arjLoggerI18N.isWarnEnabled())
      {
        tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_3", new Object[]
        { 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;

    currentStore = 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 <code>true</code> if successful, <code>false</code>
   *         otherwise.
   */

  public final boolean preventCommit ()
  {
    if (tsLogger.arjLogger.isDebugEnabled())
    {
      tsLogger.arjLogger.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "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 <code>AddOutcome</code> 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 <code>true</code> if the parameter represents an ancestor,
   *         <code>false</code> 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 currentStore variable.
   *
   * @return the object store implementation to use.
   * @see com.arjuna.ats.arjuna.objectstore.ObjectStore
   */

  public ObjectStore getStore ()
  {
    if (currentStore == null)
    {
      currentStore = TxControl.getStore();
    }

    return currentStore;
  }

  public final ObjectStore store ()
  {
    return getStore();
  }

  /**
   * 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 <code>Uid</code>.
   */

  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 <code>null</code>.
   *
   * @return <code>true</code> if successful, <code>false</code>
   *         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 <code>root</code> parameter.
   *
   * @return <code>true</code> if successful, <code>false</code>
   *         otherwise.
   */

  public boolean activate (String root)
  {
    if (tsLogger.arjLogger.isDebugEnabled())
    {
      tsLogger.arjLogger.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "BasicAction::activate() for action-id "
          + get_uid());
    }

    boolean restored = false;

    // Set up store
    ObjectStore aaStore = store();

    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
      {
        if (tsLogger.arjLoggerI18N.isWarnEnabled())
        {
          tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_5", new Object[]
          { get_uid(), type() });
        }

        restored = false;
      }

      return restored;
    }
    catch (ObjectStoreException e)
    {
      if (tsLogger.arjLogger.isWarnEnabled())
        tsLogger.arjLogger.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 <code>null</code>. It is assumed that
   * this is being called during a transaction commit.
   *
   * @return <code>true</code> on success, <code>false</code> otherwise.
   */

  public boolean deactivate ()
  {
    if (tsLogger.arjLogger.isDebugEnabled())
    {
      tsLogger.arjLogger.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "BasicAction::deactivate() for action-id "
          + get_uid());
    }

    boolean deactivated = false;

    // Set up store
    ObjectStore aaStore = store();

    if (aaStore == null)
      return false;

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

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

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

      /** If we failed to deactivate then output warning * */
      if (!deactivated)
      {
        if (tsLogger.arjLoggerI18N.isWarnEnabled())
        {
          tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_5", new Object[]
          { get_uid(), type() });
        }
      }
    }
    catch (ObjectStoreException e)
    {
      if (tsLogger.arjLogger.isWarnEnabled())
        tsLogger.arjLogger.warn(e);

      deactivated = false;
    }

    return deactivated;
  }

  /**
   * Add the current thread to the list of threads associated with this
   * transaction.
   *
   * @return <code>true</code> if successful, <code>false</code>
   *         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 <code>true</code> if successful, <code>false</code>
   *         otherwise.
   */

  public final boolean addChildThread (Thread t)
  {
    if (tsLogger.arjLoggerI18N.isDebugEnabled())
    {
      tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "com.arjuna.ats.arjuna.coordinator.BasicAction_6", new Object[]
      { get_uid(), 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.arjLoggerI18N.isDebugEnabled())
    {
      tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "com.arjuna.ats.arjuna.coordinator.BasicAction_7", new Object[]
      { get_uid(), t, new Boolean(result) });
    }

    return result;
  }

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

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

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

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

  public final boolean removeChildThread (String threadId)
  {
    if (tsLogger.arjLoggerI18N.isDebugEnabled())
    {
      tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "com.arjuna.ats.arjuna.coordinator.BasicAction_8", new Object[]
      { get_uid(), threadId });
    }

    if (threadId == null)
      return false;

    boolean result = false;

    criticalStart();

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

    criticalEnd();

    if (tsLogger.arjLoggerI18N.isDebugEnabled())
    {
      if (result)
        tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "com.arjuna.ats.arjuna.coordinator.BasicAction_9", new Object[]
        { get_uid(), threadId, "true" });
      else
        tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "com.arjuna.ats.arjuna.coordinator.BasicAction_9", new Object[]
        { get_uid(), threadId, "false" });
    }

    return result;
  }

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

  public final boolean addChildAction (BasicAction act)
  {
    if (tsLogger.arjLoggerI18N.isDebugEnabled())
    {
      tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "com.arjuna.ats.arjuna.coordinator.BasicAction_10", new Object[]
      { get_uid(), ((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.arjLoggerI18N.isDebugEnabled())
    {
      if (result)
        tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "com.arjuna.ats.arjuna.coordinator.BasicAction_11", new Object[]
        { get_uid(), act.get_uid(), "true" });
      else
        tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "com.arjuna.ats.arjuna.coordinator.BasicAction_11", new Object[]
        { get_uid(), act.get_uid(), "false" });
    }

    return result;
  }

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

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

  public final boolean removeChildAction (BasicAction act)
  {
    if (tsLogger.arjLoggerI18N.isDebugEnabled())
    {
      tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "com.arjuna.ats.arjuna.coordinator.BasicAction_12", new Object[]
      { get_uid(), ((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.arjLoggerI18N.isDebugEnabled())
    {
      if (result)
        tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "com.arjuna.ats.arjuna.coordinator.BasicAction_13", new Object[]
        { get_uid(), act.get_uid(), "true" });
      else
        tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "com.arjuna.ats.arjuna.coordinator.BasicAction_13", new Object[]
        { get_uid(), act.get_uid(), "false" });
    }

    return result;
  }

  /**
   * Add the specified CheckedAction object to this transaction.
   *
   * @return the previous <code>CheckedAction</code>.
   * @see com.arjuna.ats.arjuna.coordinator.CheckedAction
   */

  public final synchronized CheckedAction setCheckedAction (CheckedAction c)
  {
    criticalStart();

    CheckedAction toReturn = _checkedAction;

    _checkedAction = c;

    criticalEnd();

    return toReturn;
  }

  /**
   * @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));
  }

  /**
   * 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 <code>true</code> if successful, <code>false</code>
   *         otherwise.
   */

  public boolean save_state (OutputObjectState os, int ot)
  {
    if (tsLogger.arjLogger.isDebugEnabled())
    {
      tsLogger.arjLogger.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "BasicAction::save_state ()");
    }

    try
    {
      packHeader(os, new Header(get_uid(), Utility.getProcessUid()));
    }
    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.arjLoggerI18N.isDebugEnabled())
      {
        if (temp.doSave())
          tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "com.arjuna.ats.arjuna.coordinator.BasicAction_14", new Object[]
          { Integer.toString(temp.typeIs()), temp.type(), "true" });
        else
          tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "com.arjuna.ats.arjuna.coordinator.BasicAction_14", new Object[]
          { Integer.toString(temp.typeIs()), temp.type(), "false" });
      }

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

        try
        {
          if (tsLogger.arjLoggerI18N.isDebugEnabled())
          {
            tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "com.arjuna.ats.arjuna.coordinator.BasicAction_15", new Object[]
            { Integer.toString(temp.typeIs()) });
          }

          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.arjLoggerI18N.isDebugEnabled())
        {
          tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "com.arjuna.ats.arjuna.coordinator.BasicAction_16");
        }

        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.arjLoggerI18N.isDebugEnabled())
              {
                tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "com.arjuna.ats.arjuna.coordinator.BasicAction_17", new Object[]
                { Integer.toString(temp.typeIs()) });
              }

              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.arjLoggerI18N.isDebugEnabled())
            {
              tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "com.arjuna.ats.arjuna.coordinator.BasicAction_18");
            }

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

    if (res && os.notempty())
    {
      try
      {
        if (tsLogger.arjLoggerI18N.isDebugEnabled())
        {
          tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "com.arjuna.ats.arjuna.coordinator.BasicAction_19", new Object[]
          { 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;
  }

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

  public boolean restore_state (InputObjectState os, int ot)
  {
    if (tsLogger.arjLogger.isDebugEnabled())
    {
      tsLogger.arjLogger.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "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);
    }
    catch (IOException e)
    {
      return false;
    }

    try
    {
      record_type = os.unpackInt();

      if (tsLogger.arjLoggerI18N.isDebugEnabled())
      {
        tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "com.arjuna.ats.arjuna.coordinator.BasicAction_20", new Object[]
        { Integer.toString(record_type) });
      }
    }
    catch (IOException e)
    {
      res = false;
    }

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

      if (record == null)
      {
        if (tsLogger.arjLoggerI18N.isWarnEnabled())
        {
          tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_21", new Object[]
          { 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.arjLoggerI18N.isDebugEnabled())
          {
            tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "com.arjuna.ats.arjuna.coordinator.BasicAction_20", new Object[]
            { Integer.toString(record_type) });
          }
        }
        catch (IOException e)
        {
          res = false;
        }
      }
    }

    // Now deal with the heuristic list!

    int hSize = 0;

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

        if (tsLogger.arjLoggerI18N.isDebugEnabled())
        {
          tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "com.arjuna.ats.arjuna.coordinator.BasicAction_22", new Object[]
          { Integer.toString(hSize) });
        }
      }
      catch (IOException e)
      {
        res = false;
      }
    }

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

        if (tsLogger.arjLoggerI18N.isDebugEnabled())
        {
          tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "com.arjuna.ats.arjuna.coordinator.BasicAction_23", new Object[]
          { Integer.toString(record_type) });
        }
      }
      catch (IOException e)
      {
        res = false;
      }

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

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

        try
        {
          record_type = os.unpackInt();

          if (tsLogger.arjLoggerI18N.isDebugEnabled())
          {
            tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "com.arjuna.ats.arjuna.coordinator.BasicAction_23", new Object[]
            { Integer.toString(record_type) });
          }
        }
        catch (IOException e)
        {
          res = false;
        }
      }
    }

    if (res)
    {
      try
      {
        tempActionStatus = os.unpackInt();
        tempActionType = os.unpackInt();
        tempHeuristicDecision = os.unpackInt();
      }
      catch (IOException e)
      {
        if (tsLogger.arjLoggerI18N.isWarnEnabled())
          tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_24");

        res = false;
      }
    }

    if (res)
    {
      if (tsLogger.arjLoggerI18N.isDebugEnabled())
      {
        tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "com.arjuna.ats.arjuna.coordinator.BasicAction_25", new Object[]
        { ActionStatus.stringForm(tempActionStatus), Integer.toString(tempActionStatus) });

        tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "com.arjuna.ats.arjuna.coordinator.BasicAction_26", new Object[]
        { ((tempActionType == ActionType.NESTED) ? "Nested"
            : "Top-level"), Integer.toString(tempActionType) });

        tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_ATOMIC_ACTION, "com.arjuna.ats.arjuna.coordinator.BasicAction_27", new Object[]
        { 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 <code>true</code> if the transaction should save its heuristic
   *         information, <code>false</code> otherwise.
   */

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

  /**
   * Overloads <code>StateManager.destroy</code> to prevent destroy being
   * called on a BasicAction. Could be a *very* bad idea!!
   *
   * @return <code>false</code>.
   * @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.size();

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

      return c.toArray();
    }

    return null;
  }

  public boolean equals (java.lang.Object obj)
  {
    if (obj instanceof BasicAction)
    {
      if (((BasicAction) obj).get_uid().equals(get_uid()))
        return true;
    }

    return false;
  }

  /**
   * Forget any heuristics we may have received, and tell the resources which
   * generated them to forget too.
   *
   * @return <code>true</code> if heuristic information (if any) was
   *         successfully forgotten, <code>false</code> 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 <code>ActionStatus</code> indicating outcome.
   */

  protected synchronized int Begin (BasicAction parentAct)
  {
    if (tsLogger.arjLogger.isDebugEnabled())
    {
      tsLogger.arjLogger.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PROTECTED, FacilityCode.FAC_ATOMIC_ACTION, "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;
       
        if (tsLogger.arjLoggerI18N.isWarnEnabled())
                    {
                            tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.notrunning");
                    }
    }
    else
    {  
        if (actionStatus != ActionStatus.CREATED)
        {
            if (tsLogger.arjLoggerI18N.isWarnEnabled())
            {
                tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_29", new Object[]
                                                                                                           { 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)
                {
                    if (tsLogger.arjLoggerI18N.isWarnEnabled())
                    {
                        tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_30", new Object[]
                                                                                                                   { get_uid() });
                    }
                }
                else
                {
                    if (tsLogger.arjLoggerI18N.isDebugEnabled())
                    {
                        tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_31", new Object[]
                                                                                                                   { get_uid(), parentAct.get_uid(), Integer.toString(parentAct.status()) });
                    }
                }
            }

            ActionManager.manager().put(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
   * <code>true</code>.
   *
   * @return <code>ActionStatus</code> indicating outcome.
   */

  protected synchronized int End (boolean reportHeuristics)
  {
    if (tsLogger.arjLogger.isDebugEnabled())
    {
      tsLogger.arjLogger.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PROTECTED, FacilityCode.FAC_ATOMIC_ACTION, "BasicAction::End() for action-id "
          + get_uid());
    }

    /* Check for superfluous invocation */

    if ((actionStatus != ActionStatus.RUNNING)
        && (actionStatus != ActionStatus.ABORT_ONLY))
    {
      if (tsLogger.arjLoggerI18N.isWarnEnabled())
      {
        switch (actionStatus)
        {
        case ActionStatus.CREATED:
          tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_33", new Object[]
          { get_uid() });
          break;
        case ActionStatus.COMMITTED:
          tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_34", new Object[]
          { get_uid() });
          break;
        default:
          tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_35", new Object[]
          { 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)
        {
          if (tsLogger.arjLoggerI18N.isWarnEnabled())
          {
            tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_36", new Object[]
            { get_uid() });
          }

          if (heuristicDecision != TwoPhaseOutcome.PREPARE_OK)
          {
            if (tsLogger.arjLoggerI18N.isWarnEnabled())
            {
              tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_37", new Object[]
              { TwoPhaseOutcome.stringForm(heuristicDecision) });
            }
          }

          if (tsLogger.arjLoggerI18N.isWarnEnabled())
            tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_38");

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

    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 <code>ActionStatus</code> indicating outcome.
   */

  protected synchronized int Abort ()
  {
    if (tsLogger.arjLogger.isDebugEnabled())
    {
      tsLogger.arjLogger.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PROTECTED, FacilityCode.FAC_ATOMIC_ACTION, "BasicAction::Abort() for action-id "
          + get_uid());
    }

    /* Check for superfluous invocation */

    if ((actionStatus != ActionStatus.RUNNING)
        && (actionStatus != ActionStatus.ABORT_ONLY)
        && (actionStatus != ActionStatus.COMMITTING))
    {
      if (tsLogger.arjLoggerI18N.isWarnEnabled())
      {
        switch (actionStatus)
        {
        case ActionStatus.CREATED:
          tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_39", new Object[]
          { get_uid() });
          break;
        case ActionStatus.ABORTED:
          tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_40", new Object[]
          { get_uid() });
          break;
        default:
          tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_41", new Object[]
          { 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;
    currentStore = null;
    savedIntentionList = false;

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

    heuristicDecision = TwoPhaseOutcome.PREPARE_OK;
    _checkedAction = _checkedActionFactory.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;
    currentStore = null;
    savedIntentionList = false;

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

    heuristicDecision = TwoPhaseOutcome.PREPARE_OK;
    _checkedAction = _checkedActionFactory.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.arjLogger.isDebugEnabled())
    {
      tsLogger.arjLogger.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PROTECTED, FacilityCode.FAC_ATOMIC_ACTION, "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().
   */

  protected synchronized final void phase2Commit (boolean reportHeuristics)
  {
    if (tsLogger.arjLogger.isDebugEnabled())
    {
      tsLogger.arjLogger.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PROTECTED, FacilityCode.FAC_ATOMIC_ACTION, "BasicAction::phase2Commit() for action-id "
          + get_uid());
    }

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

      if (tsLogger.arjLoggerI18N.isWarnEnabled())
      {
        tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_42", new Object[]
        { get_uid(), Integer.toString(size), pendingList });
      }

      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.arjLogger.isDebugEnabled())
    {
      tsLogger.arjLogger.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PROTECTED, FacilityCode.FAC_ATOMIC_ACTION, "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();
        }
    }

  /**
   * 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 <code>TwoPhaseOutcome</code> indicating outcome.
   */

  protected synchronized final int prepare (boolean reportHeuristics)
  {
    if (tsLogger.arjLogger.isDebugEnabled())
    {
      tsLogger.arjLogger.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PROTECTED, FacilityCode.FAC_ATOMIC_ACTION, "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)
    {
      if (tsLogger.arjLoggerI18N.isWarnEnabled())
      {
        tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_43", new Object[]
        { 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 (store() == 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))
    {
      int numberOfThreads = ((pendingList != null) ? pendingList.size()
          : 0);
      Thread[] threads = new Thread[numberOfThreads];
      int i;

      /*
       * First create them in a suspended way, so that we can purge the
       * list before it is added to (in the event of failures!)
       */

      for (i = 0; i < numberOfThreads; i++)
      {
        threads[i] = AsyncPrepare.create(this, reportHeuristics, pendingList.getFront());
      }

      /*
       * Now start the threads running.
       */

      for (i = 0; i < numberOfThreads; i++)
      {
        threads[i].start();
        Thread.yield();
      }

      /*
       * If one of these threads fails (PREPARE_NOTOK) do we terminate the
       * others or simply let them finish? Currently we wait and let them
       * all terminate regardless.
       */

      /*
       * Now synchronise with the threads.
       */

      for (int j = 0; j < numberOfThreads; j++)
      {
        while (threads[j].isAlive())
        {
          try
          {
            threads[j].join();
          }
          catch (Exception e)
          {
            if (tsLogger.arjLogger.isWarnEnabled())
            {
              tsLogger.arjLogger.warn(e);
            }

            p = TwoPhaseOutcome.PREPARE_NOTOK;
          }
        }

        /*
         * Only set the outcome if the current value is PREPARE_OK.
         */

        if (p == TwoPhaseOutcome.PREPARE_OK)
          p = ((AsyncPrepare) threads[j]).outcome();

        threads[j] = null;
      }
    }
    else
    {
      boolean ok = true;

      do
      {
        try
        {
          p = doPrepare(reportHeuristics);

          /*
           * Stop as soon as we get a failure return code.
           */

          if ((p != TwoPhaseOutcome.PREPARE_OK)
              && (p != TwoPhaseOutcome.PREPARE_READONLY))
          {
            ok = false;
          }
        }
        catch (IndexOutOfBoundsException e)
        {
          // end of list!

          ok = false;
        }

      } while (ok);
    }

    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
        {
          if (tsLogger.arjLoggerI18N.isWarnEnabled())
            tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.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))
      {
        if (tsLogger.arjLoggerI18N.isWarnEnabled())
        {
          tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_45", new Object[]
          { get_uid() });
        }

        criticalEnd();

        return TwoPhaseOutcome.PREPARE_NOTOK;
      }

      if (state.notempty())
      {
        try
        {       
          if (!currentStore.write_committed(u, tn, state))
          {                                              
            if (tsLogger.arjLoggerI18N.isWarnEnabled())
            {
              tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_46", new Object[]
              { 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.arjLogger.isDebugEnabled())
    {
      tsLogger.arjLogger.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PROTECTED, FacilityCode.FAC_ATOMIC_ACTION, "BasicAction::onePhaseCommit() for action-id "
          + get_uid());
    }

    /* Are we forced to abort? */

    if (actionStatus == ActionStatus.ABORT_ONLY)
    {
      if (tsLogger.arjLoggerI18N.isWarnEnabled())
      {
        tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_43", new Object[]
        { 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.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
                                    {
                                        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!!
               */
       
              if (tsLogger.arjLoggerI18N.isWarnEnabled())
              {
                  tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_47", new Object[]
                                                                                                             { get_uid(), TwoPhaseOutcome.stringForm(p) });
              }
       
              if (reportHeuristics)
              {
                  updateHeuristic(p, true);
       
                  if (!heuristicList.insert(recordBeingHandled))
                      recordBeingHandled = null;
                  else
                  {
                      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 (store() == null)
        {
          if (tsLogger.arjLoggerI18N.isFatalEnabled())
          {
            tsLogger.arjLoggerI18N.fatal("com.arjuna.ats.arjuna.coordinator.BasicAction_48");
          }

          throw new com.arjuna.ats.arjuna.exceptions.FatalError(
              tsLogger.log_mesg.getString("com.arjuna.ats.arjuna.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;
  }

  /**
   * 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;
  }

  /*
   * Package visibility.
   */

  /*
   * 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!
   */

  /*
   * If we do not use asynchronous prepare, then we don't need to lock the
   * RecordLists - only one thread can access them anyway! However, if
   * asynchronous prepare is being used then we need to synchronize on them.
   * The 'lock' parameter (default false) indicates whether we should try to
   * synchronize the RecordList or not.
   */

  protected int doPrepare (boolean reportHeuristics)
      throws IndexOutOfBoundsException
  {
    /*
     * 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;

    do
    {
      AbstractRecord rec = ((pendingList != null) ? pendingList.getFront()
          : null);

      /*
       * 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.
       */

      if (rec != null)
      {
        /*
         * 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.
         */

        p = doPrepare(reportHeuristics, rec);

        /*
         * If failure then it's back on the pending list by now. Move on
         * or terminate?
         *
         * Move on if a cohesion, but terminate if an atom!!
         */
      }
      else
        throw new IndexOutOfBoundsException();

    } while ((p == TwoPhaseOutcome.PREPARE_OK)
        || (p == TwoPhaseOutcome.PREPARE_READONLY));

    return p;
  }

  /*
   * 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 theRecord)
      throws IndexOutOfBoundsException
  {
    /*
     * 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;
    AbstractRecord record = theRecord;

    if (record != null)
    {
      p = ((actionType == ActionType.TOP_LEVEL) ? record.topLevelPrepare()
          : record.nestedPrepare());
     
      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))
              {
                if (tsLogger.arjLoggerI18N.isWarnEnabled())
                {
                  tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_49", new Object[]
                  { 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.
             */

            record = insertRecord(pendingList, record);

            record = null;

            actionStatus = ActionStatus.PREPARED;

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

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

            if (tsLogger.arjLoggerI18N.isWarnEnabled())
            {
              tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_50", new Object[]
              { 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.)
               */
            }
          }
        }
      }
    }
    else
      // no more entries in pendingList
      throw new IndexOutOfBoundsException();

    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;
      boolean pastFirstParticipant = false;

      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.arjLogger.isDebugEnabled())
    {
      tsLogger.arjLogger.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PRIVATE, FacilityCode.FAC_ATOMIC_ACTION, "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.arjLoggerI18N.isDebugEnabled())
          {
            tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PRIVATE, FacilityCode.FAC_ATOMIC_ACTION, "com.arjuna.ats.arjuna.coordinator.BasicAction_51", new Object[]
            { get_uid(), TwoPhaseOutcome.stringForm(ok), 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);
          }
          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.
               */

              failedList.insert(recordBeingHandled);
            }
          }
        }
      }
      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 */
      }
    }

    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.arjLogger.isDebugEnabled())
    {
      tsLogger.arjLogger.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PRIVATE, FacilityCode.FAC_ATOMIC_ACTION, "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 (tsLogger.arjLoggerI18N.isWarnEnabled())
            {
              if (actionType == ActionType.TOP_LEVEL)
                tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_52", new Object[]
                { get_uid(), TwoPhaseOutcome.stringForm(ok) });
              else
                tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_53", new Object[]
                { get_uid(), TwoPhaseOutcome.stringForm(ok) });
            }

            updateHeuristic(ok, false);
            heuristicList.insert(recordBeingHandled);
          }
          else
          {
            if (ok != TwoPhaseOutcome.FINISH_OK)
            {
              if (tsLogger.arjLoggerI18N.isWarnEnabled())
              {
                if (actionType == ActionType.TOP_LEVEL)
                  tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_54", new Object[]
                  { get_uid(), TwoPhaseOutcome.stringForm(ok), RecordType.typeToClass(recordBeingHandled.typeIs()) });
                else
                  tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_55", new Object[]
                  { get_uid(), TwoPhaseOutcome.stringForm(ok), RecordType.typeToClass(recordBeingHandled.typeIs()) });
              }
            }
          }
        }

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

        recordBeingHandled = null;
      }
    }

    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 <code>false</code> 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)
    {
      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;
    }

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

  protected void updateState ()
  {
    if (tsLogger.arjLogger.isDebugEnabled())
    {
      tsLogger.arjLogger.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PRIVATE, FacilityCode.FAC_ATOMIC_ACTION, "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.
       */

      store();

      /*
       * If we have failures then rewrite the intentions list. Otherwise,
       * delete the log entry.
       */

      if (((failedList != null) && (failedList.size() > 0))
          || ((heuristicList != null) && (heuristicList.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.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_64");

          // what else?
        }

        if (state.notempty())
        {
          try
          {
            if (!currentStore.write_committed(u, tn, state))
            {
              tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_65");
            }
          }
          catch (ObjectStoreException e)
          {
            tsLogger.arjLogger.warn(e);
          }
        }
      }
      else
      {
        try
        {
          if (savedIntentionList)
          {           
            if (currentStore.remove_committed(getSavingUid(), type()))
            {
              savedIntentionList = false;
            }
          }
        }
        catch (ObjectStoreException e)
        {
          tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_70", new Object[]
          { 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 <code>true</code> if the transaction is current,
   *         <code>false</code> 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))
      {
        if (tsLogger.arjLoggerI18N.isWarnEnabled())
        {
          tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_56", new Object[]
          { 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 (tsLogger.arjLoggerI18N.isWarnEnabled())
        {
          if (isCommit)
          {
            tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_57", new Object[]
            { get_uid() });
          }
          else
          {
            tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_58", new Object[]
            { 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 = (BasicAction) iter.nextElement();

        if (child.status() != ActionStatus.ABORTED)
        {
          if (printError)
          {
            if (tsLogger.arjLoggerI18N.isWarnEnabled())
            {
              if (isCommit)
              {
                tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_59", new Object[]
                { get_uid() });
              }
              else
              {
                tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_60", new Object[]
                { get_uid() });
              }
            }

            printError = false;
          }

          if (tsLogger.arjLoggerI18N.isWarnEnabled())
          {
            tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_61", new Object[]
            { child.get_uid() });
          }

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

      iter = null;

      if (isCommit)
      {
        if (tsLogger.arjLoggerI18N.isWarnEnabled())
        {
          tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_62", new Object[]
          { ((child != null ? child.get_uid() : "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 = (Thread) iter.nextElement();

        if (tsLogger.arjLoggerI18N.isDebugEnabled())
        {
          tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PRIVATE, FacilityCode.FAC_ATOMIC_ACTION, "com.arjuna.ats.arjuna.coordinator.BasicAction_63", new Object[]
          { get_uid(), 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.arjLogger.isDebugEnabled())
    {
      tsLogger.arjLogger.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PRIVATE, FacilityCode.FAC_ATOMIC_ACTION, "BasicAction::actionInitialise() for action-id "
          + get_uid());
    }

    criticalStart();

    if (parent != null) /* ie not top_level */
    {
      if (tsLogger.arjLoggerI18N.isDebugEnabled())
      {
        tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PRIVATE, FacilityCode.FAC_ATOMIC_ACTION, "com.arjuna.ats.arjuna.coordinator.BasicAction_66", new Object[]
        { get_uid(), Integer.toString(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.arjLoggerI18N.isDebugEnabled())
        {
          tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PRIVATE, FacilityCode.FAC_ATOMIC_ACTION, "com.arjuna.ats.arjuna.coordinator.BasicAction_67", new Object[]
          { get_uid(), 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.arjLogger.isDebugEnabled())
    {
      tsLogger.arjLogger.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PRIVATE, FacilityCode.FAC_ATOMIC_ACTION, "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.arjLogger.isDebugEnabled())
    {
      tsLogger.arjLogger.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PRIVATE, FacilityCode.FAC_ATOMIC_ACTION, "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.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.BasicAction_68");
    }
  }

  /* 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 ObjectStore currentStore;

  //    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.

  /*
   * 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 Mutex _lock = new Mutex(); // TODO
 
  private static CheckedActionFactory _checkedActionFactory;
 
  static
  {
      /*
       * Make sure this can only be set once. Bad things can happen if the factory changes
       * during execution.
       */
     
      String checkedActionFactory = arjPropertyManager.getCoordinatorEnvironmentBean().getCheckedActionFactory();
     
      if (checkedActionFactory != null)
      {
          try
          {
              Class factory = Thread.currentThread().getContextClassLoader().loadClass(checkedActionFactory);
             
              _checkedActionFactory = (CheckedActionFactory) factory.newInstance();
          }
          catch (final Exception ex)
          {
              ex.printStackTrace();
             
              if (tsLogger.arjLoggerI18N.isWarnEnabled())
              {
                  tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.coordinator.checkedactionfactory",
                                              new Object[]{ checkedActionFactory }, ex);
              }
             
              throw new FatalError(tsLogger.log_mesg.getString("com.arjuna.ats.arjuna.coordinator.cafactoryerror")+" "+checkedActionFactory, ex);             
          }
      }
      else
          _checkedActionFactory = new CheckedActionFactoryImple();
  }

}
TOP

Related Classes of com.arjuna.ats.arjuna.coordinator.BasicAction

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.