/**
   * Load any available events from the database up to the configured maximum.
   *
   * @param criteria An event containing the event-selection criteria.
   * @return The next available event on the queue.
   * @exception FrameworkException Thrown on errors.
   */
  private void loadFromDatabase(Event criteria) throws FrameworkException {
    Debug.log(Debug.MSG_STATUS, "QUEUE OPERATION: Loading events from database into queue ...");

    if (!StringUtils.hasValue(criteria.channelName)) {
      throw new FrameworkException(
          "ERROR: Event channel name is a required queue search criteria.");
    }

    Connection dbConn = null;

    PreparedStatement ps = null;

    long startTime = -1;

    if (Debug.isLevelEnabled(Debug.BENCHMARK)) startTime = System.currentTimeMillis();

    try {
      dbConn = DBConnectionPool.getInstance().acquireConnection();

      if (Debug.isLevelEnabled(Debug.DB_DATA))
        Debug.log(Debug.DB_DATA, "\n" + LINE + "\nExecuting SQL:\n" + QUERY_EVENT_SQL);

      if (Debug.isLevelEnabled(Debug.DB_DATA))
        Debug.log(
            Debug.DB_DATA, "Criteria used in query against database:\n" + criteria.describe());

      ps = dbConn.prepareStatement(QUERY_EVENT_SQL);

      ps.setString(1, criteria.channelName);

      ResultSet rs = ps.executeQuery();

      for (int counter = 0; (counter < maxDatabaseEventLoadSize) && rs.next(); counter++) {
        Event event = new Event();

        event.channelName = rs.getString(CHANNEL_NAME_COL);
        event.id = rs.getInt(ID_COL);

        event.message = DBLOBUtils.getCLOB(rs, MESSAGE_COL);

        if (Debug.isLevelEnabled(Debug.MSG_LIFECYCLE))
          Debug.log(Debug.MSG_LIFECYCLE, "Event contents:\n" + event.message);

        event.arrivalTime = rs.getTimestamp(ARRIVAL_TIME_COL);
        event.errorStatus = rs.getString(ERROR_STATUS_COL);
        event.errorCount = rs.getInt(ERROR_COUNT_COL);
        event.lastErrorMessage = rs.getString(LAST_ERROR_MESSAGE_COL);
        event.lastErrorTime = rs.getTimestamp(LAST_ERROR_TIME_COL);

        // Add item to in-memory buffer.
        if (Debug.isLevelEnabled(Debug.MSG_STATUS))
          Debug.log(
              Debug.MSG_STATUS,
              "Adding event [" + event.describe() + "] to in-memory queue buffer.");

        queue.add(event);

        if (Debug.isLevelEnabled(Debug.MSG_STATUS))
          Debug.log(Debug.MSG_STATUS, "In-memory queue buffer size [" + queue.size() + "].");
      }

      if (Debug.isLevelEnabled(Debug.DB_DATA)) Debug.log(Debug.DB_DATA, "\n" + LINE);
    } catch (SQLException sqle) {
      throw new DatabaseException(
          "ERROR: Could not execute SQL statement:\n" + DBInterface.getSQLErrorMessage(sqle));
    } catch (Exception e) {
      throw new DatabaseException("ERROR: Could not execute SQL statement:\n" + e.toString());
    } finally {
      releaseDatabaseResources(dbConn, ps);

      if (Debug.isLevelEnabled(Debug.BENCHMARK) && (startTime > 0)) {
        long stopTime = System.currentTimeMillis();

        Debug.log(
            Debug.BENCHMARK,
            "ELAPSED TIME ["
                + (stopTime - startTime)
                + "] msec:  "
                + "SQL: Time to load event(s) from PersistentEvent database table.");
      }
    }
  }
  /**
   * Update the given event as indicated.
   *
   * @param event The event to update.
   * @param eventStatus The event delivery status.
   * @exception FrameworkException Thrown on errors.
   */
  public void update(Event event, EventStatus eventStatus) throws FrameworkException {
    if (Debug.isLevelEnabled(Debug.MSG_STATUS))
      Debug.log(
          Debug.MSG_STATUS,
          "QUEUE OPERATION: Updating database queue using event status ["
              + eventStatus.name
              + "] ...");

    // If no consumers were available, the event delivery wasn't attempted so leave
    // the queue in its current state.
    if (eventStatus == EventStatus.NO_CONSUMERS_AVAILABLE) {
      Debug.log(
          Debug.MSG_STATUS, "Skipping queue update, as no consumers were available to process it.");

      return;
    }

    Connection dbConn = null;

    PreparedStatement ps = null;

    long startTime = -1;

    if (Debug.isLevelEnabled(Debug.BENCHMARK)) startTime = System.currentTimeMillis();

    try {
      dbConn = DBConnectionPool.getInstance().acquireConnection();

      if (eventStatus == EventStatus.DELIVERY_SUCCESSFUL) {
        // If the event was successfully delivered, update status in database to delivered.
        if (Debug.isLevelEnabled(Debug.DB_DATA))
          Debug.log(Debug.DB_DATA, "\n" + LINE + "\nExecuting SQL:\n" + UPDATE_EVENT_SUCCESS_SQL);

        ps = dbConn.prepareStatement(UPDATE_EVENT_SUCCESS_SQL);

        java.sql.Timestamp ts = new java.sql.Timestamp(System.currentTimeMillis());

        ps.setTimestamp(1, ts);
        ps.setString(2, event.channelName);
        ps.setInt(3, event.id);
      } else if (eventStatus == EventStatus.DELIVERY_FAILED) {
        // If the event delivery failed, we mark it as failed in the database.
        if (Debug.isLevelEnabled(Debug.DB_DATA))
          Debug.log(Debug.DB_DATA, "\n" + LINE + "\nExecuting SQL:\n" + UPDATE_EVENT_ERROR_SQL);

        // Truncate error message if it's larger than database column.
        if ((event.lastErrorMessage != null)
            && (event.lastErrorMessage.length() > MAX_ERROR_MESSAGE_LENGTH))
          event.lastErrorMessage = event.lastErrorMessage.substring(0, MAX_ERROR_MESSAGE_LENGTH);

        event.lastErrorTime = new java.sql.Timestamp(System.currentTimeMillis());

        ps = dbConn.prepareStatement(UPDATE_EVENT_ERROR_SQL);

        ps.setTimestamp(1, event.lastErrorTime);
        ps.setString(2, event.lastErrorMessage);
        ps.setString(3, event.channelName);
        ps.setInt(4, event.id);
      } else {
        throw new FrameworkException(
            "ERROR: Invalid event update type [" + eventStatus.name + "].");
      }

      if (Debug.isLevelEnabled(Debug.DB_DATA))
        Debug.log(Debug.DB_DATA, "Event being operated on in database:\n" + event.describe());

      int numRows = ps.executeUpdate();

      if (numRows > 1) {
        String errMsg = "Execution of update SQL statement affected [" + numRows + "] rows.";

        Debug.error(errMsg);

        throw new FrameworkException(errMsg);
      }

      DBConnectionPool.getInstance().commit(dbConn);

      if (Debug.isLevelEnabled(Debug.DB_DATA))
        Debug.log(Debug.DB_DATA, "Successfully committed SQL operation.\n" + LINE);

      // At this point, the event should be removed from the in-memory buffer of events as well,
      // irrespective of processing outcome.
      if (Debug.isLevelEnabled(Debug.MSG_STATUS))
        Debug.log(
            Debug.MSG_STATUS,
            "Removing event [" + event.describe() + "] from in-memory queue buffer.");

      boolean removed = queue.remove(event);

      if (Debug.isLevelEnabled(Debug.MSG_STATUS))
        Debug.log(
            Debug.MSG_STATUS,
            "Event removed? ["
                + removed
                + "].  In-memory queue buffer size ["
                + queue.size()
                + "].");
    } catch (SQLException sqle) {
      throw new DatabaseException(
          "ERROR: Could not execute SQL statement:\n" + DBInterface.getSQLErrorMessage(sqle));
    } catch (Exception e) {
      throw new DatabaseException("ERROR: Could not execute SQL statement:\n" + e.toString());
    } finally {
      releaseDatabaseResources(dbConn, ps);

      if (Debug.isLevelEnabled(Debug.BENCHMARK) && (startTime > 0)) {
        long stopTime = System.currentTimeMillis();

        Debug.log(
            Debug.BENCHMARK,
            "ELAPSED TIME ["
                + (stopTime - startTime)
                + "] msec:  "
                + "SQL: Time to update event in PersistentEvent database table.");
      }
    }
  }