/**
   * Try to process a single transaction. Up to {@code MAX_ACCEPTABLE_FAIL_COUNT} attempts will be
   * made to process the transaction in cases where I/O errors or non-protocol server errors occur
   * during processing. Use of the underlying session is protected against concurrent use by other
   * threads by using the getSession/releaseSession features of this SessionManager's {@link
   * com.ausregistry.jtoolkit2.session.SessionPool}. This method guarantees that the session used
   * will be returned to the pool before the method returns.
   *
   * @throws FatalSessionException No session could be acquired to process the transaction. Check
   *     the exception message and log records for details.
   * @throws IOException Every attempt to execute the transaction command failed due to an
   *     IOException. This is the last such IOException.
   * @throws ParsingException Parsing of the response failed. Check the exception message for the
   *     cause.
   * @throws CommandFailedException The acceptable limit on the number of failed commands due to
   *     server error was exceeded in trying to process the command. This probably indicates a
   *     server limitation related to the command being processed.
   * @throws IllegalStateException The SessionManager had been shutdown or not started up prior to
   *     invoking this method.
   */
  @Override
  public void execute(Transaction tx)
      throws FatalSessionException, IOException, ParsingException, CommandFailedException,
          IllegalStateException {

    debugLogger.finest("enter");

    if (state == SMState.STOPPED) {
      throw new IllegalStateException();
    }

    Command cmd = tx.getCommand();
    Response response = tx.getResponse();

    int failCount = 0;
    boolean isExecuted = false;
    Session session = null;

    // if only processing one transaction, get a new session for each
    // attempt in case the session fails mid-transaction.
    while (!isExecuted && state != SMState.STOPPED) {
      try {
        session = sessionPool.getSession(cmd.getCommandType());
        StatsManager statsManager = session.getStatsManager();
        statsManager.incCommandCounter(cmd.getCommandType());

        tx.start();
        session.write(cmd);
        isExecuted = true;
        session.read(response);
        tx.setState(TransactionState.PROCESSED);
        statsManager.recordResponseTime(cmd.getCommandType(), tx.getResponseTime());

        Result[] results = response.getResults();
        assert results != null;
        if (results != null) {
          for (Result result : results) {
            assert result != null;
            statsManager.incResultCounter(result.getResultCode());
            int code = result.getResultCode();

            switch (code) {
              case ResultCode.CMD_FAILED:
                throw new CommandFailedException();
              case ResultCode.CMD_FAILED_CLOSING:
              default:
                throw new CommandFailedException();
            }
          }
        }
      } catch (CommandFailedException cfe) {
        userLogger.warning(cfe.getMessage());

        if (state != SMState.STOPPED && failCount < MAX_ACCEPTABLE_FAIL_COUNT) {

          failCount++;
        } else {
          throw cfe;
        }
      } catch (IOException ioe) {
        userLogger.warning(ioe.getMessage());
        userLogger.warning("net.socket.closed");
        session.close();
        if (state != SMState.STOPPED && failCount < MAX_ACCEPTABLE_FAIL_COUNT) {
          failCount++;
        } else {
          throw ioe;
        }
      } catch (InterruptedException ie) {
        // if interrupted by shutdown, then started will be false
        // Note: this still enters the finally block
        continue;
      } catch (SessionConfigurationException sce) {
        throw new FatalSessionException(sce);
      } catch (SessionOpenException soe) {
        throw new FatalSessionException(soe);
      } finally {
        sessionPool.releaseSession(session);
      }
    }

    if (!isExecuted && state == SMState.STOPPED) {
      throw new IllegalStateException();
    }

    debugLogger.finest("exit");
  }