content: /*
   * This library is part of OpenCms -
   * the Open Source Content Management System
   *
   * Copyright (c) Alkacon Software GmbH & Co. KG (https://www.alkacon.com)
   *
   * This library is free software; you can redistribute it and/or
   * modify it under the terms of the GNU Lesser General Public
   * License as published by the Free Software Foundation; either
   * version 2.1 of the License, or (at your option) any later version.
   *
   * This library is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
   * Lesser General Public License for more details.
   *
   * For further information about Alkacon Software GmbH & Co. KG, please see the
   * company website: https://www.alkacon.com
   *
   * For further information about OpenCms, please see the
   * project website: https://www.opencms.org
   *
   * You should have received a copy of the GNU Lesser General Public
   * License along with this library; if not, write to the Free Software
   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   */
  
  package org.opencms.search;
  
  import org.opencms.i18n.CmsEncoder;
  import org.opencms.main.CmsException;
  import org.opencms.main.CmsIllegalArgumentException;
  import org.opencms.main.CmsLog;
  import org.opencms.main.OpenCms;
  import org.opencms.search.fields.CmsSearchField;
  import org.opencms.util.CmsStringUtil;
  
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Iterator;
  import java.util.List;
  
  import org.apache.commons.collections.ListUtils;
  import org.apache.commons.logging.Log;
  import org.apache.lucene.search.BooleanClause;
  import org.apache.lucene.search.BooleanClause.Occur;
  import org.apache.lucene.search.Sort;
  import org.apache.lucene.search.SortField;
  
  /**
   * Contains the search parameters for a call to <code>{@link org.opencms.search.CmsSearchIndex#search(org.opencms.file.CmsObject, CmsSearchParameters)}</code>.<p>
   *
   * Primary purpose is translation of search arguments to response parameters and from request parameters as
   * well as support for creation of restrictions of several search query parameter sets. <p>
   *
   * @since 6.0.0
   */
  public class CmsSearchParameters {
  
      /**
       * Describes a specific search field query.<p>
       */
      public static class CmsSearchFieldQuery {
  
          /** The field name. */
          private String m_fieldName;
  
          /** The occur parameter for this field. */
          private Occur m_fieldOccur;
  
          /** The search term list. */
          private List<String> m_searchTerms;
  
          /** The occur parameter used for the search term combination. */
          private Occur m_termOccur;
  
          /**
           * Creates a new search field query with a variable length search term list.<p>
           *
           * @param fieldName the field name
           * @param fieldOccur the occur parameter for this field
           * @param termList the search term list
           * @param termOccur the occur parameter used for the search term combination
           */
          public CmsSearchFieldQuery(String fieldName, Occur fieldOccur, List<String> termList, Occur termOccur) {
  
              super();
              m_fieldName = fieldName;
              m_fieldOccur = fieldOccur;
              m_searchTerms = termList;
              m_termOccur = termOccur;
          }
  
          /**
           * Creates a new search field query with just a single search term.<p>
           *
           * Please note: Since there is only one term, the ocucr parameter for the term combination is
           * not required and set to <code>null</code>.<p>
           *
           * @param fieldName the field name
           * @param searchTerm the search term
           * @param fieldOccur the occur parameter for this field
           */
          public CmsSearchFieldQuery(String fieldName, String searchTerm, Occur fieldOccur) {
  
              this(fieldName, fieldOccur, Arrays.asList(searchTerm), null);
          }
  
          /**
           * Returns the field name.<p>
           *
           * @return the field name
           */
          public String getFieldName() {
  
              return m_fieldName;
          }
  
          /**
           * Returns the occur parameter for this field query.<p>
           *
           * @return the occur parameter for this field query
           */
          public Occur getOccur() {
  
              return m_fieldOccur;
          }
  
          /**
           * Returns the first entry from the term list.<p>
           *
           * @return the search query
           *
           * @deprecated use {@link #getSearchTerms()} instead
           */
          @Deprecated
          public String getSearchQuery() {
  
              return m_searchTerms.get(0);
          }
  
          /**
           * Returns the search term list.<p>
           *
           * @return the search term list
           */
          public List<String> getSearchTerms() {
  
              return m_searchTerms;
          }
  
          /**
           * Returns the occur parameter used for the search term combination of this field query.<p>
           *
           * @return the occur parameter used for the search term combination of this field query
           */
          public Occur getTermOccur() {
  
              return m_termOccur;
          }
  
          /**
           * Sets the name of the field to use this query for.<p>
           *
           * @param fieldName the name of the field to use this query for
           */
          public void setFieldName(String fieldName) {
  
              m_fieldName = fieldName;
          }
  
          /**
           * Sets the occur parameter for this field query.<p>
           *
           * @param occur the occur parameter to set
           */
          public void setOccur(BooleanClause.Occur occur) {
  
              m_fieldOccur = occur;
          }
  
          /**
           * Sets the search keywords to just a single entry.<p>
           *
           * @param searchQuery the single search keyword to set
           *
           * @deprecated use {@link #setSearchTerms(List)} instead
           */
          @Deprecated
          public void setSearchQuery(String searchQuery) {
  
              setSearchTerms(Arrays.asList(searchQuery));
          }
  
          /**
           * Sets the search terms.<p>
           *
           * @param searchTerms the search terms to set
           */
          public void setSearchTerms(List<String> searchTerms) {
  
              m_searchTerms = searchTerms;
          }
      }
  
      /** Sort result documents by date of creation, then score. */
      public static final Sort SORT_DATE_CREATED = new Sort(
          new SortField(CmsSearchField.FIELD_DATE_CREATED, SortField.Type.STRING, true));
  
      /** Sort result documents by date of last modification, then score. */
      public static final Sort SORT_DATE_LASTMODIFIED = new Sort(
          new SortField(CmsSearchField.FIELD_DATE_LASTMODIFIED, SortField.Type.STRING, true));
  
      /** Default sort order (by document score). */
      public static final Sort SORT_DEFAULT = Sort.RELEVANCE;
  
      /** Names of the default sort options. */
      public static final String[] SORT_NAMES = {
          "SORT_DEFAULT",
          "SORT_DATE_CREATED",
          "SORT_DATE_LASTMODIFIED",
          "SORT_TITLE"};
  
      /** Sort result documents by title, then score. */
      public static final Sort SORT_TITLE = new Sort(
          new SortField[] {new SortField(CmsSearchField.FIELD_TITLE, SortField.Type.STRING), SortField.FIELD_SCORE});
  
      /** The log object for this class. */
      private static final Log LOG = CmsLog.getLog(CmsSearchParameters.class);
  
      /** The number of displayed pages returned by getPageLinks(). */
      protected int m_displayPages;
  
      /** The number of matches per page. */
      protected int m_matchesPerPage;
  
      /** If <code>true</code>, the category count is calculated for all search results. */
      private boolean m_calculateCategories;
  
      /** The list of categories to limit the search to. */
      private List<String> m_categories;
  
      /** Indicates if all fields should be used for generating the excerpt, regardless if they have been searched or not. */
      private boolean m_excerptOnlySearchedFields;
  
      /** The map of individual search field queries. */
      private List<CmsSearchFieldQuery> m_fieldQueries;
  
      /** The list of search index fields to search in. */
      private List<String> m_fields;
  
      /** The index to search. */
      private CmsSearchIndex m_index;
  
      /** Indicates if the query part should be ignored so that only filters are used for searching. */
      private boolean m_isIgnoreQuery;
  
      /** The creation date the resources have to have as maximum. */
      private long m_maxDateCreated;
  
      /** The last modification date the resources have to have as maximum. */
      private long m_maxDateLastModified;
  
      /** The creation date the resources have to have as minimum. */
      private long m_minDateCreated;
  
      /** The last modification date the resources have to have as minimum. */
      private long m_minDateLastModified;
  
      /** The current result page. */
      private int m_page;
  
      /** The pre-parsed query. */
      private String m_parsedQuery;
  
      /** The search query to use. */
      private String m_query;
  
      /** The minimum length of the search query. */
      private int m_queryLength;
  
      /** The list of resource types to limit the search to. */
      private List<String> m_resourceTypes;
  
      /** Only resource that are sub-resource of one of the search roots are included in the search result. */
      private List<String> m_roots;
  
      /** The sort order for the search. */
      private Sort m_sort;
  
      /**
       * Creates a new search parameter instance with no search query and
       * default values for the remaining parameters. <p>
       *
       * Before using this search parameters for a search method
       * <code>{@link #setQuery(String)}</code> has to be invoked. <p>
       *
       */
      public CmsSearchParameters() {
  
          this("");
      }
  
      /**
       * Creates a new search parameter instance with the provided search query and
       * default values for the remaining parameters. <p>
       *
       * Only the "meta" field (combination of content and title) will be used for search.
       * No search root restriction is chosen.
       * No category restriction is used.
       * No category counts are calculated for the result.
       * Sorting is turned off. This is a simple but fast setup. <p>
       *
       * @param query the query to search for
       */
      public CmsSearchParameters(String query) {
  
          this(query, null, null, null, null, false, null);
  
      }
  
      /**
       * Creates a new search parameter instance with the provided parameter values.<p>
       *
       * @param query the search term to search the index
       * @param fields the list of fields to search
       * @param roots only resource that are sub-resource of one of the search roots are included in the search result
       * @param categories the list of categories to limit the search to
       * @param resourceTypes the list of resource types to limit the search to
       * @param calculateCategories if <code>true</code>, the category count is calculated for all search results
       *      (use with caution, this option uses much performance)
       * @param sort the sort order for the search
       */
      public CmsSearchParameters(
          String query,
          List<String> fields,
          List<String> roots,
          List<String> categories,
          List<String> resourceTypes,
          boolean calculateCategories,
          Sort sort) {
  
          super();
          m_query = (query == null) ? "" : query;
          if (fields == null) {
              fields = new ArrayList<String>(2);
              fields.add(CmsSearchIndex.DOC_META_FIELDS[0]);
              fields.add(CmsSearchIndex.DOC_META_FIELDS[1]);
          }
          m_fields = fields;
          if (roots == null) {
              roots = new ArrayList<String>(2);
          }
          m_roots = roots;
          m_categories = (categories == null) ? new ArrayList<String>() : categories;
          m_resourceTypes = (resourceTypes == null) ? new ArrayList<String>() : resourceTypes;
          m_calculateCategories = calculateCategories;
          // null sort is allowed default
          m_sort = sort;
          m_page = 1;
          m_queryLength = -1;
          m_matchesPerPage = 10;
          m_displayPages = 10;
          m_isIgnoreQuery = false;
  
          m_minDateCreated = Long.MIN_VALUE;
          m_maxDateCreated = Long.MAX_VALUE;
          m_minDateLastModified = Long.MIN_VALUE;
          m_maxDateLastModified = Long.MAX_VALUE;
      }
  
      /**
       * Adds an individual query for a search field.<p>
       *
       * If this is used, any setting made with {@link #setQuery(String)} and {@link #setFields(List)}
       * will be ignored and only the individual field search settings will be used.<p>
       *
       * @param query the query to add
       *
       * @since 7.5.1
       */
      public void addFieldQuery(CmsSearchFieldQuery query) {
  
          if (m_fieldQueries == null) {
              m_fieldQueries = new ArrayList<CmsSearchFieldQuery>();
              m_fields = new ArrayList<String>();
          }
          m_fieldQueries.add(query);
          // add the used field used in the fields query to the list of fields used in the search
          if (!m_fields.contains(query.getFieldName())) {
              m_fields.add(query.getFieldName());
          }
      }
  
      /**
       * Adds an individual query for a search field.<p>
       *
       * If this is used, any setting made with {@link #setQuery(String)} and {@link #setFields(List)}
       * will be ignored and only the individual field search settings will be used.<p>
       *
       * @param fieldName the field name
       * @param searchQuery the search query
       * @param occur the occur parameter for the query in the field
       *
       * @since 7.5.1
       */
      public void addFieldQuery(String fieldName, String searchQuery, Occur occur) {
  
          CmsSearchFieldQuery newQuery = new CmsSearchFieldQuery(fieldName, searchQuery, occur);
          addFieldQuery(newQuery);
      }
  
      /**
       * Returns whether category counts are calculated for search results or not. <p>
       *
       * @return a boolean that tells whether category counts are calculated for search results or not
       */
      public boolean getCalculateCategories() {
  
          return m_calculateCategories;
      }
  
      /**
       * Returns the list of categories to limit the search to.<p>
       *
       * @return the list of categories to limit the search to
       */
      public List<String> getCategories() {
  
          return m_categories;
      }
  
      /**
       * Returns the maximum number of pages which should be shown.<p>
       *
       * @return the maximum number of pages which should be shown
       */
      public int getDisplayPages() {
  
          return m_displayPages;
      }
  
      /**
       * Returns the list of individual field queries.<p>
       *
       * @return the list of individual field queries
       *
       * @since 7.5.1
       */
      public List<CmsSearchFieldQuery> getFieldQueries() {
  
          return m_fieldQueries;
      }
  
      /**
       * Returns the list of search index field names (Strings) to search in.<p>
       *
       * @return the list of search index field names (Strings) to search in
       */
      public List<String> getFields() {
  
          return m_fields;
      }
  
      /**
       * Get the name of the index for the search.<p>
       *
       * @return the name of the index for the search
       */
      public String getIndex() {
  
          return m_index.getName();
      }
  
      /**
       * Gets the number of matches displayed on each page.<p>
       *
       * @return matches per result page
       */
      public int getMatchesPerPage() {
  
          return m_matchesPerPage;
      }
  
      /**
       * Returns the maximum creation date a resource must have to be included in the search result.<p>
       *
       * @return the maximum creation date a resource must have to be included in the search result
       */
      public long getMaxDateCreated() {
  
          return m_maxDateCreated;
      }
  
      /**
       * Returns the maximum last modification date a resource must have to be included in the search result.<p>
       *
       * @return the maximum last modification date a resource must have to be included in the search result
       */
      public long getMaxDateLastModified() {
  
          return m_maxDateLastModified;
      }
  
      /**
       * Returns the minimum creation date a resource must have to be included in the search result.<p>
       *
       * @return the minimum creation date a resource must have to be included in the search result
       */
      public long getMinDateCreated() {
  
          return m_minDateCreated;
      }
  
      /**
       * Returns the minimum last modification date a resource must have to be included in the search result.<p>
       *
       * @return the minimum last modification date a resource must have to be included in the search result
       */
      public long getMinDateLastModified() {
  
          return m_minDateLastModified;
      }
  
      /**
       * Returns the parsed query.<p>
       *
       * The parsed query is automatically set by the OpenCms search index when a query is created
       * with either {@link #setQuery(String)} or {@link #addFieldQuery(CmsSearchFieldQuery)}.
       * The Lucene query build from the parameters is stored here and can be later used
       * for paging through the results.<p>
       *
       * Please note that this returns only to the query part, not the filter part of the search.<p>
       *
       * @return the parsed query
       */
      public String getParsedQuery() {
  
          return m_parsedQuery;
      }
  
      /**
       * Returns the search query to use.<p>
       *
       * @return the search query to use
       */
      public String getQuery() {
  
          return m_query;
      }
  
      /**
       * Gets the minimum search query length.<p>
       *
       * @return the minimum search query length
       */
      public int getQueryLength() {
  
          return m_queryLength;
      }
  
      /**
       * Returns the list of resource types to limit the search to.<p>
       *
       * @return the list of resource types to limit the search to
       *
       * @since 7.5.1
       */
      public List<String> getResourceTypes() {
  
          return m_resourceTypes;
      }
  
      /**
       * Returns the list of strings of search roots to use.<p>
       *
       * Only resource that are sub-resource of one of the search roots are included in the search result.<p>
       *
       * @return the list of strings of search roots to use
       */
      public List<String> getRoots() {
  
          return m_roots;
      }
  
      /**
       * Returns the list of categories to limit the search to.<p>
       *
       * @return the list of categories to limit the search to
       */
      public String getSearchCategories() {
  
          return toSeparatedString(getCategories(), ',');
      }
  
      /**
       * Returns the search index to search in or null if not set before
       * (<code>{@link #setSearchIndex(CmsSearchIndex)}</code>). <p>
       *
       * @return the search index to search in or null if not set before (<code>{@link #setSearchIndex(CmsSearchIndex)}</code>)
       */
      public CmsSearchIndex getSearchIndex() {
  
          return m_index;
      }
  
      /**
       * Returns the search page to display.<p>
       *
       * @return the search page to display
       */
      public int getSearchPage() {
  
          return m_page;
      }
  
      /**
       * Returns the comma separated lists of root folder names to restrict search to.<p>
       *
       * This method is a "sibling" to method <code>{@link #getRoots()}</code> but with
       * the support of being usable with widget technology. <p>
       *
       * @return the comma separated lists of field names to search in
       *
       * @see #setSortName(String)
       */
      public String getSearchRoots() {
  
          return toSeparatedString(m_roots, ',');
      }
  
      /**
       * Returns the instance that defines the sort order for the results.
       *
       * @return the instance that defines the sort order for the results
       */
      public Sort getSort() {
  
          return m_sort;
      }
  
      /**
       * Returns the name of the sort option being used.<p>
       * @return the name of the sort option being used
       *
       * @see #SORT_NAMES
       * @see #setSortName(String)
       */
      public String getSortName() {
  
          if (m_sort == SORT_DATE_CREATED) {
              return SORT_NAMES[1];
          }
          if (m_sort == SORT_DATE_LASTMODIFIED) {
              return SORT_NAMES[2];
          }
          if (m_sort == SORT_TITLE) {
              return SORT_NAMES[3];
          }
          return SORT_NAMES[0];
      }
  
      /**
       * Returns <code>true</code> if the category count is calculated for all search results.<p>
       *
       * @return <code>true</code> if the category count is calculated for all search results
       */
      public boolean isCalculateCategories() {
  
          return m_calculateCategories;
      }
  
      /**
       * Returns <code>true</code> if fields configured for the excerpt should be used for generating the excerpt only
       * if they have been actually searched in.<p>
       *
       * The default setting is <code>false</code>, which means all text fields configured for the excerpt will
       * be used to generate the excerpt, regardless if they have been searched in or not.<p>
       *
       * Please note: A field will only be included in the excerpt if it has been configured as <code>excerpt="true"</code>
       * in <code>opencms-search.xml</code>. This method controls if so configured fields are used depending on the
       * fields searched, see {@link #setFields(List)}.<p>
       *
       * @return <code>true</code> if fields configured for the excerpt should be used for generating the excerpt only
       * if they have been actually searched in
       */
      public boolean isExcerptOnlySearchedFields() {
  
          return m_excerptOnlySearchedFields;
      }
  
      /**
       * Returns <code>true</code> if the query part should be ignored so that only filters are used for searching.<p>
       *
       * @return <code>true</code> if the query part should be ignored so that only filters are used for searching
       *
       * @since 8.0.0
       */
      public boolean isIgnoreQuery() {
  
          return m_isIgnoreQuery;
      }
  
      /**
       * Creates a merged parameter set from this parameters, restricted by the given other parameters.<p>
       *
       * This is mainly intended for "search in search result" functions.<p>
       *
       * The restricted query is build of the queries of both parameters, appended with AND.<p>
       *
       * The lists in the restriction for <code>{@link #getFields()}</code>, <code>{@link #getRoots()}</code> and
       * <code>{@link #getCategories()}</code> are <b>intersected</b> with the lists of this search parameters. Only
       * elements contained in both lists are included for the created search parameters.
       * If a list in either the restriction or in this search parameters is <code>null</code>,
       * the list from the other search parameters is used directly.<p>
       *
       * The values for
       * <code>{@link #isCalculateCategories()}</code>
       * and <code>{@link #getSort()}</code> of this parameters are used for the restricted parameters.<p>
       *
       * @param restriction the parameters to restrict this parameters with
       * @return the restricted parameters
       */
      public CmsSearchParameters restrict(CmsSearchParameters restriction) {
  
          // append queries
          StringBuffer query = new StringBuffer(256);
          if (getQuery() != null) {
              // don't blow up unnecessary closures (if CmsSearch is reused and restricted several times)
              boolean closure = !getQuery().startsWith("+(");
              if (closure) {
                  query.append("+(");
              }
              query.append(getQuery());
              if (closure) {
                  query.append(")");
              }
          }
          if (restriction.getQuery() != null) {
              // don't let Lucene max terms be exceeded in case someone reuses a CmsSearch and continuously restricts
              // query with the same restrictions...
              if (query.indexOf(restriction.getQuery()) < 0) {
                  query.append(" +(");
                  query.append(restriction.getQuery());
                  query.append(")");
              }
          }
  
          // restrict fields
          List<String> fields = null;
          if ((m_fields != null) && (m_fields.size() > 0)) {
              if ((restriction.getFields() != null) && (restriction.getFields().size() > 0)) {
                  fields = ListUtils.intersection(m_fields, restriction.getFields());
              } else {
                  fields = m_fields;
              }
          } else {
              fields = restriction.getFields();
          }
  
          // restrict roots
          List<String> roots = null;
          if ((m_roots != null) && (m_roots.size() > 0)) {
              if ((restriction.getRoots() != null) && (restriction.getRoots().size() > 0)) {
                  roots = ListUtils.intersection(m_roots, restriction.getRoots());
                  // TODO: This only works if there are equal paths in both parameter sets - for two distinct sets
                  //       all root restrictions are dropped with an empty list.
              } else {
                  roots = m_roots;
              }
          } else {
              roots = restriction.getRoots();
          }
  
          // restrict categories
          List<String> categories = null;
          if ((m_categories != null) && (m_categories.size() > 0)) {
              if ((restriction.getCategories() != null) && (restriction.getCategories().size() > 0)) {
                  categories = ListUtils.intersection(m_categories, restriction.getCategories());
              } else {
                  categories = m_categories;
              }
          } else {
              categories = restriction.getCategories();
          }
  
          // restrict resource types
          List<String> resourceTypes = null;
          if ((m_resourceTypes != null) && (m_resourceTypes.size() > 0)) {
              if ((restriction.getResourceTypes() != null) && (restriction.getResourceTypes().size() > 0)) {
                  resourceTypes = ListUtils.intersection(m_resourceTypes, restriction.getResourceTypes());
              } else {
                  resourceTypes = m_resourceTypes;
              }
          } else {
              resourceTypes = restriction.getResourceTypes();
          }
  
          // create the new search parameters
          CmsSearchParameters result = new CmsSearchParameters(
              query.toString(),
              fields,
              roots,
              categories,
              resourceTypes,
              m_calculateCategories,
              m_sort);
          result.setIndex(getIndex());
          return result;
      }
  
      /**
       * Set whether category counts shall be calculated for the corresponding search results or not.<p>
       *
       * @param flag true if category counts shall be calculated for the corresponding search results or false if not
       */
      public void setCalculateCategories(boolean flag) {
  
          m_calculateCategories = flag;
      }
  
      /**
       * Set the list of categories (strings) to this parameters. <p>
       *
       * @param categories the list of categories (strings) of this parameters
       */
      public void setCategories(List<String> categories) {
  
          m_categories = categories;
      }
  
      /**
       * Sets the maximum number of pages which should be shown.<p>
       *
       * Enter an odd value to achieve a nice, "symmetric" output.<p>
       *
       * @param value the maximum number of pages which should be shown
       */
      public void setDisplayPages(int value) {
  
          m_displayPages = value;
      }
  
      /**
       * Controls if the excerpt from a field is generated only for searched fields, or for all fields (the default).<p>
       *
       * @param excerptOnlySearchedFields if <code>true</code>, the excerpt is generated only from the fields actually searched in
       *
       * @see #isExcerptOnlySearchedFields()
       */
      public void setExcerptOnlySearchedFields(boolean excerptOnlySearchedFields) {
  
          m_excerptOnlySearchedFields = excerptOnlySearchedFields;
      }
  
      /**
       * Sets the list of strings of names of fields to search in. <p>
       *
       * @param fields the list of strings of names of fields to search in to set
       */
      public void setFields(List<String> fields) {
  
          m_fields = fields;
      }
  
      /**
       * Sets the flag to indicate if the query part should be ignored so that only filters are used for searching.<p>
       *
       * @param isIgnoreQuery the flag to indicate if the query part should be ignored
       *
       * @since 8.0.0
       */
      public void setIgnoreQuery(boolean isIgnoreQuery) {
  
          m_isIgnoreQuery = isIgnoreQuery;
      }
  
      /**
       * Set the name of the index to search.<p>
       *
       * @param indexName the name of the index
       */
      public void setIndex(String indexName) {
  
          CmsSearchIndex index;
          if (CmsStringUtil.isNotEmpty(indexName)) {
              try {
                  I_CmsSearchIndex idx = OpenCms.getSearchManager().getIndex(indexName);
                  index = idx instanceof CmsSearchIndex ? (CmsSearchIndex)idx : null;
                  if (index == null) {
                      throw new CmsException(Messages.get().container(Messages.ERR_INDEX_NOT_FOUND_1, indexName));
                  }
                  setSearchIndex(index);
              } catch (Exception exc) {
                  if (LOG.isErrorEnabled()) {
                      LOG.error(Messages.get().getBundle().key(Messages.LOG_INDEX_ACCESS_FAILED_1, indexName), exc);
                  }
              }
          }
      }
  
      /**
       * Sets the number of matches per page.<p>
       *
       * @param matches the number of matches per page
       */
      public void setMatchesPerPage(int matches) {
  
          m_matchesPerPage = matches;
      }
  
      /**
       * Sets the maximum creation date a resource must have to be included in the search result.<p>
       *
       * @param maxDateCreated the maximum creation date to set
       */
      public void setMaxDateCreated(long maxDateCreated) {
  
          m_maxDateCreated = maxDateCreated;
      }
  
      /**
       * Sets the maximum last modification date a resource must have to be included in the search result.<p>
       *
       * @param maxDateLastModified the maximum last modification date to set
       */
      public void setMaxDateLastModified(long maxDateLastModified) {
  
          m_maxDateLastModified = maxDateLastModified;
      }
  
      /**
       * Sets the minimum creation date a resource must have to be included in the search result.<p>
       *
       * @param minDateCreated the minimum creation date to set
       */
      public void setMinDateCreated(long minDateCreated) {
  
          m_minDateCreated = minDateCreated;
      }
  
      /**
       * Sets the minimum last modification date a resource must have to be included in the search result.<p>
       *
       * @param minDateLastModified he minimum last modification date to set
       */
      public void setMinDateLastModified(long minDateLastModified) {
  
          m_minDateLastModified = minDateLastModified;
      }
  
      /**
       * Sets the parsed query.<p>
       *
       * The parsed query is automatically set by the OpenCms search index when a query is created
       * with either {@link #setQuery(String)} or {@link #addFieldQuery(CmsSearchFieldQuery)}.
       * The Lucene query build from the parameters is stored here and can be later used
       * for paging through the results.<p>
       *
       * Please note that this applies only to the query part, not the filter part of the search.<p>
       *
       * @param parsedQuery the parsed query to set
       */
      public void setParsedQuery(String parsedQuery) {
  
          m_parsedQuery = parsedQuery;
      }
  
      /**
       * Sets the query to search for. <p>
       *
       * The decoding here is tailored for query strings that are
       * additionally manually UTF-8 encoded at client side (javascript) to get around an
       * issue with special chars in applications that use non- UTF-8 encoding
       * (e.g. ISO-8859-1) OpenCms applications. It is not recommended to use this with
       * front ends that don't encode manually as characters like sole "%" (without number suffix)
       * will cause an Exception.<p>
       *
       * @param query the query to search for to set
       */
      public void setQuery(String query) {
  
          // for use with widgets the exception is thrown here to enforce the error message next to the widget
          if (query.trim().length() < getQueryLength()) {
              throw new CmsIllegalArgumentException(
                  Messages.get().container(Messages.ERR_QUERY_TOO_SHORT_1, Integer.valueOf(getQueryLength())));
          }
          m_query = query;
      }
  
      /**
       * Sets the minimum length of the search query.<p>
       *
       * @param length the minimum search query length
       */
      public void setQueryLength(int length) {
  
          m_queryLength = length;
      }
  
      /**
       * Set the list of resource types (strings) to limit the search to. <p>
       *
       * @param resourceTypes the list of resource types (strings) to limit the search to
       *
       * @since 7.5.1
       */
      public void setResourceTypes(List<String> resourceTypes) {
  
          m_resourceTypes = resourceTypes;
      }
  
      /**
       * Sets the list of strings of roots to search under for the search.<p>
       *
       * @param roots  the list of strings of roots to search under for the search to set
       */
      public void setRoots(List<String> roots) {
  
          m_roots = roots;
      }
  
      /**
       * Set the comma separated search root names to  restrict search to.<p>
       *
       * @param categories the comma separated category names to  restrict search to
       */
      public void setSearchCategories(String categories) {
  
          setCategories(CmsStringUtil.splitAsList(categories, ','));
      }
  
      /**
       * Sets the search index to use for the search. <p>
       *
       * @param index the search index to use for the search to set.
       *
       * @throws CmsIllegalArgumentException if null is given as argument
       */
      public void setSearchIndex(CmsSearchIndex index) throws CmsIllegalArgumentException {
  
          if (index == null) {
              throw new CmsIllegalArgumentException(Messages.get().container(Messages.ERR_INDEX_NULL_0));
          }
          m_index = index;
      }
  
      /**
       * Set the search page to display. <p>
       *
       * @param page the search page to display
       */
      public void setSearchPage(int page) {
  
          m_page = page;
      }
  
      /**
       * Set the comma separated search root names to  restrict search to.<p>
       *
       * @param rootNameList the comma separated search root names to  restrict search to
       */
      public void setSearchRoots(String rootNameList) {
  
          m_roots = CmsStringUtil.splitAsList(rootNameList, ',');
      }
  
      /**
       * Set the instance that defines the sort order for search results.
       *
       * @param sortOrder the instance that defines the sort order for search results to set
       */
      public void setSort(Sort sortOrder) {
  
          m_sort = sortOrder;
      }
  
      /**
       * Sets the internal member of type <code>{@link Sort}</code> according to
       * the given sort name. <p>
       *
       * For a list of valid sort names, please see <code>{@link #SORT_NAMES}</code>.<p>
       *
       * @param sortName the name of the sort to use
       *
       * @see #SORT_NAMES
       */
      public void setSortName(String sortName) {
  
          if (sortName.equals(SORT_NAMES[1])) {
              m_sort = SORT_DATE_CREATED;
          } else if (sortName.equals(SORT_NAMES[2])) {
              m_sort = SORT_DATE_LASTMODIFIED;
          } else if (sortName.equals(SORT_NAMES[3])) {
              m_sort = SORT_TITLE;
          } else {
              m_sort = SORT_DEFAULT;
          }
      }
  
      /**
       * Creates a query String build from this search parameters for HTML links.<p>
       *
       * @return a query String build from this search parameters for HTML links
       */
      public String toQueryString() {
  
          StringBuffer result = new StringBuffer(128);
          result.append("?action=search");
          if (getParsedQuery() != null) {
              result.append("&parsedQuery=");
              result.append(CmsEncoder.encodeParameter(getParsedQuery()));
          } else {
              result.append("&query=");
              result.append(CmsEncoder.encodeParameter(getQuery()));
          }
  
          result.append("&matchesPerPage=");
          result.append(getMatchesPerPage());
          result.append("&displayPages=");
          result.append(getDisplayPages());
          result.append("&index=");
          result.append(CmsEncoder.encodeParameter(getIndex()));
  
          Sort sort = getSort();
          if (sort != CmsSearchParameters.SORT_DEFAULT) {
              result.append("&sort=");
              if (sort == CmsSearchParameters.SORT_TITLE) {
                  result.append("title");
              } else if (sort == CmsSearchParameters.SORT_DATE_CREATED) {
                  result.append("date-created");
              } else if (sort == CmsSearchParameters.SORT_DATE_LASTMODIFIED) {
                  result.append("date-lastmodified");
              }
          }
  
          if ((getCategories() != null) && (getCategories().size() > 0)) {
              result.append("&category=");
              Iterator<String> it = getCategories().iterator();
              while (it.hasNext()) {
                  result.append(it.next());
                  if (it.hasNext()) {
                      result.append(',');
                  }
              }
          }
  
          if (getMinDateCreated() > Long.MIN_VALUE) {
              result.append("&minDateCreated=");
              result.append(getMinDateCreated());
          }
          if (getMinDateLastModified() > Long.MIN_VALUE) {
              result.append("&minDateLastModified=");
              result.append(getMinDateLastModified());
          }
          if (getMaxDateCreated() < Long.MAX_VALUE) {
              result.append("&maxDateCreated=");
              result.append(getMaxDateCreated());
          }
          if (getMaxDateLastModified() < Long.MAX_VALUE) {
              result.append("&maxDateLastModified=");
              result.append(getMaxDateLastModified());
          }
  
          if ((getRoots() != null) && (getRoots().size() > 0)) {
              result.append("&searchRoots=");
              Iterator<String> it = getRoots().iterator();
              while (it.hasNext()) {
                  result.append(CmsEncoder.encode(it.next()));
                  if (it.hasNext()) {
                      result.append(',');
                  }
              }
          }
  
          if (isExcerptOnlySearchedFields()) {
              result.append("&excerptOnlySearchedFields=true");
          }
  
          return result.toString();
      }
  
      /**
       * @see java.lang.Object#toString()
       */
      @Override
      public String toString() {
  
          StringBuffer result = new StringBuffer();
          result.append("query:[");
          result.append(m_query);
          result.append("] ");
          if ((m_fields != null) && (m_fields.size() > 0)) {
              result.append("fields:[");
              for (int i = 0; i < m_fields.size(); i++) {
                  result.append(m_fields.get(i));
                  if ((i + 1) < m_fields.size()) {
                      result.append(", ");
                  }
              }
              result.append("] ");
          }
          if ((m_roots != null) && (m_roots.size() > 0)) {
              result.append("roots:[");
              for (int i = 0; i < m_roots.size(); i++) {
                  result.append(m_roots.get(i));
                  if ((i + 1) < m_roots.size()) {
                      result.append(", ");
                  }
              }
              result.append("] ");
          }
          if ((m_categories != null) && (m_categories.size() > 0)) {
              result.append("categories:[");
              for (int i = 0; i < m_categories.size(); i++) {
                  result.append(m_categories.get(i));
                  if ((i + 1) < m_categories.size()) {
                      result.append(", ");
                  }
              }
              result.append("] ");
          }
          if ((m_resourceTypes != null) && (m_resourceTypes.size() > 0)) {
              result.append("resourceTypes:[");
              for (int i = 0; i < m_resourceTypes.size(); i++) {
                  result.append(m_resourceTypes.get(i));
                  if ((i + 1) < m_resourceTypes.size()) {
                      result.append(", ");
                  }
              }
              result.append("] ");
          }
          if (m_calculateCategories) {
              result.append("calculate-categories ");
          }
          if (m_excerptOnlySearchedFields) {
              result.append("excerpt-searched-fields-only ");
          }
          result.append("sort:[");
          if (m_sort == CmsSearchParameters.SORT_DEFAULT) {
              result.append("default");
          } else if (m_sort == CmsSearchParameters.SORT_TITLE) {
              result.append("title");
          } else if (m_sort == CmsSearchParameters.SORT_DATE_CREATED) {
              result.append("date-created");
          } else if (m_sort == CmsSearchParameters.SORT_DATE_LASTMODIFIED) {
              result.append("date-lastmodified");
          } else {
              result.append("unknown");
          }
          result.append("]");
  
          return result.toString();
      }
  
      /**
       * Concatenates the elements of the string list separated by the given separator character.<p>
       *
       * @param stringList the list
       * @param separator the separator
       *
       * @return the concatenated string
       */
      private String toSeparatedString(List<String> stringList, char separator) {
  
          StringBuffer result = new StringBuffer();
          Iterator<String> it = stringList.iterator();
          while (it.hasNext()) {
              result.append(it.next());
              if (it.hasNext()) {
                  result.append(separator);
              }
          }
          return result.toString();
      }
  }
  