/** Generate a sort string for the given DC metadata */
  public static String makeSortString(String value, String language, String type) {
    OrderFormatDelegate delegate = null;

    // If there is no value, return null
    if (value == null) return null;

    // If a named index has been supplied
    if (type != null && type.length() > 0) {
      // Use a delegate if one is configured
      if ((delegate = OrderFormat.getDelegate(type)) != null) {
        return delegate.makeSortString(value, language);
      }

      // No delegates found, so apply defaults
      if (type.equalsIgnoreCase(OrderFormat.AUTHOR) && authorDelegate != null) {
        return authorDelegate.makeSortString(value, language);
      }

      if (type.equalsIgnoreCase(OrderFormat.TITLE) && titleDelegate != null) {
        return titleDelegate.makeSortString(value, language);
      }

      if (type.equalsIgnoreCase(OrderFormat.TEXT) && textDelegate != null) {
        return textDelegate.makeSortString(value, language);
      }
    }

    return value;
  }
  /**
   * Return a normalized focus value. If there is no normalization that can be performed, return the
   * focus value that is passed in.
   *
   * @param value a focus value to normalize
   * @return the normalized focus value
   * @throws BrowseException
   */
  private String normalizeJumpToValue(String value) throws BrowseException {
    // If the scope has a focus value (focus by value)
    if (scope.hasJumpToValue()) {
      // Normalize it based on the specified language as appropriate for this index
      return OrderFormat.makeSortString(
          scope.getJumpToValue(), scope.getJumpToValueLang(), scope.getBrowseIndex().getDataType());
    } else if (scope.hasStartsWith()) {
      // Scope has a starts with, so normalize that instead
      return OrderFormat.makeSortString(
          scope.getStartsWith(), null, scope.getBrowseIndex().getDataType());
    }

    // No focus value on the scope (ie. focus by id), so just return the passed focus value
    // This is useful in cases where we have pulled a focus value from the index
    // which will already be normalized, and avoids another DB lookup
    return value;
  }
  /**
   * Browse the archive by the full item browse mechanism. This produces a BrowseInfo object which
   * contains full BrowseItem objects as its result set.
   *
   * @param bs the scope of the browse
   * @return the results of the browse
   * @throws BrowseException
   */
  private BrowseInfo browseByItem(BrowserScope bs) throws BrowseException {
    log.info(LogManager.getHeader(context, "browse_by_item", ""));
    try {
      // get the table name that we are going to be getting our data from
      dao.setTable(browseIndex.getTableName());

      // tell the browse query whether we are ascending or descending on the value
      dao.setAscending(scope.isAscending());

      // assemble the value clause
      String rawValue = null;
      if (scope.hasFilterValue() && scope.isSecondLevel()) {
        String value = scope.getFilterValue();
        rawValue = value;

        // make sure the incoming value is normalised
        value =
            OrderFormat.makeSortString(
                value, scope.getFilterValueLang(), scope.getBrowseIndex().getDataType());

        dao.setAuthorityValue(scope.getAuthorityValue());

        // set the values in the Browse Query
        if (scope.isSecondLevel()) {
          dao.setFilterValueField("value");
          dao.setFilterValue(rawValue);
        } else {
          dao.setFilterValueField("sort_value");
          dao.setFilterValue(value);
        }
        dao.setFilterValuePartial(scope.getFilterValuePartial());

        // to apply the filtering, we need the distinct and map tables for the index
        dao.setFilterMappingTables(
            browseIndex.getDistinctTableName(), browseIndex.getMapTableName());
      }

      // define a clause for the WHERE clause which will allow us to constrain
      // our browse to a specified community or collection
      if (scope.inCollection() || scope.inCommunity()) {
        if (scope.inCollection()) {
          Collection col = (Collection) scope.getBrowseContainer();
          dao.setContainerTable("collection2item");
          dao.setContainerIDField("collection_id");
          dao.setContainerID(col.getID());
        } else if (scope.inCommunity()) {
          Community com = (Community) scope.getBrowseContainer();
          dao.setContainerTable("communities2item");
          dao.setContainerIDField("community_id");
          dao.setContainerID(com.getID());
        }
      }

      // this is the total number of results in answer to the query
      int total = getTotalResults();

      // assemble the ORDER BY clause
      String orderBy = browseIndex.getSortField(scope.isSecondLevel());
      if (scope.getSortBy() > 0) {
        orderBy = "sort_" + Integer.toString(scope.getSortBy());
      }
      dao.setOrderField(orderBy);

      int offset = scope.getOffset();
      String rawFocusValue = null;
      if (offset < 1
          && (scope.hasJumpToItem() || scope.hasJumpToValue() || scope.hasStartsWith())) {
        // We need to convert these to an offset for the actual browse query.
        // First, get a value that we can look up in the ordering field
        rawFocusValue = getJumpToValue();

        // make sure the incoming value is normalised
        String focusValue = normalizeJumpToValue(rawFocusValue);

        log.debug("browsing using focus: " + focusValue);

        // Convert the focus value into an offset
        offset = getOffsetForValue(focusValue);
      }

      dao.setOffset(offset);

      // assemble the LIMIT clause
      dao.setLimit(scope.getResultsPerPage());

      // Holder for the results
      List<BrowseItem> results = null;

      // Does this browse have any contents?
      if (total > 0) {
        // now run the query
        results = dao.doQuery();

        // now, if we don't have any results, we are at the end of the browse.  This will
        // be because a starts_with value has been supplied for which we don't have
        // any items.
        if (results.size() == 0) {
          // In this case, we will calculate a new offset for the last page of results
          offset = total - scope.getResultsPerPage();
          if (offset < 0) {
            offset = 0;
          }

          // And rerun the query
          dao.setOffset(offset);
          results = dao.doQuery();
        }
      } else {
        // No records, so make an empty list
        results = new ArrayList<BrowseItem>();
      }

      // construct the BrowseInfo object to pass back
      //            BrowseInfo browseInfo = new BrowseInfo(results, position, total, offset);
      BrowseInfo browseInfo = new BrowseInfo(results, offset, total, offset);

      if (offset + scope.getResultsPerPage() < total) {
        browseInfo.setNextOffset(offset + scope.getResultsPerPage());
      }

      if (offset - scope.getResultsPerPage() > -1) {
        browseInfo.setPrevOffset(offset - scope.getResultsPerPage());
      }

      // add the browse index to the Browse Info
      browseInfo.setBrowseIndex(browseIndex);

      // set the sort option for the Browse Info
      browseInfo.setSortOption(scope.getSortOption());

      // tell the Browse Info which way we are sorting
      browseInfo.setAscending(scope.isAscending());

      // tell the Browse Info which level of browse we are at
      browseInfo.setBrowseLevel(scope.getBrowseLevel());

      // set the browse value if there is one
      browseInfo.setValue(rawValue);

      // set the browse authority key if there is one
      browseInfo.setAuthority(scope.getAuthorityValue());

      // set the focus value if there is one
      browseInfo.setFocus(rawFocusValue);

      if (scope.hasJumpToItem()) {
        browseInfo.setFocusItem(scope.getJumpToItem());
      }

      // tell the browse info if it is working from a starts with parameter
      browseInfo.setStartsWith(scope.hasStartsWith());

      // tell the browse info what the container for the browse was
      if (scope.inCollection() || scope.inCommunity()) {
        browseInfo.setBrowseContainer(scope.getBrowseContainer());
      }

      browseInfo.setResultsPerPage(scope.getResultsPerPage());

      browseInfo.setEtAl(scope.getEtAl());

      return browseInfo;
    } catch (SQLException e) {
      log.error("caught exception: ", e);
      throw new BrowseException(e);
    }
  }