示例#1
0
  /**
   * Normally returns the current state of the log entry. However, this is never called during
   * normal (non-recovery) execution. Therefore, the overhead of having to scan all of the logs (if
   * it's not one we're using) is minimal.
   */
  public int currentState(Uid objUid, String tName) throws ObjectStoreException {
    InputObjectState ios = new InputObjectState();

    /*
     * TODO
     *
     * It's possible that the entry has been marked to be deleted but
     * that the removal entry hasn't been written yet. We could check the
     * async cache. However, since we really only care about this during
     * recovery, it's not going to cause us  problems anyway.
     */

    if (allObjUids(tName, ios, StateStatus.OS_UNKNOWN)) {
      Uid tempUid = new Uid(Uid.nullUid());

      do {
        try {
          tempUid = UidHelper.unpackFrom(ios);
        } catch (final Exception ex) {
          ex.printStackTrace();

          return StateStatus.OS_UNKNOWN;
        }

        if (tempUid.equals(objUid)) return StateStatus.OS_COMMITTED;

      } while (tempUid.notEquals(Uid.nullUid()));

      return StateStatus.OS_UNKNOWN;
    } else return StateStatus.OS_UNKNOWN;
  }
示例#2
0
  private static XID getXid(Uid uid, Uid branch, int formatId, Integer eisName)
      throws IllegalStateException {
    if (uid == null) {
      throw new IllegalStateException();
    }

    XID xid = new XID();
    xid.formatID = formatId;

    // gtrid is uid byte form followed by as many chars of the node name as will fit.
    byte[] gtridUid = uid.getBytes();

    if (gtridUid.length > XID.MAXGTRIDSIZE) {
      throw new IllegalStateException(); // Uid is too long!!!!
    }

    String nodeName = TxControl.getXANodeName();
    int nodeNameLengthToUse = nodeName.getBytes().length;
    xid.gtrid_length = gtridUid.length + nodeNameLengthToUse;

    // src, srcPos, dest, destPos, length
    System.arraycopy(gtridUid, 0, xid.data, 0, gtridUid.length);
    System.arraycopy(nodeName.getBytes(), 0, xid.data, gtridUid.length, nodeNameLengthToUse);

    if (branch.notEquals(Uid.nullUid())) {
      // bqual is uid byte form plus EIS name.
      byte[] bqualUid = branch.getBytes();

      if (bqualUid.length > XID.MAXBQUALSIZE) {
        throw new IllegalStateException(); // Uid is too long!!!!
      }

      int spareBqualBytes = XID.MAXBQUALSIZE - (bqualUid.length + 4);

      xid.bqual_length = bqualUid.length + 4 + 4;

      // src, srcPos, dest, destPos, length
      int offset = xid.gtrid_length;
      System.arraycopy(bqualUid, 0, xid.data, offset, bqualUid.length);
      setEisName(xid, eisName);
    } else {
      /*
       * Note: for some dbs we seem to be able to get
       * away with setting the size field to the size
       * of the actual branch. However, for Oracle,
       * it appears as though it must always be 64.
       * (At least for zero branches.)
       */
      xid.data[xid.gtrid_length] = (byte) 0;
      xid.bqual_length = 64;
    }

    return xid;
  }
示例#3
0
  public static Xid getXid(Uid uid, boolean branch, int formatId) throws IllegalStateException {
    XID xid;
    if (branch) xid = getXid(uid, new Uid(), formatId, null);
    else xid = getXid(uid, Uid.nullUid(), formatId, null);

    return new XidImple(xid);
  }
示例#4
0
  public static void setBranchUID(XID xid, Uid uid) {
    if (xid == null || xid.formatID != FORMAT_ID) {
      return;
    }

    byte[] bqual = uid.getBytes();
    System.arraycopy(bqual, 0, xid.data, xid.gtrid_length, Uid.UID_SIZE);
  }
示例#5
0
  public InputObjectState() {
    if (tsLogger.logger.isTraceEnabled()) {
      tsLogger.logger.trace("InputObjectState::InputObjectState()");
    }

    bufferUid = new Uid(Uid.nullUid());
    super._valid = false;
    imageType = null;
  }
示例#6
0
  public static Uid getUid(XID xid) {
    if (xid == null || xid.formatID != FORMAT_ID) {
      return Uid.nullUid();
    }

    // The Uid byte are the first in the data array, so we just pass
    // in the whole thing and the additional trailing data is ignored.
    Uid tx = new Uid(xid.data);
    return tx;
  }
示例#7
0
  private final void listLogs(String type) throws IOException {
    InputObjectState buff = new InputObjectState();

    try {
      if (StoreManager.getRecoveryStore()
          .allObjUids(TransactionTypeManager.getInstance().getTransactionType(type), buff)) {
        Uid u = null;

        do {
          u = UidHelper.unpackFrom(buff);

          if (Uid.nullUid().notEquals(u)) {
            System.out.println("Log: " + u);
          }
        } while (Uid.nullUid().notEquals(u));
      }
    } catch (final ObjectStoreException ex) {
      throw new IOException();
    }
  }
示例#8
0
  protected static OSRecordHolder readObjectStoreRecord(String type) {
    try {
      RecoveryStore recoveryStore = StoreManager.getRecoveryStore();
      InputObjectState states = new InputObjectState();

      if (recoveryStore.allObjUids(type, states) && states.notempty()) {

        Uid uid = UidHelper.unpackFrom(states);

        if (uid.notEquals(Uid.nullUid())) {
          InputObjectState ios = recoveryStore.read_committed(uid, type);

          return new OSRecordHolder(uid, type, ios);
        }
      }
    } catch (Exception e) {
      e.printStackTrace();
    }

    return null;
  }
示例#9
0
  private static void clearObjectStore(String type) {
    try {
      RecoveryStore recoveryStore = StoreManager.getRecoveryStore();
      InputObjectState states = new InputObjectState();

      if (recoveryStore.allObjUids(type, states) && states.notempty()) {
        boolean finished = false;

        do {
          Uid uid = UidHelper.unpackFrom(states);

          if (uid.notEquals(Uid.nullUid())) {
            recoveryStore.remove_committed(uid, type);
          } else {
            finished = true;
          }
        } while (!finished);
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
示例#10
0
  public static Uid getBranchUid(XID xid) {
    if (xid == null || xid.formatID != FORMAT_ID) {
      return Uid.nullUid();
    }

    byte[] bqual = new byte[xid.bqual_length];
    System.arraycopy(xid.data, xid.gtrid_length, bqual, 0, xid.bqual_length);

    // The Uid byte are the first in the data array, so we just pass
    // in the whole thing and the additional trailing data is ignored.
    Uid tx = new Uid(bqual);
    return tx;
  }
示例#11
0
  public final InputObjectState allObjUids() throws ObjectStoreException {
    OutputObjectState state = new OutputObjectState();
    Iterator<Uid> iter = _ids.keySet().iterator();

    try {
      while (iter.hasNext()) {
        UidHelper.packInto(iter.next(), state);
      }

      // don't forget to null terminate

      UidHelper.packInto(Uid.nullUid(), state);
    } catch (final IOException ex) {
      throw new ObjectStoreException(ex);
    }

    return new InputObjectState(state);
  }
示例#12
0
  private final boolean supportedLog(String logID) throws ObjectStoreException, IOException {
    Uid id = new Uid(logID);

    if (id.equals(Uid.nullUid())) return false;

    ObjectStoreIterator iter =
        new ObjectStoreIterator(
            StoreManager.getRecoveryStore(),
            TransactionTypeManager.getInstance().getTransactionType(_transactionType));
    Uid u;

    do {
      u = iter.iterate();

      if (u.equals(id)) return true;
    } while (Uid.nullUid().notEquals(u));

    return false;
  }
示例#13
0
 public LogRecordWrapper(Uid uid) {
   super(null);
   _uidWrapper.setName(
       "jboss.jta:type=ObjectStore,itype=uninitialised,puid=" + uid.fileStringForm());
 }
示例#14
0
 static XID getXid(Uid uid, boolean branch, Integer eisName) throws IllegalStateException {
   if (branch) return getXid(uid, new Uid(), FORMAT_ID, eisName);
   else return getXid(uid, Uid.nullUid(), FORMAT_ID, eisName);
 }
示例#15
0
  /**
   * This is a recovery-only method and should not be called during normal execution. As such we
   * need to load in all of the logs we can find that aren't already loaded (or activated).
   */
  public boolean allObjUids(String tName, InputObjectState state, int match)
      throws ObjectStoreException {
    /*
     * match will always be OS_COMMITTED since that's all we ever write for
     * the logs.
     */

    // in case of asynchronous removals trigger the purger now.

    _purger.trigger();

    /*
     * Get a list of logs. Load them in to memory if we aren't already
     * working on them/it. But we can prune the entry once we're
     * finished or the memory footprint will grow. We should do this
     * for all frozen entries eventually too.
     */

    InputObjectState logs = new InputObjectState();
    OutputObjectState objUids = new OutputObjectState();

    /*
     * We never call this method except during recovery. As such we shouldn't
     * need to worry about optimizations such as checking whether or not the
     * log is in current working memory.
     */

    if (!super.allObjUids(tName, logs, match)) return false;
    else {
      /*
       * Now we have all of the log names let's attach to each one
       * and locate the committed instances (not deleted.)
       */

      Uid logName = new Uid(Uid.nullUid());

      try {
        do {
          logName = UidHelper.unpackFrom(logs);

          if (logName.notEquals(Uid.nullUid())) {
            /*
             * Could check to see if log is in current working memory.
             */

            /*
             * TODO
             *
             * First purge the log if we can, but we need to know that
             * we're not playing with an instance that is being manipulated
             * from another VM instance.
             */

            ArrayList<InputObjectState> txs = scanLog(logName, tName);

            if (txs.size() > 0) {
              for (int i = 0; i < txs.size(); i++) {
                UidHelper.packInto(txs.get(i).stateUid(), objUids);
              }
            }
          }
        } while (logName.notEquals(Uid.nullUid()));

        // remember null terminator

        UidHelper.packInto(Uid.nullUid(), objUids);

        state.setBuffer(objUids.buffer());
      } catch (final IOException ex) {
        ex.printStackTrace();

        return false;
      }

      return true;
    }
  }
示例#16
0
  /**
   * write_state saves the ObjectState in a file named by the type and Uid of the ObjectState. If
   * the second argument is SHADOW, then the file name is different so that a subsequent
   * commit_state invocation will rename the file.
   *
   * <p>We need to make sure that each entry is written to the next empty location in the log even
   * if there's already an entry for this tx.
   */
  protected boolean write_state(Uid objUid, String tName, OutputObjectState state, int ft)
      throws ObjectStoreException {
    if (tsLogger.logger.isTraceEnabled()) {
      tsLogger.logger.trace(
          "ShadowingStore.write_state("
              + objUid
              + ", "
              + tName
              + ", "
              + StateType.stateTypeString(ft)
              + ")");
    }

    String fname = null;
    File fd = null;

    if (tName != null) {
      int imageSize = (int) state.length();
      byte[] uidString = objUid.stringForm().getBytes();
      int buffSize =
          _redzone.length
              + uidString.length
              + imageSize
              + 8; // don't put in endOfLog since we keep overwriting that.
      RandomAccessFile ofile = null;
      java.nio.channels.FileLock lock = null;

      if (imageSize > 0) {
        TransactionData theLogEntry =
            getLogName(objUid, tName, buffSize); // always adds entry to log
        LogInstance theLog = theLogEntry.container;

        if (theLog == null) throw new ObjectStoreException();

        fname = genPathName(theLog.getName(), tName, ft);
        fd = openAndLock(fname, FileLock.F_WRLCK, true);

        if (fd == null) {
          tsLogger.i18NLogger.warn_objectstore_ShadowingStore_18(fname);

          return false;
        }

        boolean setLength = !fd.exists();

        try {
          ofile = new RandomAccessFile(fd, FILE_MODE);

          if (setLength) {
            ofile.setLength(_maxFileSize);
          } else {
            // may have to resize file if we keep updating this transaction info

            if (theLog.remaining() < buffSize) {
              long size = ofile.length() + buffSize - theLog.remaining();

              ofile.setLength(size);

              theLog.resize(size);
            }
          }

          java.nio.ByteBuffer buff = java.nio.ByteBuffer.allocate(buffSize);

          buff.put(_redzone);
          buff.putInt(uidString.length);
          buff.put(uidString);
          buff.putInt(imageSize);
          buff.put(state.buffer());

          synchronized (_lock) {
            ofile.seek(theLogEntry.offset);

            ofile.write(buff.array());
          }
        } catch (SyncFailedException e) {
          unlockAndClose(fd, ofile);

          throw new ObjectStoreException(
              "ShadowingStore::write_state() - write failed to sync for " + fname, e);
        } catch (FileNotFoundException e) {
          unlockAndClose(fd, ofile);

          e.printStackTrace();

          throw new ObjectStoreException(
              "ShadowingStore::write_state() - write failed to locate file " + fname + ": " + e, e);
        } catch (IOException e) {
          unlockAndClose(fd, ofile);

          e.printStackTrace();

          throw new ObjectStoreException(
              "ShadowingStore::write_state() - write failed for " + fname + ": " + e, e);
        } finally {
          try {
            if (lock != null) lock.release();
          } catch (IOException ex) {
            ex.printStackTrace();
          }
        }
      }

      if (!unlockAndClose(fd, ofile)) {
        tsLogger.i18NLogger.warn_objectstore_ShadowingStore_19(fname);
      }

      super.addToCache(fname);

      return true;
    } else
      throw new ObjectStoreException(
          "ShadowStore::write_state - "
              + tsLogger.i18NLogger.get_objectstore_notypenameuid()
              + objUid);
  }
示例#17
0
/**
 * @author Mark Little ([email protected])
 * @version $Id: XAResourceRecord.java 2342 2006-03-30 13:06:17Z $
 * @since JTS 1.2.4.
 */
public class XAResourceRecord extends AbstractRecord {

  public static final int XACONNECTION = 0;

  private static final Uid START_XARESOURCE = Uid.minUid();

  private static final Uid END_XARESOURCE = Uid.maxUid();

  /**
   * The params represent specific parameters we need to recreate the connection to the database in
   * the event of a failure. If they're not set then recovery is out of our control.
   *
   * <p>Could also use it to pass other information, such as the readonly flag.
   */
  public XAResourceRecord(TransactionImple tx, XAResource res, Xid xid, Object[] params) {
    super(new Uid(), null, ObjectType.ANDPERSISTENT);

    if (jtaLogger.logger.isTraceEnabled()) {
      jtaLogger.logger.trace("XAResourceRecord.XAResourceRecord ( " + xid + ", " + res + " )");
    }

    _theXAResource = res;
    if (_xaResourceRecordWrappingPlugin != null) {
      _xaResourceRecordWrappingPlugin.transcribeWrapperData(this);
    }

    _recoveryObject = null;
    _tranID = xid;

    _valid = true;

    if (params != null) {
      if (params.length >= XACONNECTION) {
        if (params[XACONNECTION] instanceof RecoverableXAConnection)
          _recoveryObject = (RecoverableXAConnection) params[XACONNECTION];
      }
    }

    _prepared = false;
    _heuristic = TwoPhaseOutcome.FINISH_OK;

    _theTransaction = tx;
  }

  public final Xid getXid() {
    return _tranID;
  }

  public Uid order() {
    if (_theXAResource instanceof StartXAResource) return START_XARESOURCE;
    else {
      if (_theXAResource instanceof EndXAResource) return END_XARESOURCE;
      else return super.order();
    }
  }

  public boolean propagateOnCommit() {
    return false; // cannot ever be nested!
  }

  public int typeIs() {
    return RecordType.JTA_RECORD;
  }

  public Object value() {
    return _theXAResource;
  }

  public void setValue(Object o) {
    jtaLogger.i18NLogger.warn_resources_arjunacore_setvalue("XAResourceRecord::set_value()");
  }

  public int nestedAbort() {
    return TwoPhaseOutcome.FINISH_OK;
  }

  public int nestedCommit() {
    return TwoPhaseOutcome.FINISH_OK;
  }

  /*
   * XA is not subtransaction aware.
   */

  public int nestedPrepare() {
    return TwoPhaseOutcome.PREPARE_OK; // do nothing
  }

  public int topLevelPrepare() {
    if (jtaLogger.logger.isTraceEnabled()) {
      jtaLogger.logger.trace("XAResourceRecord.topLevelPrepare for " + this);
    }

    if (!_valid || (_theXAResource == null) || (_tranID == null)) {
      jtaLogger.i18NLogger.warn_resources_arjunacore_preparenulltx("XAResourceRecord.prepare");

      removeConnection();

      return TwoPhaseOutcome.PREPARE_NOTOK;
    }

    try {
      if (endAssociation()) {
        _theXAResource.end(_tranID, XAResource.TMSUCCESS);
      }

      _prepared = true;

      if (_theXAResource.prepare(_tranID) == XAResource.XA_RDONLY) {
        if (TxControl.isReadonlyOptimisation()) {
          // we won't be called again, so we need to tidy up now
          removeConnection();
        }

        return TwoPhaseOutcome.PREPARE_READONLY;
      } else return TwoPhaseOutcome.PREPARE_OK;
    } catch (XAException e1) {
      jtaLogger.i18NLogger.warn_resources_arjunacore_preparefailed(
          XAHelper.xidToString(_tranID),
          _theXAResource.toString(),
          XAHelper.printXAErrorCode(e1),
          e1);

      /*
       * XA_RB*, XAER_RMERR, XAER_RMFAIL, XAER_NOTA, XAER_INVAL, or
       * XAER_PROTO.
       */

      if (_rollbackOptimization) // won't have rollback called on it
      removeConnection();

      switch (e1.errorCode) {
        case XAException.XAER_RMERR:
        case XAException.XAER_RMFAIL:
        case XAException.XA_RBROLLBACK:
        case XAException.XA_RBEND:
        case XAException.XA_RBCOMMFAIL:
        case XAException.XA_RBDEADLOCK:
        case XAException.XA_RBINTEGRITY:
        case XAException.XA_RBOTHER:
        case XAException.XA_RBPROTO:
        case XAException.XA_RBTIMEOUT:
        case XAException.XAER_INVAL:
        case XAException.XAER_PROTO:
        case XAException
            .XAER_NOTA: // resource may have arbitrarily rolled back (shouldn't, but ...)
          return TwoPhaseOutcome.PREPARE_NOTOK; // will not call rollback
        default:
          return TwoPhaseOutcome
              .HEURISTIC_HAZARD; // we're not really sure (shouldn't get here though).
      }
    } catch (Exception e2) {
      jtaLogger.i18NLogger.warn_resources_arjunacore_preparefailed(
          XAHelper.xidToString(_tranID), _theXAResource.toString(), "-", e2);

      if (_rollbackOptimization) // won't have rollback called on it
      removeConnection();

      return TwoPhaseOutcome.PREPARE_NOTOK;
    }
  }

  public int topLevelAbort() {
    if (jtaLogger.logger.isTraceEnabled()) {
      jtaLogger.logger.trace("XAResourceRecord.topLevelAbort for " + this);
    }

    if (!_valid) return TwoPhaseOutcome.FINISH_ERROR;

    if (_theTransaction != null
        && _theTransaction.getXAResourceState(_theXAResource) == TxInfo.OPTIMIZED_ROLLBACK) {
      /*
       * Already rolledback during delist.
       */

      return TwoPhaseOutcome.FINISH_OK;
    }

    if (_tranID == null) {
      jtaLogger.i18NLogger.warn_resources_arjunacore_rollbacknulltx("XAResourceRecord.rollback");

      return TwoPhaseOutcome.FINISH_ERROR;
    } else {
      if (_theXAResource == null) _theXAResource = getNewXAResource();

      if (_theXAResource != null) {
        if (_heuristic != TwoPhaseOutcome.FINISH_OK) return _heuristic;

        try {
          if (!_prepared) {
            if (endAssociation()) {
              _theXAResource.end(_tranID, XAResource.TMSUCCESS);
            }
          }
        } catch (XAException e1) {
          if ((e1.errorCode >= XAException.XA_RBBASE) && (e1.errorCode < XAException.XA_RBEND)) {
            /*
             * Has been marked as rollback-only. We still
             * need to call rollback.
             */
          } else {
            jtaLogger.i18NLogger.warn_resources_arjunacore_rollbackerror(
                XAHelper.xidToString(_tranID),
                _theXAResource.toString(),
                XAHelper.printXAErrorCode(e1),
                e1);

            removeConnection();

            return TwoPhaseOutcome.FINISH_ERROR;
          }
        } catch (RuntimeException e) {
          jtaLogger.i18NLogger.warn_resources_arjunacore_rollbackerror(
              XAHelper.xidToString(_tranID), _theXAResource.toString(), "-", e);

          throw e;
        }

        try {
          _theXAResource.rollback(_tranID);
        } catch (XAException e1) {
          if (notAProblem(e1, false)) {
            // some other thread got there first (probably)
          } else {
            jtaLogger.i18NLogger.warn_resources_arjunacore_rollbackerror(
                XAHelper.xidToString(_tranID),
                _theXAResource.toString(),
                XAHelper.printXAErrorCode(e1),
                e1);

            switch (e1.errorCode) {
              case XAException.XAER_RMERR:
                if (!_prepared) break; // just do the finally block
              case XAException.XA_HEURHAZ:
                return TwoPhaseOutcome.HEURISTIC_HAZARD;
              case XAException.XA_HEURCOM:
                return TwoPhaseOutcome.HEURISTIC_COMMIT;
              case XAException.XA_HEURMIX:
                return TwoPhaseOutcome.HEURISTIC_MIXED;
              case XAException.XAER_NOTA:
                if (_recovered) break; // rolled back previously and recovery completed
              case XAException.XA_HEURRB: // forget?
              case XAException.XA_RBROLLBACK:
              case XAException.XA_RBEND:
              case XAException.XA_RBCOMMFAIL:
              case XAException.XA_RBDEADLOCK:
              case XAException.XA_RBINTEGRITY:
              case XAException.XA_RBOTHER:
              case XAException.XA_RBPROTO:
              case XAException.XA_RBTIMEOUT:
                break;
              default:
                return TwoPhaseOutcome.FINISH_ERROR;
            }
          }
        } catch (Exception e2) {
          jtaLogger.i18NLogger.warn_resources_arjunacore_rollbackerror(
              XAHelper.xidToString(_tranID), _theXAResource.toString(), "-", e2);

          return TwoPhaseOutcome.FINISH_ERROR;
        } finally {
          if (!_prepared) removeConnection();
        }
      } else {
        jtaLogger.i18NLogger.warn_resources_arjunacore_noresource(XAHelper.xidToString(_tranID));

        if (XAResourceRecord._assumedComplete) {
          jtaLogger.i18NLogger.info_resources_arjunacore_assumecomplete(
              XAHelper.xidToString(_tranID));

          return TwoPhaseOutcome.FINISH_OK;
        } else return TwoPhaseOutcome.FINISH_ERROR;
      }
    }

    return TwoPhaseOutcome.FINISH_OK;
  }

  public int topLevelCommit() {
    if (jtaLogger.logger.isTraceEnabled()) {
      jtaLogger.logger.trace("XAResourceRecord.topLevelCommit for " + this);
    }

    if (!_prepared) return TwoPhaseOutcome.NOT_PREPARED;

    if (_tranID == null) {
      jtaLogger.i18NLogger.warn_resources_arjunacore_commitnulltx("XAResourceRecord.commit");

      return TwoPhaseOutcome.FINISH_ERROR;
    } else {
      if (_theXAResource == null) _theXAResource = getNewXAResource();

      if (_theXAResource != null) {
        if (_heuristic != TwoPhaseOutcome.FINISH_OK) return _heuristic;

        /*
         * No need for end call here since we can only get to this
         * point by going through prepare.
         */

        try {
          _theXAResource.commit(_tranID, false);
        } catch (XAException e1) {
          if (notAProblem(e1, true)) {
            // some other thread got there first (probably)
          } else {
            jtaLogger.i18NLogger.warn_resources_arjunacore_commitxaerror(
                XAHelper.xidToString(_tranID),
                _theXAResource.toString(),
                XAHelper.printXAErrorCode(e1),
                e1);

            /*
             * XA_HEURHAZ, XA_HEURCOM, XA_HEURRB, XA_HEURMIX,
             * XAER_RMERR, XAER_RMFAIL, XAER_NOTA, XAER_INVAL, or
             * XAER_PROTO.
             */

            switch (e1.errorCode) {
              case XAException.XA_HEURHAZ:
                return TwoPhaseOutcome.HEURISTIC_HAZARD;
              case XAException.XA_HEURCOM: // what about forget?
                // OTS doesn't support
                // this code here.
                break;
              case XAException.XA_HEURRB:
              case XAException
                  .XA_RBROLLBACK: // could really do with an ABORTED status in TwoPhaseOutcome to
                                  // differentiate
              case XAException.XA_RBCOMMFAIL:
              case XAException.XA_RBDEADLOCK:
              case XAException.XA_RBINTEGRITY:
              case XAException.XA_RBOTHER:
              case XAException.XA_RBPROTO:
              case XAException.XA_RBTIMEOUT:
              case XAException.XA_RBTRANSIENT:
              case XAException.XAER_RMERR:
              case XAException.XAER_PROTO: // XA spec implies rollback
                return TwoPhaseOutcome.HEURISTIC_ROLLBACK;
              case XAException.XA_HEURMIX:
                return TwoPhaseOutcome.HEURISTIC_MIXED;
              case XAException.XAER_NOTA:
                if (_recovered) break; // committed previously and recovery completed
                else
                  return TwoPhaseOutcome.HEURISTIC_HAZARD; // something terminated the transaction!
              case XAException.XA_RETRY:
              case XAException.XAER_RMFAIL:
                _committed = true; // will cause log to be rewritten

                /*
                 * Could do timeout retry here, but that could cause other resources in the list to go down the
                 * heuristic path (some are far too keen to do this). Fail and let recovery retry. Meanwhile
                 * the coordinator will continue to commit the other resources immediately.
                 */
                return TwoPhaseOutcome.FINISH_ERROR;
              case XAException.XAER_INVAL: // resource manager failed, did it rollback?
              default:
                return TwoPhaseOutcome.HEURISTIC_HAZARD;
            }
          }
        } catch (Exception e2) {
          jtaLogger.i18NLogger.warn_resources_arjunacore_commitxaerror(
              XAHelper.xidToString(_tranID), _theXAResource.toString(), "-", e2);

          return TwoPhaseOutcome.FINISH_ERROR;
        } finally {
          removeConnection();
        }
      } else {
        jtaLogger.i18NLogger.warn_resources_arjunacore_noresource(XAHelper.xidToString(_tranID));

        if (XAResourceRecord._assumedComplete) {
          jtaLogger.i18NLogger.info_resources_arjunacore_assumecomplete(
              XAHelper.xidToString(_tranID));

          return TwoPhaseOutcome.FINISH_OK;
        } else return TwoPhaseOutcome.FINISH_ERROR;
      }
    }

    return TwoPhaseOutcome.FINISH_OK;
  }

  /**
   * Is the XAException a non-error when received in reply to commit or rollback ? It normally is,
   * but may be overridden in recovery.
   */
  protected boolean notAProblem(XAException ex, boolean commit) {
    return XAResourceErrorHandler.notAProblem(_theXAResource, ex, commit);
  }

  public int nestedOnePhaseCommit() {
    return TwoPhaseOutcome.FINISH_ERROR;
  }

  /**
   * For commit_one_phase we can do whatever we want since the transaction outcome is whatever we
   * want. Therefore, we do not need to save any additional recoverable state, such as a reference
   * to the transaction coordinator, since it will not have an intentions list anyway.
   */
  public int topLevelOnePhaseCommit() {
    if (jtaLogger.logger.isTraceEnabled()) {
      jtaLogger.logger.trace("XAResourceRecord.topLevelOnePhaseCommit for " + this);
    }

    boolean commit = true;

    if (_tranID == null) {
      jtaLogger.i18NLogger.warn_resources_arjunacore_opcnulltx("XAResourceRecord.1pc");

      return TwoPhaseOutcome.ONE_PHASE_ERROR; // rolled back!!
    } else {
      if (_theXAResource != null) {
        if (_heuristic != TwoPhaseOutcome.FINISH_OK) return _heuristic;

        XAException endHeuristic = null;
        XAException endRBOnly = null;

        try {
          /*
           * TODO in Oracle the end is not needed. Is this common
           * across other RMs?
           */

          if (endAssociation()) {
            _theXAResource.end(_tranID, XAResource.TMSUCCESS);
          }
        } catch (XAException e1) {
          /*
           * Now it's not legal to return a heuristic from end, but
           * apparently Oracle does (http://jira.jboss.com/jira/browse/JBTM-343)
           * Since this is 1PC we can call forget: the outcome of the
           * transaction is the outcome of the participant.
           */

          switch (e1.errorCode) {
            case XAException.XA_HEURHAZ:
            case XAException.XA_HEURMIX:
            case XAException.XA_HEURCOM:
            case XAException.XA_HEURRB:
              endHeuristic = e1;
              break;
            case XAException.XA_RBROLLBACK:
            case XAException.XA_RBCOMMFAIL:
            case XAException.XA_RBDEADLOCK:
            case XAException.XA_RBINTEGRITY:
            case XAException.XA_RBOTHER:
            case XAException.XA_RBPROTO:
            case XAException.XA_RBTIMEOUT:
            case XAException.XA_RBTRANSIENT:
              /*
               * Has been marked as rollback-only. We still
               * need to call rollback.
               */

              endRBOnly = e1;
              commit = false;
              break;
            case XAException.XAER_RMERR:
            case XAException.XAER_NOTA:
            case XAException.XAER_PROTO:
            case XAException.XAER_INVAL:
            case XAException.XAER_RMFAIL:
            default:
              {
                jtaLogger.i18NLogger.warn_resources_arjunacore_opcerror(
                    XAHelper.xidToString(_tranID),
                    _theXAResource.toString(),
                    XAHelper.printXAErrorCode(e1),
                    e1);

                removeConnection();

                return TwoPhaseOutcome.FINISH_ERROR;
              }
          }
        } catch (RuntimeException e) {
          jtaLogger.i18NLogger.warn_resources_arjunacore_opcerror(
              XAHelper.xidToString(_tranID), _theXAResource.toString(), "-", e);

          throw e;
        }

        try {
          /*
           * Not strictly necessary since calling commit will
           * do the rollback if end failed as above.
           */

          if (endHeuristic
              != null) // catch those RMs that terminate in end rather than follow the spec
          throw endHeuristic;

          if (commit) _theXAResource.commit(_tranID, true);
          else {
            _theXAResource.rollback(_tranID);
            throw endRBOnly;
          }
        } catch (XAException e1) {
          jtaLogger.i18NLogger.warn_resources_arjunacore_opcerror(
              XAHelper.xidToString(_tranID),
              _theXAResource.toString(),
              XAHelper.printXAErrorCode(e1),
              e1);

          /*
           * XA_HEURHAZ, XA_HEURCOM, XA_HEURRB, XA_HEURMIX,
           * XAER_RMERR, XAER_RMFAIL, XAER_NOTA, XAER_INVAL, or
           * XAER_PROTO. XA_RB*
           */

          switch (e1.errorCode) {
            case XAException.XA_HEURHAZ:
            case XAException.XA_HEURMIX:
              return TwoPhaseOutcome.HEURISTIC_HAZARD;
            case XAException.XA_HEURCOM:
              forget();
              break;
            case XAException.XA_HEURRB:
              forget();
              return TwoPhaseOutcome.ONE_PHASE_ERROR;
            case XAException.XA_RBROLLBACK:
            case XAException.XA_RBCOMMFAIL:
            case XAException.XA_RBDEADLOCK:
            case XAException.XA_RBINTEGRITY:
            case XAException.XA_RBOTHER:
            case XAException.XA_RBPROTO:
            case XAException.XA_RBTIMEOUT:
            case XAException.XA_RBTRANSIENT:
            case XAException.XAER_RMERR:
              return TwoPhaseOutcome.ONE_PHASE_ERROR;
            case XAException.XAER_NOTA:
              return TwoPhaseOutcome
                  .HEURISTIC_HAZARD; // something committed or rolled back without asking us!
            case XAException.XAER_INVAL: // resource manager failed, did it rollback?
              return TwoPhaseOutcome.HEURISTIC_HAZARD;
            case XAException.XA_RETRY: // XA does not allow this to be thrown for 1PC!
            case XAException.XAER_PROTO:
              return TwoPhaseOutcome.ONE_PHASE_ERROR; // assume rollback
            case XAException.XAER_RMFAIL:
            default:
              _committed = true; // will cause log to be rewritten
              return TwoPhaseOutcome.FINISH_ERROR; // recovery should retry
          }
        } catch (Exception e2) {
          jtaLogger.i18NLogger.warn_resources_arjunacore_opcerror(
              XAHelper.xidToString(_tranID), _theXAResource.toString(), "-", e2);

          return TwoPhaseOutcome.FINISH_ERROR;
        } finally {
          removeConnection();
        }
      } else return TwoPhaseOutcome.ONE_PHASE_ERROR;
    }

    if (commit) return TwoPhaseOutcome.FINISH_OK;
    else return TwoPhaseOutcome.FINISH_ERROR;
  }

  public boolean forgetHeuristic() {
    if (jtaLogger.logger.isTraceEnabled()) {
      jtaLogger.logger.trace("XAResourceRecord.forget for " + this);
    }

    forget();

    removeConnection();

    return true;
  }

  private void forget() {
    if ((_theXAResource != null) && (_tranID != null)) {
      _heuristic = TwoPhaseOutcome.FINISH_OK;

      try {
        _theXAResource.forget(_tranID);
      } catch (Exception e) {
      }
    }
  }

  /*
   * Independant recovery cannot occur. Must be driven by the recovery of the
   * local transaction, i.e., top-down recovery.
   */

  protected int recover() {
    if (jtaLogger.logger.isTraceEnabled()) {
      jtaLogger.logger.trace("XAResourceRecord.recover");
    }

    if (_committed) {
      /*
       * A previous commit attempt failed, but we know the intention
       * was to commit. So let's try again.
       */

      if (topLevelCommit() == TwoPhaseOutcome.FINISH_OK) return XARecoveryResource.RECOVERED_OK;
      else return XARecoveryResource.FAILED_TO_RECOVER;
    } else return XARecoveryResource.WAITING_FOR_RECOVERY;
  }

  public boolean save_state(OutputObjectState os, int t) {
    boolean res = false;

    try {
      os.packInt(_heuristic);
      os.packBoolean(_committed);

      /*
       * Since we don't know what type of Xid we are using, leave it up to
       * XID to pack.
       */

      XidImple.pack(os, _tranID);

      /*
       * If no recovery object set then rely upon object serialisation!
       */

      if (_recoveryObject == null) {
        os.packInt(RecoverableXAConnection.OBJECT_RECOVERY);

        os.packString(_productName);
        os.packString(_productVersion);
        os.packString(_jndiName);

        if (_theXAResource instanceof Serializable) {
          try {
            ByteArrayOutputStream s = new ByteArrayOutputStream();
            ObjectOutputStream o = new ObjectOutputStream(s);

            o.writeObject(_theXAResource);
            o.close();

            os.packBoolean(true);
            String name = _theXAResource.getClass().getName();
            os.packString(name);

            os.packBytes(s.toByteArray());
          } catch (NotSerializableException ex) {
            jtaLogger.i18NLogger.warn_resources_arjunacore_savestate();

            return false;
          }
        } else {
          // have to rely upon XAResource.recover!

          os.packBoolean(false);
        }
      } else {
        os.packInt(RecoverableXAConnection.AUTO_RECOVERY);
        os.packString(_recoveryObject.getClass().getName());

        _recoveryObject.packInto(os);
      }

      res = true;
    } catch (Exception e) {
      jtaLogger.i18NLogger.warn_resources_arjunacore_savestateerror(
          _theXAResource.toString(), XAHelper.xidToString(_tranID), e);

      res = false;
    }

    if (res) res = super.save_state(os, t);

    return res;
  }

  public boolean restore_state(InputObjectState os, int t) {
    boolean res = false;

    try {
      _heuristic = os.unpackInt();
      _committed = os.unpackBoolean();

      _tranID = XidImple.unpack(os);

      _theXAResource = null;
      _recoveryObject = null;

      if (os.unpackInt() == RecoverableXAConnection.OBJECT_RECOVERY) {
        _productName = os.unpackString();
        _productVersion = os.unpackString();
        _jndiName = os.unpackString();

        boolean haveXAResource = os.unpackBoolean();

        if (haveXAResource) {
          try {
            // Read the classname of the serialized XAResource
            String className = os.unpackString();

            byte[] b = os.unpackBytes();

            ByteArrayInputStream s = new ByteArrayInputStream(b);
            ObjectInputStream o = new ObjectInputStream(s);

            // Give the list of deserializers a chance to deserialize the record
            boolean deserialized = false;
            Iterator<SerializableXAResourceDeserializer> iterator =
                getXAResourceDeserializers().iterator();
            while (iterator.hasNext()) {
              SerializableXAResourceDeserializer proxyXAResourceDeserializer = iterator.next();
              if (proxyXAResourceDeserializer.canDeserialze(className)) {
                _theXAResource = proxyXAResourceDeserializer.deserialze(o);
                deserialized = true;
                break;
              }
            }

            // Give it a go ourselves
            if (!deserialized) {
              _theXAResource = (XAResource) o.readObject();
            }
            o.close();

            if (jtaLogger.logger.isTraceEnabled()) {
              jtaLogger.logger.trace("XAResourceRecord.restore_state - XAResource de-serialized");
            }
          } catch (Exception ex) {
            // not serializable in the first place!

            jtaLogger.i18NLogger.warn_resources_arjunacore_restorestate(ex);

            return false;
          }
        } else {
          /*
           * Lookup new XAResource via XARecoveryModule if possible.
           */

          _theXAResource = getNewXAResource();

          if (_theXAResource == null) {
            jtaLogger.i18NLogger.warn_resources_arjunacore_norecoveryxa(toString());

            /*
             * Don't prevent tx from activating because there may be
             * other participants that can still recover. Plus, we will
             * try to get a new XAResource later for this instance.
             */

            res = true;
          }
        }
      } else {
        String creatorName = os.unpackString();

        _recoveryObject =
            ClassloadingUtility.loadAndInstantiateClass(
                RecoverableXAConnection.class, creatorName, null);
        if (_recoveryObject == null) {
          throw new ClassNotFoundException();
        }

        _recoveryObject.unpackFrom(os);
        _theXAResource = _recoveryObject.getResource();

        if (jtaLogger.logger.isTraceEnabled()) {
          jtaLogger.logger.trace(
              "XAResourceRecord.restore_state - XAResource got from " + creatorName);
        }
      }

      res = true;
    } catch (Exception e) {
      jtaLogger.i18NLogger.warn_resources_arjunacore_restorestateerror(
          _theXAResource.toString(), XAHelper.xidToString(_tranID), e);

      res = false;
    }

    if (res) res = super.restore_state(os, t);

    return res;
  }

  public String type() {
    return XAResourceRecord.typeName();
  }

  public static String typeName() {
    return "/StateManager/AbstractRecord/XAResourceRecord";
  }

  public boolean doSave() {
    return true;
  }

  public void merge(AbstractRecord a) {}

  public void alter(AbstractRecord a) {}

  public boolean shouldAdd(AbstractRecord a) {
    return false;
  }

  public boolean shouldAlter(AbstractRecord a) {
    return false;
  }

  public boolean shouldMerge(AbstractRecord a) {
    return false;
  }

  public boolean shouldReplace(AbstractRecord a) {
    return false;
  }

  /**
   * Returns the resource manager product name.
   *
   * @return the product name
   */
  public String getProductName() {
    return _productName;
  }

  /**
   * Sets the resource manager product name.
   *
   * @param productName the product name
   */
  public void setProductName(String productName) {
    this._productName = productName;
  }

  /**
   * Returns the resource manager product version.
   *
   * @return the product version
   */
  public String getProductVersion() {
    return _productVersion;
  }

  /**
   * Sets the resource manager product version.
   *
   * @param productVersion the product version
   */
  public void setProductVersion(String productVersion) {
    this._productVersion = productVersion;
  }

  /**
   * Returns the resource manager JNDI name for e.g. xa datasource. Note this is not used for
   * lookup, only for information.
   *
   * @return the JNDI name.
   */
  public String getJndiName() {
    return _jndiName;
  }

  /**
   * Sets the resource manager JNDI name. Note this is not used for lookup, only for information.
   *
   * @param jndiName the JNDI name.
   */
  public void setJndiName(String jndiName) {
    this._jndiName = jndiName;
  }

  public XAResourceRecord() {
    super();

    _theXAResource = null;
    _recoveryObject = null;
    _tranID = null;
    _prepared = true;
    _heuristic = TwoPhaseOutcome.FINISH_OK;
    _valid = true;
    _theTransaction = null;
    _recovered = true;
  }

  public XAResourceRecord(Uid u) {
    super(u, null, ObjectType.ANDPERSISTENT);

    _theXAResource = null;
    _recoveryObject = null;
    _tranID = null;
    _prepared = true;
    _heuristic = TwoPhaseOutcome.FINISH_OK;
    _valid = true;
    _theTransaction = null;
    _recovered = true;
  }

  public String toString() {
    return "XAResourceRecord < resource:"
        + _theXAResource
        + ", txid:"
        + _tranID
        + ", heuristic: "
        + TwoPhaseOutcome.stringForm(_heuristic)
        + ((_productName != null && _productVersion != null)
            ? ", product: " + _productName + "/" + _productVersion
            : "")
        + ((_jndiName != null) ? ", jndiName: " + _jndiName : "")
        + " "
        + super.toString()
        + " >";
  }

  private List<SerializableXAResourceDeserializer> getXAResourceDeserializers() {
    if (serializableXAResourceDeserializers != null) {
      return serializableXAResourceDeserializers;
    }
    synchronized (this) {
      if (serializableXAResourceDeserializers != null) {
        return serializableXAResourceDeserializers;
      }
      serializableXAResourceDeserializers = new ArrayList<SerializableXAResourceDeserializer>();
      for (RecoveryModule recoveryModule : RecoveryManager.manager().getModules()) {
        if (recoveryModule instanceof XARecoveryModule) {
          XARecoveryModule xaRecoveryModule = (XARecoveryModule) recoveryModule;
          serializableXAResourceDeserializers.addAll(
              xaRecoveryModule.getSeriablizableXAResourceDeserializers());
          return serializableXAResourceDeserializers;
        }
      }
    }
    return serializableXAResourceDeserializers;
  }

  /**
   * This routine finds the new XAResource for the transaction that used the old resource we
   * couldn't serialize. It does this by looking up the XARecoveryModule in the recovery manager and
   * asking it for the XAResource. The recovery manager will then look through its list of
   * registered XARecoveryResource implementations for the appropriate instance. If the
   * XARecoveryModule hasn't been initialised yet then this routine will fail, but on the next scan
   * it should work.
   */
  private final XAResource getNewXAResource() {
    RecoveryManager recMan = RecoveryManager.manager();
    Vector recoveryModules = recMan.getModules();

    if (recoveryModules != null) {
      Enumeration modules = recoveryModules.elements();

      while (modules.hasMoreElements()) {
        RecoveryModule m = (RecoveryModule) modules.nextElement();

        if (m instanceof XARecoveryModule) {
          /*
           * Blaargh! There are better ways to do this!
           */

          return ((XARecoveryModule) m).getNewXAResource(this);
        }
      }
    }

    return null;
  }

  private final void removeConnection() {
    /*
     * Should only be called once. Remove the connection so that user can
     * reuse the driver as though it were fresh (e.g., can do read only
     * optimisation).
     */

    if (_recoveryObject != null) {
      _recoveryObject.close();
      _recoveryObject = null;
    }

    if (_theTransaction != null) _theTransaction = null;
  }

  /*
   * Ask the transaction whether or not this XAResource is still associated
   * with the thread, i.e., has end already been called on it?
   */

  private final boolean endAssociation() {
    boolean doEnd = true;

    if (_theTransaction != null) {
      int txInfo = _theTransaction.getXAResourceState(_theXAResource);
      if ((txInfo == TxInfo.NOT_ASSOCIATED) || (txInfo == TxInfo.FAILED)) {
        // end has been called so we don't need to do it again!

        doEnd = false;
      }
    } else doEnd = false; // Recovery mode

    return doEnd;
  }

  protected XAResource _theXAResource;

  private RecoverableXAConnection _recoveryObject;
  private Xid _tranID;

  private boolean _prepared;

  private boolean _valid;

  private int _heuristic;

  private boolean _committed = false; // try to optimize recovery

  private TransactionImple _theTransaction;
  private boolean _recovered = false;

  // extra metadata from the wrapper, if present
  private String _productName;
  private String _productVersion;
  private String _jndiName;
  private static final XAResourceRecordWrappingPlugin _xaResourceRecordWrappingPlugin =
      jtaPropertyManager.getJTAEnvironmentBean().getXAResourceRecordWrappingPlugin();

  private static final boolean _rollbackOptimization =
      jtaPropertyManager.getJTAEnvironmentBean().isXaRollbackOptimization();

  /*
   * WARNING: USE WITH EXTEREME CARE!!
   *
   * This assumes that if there is no XAResource that can deal with an Xid
   * after recovery, then we failed after successfully committing the transaction
   * but before updating the log. In which case we just need to ignore this
   * resource and remove the entry from the log.
   *
   * BUT if not all XAResourceRecovery instances are correctly implemented
   * (or present) we may end up removing participants that have not been dealt
   * with. Hence USE WITH EXTREME CARE!!
   */
  private static final boolean _assumedComplete =
      jtaPropertyManager.getJTAEnvironmentBean().isXaAssumeRecoveryComplete();

  private List<SerializableXAResourceDeserializer> serializableXAResourceDeserializers;
}