/**
   * 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. flag must be one of TMSTARTRSCAN, TMENDRSCAN, TMNOFLAGS or TMSTARTTRSCAN |
   * TMENDRSCAN 2. if flag isn't TMSTARTRSCAN or TMSTARTRSCAN | TMENDRSCAN, a recovery scan must be
   * in progress
   *
   * <p>Postconditions: 1. list of prepared xids is returned
   */
  public Xid[] recover(int flag) throws XAException {
    // Check preconditions
    if (flag != TMSTARTRSCAN
        && flag != TMENDRSCAN
        && flag != TMNOFLAGS
        && flag != (TMSTARTRSCAN | TMENDRSCAN))
      throw new PGXAException(GT.tr("Invalid flag"), XAException.XAER_INVAL);

    // We don't check for precondition 2, because we would have to add some additional state in
    // this object to keep track of recovery scans.

    // All clear. We return all the xids in the first TMSTARTRSCAN call, and always return
    // an empty array otherwise.
    if ((flag & TMSTARTRSCAN) == 0) return new Xid[0];
    else {
      try {
        Statement stmt = conn.createStatement();
        try {
          // If this connection is simultaneously used for a transaction,
          // this query gets executed inside that transaction. It's OK,
          // except if the transaction is in abort-only state and the
          // backed refuses to process new queries. Hopefully not a problem
          // in practise.
          ResultSet rs = stmt.executeQuery("SELECT gid FROM pg_prepared_xacts");
          LinkedList l = new LinkedList();
          while (rs.next()) {
            Xid recoveredXid = RecoveredXid.stringToXid(rs.getString(1));
            if (recoveredXid != null) l.add(recoveredXid);
          }
          rs.close();

          return (Xid[]) l.toArray(new Xid[l.size()]);
        } finally {
          stmt.close();
        }
      } catch (SQLException ex) {
        throw new PGXAException(GT.tr("Error during recover"), ex, XAException.XAER_RMERR);
      }
    }
  }
  /**
   * 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);
    }
  }