001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software GmbH & Co. KG, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.search;
029
030import org.opencms.file.CmsObject;
031import org.opencms.i18n.CmsEncoder;
032import org.opencms.main.CmsException;
033import org.opencms.main.CmsIllegalArgumentException;
034import org.opencms.main.CmsLog;
035import org.opencms.main.OpenCms;
036import org.opencms.search.CmsSearchParameters.CmsSearchFieldQuery;
037import org.opencms.util.CmsStringUtil;
038
039import java.util.ArrayList;
040import java.util.Arrays;
041import java.util.Collections;
042import java.util.Iterator;
043import java.util.List;
044import java.util.Map;
045import java.util.TreeMap;
046
047import org.apache.commons.logging.Log;
048import org.apache.lucene.search.BooleanClause;
049import org.apache.lucene.search.BooleanClause.Occur;
050import org.apache.lucene.search.Sort;
051
052/**
053 * Helper class to access the search facility within a jsp.<p>
054 *
055 * Typically, the following fields are available for searching:
056 * <ul>
057 * <li>title - the title of a resource</li>
058 * <li>keywords - the keywords of a resource</li>
059 * <li>description - the description of a resource</li>
060 * <li>content - the aggregated content of a resource</li>
061 * <li>created - the creation date of a resource</li>
062 * <li>lastmodified - the date of the last modification of a resource</li>
063 * <li>path - the path to display the resource</li>
064 * <li>channel - the channel of a resource</li>
065 * <li>contentdefinition - the name of the content definition class of a resource</li>
066 * </ul>
067 *
068 * @since 6.0.0
069 */
070public class CmsSearch {
071
072    /** The log object for this class. */
073    private static final Log LOG = CmsLog.getLog(CmsSearch.class);
074
075    /** The result categories of a search. */
076    protected Map<String, Integer> m_categoriesFound;
077
078    /** The cms object. */
079    protected transient CmsObject m_cms;
080
081    /** The latest exception. */
082    protected Exception m_lastException;
083
084    /** The URL which leads to the next result page. */
085    protected String m_nextUrl;
086
087    /** The number of pages for the result list. */
088    protected int m_pageCount;
089
090    /** The restriction for the search parameters, used for "search in search result". */
091    protected CmsSearchParameters m_parameterRestriction;
092
093    /** The search parameters used for searching, build out of the given individual parameter values. */
094    protected CmsSearchParameters m_parameters;
095
096    /** The URL which leads to the previous result page. */
097    protected String m_prevUrl;
098
099    /** The current search result. */
100    protected List<CmsSearchResult> m_result;
101
102    /** The search parameter String. */
103    protected String m_searchParameters;
104
105    /** The total number of search results matching the query. */
106    protected int m_searchResultCount;
107
108    /** Indicates if the parsed query was deliberately set on this instance. */
109    private boolean m_parsedQuerySet;
110
111    /**
112     * Default constructor, used to instantiate the search facility as a bean.<p>
113     */
114    public CmsSearch() {
115
116        m_parameters = new CmsSearchParameters();
117        m_parameters.setSearchRoots("");
118        m_parameters.setSearchPage(1);
119        m_searchResultCount = 0;
120        m_parameters.setSort(CmsSearchParameters.SORT_DEFAULT);
121        m_parameters.setFields(Arrays.asList(CmsSearchIndex.DOC_META_FIELDS));
122        m_parsedQuerySet = false;
123    }
124
125    /**
126     * Adds an individual query for a search field.<p>
127     *
128     * If this is used, any setting made with {@link #setQuery(String)} and {@link #setField(String[])}
129     * will be ignored and only the individual field search settings will be used.<p>
130     *
131     * When combining occurrences of SHOULD, MUST and MUST_NOT, keep the following in mind:
132     * All SHOULD clauses will be grouped and wrapped in one query,
133     * all MUST and MUST_NOT clauses will be grouped in another query.
134     * This means that at least one of the terms which are given as a SHOULD query must occur in the
135     * search result.<p>
136     *
137     * @param fieldQuery the field query to use
138     *
139     * @since 8.0.2
140     */
141    public void addFieldQuery(CmsSearchFieldQuery fieldQuery) {
142
143        m_parameters.addFieldQuery(fieldQuery);
144        resetLastResult();
145    }
146
147    /**
148     * Adds an individual query for a search field.<p>
149     *
150     * If this is used, any setting made with {@link #setQuery(String)} and {@link #setField(String[])}
151     * will be ignored and only the individual field search settings will be used.<p>
152     *
153     * When combining occurrences of SHOULD, MUST and MUST_NOT, keep the following in mind:
154     * All SHOULD clauses will be grouped and wrapped in one query,
155     * all MUST and MUST_NOT clauses will be grouped in another query.
156     * This means that at least one of the terms which are given as a SHOULD query must occur in the
157     * search result.<p>
158     *
159     * @param fieldName the field name
160     * @param searchQuery the search query
161     * @param occur the occur parameter for the query in the field
162     *
163     * @since 7.5.1
164     */
165    public void addFieldQuery(String fieldName, String searchQuery, Occur occur) {
166
167        addFieldQuery(new CmsSearchParameters.CmsSearchFieldQuery(fieldName, searchQuery, occur));
168    }
169
170    /**
171     * Adds an individual query for a search field that MUST occur.<p>
172     *
173     * If this is used, any setting made with {@link #setQuery(String)} and {@link #setField(String[])}
174     * will be ignored and only the individual field search settings will be used.<p>
175     *
176     * When combining occurrences of SHOULD, MUST and MUST_NOT, keep the following in mind:
177     * All SHOULD clauses will be grouped and wrapped in one query,
178     * all MUST and MUST_NOT clauses will be grouped in another query.
179     * This means that at least one of the terms which are given as a SHOULD query must occur in the
180     * search result.<p>
181     *
182     * @param fieldName the field name
183     * @param searchQuery the search query
184     *
185     * @since 7.5.1
186     */
187    public void addFieldQueryMust(String fieldName, String searchQuery) {
188
189        addFieldQuery(fieldName, searchQuery, BooleanClause.Occur.MUST);
190    }
191
192    /**
193     * Adds an individual query for a search field that MUST NOT occur.<p>
194     *
195     * If this is used, any setting made with {@link #setQuery(String)} and {@link #setField(String[])}
196     * will be ignored and only the individual field search settings will be used.<p>
197     *
198     * When combining occurrences of SHOULD, MUST and MUST_NOT, keep the following in mind:
199     * All SHOULD clauses will be grouped and wrapped in one query,
200     * all MUST and MUST_NOT clauses will be grouped in another query.
201     * This means that at least one of the terms which are given as a SHOULD query must occur in the
202     * search result.<p>
203     *
204     * @param fieldName the field name
205     * @param searchQuery the search query
206     *
207     * @since 7.5.1
208     */
209    public void addFieldQueryMustNot(String fieldName, String searchQuery) {
210
211        addFieldQuery(fieldName, searchQuery, BooleanClause.Occur.MUST_NOT);
212    }
213
214    /**
215     * Adds an individual query for a search field that SHOULD occur.<p>
216     *
217     * If this is used, any setting made with {@link #setQuery(String)} and {@link #setField(String[])}
218     * will be ignored and only the individual field search settings will be used.<p>
219     *
220     * When combining occurrences of SHOULD, MUST and MUST_NOT, keep the following in mind:
221     * All SHOULD clauses will be grouped and wrapped in one query,
222     * all MUST and MUST_NOT clauses will be grouped in another query.
223     * This means that at least one of the terms which are given as a SHOULD query must occur in the
224     * search result.<p>
225     *
226     * @param fieldName the field name
227     * @param searchQuery the search query
228     *
229     * @since 7.5.1
230     */
231    public void addFieldQueryShould(String fieldName, String searchQuery) {
232
233        addFieldQuery(fieldName, searchQuery, BooleanClause.Occur.SHOULD);
234    }
235
236    /**
237     * Returns <code>true</code> if a category overview should be shown as part of the result.<p>
238     *
239     * <b>Please note:</b> The calculation of the category count slows down the search time by an order
240     * of magnitude. Make sure that you only use this feature if it's really required!
241     * Be especially careful if your search result list can become large (> 1000 documents), since in this case
242     * overall system performance will certainly be impacted considerably when calculating the categories.<p>
243     *
244     * @return <code>true</code> if a category overview should be shown as part of the result
245     */
246    public boolean getCalculateCategories() {
247
248        return m_parameters.getCalculateCategories();
249    }
250
251    /**
252     * Returns the search categories.<p>
253     *
254     * @return the search categories
255     */
256    public String[] getCategories() {
257
258        List<String> l = m_parameters.getCategories();
259        return l.toArray(new String[l.size()]);
260    }
261
262    /**
263     * Returns the maximum number of pages which should be shown.<p>
264     *
265     * @return the maximum number of pages which should be shown
266     */
267    public int getDisplayPages() {
268
269        return m_parameters.getDisplayPages();
270    }
271
272    /**
273     * Gets the current fields list.<p>
274     *
275     * @return the fields to search
276     */
277    public String getFields() {
278
279        if (m_parameters.getFields() == null) {
280            return "";
281        }
282        StringBuffer result = new StringBuffer();
283        Iterator<String> it = m_parameters.getFields().iterator();
284        while (it.hasNext()) {
285            result.append(it.next());
286            result.append(" ");
287        }
288        return result.toString();
289    }
290
291    /**
292     * Gets the name of the current search index.<p>
293     *
294     * @return the name of the index
295     */
296    public String getIndex() {
297
298        return m_parameters.getSearchIndex().getName();
299    }
300
301    /**
302     * Gets the last exception after a search operation.<p>
303     *
304     * @return the exception occurred in a search operation or null
305     */
306    public Exception getLastException() {
307
308        return m_lastException;
309    }
310
311    /**
312     * Gets the number of matches displayed on each page.<p>
313     *
314     * @return matches per result page
315     */
316    public int getMatchesPerPage() {
317
318        return m_parameters.getMatchesPerPage();
319    }
320
321    /**
322     * Returns the maximum creation date a resource must have to be included in the search result.<p>
323     *
324     * @return the maximum creation date a resource must have to be included in the search result
325     */
326    public long getMaxDateCreated() {
327
328        return m_parameters.getMaxDateCreated();
329    }
330
331    /**
332     * Returns the maximum last modification date a resource must have to be included in the search result.<p>
333     *
334     * @return the maximum last modification date a resource must have to be included in the search result
335     */
336    public long getMaxDateLastModified() {
337
338        return m_parameters.getMaxDateLastModified();
339    }
340
341    /**
342     * Returns the minimum creation date a resource must have to be included in the search result.<p>
343     *
344     * @return the minimum creation date a resource must have to be included in the search result
345     */
346    public long getMinDateCreated() {
347
348        return m_parameters.getMinDateCreated();
349    }
350
351    /**
352     * Returns the minimum last modification date a resource must have to be included in the search result.<p>
353     *
354     * @return the minimum last modification date a resource must have to be included in the search result
355     */
356    public long getMinDateLastModified() {
357
358        return m_parameters.getMinDateLastModified();
359    }
360
361    /**
362     * Gets the URL for the link to the next result page.<p>
363     *
364     * @return the URL to the next result page
365     */
366    public String getNextUrl() {
367
368        return m_nextUrl;
369    }
370
371    /**
372     * Creates a sorted map of URLs to link to other search result pages.<p>
373     *
374     * The key values are Integers representing the page number, the entry
375     * holds the corresponding link.<p>
376     *
377     * @return a map with String URLs
378     */
379    public Map<Integer, String> getPageLinks() {
380
381        Map<Integer, String> links = new TreeMap<Integer, String>();
382        if (m_pageCount <= 1) {
383            return links;
384        }
385        int startIndex, endIndex;
386        String link = m_cms.getRequestContext().getUri() + m_parameters.toQueryString() + "&searchPage=";
387        if (getDisplayPages() < 1) {
388            // number of displayed pages not limited, build a map with all available page links
389            startIndex = 1;
390            endIndex = m_pageCount;
391        } else {
392            // limited number of displayed pages, calculate page range
393            int currentPage = getSearchPage();
394            int countBeforeCurrent = getDisplayPages() / 2;
395            int countAfterCurrent;
396            if ((currentPage - countBeforeCurrent) < 1) {
397                // set count before to number of available pages
398                countBeforeCurrent = currentPage - 1;
399            }
400            // set count after to number of remaining pages (- 1 for current page)
401            countAfterCurrent = getDisplayPages() - countBeforeCurrent - 1;
402            // calculate start and end index
403            startIndex = currentPage - countBeforeCurrent;
404            endIndex = currentPage + countAfterCurrent;
405            // check end index
406            if (endIndex > m_pageCount) {
407                int delta = endIndex - m_pageCount;
408                // decrease start index with delta to get the right number of displayed pages
409                startIndex -= delta;
410                // check start index to avoid values < 1
411                if (startIndex < 1) {
412                    startIndex = 1;
413                }
414                endIndex = m_pageCount;
415            }
416        }
417
418        // build the sorted tree map of page links
419        for (int i = startIndex; i <= endIndex; i++) {
420            links.put(Integer.valueOf(i), (link + i));
421        }
422        return links;
423    }
424
425    /**
426     * Returns the search parameters used for searching, build out of the given individual parameter values.<p>
427     *
428     * @return the search parameters used for searching, build out of the given individual parameter values
429     */
430    public CmsSearchParameters getParameters() {
431
432        if (m_parameterRestriction != null) {
433            m_parameters = m_parameters.restrict(m_parameterRestriction);
434        }
435        return m_parameters;
436
437    }
438
439    /**
440     * Returns the parsed query.<p>
441     *
442     * The parsed query is automatically set by the OpenCms search index when a query is created
443     * with either {@link #setQuery(String)} or {@link #addFieldQuery(CmsSearchFieldQuery)}.
444     * The Lucene query build from the parameters is stored here and can be later used
445     * for paging through the results.<p>
446     *
447     * Please note that this returns only to the query part, not the filter part of the search.<p>
448     *
449     * @return the parsed query
450     */
451    public String getParsedQuery() {
452
453        return m_parameters.getParsedQuery();
454    }
455
456    /**
457     * Gets the URL for the link to the previous result page.<p>
458     *
459     * @return the URL to the previous result page
460     */
461    public String getPreviousUrl() {
462
463        return m_prevUrl;
464    }
465
466    /**
467     * Gets the current search query.<p>
468     *
469     * @return the current query string or null if no query was set before
470     */
471    public String getQuery() {
472
473        return m_parameters.getQuery();
474    }
475
476    /**
477     * Gets the minimum search query length.<p>
478     *
479     * @return the minimum search query length
480     */
481    public int getQueryLength() {
482
483        return m_parameters.getQueryLength();
484    }
485
486    /**
487     * Gets the current result page.<p>
488     *
489     * @return the current result page
490     */
491    public int getSearchPage() {
492
493        return m_parameters.getSearchPage();
494    }
495
496    /**
497     * Returns the search result for the current query, as a list of <code>{@link CmsSearchResult}</code> objects.<p>
498     *
499     * @return the search result (may be empty) or null if no index or query was set before
500     */
501    public List<CmsSearchResult> getSearchResult() {
502
503        if ((m_cms != null)
504            && (m_result == null)
505            && (m_parameters.getIndex() != null)
506            && (m_parameters.isIgnoreQuery()
507                || CmsStringUtil.isNotEmpty(m_parameters.getQuery())
508                || CmsStringUtil.isNotEmpty(m_parameters.getParsedQuery())
509                || (m_parameters.getFieldQueries() != null))) {
510
511            if (!m_parameters.isIgnoreQuery()
512                && CmsStringUtil.isEmpty(m_parameters.getParsedQuery())
513                && (getQueryLength() > 0)) {
514
515                if (m_parameters.getFieldQueries() != null) {
516                    // check all field queries if the length of the query is ok
517                    for (CmsSearchParameters.CmsSearchFieldQuery fq : m_parameters.getFieldQueries()) {
518                        for (String keyword : fq.getSearchTerms()) {
519                            if (CmsStringUtil.isEmpty(keyword) || (keyword.trim().length() < getQueryLength())) {
520
521                                m_lastException = new CmsSearchException(
522                                    Messages.get().container(
523                                        Messages.ERR_QUERY_TOO_SHORT_1,
524                                        Integer.valueOf(getQueryLength())));
525
526                                return null;
527                            }
528                        }
529                    }
530
531                } else if (m_parameters.getQuery().trim().length() < getQueryLength()) {
532
533                    m_lastException = new CmsSearchException(
534                        Messages.get().container(Messages.ERR_QUERY_TOO_SHORT_1, Integer.valueOf(getQueryLength())));
535
536                    return null;
537                }
538            }
539
540            try {
541
542                CmsSearchResultList result = m_parameters.getSearchIndex().search(m_cms, getParameters());
543
544                if (result.size() > 0) {
545
546                    m_result = result;
547                    m_searchResultCount = result.getHitCount();
548                    m_categoriesFound = result.getCategories();
549
550                    // re-caluclate the number of pages for this search result
551                    m_pageCount = m_searchResultCount / m_parameters.getMatchesPerPage();
552                    if ((m_searchResultCount % m_parameters.getMatchesPerPage()) != 0) {
553                        m_pageCount++;
554                    }
555
556                    // re-calculate the URLs to browse forward and backward in the search result
557                    String url = m_cms.getRequestContext().getUri() + m_parameters.toQueryString() + "&searchPage=";
558                    if (m_parameters.getSearchPage() > 1) {
559                        m_prevUrl = url + (m_parameters.getSearchPage() - 1);
560                    }
561                    if (m_parameters.getSearchPage() < m_pageCount) {
562                        m_nextUrl = url + (m_parameters.getSearchPage() + 1);
563                    }
564                } else {
565                    m_result = Collections.emptyList();
566                    m_searchResultCount = 0;
567                    m_categoriesFound = null;
568                    m_pageCount = 0;
569                    m_prevUrl = null;
570                    m_nextUrl = null;
571                }
572            } catch (Exception exc) {
573
574                if (LOG.isDebugEnabled()) {
575                    LOG.debug(Messages.get().getBundle().key(Messages.LOG_SEARCHING_FAILED_0), exc);
576                }
577
578                m_result = null;
579                m_searchResultCount = 0;
580                m_pageCount = 0;
581
582                m_lastException = exc;
583            }
584        }
585
586        return m_result;
587    }
588
589    /**
590     * Returns a map of categories (Strings) for the last search result, mapped to the hit count (Integer) of
591     * the documents in this category, or <code>null</code> if the categories have not been calculated.<p>
592     *
593     * @return a map of categories for the last search result
594     *
595     * @see CmsSearch#getCalculateCategories()
596     * @see CmsSearch#setCalculateCategories(boolean)
597     */
598    public Map<String, Integer> getSearchResultCategories() {
599
600        return m_categoriesFound;
601    }
602
603    /**
604     * Returns the total number of search results matching the query.<p>
605     *
606     * @return the total number of search results matching the query
607     */
608    public int getSearchResultCount() {
609
610        return m_searchResultCount;
611    }
612
613    /**
614     * Returns the search roots.<p>
615     *
616     * Only resources that are sub-resources of one of the search roots
617     * are included in the search result.<p>
618     *
619     * The search roots are used <i>in addition to</i> the current site root
620     * of the user performing the search.<p>
621     *
622     * By default, the search roots contain only one entry with an empty string.<p>
623     *
624     * @return the search roots
625     */
626    public String[] getSearchRoots() {
627
628        List<String> l = m_parameters.getRoots();
629        return l.toArray(new String[l.size()]);
630    }
631
632    /**
633     * Returns the sort order used for sorting the results of s search.<p>
634     *
635     * @return the sort order used for sorting the results of s search
636     */
637    public Sort getSortOrder() {
638
639        return m_parameters.getSort();
640    }
641
642    /**
643     * Initializes the bean with the cms object.<p>
644     *
645     * @param cms the cms object
646     */
647    public void init(CmsObject cms) {
648
649        m_cms = cms;
650        m_result = null;
651        m_lastException = null;
652        m_pageCount = 0;
653        m_nextUrl = null;
654        m_prevUrl = null;
655    }
656
657    /**
658     * Sets the flag that controls calculation of result categories for the next search,
659     * use this only if it's really required since the search can become very slow using this option.<p>
660     *
661     * <b>Please note:</b> The calculation of the category count slows down the search time by an order
662     * of magnitude. Make sure that you only use this feature if it's really required!
663     * Be especially careful if your search result list can become large (> 1000 documents), since in this case
664     * overall system performance will certainly be impacted considerably when calculating the categories.<p>
665     *
666     * @param calculateCategories if <code>true</code>, the category count will be calculated for the next search
667     */
668    public void setCalculateCategories(boolean calculateCategories) {
669
670        m_parameters.setCalculateCategories(calculateCategories);
671    }
672
673    /**
674     * Sets the search categories, all search results must be in one of the categories,
675     * the category set must match the indexed category exactly.<p>
676     *
677     * All categories will automatically be trimmed and lower cased, since search categories
678     * are also stored this way in the index.<p>
679     *
680     * @param categories the categories to set
681     */
682    public void setCategories(String[] categories) {
683
684        List<String> setCategories = new ArrayList<String>();
685        if (categories != null) {
686            if (categories.length != 0) {
687                // ensure all categories are not null, trimmed, not-empty and lowercased
688                String cat;
689                for (int i = 0; i < categories.length; i++) {
690                    cat = categories[i];
691                    if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(cat)) {
692                        // all categories must internally be lower case,
693                        // since the index keywords are lower cased as well
694                        cat = cat.trim().toLowerCase();
695                        setCategories.add(cat);
696                    }
697                }
698            }
699        }
700        m_parameters.setCategories(setCategories);
701        resetLastResult();
702    }
703
704    /**
705     * Sets the maximum number of pages which should be shown.<p>
706     *
707     * Enter an odd value to achieve a nice, "symmetric" output.<p>
708     *
709     * @param value the maximum number of pages which should be shown
710     */
711    public void setDisplayPages(int value) {
712
713        m_parameters.setDisplayPages(value);
714    }
715
716    /**
717     * Controls if the excerpt from a field is generated only for searched fields, or for all fields (the default).<p>
718     *
719     * The default setting is <code>false</code>, which means all text fields configured for the excerpt will
720     * be used to generate the excerpt, regardless if they have been searched in or not.<p>
721     *
722     * Please note: A field will only be included in the excerpt if it has been configured as <code>excerpt="true"</code>
723     * in <code>opencms-search.xml</code>. This method controls if so configured fields are used depending on the
724     * fields searched, see {@link #setField(String[])}.<p>
725     *
726     * @param value if <code>true</code>, the excerpt is generated only from the fields actually searched in
727     */
728    public void setExcerptOnlySearchedFields(boolean value) {
729
730        m_parameters.setExcerptOnlySearchedFields(value);
731        resetLastResult();
732    }
733
734    /**
735     * Sets the fields to search.<p>
736     *
737     * If the fields are set to <code>null</code>,
738     * or not set at all, the default fields "content" and "meta" are used.<p>
739     *
740     * For a list of valid field names, see the Interface constants of
741     * <code>{@link org.opencms.search.documents.I_CmsDocumentFactory}</code>.
742     *
743     * @param fields the fields to search
744     */
745    public void setField(String[] fields) {
746
747        m_parameters.setFields(Arrays.asList(fields));
748        resetLastResult();
749    }
750
751    /**
752     * Set the name of the index to search.<p>
753     *
754     * A former search result will be deleted.<p>
755     *
756     * @param indexName the name of the index
757     */
758    public void setIndex(String indexName) {
759
760        resetLastResult();
761        if (CmsStringUtil.isNotEmpty(indexName)) {
762            try {
763                I_CmsSearchIndex idx = OpenCms.getSearchManager().getIndex(indexName);
764                CmsSearchIndex index = idx instanceof CmsSearchIndex ? (CmsSearchIndex)idx : null;
765                if (index == null) {
766                    throw new CmsException(Messages.get().container(Messages.ERR_INDEX_NOT_FOUND_1, indexName));
767                }
768                m_parameters.setSearchIndex(index);
769            } catch (Exception exc) {
770                if (LOG.isDebugEnabled()) {
771                    LOG.debug(Messages.get().getBundle().key(Messages.LOG_INDEX_ACCESS_FAILED_1, indexName), exc);
772                }
773                m_lastException = exc;
774            }
775        }
776    }
777
778    /**
779     * Sets the number of matches per page.<p>
780     *
781     * @param matches the number of matches per page
782     */
783    public void setMatchesPerPage(int matches) {
784
785        m_parameters.setMatchesPerPage(matches);
786        resetLastResult();
787    }
788
789    /**
790     * Sets the maximum creation date a resource must have to be included in the search result.<p>
791     *
792     * @param maxDateCreated the maximum creation date to set
793     */
794    public void setMaxDateCreated(String maxDateCreated) {
795
796        m_parameters.setMaxDateCreated(CmsStringUtil.getLongValue(maxDateCreated, Long.MAX_VALUE, "maxDateCreated"));
797    }
798
799    /**
800     * Sets the maximum last modification date a resource must have to be included in the search result.<p>
801     *
802     * @param maxDateLastModified the maximum last modification date to set
803     */
804    public void setMaxDateLastModified(String maxDateLastModified) {
805
806        m_parameters.setMaxDateLastModified(
807            CmsStringUtil.getLongValue(maxDateLastModified, Long.MAX_VALUE, "maxDateLastModified"));
808    }
809
810    /**
811     * Sets the minimum creation date a resource must have to be included in the search result.<p>
812     *
813     * @param minDateCreated the minimum creation date to set
814     */
815    public void setMinDateCreated(String minDateCreated) {
816
817        m_parameters.setMinDateCreated(CmsStringUtil.getLongValue(minDateCreated, Long.MIN_VALUE, "minDateCreated"));
818    }
819
820    /**
821     * Sets the minimum last modification date a resource must have to be included in the search result.<p>
822     *
823     * @param minDateLastModified he minimum last modification date to set
824     */
825    public void setMinDateLastModified(String minDateLastModified) {
826
827        m_parameters.setMinDateLastModified(
828            CmsStringUtil.getLongValue(minDateLastModified, Long.MIN_VALUE, "minDateLastModified"));
829    }
830
831    /**
832     * Set the parameters to use if a non null instance is provided. <p>
833     *
834     * @param parameters the parameters to use for the search if a non null instance is provided
835     *
836     */
837    public void setParameters(CmsSearchParameters parameters) {
838
839        if (parameters != null) {
840            m_parameters = parameters;
841        }
842    }
843
844    /**
845     * Sets the parsed query, which will be parameter decoded first.<p>
846     *
847     * The parsed query is automatically set by the OpenCms search index when a query is created
848     * with either {@link #setQuery(String)} or {@link #addFieldQuery(CmsSearchFieldQuery)}.
849     * The Lucene query build from the parameters is stored here and can be later used
850     * for paging through the results.<p>
851     *
852     * Please note that this applies only to the query part, not the filter part of the search.<p>
853     *
854     * @param parsedQuery the parsed query to set
855     */
856    public void setParsedQuery(String parsedQuery) {
857
858        try {
859            m_parsedQuerySet = true;
860            m_parameters.setParsedQuery(CmsEncoder.decodeParameter(parsedQuery));
861        } catch (CmsIllegalArgumentException iae) {
862            m_lastException = iae;
863        }
864    }
865
866    /**
867     * Sets the search query.<p>
868     *
869     * The syntax of the query depends on the search engine used.
870     * A former search result will be deleted.<p>
871     *
872     * @param query the search query (escaped format)
873     */
874    public void setQuery(String query) {
875
876        try {
877            m_parameters.setQuery(CmsEncoder.decodeParameter(query));
878        } catch (CmsIllegalArgumentException iae) {
879            m_lastException = iae;
880        }
881        resetLastResult();
882    }
883
884    /**
885     * Sets the minimum length of the search query.<p>
886     *
887     * @param length the minimum search query length
888     */
889    public void setQueryLength(int length) {
890
891        m_parameters.setQueryLength(length);
892    }
893
894    /**
895     * Limits the search to a given of resource type only.<p>
896     *
897     * @param resourceType the resource type to limit the search result to
898     *
899     * @since 7.5.1
900     */
901    public void setResourceType(String resourceType) {
902
903        setResourceTypes(new String[] {resourceType});
904    }
905
906    /**
907     * Limits the search to a given list of resource types only.<p>
908     *
909     * @param resourceTypes the resource types to limit the search result to
910     */
911    public void setResourceTypes(String[] resourceTypes) {
912
913        if (resourceTypes != null) {
914            m_parameters.setResourceTypes(Arrays.asList(resourceTypes));
915        } else {
916            m_parameters.setResourceTypes(null);
917        }
918        resetLastResult();
919    }
920
921    /**
922     * Restrict the result of the next search to the results of the last search,
923     * restricted with the provided parameters.<p>
924     *
925     * Use this for "seach in search result" functions.<p>
926     *
927     * @param restriction the restriction to use
928     *
929     * @see CmsSearchParameters#restrict(CmsSearchParameters)
930     */
931    public void setResultRestriction(CmsSearchParameters restriction) {
932
933        resetLastResult();
934        m_parameterRestriction = restriction;
935    }
936
937    /**
938     * Sets the current result page.<p>
939     *
940     * Works with jsp bean mechanism for request parameter "searchPage"
941     * that is generated here for page links.<p>
942     *
943     * @param page the current result page
944     */
945    public void setSearchPage(int page) {
946
947        m_parameters.setSearchPage(page);
948        resetLastResult();
949    }
950
951    /**
952     * Convenience method to set exactly one search root.<p>
953     *
954     * @param searchRoot the search root to set
955     *
956     * @see #setSearchRoots(String[])
957     */
958    public void setSearchRoot(String searchRoot) {
959
960        setSearchRoots(CmsStringUtil.splitAsArray(searchRoot, ","));
961    }
962
963    /**
964     * Sets the search root list.<p>
965     *
966     * Only resources that are sub-resources of one of the search roots
967     * are included in the search result.<p>
968     *
969     * The search roots set here are used <i>in addition to</i> the current site root
970     * of the user performing the search.<p>
971     *
972     * By default, the search roots contain only one entry with an empty string.<p>
973     *
974     * @param searchRoots the search roots to set
975     */
976    public void setSearchRoots(String[] searchRoots) {
977
978        List<String> l = new ArrayList<String>(Arrays.asList(searchRoots));
979        m_parameters.setRoots(l);
980        resetLastResult();
981    }
982
983    /**
984     * Sets the sort order used for sorting the results of s search.<p>
985     *
986     * @param sortOrder the sort order to set
987     */
988    public void setSortOrder(Sort sortOrder) {
989
990        m_parameters.setSort(sortOrder);
991        resetLastResult();
992    }
993
994    /**
995     * Resets the last search result.<p>
996     */
997    private void resetLastResult() {
998
999        m_result = null;
1000        m_lastException = null;
1001        m_categoriesFound = null;
1002        m_parameterRestriction = null;
1003        if (!m_parsedQuerySet) {
1004            // don't reset parsed query if it was deliberately set, otherwise initializing search bean from JSP might fail
1005            m_parameters.setParsedQuery(null);
1006        }
1007    }
1008}