/**
   * Preconditions: 1. xid must be in prepared state in the server
   *
   * <p>Implementation deficiency preconditions: 1. Connection must be in idle state
   *
   * <p>Postconditions: 1. Transaction is committed
   */
  private void commitPrepared(Xid xid) throws XAException {
    try {
      // Check preconditions. The connection mustn't be used for another
      // other XA or local transaction, or the COMMIT PREPARED command
      // would mess it up.
      if (state != STATE_IDLE || conn.getTransactionState() != ProtocolConnection.TRANSACTION_IDLE)
        throw new PGXAException(
            GT.tr("Not implemented: 2nd phase commit must be issued using an idle connection"),
            XAException.XAER_RMERR);

      String s = RecoveredXid.xidToString(xid);

      localAutoCommitMode = conn.getAutoCommit();
      conn.setAutoCommit(true);
      Statement stmt = conn.createStatement();
      try {
        stmt.executeUpdate("COMMIT PREPARED '" + s + "'");
      } finally {
        stmt.close();
        conn.setAutoCommit(localAutoCommitMode);
      }
    } catch (SQLException ex) {
      throw new PGXAException(
          GT.tr("Error committing prepared transaction"), ex, XAException.XAER_RMERR);
    }
  }
  /**
   * Preconditions: 1. xid is known to the RM or it's in prepared state
   *
   * <p>Implementation deficiency preconditions: 1. xid must be associated with this connection if
   * it's not in prepared state.
   *
   * <p>Postconditions: 1. Transaction is rolled back and disassociated from connection
   */
  public void rollback(Xid xid) throws XAException {
    if (logger.logDebug()) debug("rolling back xid = " + xid);

    // We don't explicitly check precondition 1.

    try {
      if (currentXid != null && xid.equals(currentXid)) {
        state = STATE_IDLE;
        currentXid = null;
        conn.rollback();
        conn.setAutoCommit(localAutoCommitMode);
      } else {
        String s = RecoveredXid.xidToString(xid);

        conn.setAutoCommit(true);
        Statement stmt = conn.createStatement();
        try {
          stmt.executeUpdate("ROLLBACK PREPARED '" + s + "'");
        } finally {
          stmt.close();
        }
      }
    } catch (SQLException ex) {
      throw new PGXAException(
          GT.tr("Error rolling back prepared transaction"), ex, XAException.XAER_RMERR);
    }
  }
  /**
   * Preconditions: 1. xid must in ended state.
   *
   * <p>Implementation deficiency preconditions: 1. this connection must have been used to run the
   * transaction
   *
   * <p>Postconditions: 1. Transaction is committed
   */
  private void commitOnePhase(Xid xid) throws XAException {
    try {
      // Check preconditions
      if (currentXid == null || !currentXid.equals(xid)) {
        // In fact, we don't know if xid is bogus, or if it just wasn't associated with this
        // connection.
        // Assume it's our fault.
        throw new PGXAException(
            GT.tr(
                "Not implemented: one-phase commit must be issued using the same connection that was used to start it"),
            XAException.XAER_RMERR);
      }
      if (state != STATE_ENDED)
        throw new PGXAException(GT.tr("commit called before end"), XAException.XAER_PROTO);

      // Preconditions are met. Commit
      state = STATE_IDLE;
      currentXid = null;

      conn.commit();
      conn.setAutoCommit(localAutoCommitMode);
    } catch (SQLException ex) {
      throw new PGXAException(GT.tr("Error during one-phase commit"), ex, XAException.XAER_RMERR);
    }
  }
  /**
   * Preconditions: 1. flags must be one of TMNOFLAGS, TMRESUME or TMJOIN 2. xid != null 3.
   * connection must not be associated with a transaction 4. the TM hasn't seen the xid before
   *
   * <p>Implementation deficiency preconditions: 1. TMRESUME not supported. 2. if flags is TMJOIN,
   * we must be in ended state, and xid must be the current transaction 3. unless flags is TMJOIN,
   * previous transaction using the connection must be committed or prepared or rolled back
   *
   * <p>Postconditions: 1. Connection is associated with the transaction
   */
  public void start(Xid xid, int flags) throws XAException {
    if (logger.logDebug()) debug("starting transaction xid = " + xid);

    // Check preconditions
    if (flags != XAResource.TMNOFLAGS && flags != XAResource.TMRESUME && flags != XAResource.TMJOIN)
      throw new PGXAException(GT.tr("Invalid flags"), XAException.XAER_INVAL);

    if (xid == null) throw new PGXAException(GT.tr("xid must not be null"), XAException.XAER_INVAL);

    if (state == STATE_ACTIVE)
      throw new PGXAException(
          GT.tr("Connection is busy with another transaction"), XAException.XAER_PROTO);

    // We can't check precondition 4 easily, so we don't. Duplicate xid will be catched in prepare
    // phase.

    // Check implementation deficiency preconditions
    if (flags == TMRESUME)
      throw new PGXAException(GT.tr("suspend/resume not implemented"), XAException.XAER_RMERR);

    // It's ok to join an ended transaction. WebLogic does that.
    if (flags == TMJOIN) {
      if (state != STATE_ENDED)
        throw new PGXAException(
            GT.tr("Transaction interleaving not implemented"), XAException.XAER_RMERR);

      if (!xid.equals(currentXid))
        throw new PGXAException(
            GT.tr("Transaction interleaving not implemented"), XAException.XAER_RMERR);
    } else if (state == STATE_ENDED)
      throw new PGXAException(
          GT.tr("Transaction interleaving not implemented"), XAException.XAER_RMERR);

    try {
      localAutoCommitMode = conn.getAutoCommit();
      conn.setAutoCommit(false);
    } catch (SQLException ex) {
      throw new PGXAException(GT.tr("Error disabling autocommit"), ex, XAException.XAER_RMERR);
    }

    // Preconditions are met, Associate connection with the transaction
    state = STATE_ACTIVE;
    currentXid = xid;
  }
  /**
   * Preconditions: 1. xid != null 2. xid is in ended state
   *
   * <p>Implementation deficiency preconditions: 1. xid was associated with this connection
   *
   * <p>Postconditions: 1. Transaction is prepared
   */
  public int prepare(Xid xid) throws XAException {
    if (logger.logDebug()) debug("preparing transaction xid = " + xid);

    // Check preconditions
    if (!currentXid.equals(xid)) {
      throw new PGXAException(
          GT.tr(
              "Not implemented: Prepare must be issued using the same connection that started the transaction"),
          XAException.XAER_RMERR);
    }
    if (state != STATE_ENDED)
      throw new PGXAException(GT.tr("Prepare called before end"), XAException.XAER_INVAL);

    state = STATE_IDLE;
    currentXid = null;

    if (!conn.haveMinimumServerVersion("8.1"))
      throw new PGXAException(
          GT.tr("Server versions prior to 8.1 do not support two-phase commit."),
          XAException.XAER_RMERR);

    try {
      String s = RecoveredXid.xidToString(xid);

      Statement stmt = conn.createStatement();
      try {
        stmt.executeUpdate("PREPARE TRANSACTION '" + s + "'");
      } finally {
        stmt.close();
      }
      conn.setAutoCommit(localAutoCommitMode);

      return XA_OK;
    } catch (SQLException ex) {
      throw new PGXAException(GT.tr("Error preparing transaction"), ex, XAException.XAER_RMERR);
    }
  }