/**
  * Retrieve a list of schemaLocation values associated with the specified oaiIdentifier.
  *
  * @param oaiIdentifier the OAI identifier
  * @return a List<String> containing schemaLocation Strings
  * @throws OAIInternalServerError signals an http status code 500 problem
  * @throws IdDoesNotExistException the specified oaiIdentifier can't be found
  * @throws NoMetadataFormatsException the specified oaiIdentifier was found but the item is
  *     flagged as deleted and thus no schemaLocations (i.e. metadataFormats) can be produced.
  */
 public List<String> getSchemaLocations(String oaiIdentifier)
     throws OAIInternalServerError, IdDoesNotExistException, NoMetadataFormatsException {
   StatementResultSet stmtRs = null;
   try {
     stmtRs = new StatementResultSet(populateIdentifierQuery(oaiIdentifier));
     /*
      * Let your recordFactory decide which schemaLocations
      * (i.e. metadataFormats) it can produce from the record.
      * Doing so will preserve the separation of database access
      * (which happens here) from the record content interpretation
      * (which is the responsibility of the RecordFactory implementation).
      */
     if (!stmtRs.next()) {
       throw new IdDoesNotExistException(oaiIdentifier);
     } else {
       /* Make sure the identifierQuery returns the columns you need
        * (if any) to determine the supported schemaLocations for this item */
       Map<String, Object> nativeItem = stmtRs.getColumnValues();
       return getRecordFactory().getSchemaLocations(nativeItem);
     }
   } catch (SQLException e) {
     LOGGER.error("An Exception occured", e);
     throw new OAIInternalServerError(e.getMessage());
   } finally {
     try {
       if (stmtRs != null) {
         stmtRs.close();
       }
     } catch (SQLException e) {
       LOGGER.error("An Exception occured", e);
       throw new OAIInternalServerError(e.getMessage());
     }
   }
 }
 /**
  * get an Iterator containing the abouts for the nativeItem
  *
  * @param nativeItem
  * @return an Iterator containing the list of about values for this nativeItem
  */
 private Iterator getAbouts(Map<String, Object> nativeItem) throws OAIInternalServerError {
   StatementResultSet stmtRs = null;
   try {
     List<String> abouts = new ArrayList<String>();
     if (aboutQuery != null) {
       RecordFactory rf = getRecordFactory();
       String oaiIdentifier = rf.getOAIIdentifier(nativeItem);
       stmtRs = new StatementResultSet(populateAboutQuery(oaiIdentifier));
       while (stmtRs.next()) {
         Map<String, Object> aboutMap = stmtRs.getColumnValues();
         abouts.add((String) aboutMap.get(aboutValueLabel));
       }
     }
     return abouts.iterator();
   } catch (SQLException e) {
     LOGGER.error("An Exception occured", e);
     throw new OAIInternalServerError(e.getMessage());
   } finally {
     try {
       stmtRs.close();
     } catch (SQLException e) {
       LOGGER.error("An Exception occured", e);
       throw new OAIInternalServerError(e.getMessage());
     }
   }
 }
 /**
  * Retrieve the specified metadata for the specified oaiIdentifier
  *
  * @param oaiIdentifier the OAI identifier
  * @param metadataPrefix the OAI metadataPrefix
  * @return the <record/> portion of the XML response.
  * @throws OAIInternalServerError signals an http status code 500 problem
  * @throws CannotDisseminateFormatException the metadataPrefix is not supported by the item.
  * @throws IdDoesNotExistException the oaiIdentifier wasn't found
  */
 public String getRecord(String oaiIdentifier, String metadataPrefix)
     throws OAIInternalServerError, CannotDisseminateFormatException, IdDoesNotExistException {
   StatementResultSet stmtRs = null;
   try {
     stmtRs = new StatementResultSet(populateIdentifierQuery(oaiIdentifier));
     if (!stmtRs.next()) {
       throw new IdDoesNotExistException(oaiIdentifier);
     }
     Map<String, Object> nativeItem = stmtRs.getColumnValues();
     return constructRecord(nativeItem, metadataPrefix);
   } catch (SQLException e) {
     LOGGER.error("An Exception occured", e);
     throw new OAIInternalServerError(e.getMessage());
   } finally {
     try {
       if (stmtRs != null) {
         stmtRs.close();
       }
     } catch (SQLException e) {
       LOGGER.error("An Exception occured", e);
       throw new OAIInternalServerError(e.getMessage());
     }
   }
 }
  /**
   * Retrieve a list of sets that satisfy the specified criteria
   *
   * @return a Map object containing "sets" Iterator object (contains <setSpec/> XML Strings) as
   *     well as an optional resumptionMap Map.
   * @throws OAIInternalServerError signals an http status code 500 problem
   */
  public Map listSets() throws NoSetHierarchyException, OAIInternalServerError {
    StatementResultSet stmtRs = null;
    if (setQuery == null) {
      if (sets.size() == 0) {
        throw new NoSetHierarchyException();
      }
      Map<String, Object> listSetsMap = new HashMap<String, Object>();
      listSetsMap.put("sets", sets.iterator());
      return listSetsMap;
    } else {
      purge(); // clean out old resumptionTokens
      Map<String, Object> listSetsMap = new HashMap<String, Object>();
      List<String> sets = new ArrayList<String>();

      try {
        LOGGER.debug(setQuery);

        /* Get some records from your database */
        stmtRs = new StatementResultSet(setQuery);
        stmtRs.last();
        int numRows = stmtRs.getRow();
        stmtRs.beforeFirst();
        int count;

        /* load the sets ArrayLists. */
        for (count = 0; count < maxListSize && stmtRs.next(); ++count) {
          /* Use the RecordFactory to extract header/set pairs for each item */
          Map<String, Object> nativeItem = stmtRs.getColumnValues();
          sets.add(getSetXML(nativeItem));
          LOGGER.debug("JDBCOAICatalog.listSets: adding an entry");
        }

        /* decide if you're done */
        if (count < numRows) {
          String resumptionId = getResumptionId();
          /**
           * *************************************************************** Note that storing the
           * ResultSet in the resumptionResult means the token can't be reused.
           * ***************************************************************
           */
          resumptionResults.put(resumptionId, stmtRs);

          /**
           * *************************************************************** Construct the
           * resumptionToken String however you see fit.
           * ***************************************************************
           */
          StringBuilder resumptionTokenSb = new StringBuilder();
          resumptionTokenSb.append(resumptionId);
          resumptionTokenSb.append("!");
          resumptionTokenSb.append(Integer.toString(count));
          resumptionTokenSb.append("!");
          resumptionTokenSb.append(Integer.toString(numRows));

          /**
           * *************************************************************** Use the following line
           * if you wish to include the optional resumptionToken attributes in the response.
           * Otherwise, use the line after it that I've commented out.
           * ***************************************************************
           */
          listSetsMap.put(
              "resumptionMap", getResumptionMap(resumptionTokenSb.toString(), numRows, 0));
        } else {
          stmtRs.close();
          stmtRs = null;
        }
      } catch (SQLException e) {
        if (stmtRs != null) {
          try {
            stmtRs.close();
          } catch (SQLException e1) {
            e1.printStackTrace();
          }
        }
        LOGGER.error("An Exception occured", e);
        throw new OAIInternalServerError(e.getMessage());
      }

      listSetsMap.put("sets", sets.iterator());
      return listSetsMap;
    }
  }
  /**
   * Retrieve the next set of records associated with the resumptionToken
   *
   * @param resumptionToken implementation-dependent format taken from the previous listRecords()
   *     Map result.
   * @return a Map object containing entries for "headers" and "identifiers" Iterators (both
   *     containing Strings) as well as an optional "resumptionMap" Map.
   * @throws OAIInternalServerError signals an http status code 500 problem
   * @throws BadResumptionTokenException the value of the resumptionToken argument is invalid or
   *     expired.
   */
  public Map<String, Object> listRecords(String resumptionToken)
      throws BadResumptionTokenException, OAIInternalServerError {
    Map<String, Object> listRecordsMap = new HashMap<String, Object>();
    List<String> records = new ArrayList<String>();
    purge(); // clean out old resumptionTokens

    /**
     * ******************************************************************** parse your
     * resumptionToken and look it up in the resumptionResults, if necessary
     * ********************************************************************
     */
    StringTokenizer tokenizer = new StringTokenizer(resumptionToken, "!");
    String resumptionId;
    int oldCount;
    int numRows;
    String metadataPrefix;
    StatementResultSet stmtRs = null;
    try {
      resumptionId = tokenizer.nextToken();
      oldCount = Integer.parseInt(tokenizer.nextToken());
      numRows = Integer.parseInt(tokenizer.nextToken());
      metadataPrefix = tokenizer.nextToken();
    } catch (NoSuchElementException e) {
      throw new BadResumptionTokenException();
    }

    try {
      /* Get some more records from your database */
      stmtRs = (StatementResultSet) resumptionResults.get(resumptionId);
      if (stmtRs == null) {
        throw new BadResumptionTokenException();
      }

      if (stmtRs.getRow() != oldCount) {
        stmtRs.absolute(oldCount);
      }

      int count;

      /* load the headers and identifiers ArrayLists. */
      for (count = 0; count < maxListSize && stmtRs.next(); ++count) {
        try {
          Map<String, Object> nativeItem = stmtRs.getColumnValues();
          String record = constructRecord(nativeItem, metadataPrefix);
          records.add(record);
        } catch (CannotDisseminateFormatException e) {
          /* the client hacked the resumptionToken beyond repair */
          throw new BadResumptionTokenException();
        }
      }

      /* decide if you're done */
      if (oldCount + count < numRows) {
        /**
         * *************************************************************** Construct the
         * resumptionToken String however you see fit.
         * ***************************************************************
         */
        StringBuilder resumptionTokenSb = new StringBuilder();
        resumptionTokenSb.append(resumptionId);
        resumptionTokenSb.append("!");
        resumptionTokenSb.append(Integer.toString(oldCount + count));
        resumptionTokenSb.append("!");
        resumptionTokenSb.append(Integer.toString(numRows));
        resumptionTokenSb.append("!");
        resumptionTokenSb.append(metadataPrefix);

        /**
         * *************************************************************** Use the following line if
         * you wish to include the optional resumptionToken attributes in the response. Otherwise,
         * use the line after it that I've commented out.
         * ***************************************************************
         */
        listRecordsMap.put(
            "resumptionMap", getResumptionMap(resumptionTokenSb.toString(), numRows, oldCount));
      } else {
        stmtRs.close();
        stmtRs = null;
      }
    } catch (SQLException e) {
      if (stmtRs != null) {
        try {
          stmtRs.close();
        } catch (SQLException e1) {
          e1.printStackTrace();
        }
      }
      LOGGER.error("An Exception occured", e);
      throw new OAIInternalServerError(e.getMessage());
    }

    listRecordsMap.put("records", records.iterator());
    return listRecordsMap;
  }
  /**
   * Retrieve a list of records that satisfy the specified criteria. Note, though, that unlike the
   * other OAI verb type methods implemented here, both of the listRecords methods are already
   * implemented in AbstractCatalog rather than abstracted. This is because it is possible to
   * implement ListRecords as a combination of ListIdentifiers and GetRecord combinations.
   * Nevertheless, I suggest that you override both the AbstractCatalog.listRecords methods here
   * since it will probably improve the performance if you create the response in one fell swoop
   * rather than construct it one GetRecord at a time.
   *
   * @param from beginning date using the proper granularity
   * @param until ending date using the proper granularity
   * @param set the set name or null if no such limit is requested
   * @param metadataPrefix the OAI metadataPrefix or null if no such limit is requested
   * @return a Map object containing entries for a "records" Iterator object (containing XML
   *     <record/> Strings) and an optional "resumptionMap" Map.
   * @throws OAIInternalServerError signals an http status code 500 problem
   * @throws CannotDisseminateFormatException the metadataPrefix isn't supported by the item.
   */
  public Map listRecords(String from, String until, String set, String metadataPrefix)
      throws CannotDisseminateFormatException, NoItemsMatchException, OAIInternalServerError {
    purge(); // clean out old resumptionTokens
    Map<String, Object> listRecordsMap = new HashMap<String, Object>();
    List<String> records = new ArrayList<String>();
    StatementResultSet stmtRs = null;

    try {
      stmtRs = new StatementResultSet(populateRangeQuery(from, until, set));
      stmtRs.last();
      int numRows = stmtRs.getRow();
      if (numRows == 0) {
        throw new NoItemsMatchException();
      }
      stmtRs.beforeFirst();
      int count;

      /* load the records ArrayList */
      for (count = 0; count < maxListSize && stmtRs.next(); ++count) {
        Map<String, Object> nativeItem = stmtRs.getColumnValues();
        String record = constructRecord(nativeItem, metadataPrefix);
        records.add(record);
      }

      /* decide if you're done */
      if (count < numRows) {
        String resumptionId = getResumptionId();
        resumptionResults.put(resumptionId, stmtRs);

        /**
         * *************************************************************** Construct the
         * resumptionToken String however you see fit.
         * ***************************************************************
         */
        StringBuilder resumptionTokenSb = new StringBuilder();
        resumptionTokenSb.append(resumptionId);
        resumptionTokenSb.append("!");
        resumptionTokenSb.append(Integer.toString(count));
        resumptionTokenSb.append("!");
        resumptionTokenSb.append(Integer.toString(numRows));
        resumptionTokenSb.append("!");
        resumptionTokenSb.append(metadataPrefix);

        /**
         * *************************************************************** Use the following line if
         * you wish to include the optional resumptionToken attributes in the response. Otherwise,
         * use the line after it that I've commented out.
         * ***************************************************************
         */
        listRecordsMap.put(
            "resumptionMap", getResumptionMap(resumptionTokenSb.toString(), numRows, 0));
      } else {
        stmtRs.close();
        stmtRs = null;
      }
    } catch (SQLException e) {
      if (stmtRs != null) {
        try {
          stmtRs.close();
        } catch (SQLException e1) {
          e1.printStackTrace();
        }
      }
      LOGGER.error("An Exception occured", e);
      throw new OAIInternalServerError(e.getMessage());
    }

    listRecordsMap.put("records", records.iterator());
    return listRecordsMap;
  }
  /**
   * Retrieve the next set of sets associated with the resumptionToken
   *
   * @param resumptionToken implementation-dependent format taken from the previous listSets() Map
   *     result.
   * @return a Map object containing "sets" Iterator object (contains <setSpec/> XML Strings) as
   *     well as an optional resumptionMap Map.
   * @throws BadResumptionTokenException the value of the resumptionToken is invalid or expired.
   * @throws OAIInternalServerError signals an http status code 500 problem
   */
  public Map<String, Object> listSets(String resumptionToken)
      throws OAIInternalServerError, BadResumptionTokenException {
    StatementResultSet stmtRs = null;

    if (setQuery == null) {
      throw new BadResumptionTokenException();
    } else {
      purge(); // clean out old resumptionTokens
      Map<String, Object> listSetsMap = new HashMap<String, Object>();
      List<String> sets = new ArrayList<String>();

      /**
       * ******************************************************************** parse your
       * resumptionToken and look it up in the resumptionResults, if necessary
       * ********************************************************************
       */
      StringTokenizer tokenizer = new StringTokenizer(resumptionToken, "!");
      String resumptionId;
      int oldCount;
      int numRows;
      try {
        resumptionId = tokenizer.nextToken();
        oldCount = Integer.parseInt(tokenizer.nextToken());
        numRows = Integer.parseInt(tokenizer.nextToken());
      } catch (NoSuchElementException e) {
        throw new BadResumptionTokenException();
      }

      try {
        /* Get some more records from your database */
        stmtRs = (StatementResultSet) resumptionResults.get(resumptionId);
        if (stmtRs == null) {
          throw new BadResumptionTokenException();
        }

        if (stmtRs.getRow() != oldCount) {
          stmtRs.absolute(oldCount);
        }

        int count;

        /* load the sets ArrayLists. */
        for (count = 0; count < maxListSize && stmtRs.next(); ++count) {
          Map<String, Object> nativeItem = stmtRs.getColumnValues();
          /* Use the RecordFactory to extract set for each item */
          sets.add(getSetXML(nativeItem));
        }

        /* decide if you're done. */
        if (oldCount + count < numRows) {
          /**
           * *************************************************************** Construct the
           * resumptionToken String however you see fit.
           * ***************************************************************
           */
          StringBuilder resumptionTokenSb = new StringBuilder();
          resumptionTokenSb.append(resumptionId);
          resumptionTokenSb.append("!");
          resumptionTokenSb.append(Integer.toString(oldCount + count));
          resumptionTokenSb.append("!");
          resumptionTokenSb.append(Integer.toString(numRows));

          /**
           * *************************************************************** Use the following line
           * if you wish to include the optional resumptionToken attributes in the response.
           * Otherwise, use the line after it that I've commented out.
           * ***************************************************************
           */
          listSetsMap.put(
              "resumptionMap", getResumptionMap(resumptionTokenSb.toString(), numRows, oldCount));
        } else {
          stmtRs.close();
          stmtRs = null;
        }
      } catch (SQLException e) {
        if (stmtRs != null) {
          try {
            stmtRs.close();
          } catch (SQLException e1) {
            e1.printStackTrace();
          }
        }
        LOGGER.error("An Exception occured", e);
        throw new OAIInternalServerError(e.getMessage());
      }

      listSetsMap.put("sets", sets.iterator());
      return listSetsMap;
    }
  }