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