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.i18n.CmsEncoder;
031import org.opencms.main.CmsException;
032import org.opencms.main.CmsIllegalArgumentException;
033import org.opencms.main.CmsLog;
034import org.opencms.main.OpenCms;
035import org.opencms.search.fields.CmsSearchField;
036import org.opencms.util.CmsStringUtil;
037
038import java.util.ArrayList;
039import java.util.Arrays;
040import java.util.Iterator;
041import java.util.List;
042
043import org.apache.commons.collections.ListUtils;
044import org.apache.commons.logging.Log;
045import org.apache.lucene.search.BooleanClause;
046import org.apache.lucene.search.BooleanClause.Occur;
047import org.apache.lucene.search.Sort;
048import org.apache.lucene.search.SortField;
049
050/**
051 * Contains the search parameters for a call to <code>{@link org.opencms.search.CmsSearchIndex#search(org.opencms.file.CmsObject, CmsSearchParameters)}</code>.<p>
052 *
053 * Primary purpose is translation of search arguments to response parameters and from request parameters as
054 * well as support for creation of restrictions of several search query parameter sets. <p>
055 *
056 * @since 6.0.0
057 */
058public class CmsSearchParameters {
059
060    /**
061     * Describes a specific search field query.<p>
062     */
063    public static class CmsSearchFieldQuery {
064
065        /** The field name. */
066        private String m_fieldName;
067
068        /** The occur parameter for this field. */
069        private Occur m_fieldOccur;
070
071        /** The search term list. */
072        private List<String> m_searchTerms;
073
074        /** The occur parameter used for the search term combination. */
075        private Occur m_termOccur;
076
077        /**
078         * Creates a new search field query with a variable length search term list.<p>
079         *
080         * @param fieldName the field name
081         * @param fieldOccur the occur parameter for this field
082         * @param termList the search term list
083         * @param termOccur the occur parameter used for the search term combination
084         */
085        public CmsSearchFieldQuery(String fieldName, Occur fieldOccur, List<String> termList, Occur termOccur) {
086
087            super();
088            m_fieldName = fieldName;
089            m_fieldOccur = fieldOccur;
090            m_searchTerms = termList;
091            m_termOccur = termOccur;
092        }
093
094        /**
095         * Creates a new search field query with just a single search term.<p>
096         *
097         * Please note: Since there is only one term, the ocucr parameter for the term combination is
098         * not required and set to <code>null</code>.<p>
099         *
100         * @param fieldName the field name
101         * @param searchTerm the search term
102         * @param fieldOccur the occur parameter for this field
103         */
104        public CmsSearchFieldQuery(String fieldName, String searchTerm, Occur fieldOccur) {
105
106            this(fieldName, fieldOccur, Arrays.asList(searchTerm), null);
107        }
108
109        /**
110         * Returns the field name.<p>
111         *
112         * @return the field name
113         */
114        public String getFieldName() {
115
116            return m_fieldName;
117        }
118
119        /**
120         * Returns the occur parameter for this field query.<p>
121         *
122         * @return the occur parameter for this field query
123         */
124        public Occur getOccur() {
125
126            return m_fieldOccur;
127        }
128
129        /**
130         * Returns the first entry from the term list.<p>
131         *
132         * @return the search query
133         *
134         * @deprecated use {@link #getSearchTerms()} instead
135         */
136        @Deprecated
137        public String getSearchQuery() {
138
139            return m_searchTerms.get(0);
140        }
141
142        /**
143         * Returns the search term list.<p>
144         *
145         * @return the search term list
146         */
147        public List<String> getSearchTerms() {
148
149            return m_searchTerms;
150        }
151
152        /**
153         * Returns the occur parameter used for the search term combination of this field query.<p>
154         *
155         * @return the occur parameter used for the search term combination of this field query
156         */
157        public Occur getTermOccur() {
158
159            return m_termOccur;
160        }
161
162        /**
163         * Sets the name of the field to use this query for.<p>
164         *
165         * @param fieldName the name of the field to use this query for
166         */
167        public void setFieldName(String fieldName) {
168
169            m_fieldName = fieldName;
170        }
171
172        /**
173         * Sets the occur parameter for this field query.<p>
174         *
175         * @param occur the occur parameter to set
176         */
177        public void setOccur(BooleanClause.Occur occur) {
178
179            m_fieldOccur = occur;
180        }
181
182        /**
183         * Sets the search keywords to just a single entry.<p>
184         *
185         * @param searchQuery the single search keyword to set
186         *
187         * @deprecated use {@link #setSearchTerms(List)} instead
188         */
189        @Deprecated
190        public void setSearchQuery(String searchQuery) {
191
192            setSearchTerms(Arrays.asList(searchQuery));
193        }
194
195        /**
196         * Sets the search terms.<p>
197         *
198         * @param searchTerms the search terms to set
199         */
200        public void setSearchTerms(List<String> searchTerms) {
201
202            m_searchTerms = searchTerms;
203        }
204    }
205
206    /** Sort result documents by date of creation, then score. */
207    public static final Sort SORT_DATE_CREATED = new Sort(
208        new SortField(CmsSearchField.FIELD_DATE_CREATED, SortField.Type.STRING, true));
209
210    /** Sort result documents by date of last modification, then score. */
211    public static final Sort SORT_DATE_LASTMODIFIED = new Sort(
212        new SortField(CmsSearchField.FIELD_DATE_LASTMODIFIED, SortField.Type.STRING, true));
213
214    /** Default sort order (by document score). */
215    public static final Sort SORT_DEFAULT = Sort.RELEVANCE;
216
217    /** Names of the default sort options. */
218    public static final String[] SORT_NAMES = {
219        "SORT_DEFAULT",
220        "SORT_DATE_CREATED",
221        "SORT_DATE_LASTMODIFIED",
222        "SORT_TITLE"};
223
224    /** Sort result documents by title, then score. */
225    public static final Sort SORT_TITLE = new Sort(
226        new SortField[] {new SortField(CmsSearchField.FIELD_TITLE, SortField.Type.STRING), SortField.FIELD_SCORE});
227
228    /** The log object for this class. */
229    private static final Log LOG = CmsLog.getLog(CmsSearchParameters.class);
230
231    /** The number of displayed pages returned by getPageLinks(). */
232    protected int m_displayPages;
233
234    /** The number of matches per page. */
235    protected int m_matchesPerPage;
236
237    /** If <code>true</code>, the category count is calculated for all search results. */
238    private boolean m_calculateCategories;
239
240    /** The list of categories to limit the search to. */
241    private List<String> m_categories;
242
243    /** Indicates if all fields should be used for generating the excerpt, regardless if they have been searched or not. */
244    private boolean m_excerptOnlySearchedFields;
245
246    /** The map of individual search field queries. */
247    private List<CmsSearchFieldQuery> m_fieldQueries;
248
249    /** The list of search index fields to search in. */
250    private List<String> m_fields;
251
252    /** The index to search. */
253    private CmsSearchIndex m_index;
254
255    /** Indicates if the query part should be ignored so that only filters are used for searching. */
256    private boolean m_isIgnoreQuery;
257
258    /** The creation date the resources have to have as maximum. */
259    private long m_maxDateCreated;
260
261    /** The last modification date the resources have to have as maximum. */
262    private long m_maxDateLastModified;
263
264    /** The creation date the resources have to have as minimum. */
265    private long m_minDateCreated;
266
267    /** The last modification date the resources have to have as minimum. */
268    private long m_minDateLastModified;
269
270    /** The current result page. */
271    private int m_page;
272
273    /** The search query to use. */
274    private String m_query;
275
276    /** The minimum length of the search query. */
277    private int m_queryLength;
278
279    /** The list of resource types to limit the search to. */
280    private List<String> m_resourceTypes;
281
282    /** Only resource that are sub-resource of one of the search roots are included in the search result. */
283    private List<String> m_roots;
284
285    /** The sort order for the search. */
286    private Sort m_sort;
287
288    /**
289     * Creates a new search parameter instance with no search query and
290     * default values for the remaining parameters. <p>
291     *
292     * Before using this search parameters for a search method
293     * <code>{@link #setQuery(String)}</code> has to be invoked. <p>
294     *
295     */
296    public CmsSearchParameters() {
297
298        this("");
299    }
300
301    /**
302     * Creates a new search parameter instance with the provided search query and
303     * default values for the remaining parameters. <p>
304     *
305     * Only the "meta" field (combination of content and title) will be used for search.
306     * No search root restriction is chosen.
307     * No category restriction is used.
308     * No category counts are calculated for the result.
309     * Sorting is turned off. This is a simple but fast setup. <p>
310     *
311     * @param query the query to search for
312     */
313    public CmsSearchParameters(String query) {
314
315        this(query, null, null, null, null, false, null);
316
317    }
318
319    /**
320     * Creates a new search parameter instance with the provided parameter values.<p>
321     *
322     * @param query the search term to search the index
323     * @param fields the list of fields to search
324     * @param roots only resource that are sub-resource of one of the search roots are included in the search result
325     * @param categories the list of categories to limit the search to
326     * @param resourceTypes the list of resource types to limit the search to
327     * @param calculateCategories if <code>true</code>, the category count is calculated for all search results
328     *      (use with caution, this option uses much performance)
329     * @param sort the sort order for the search
330     */
331    public CmsSearchParameters(
332        String query,
333        List<String> fields,
334        List<String> roots,
335        List<String> categories,
336        List<String> resourceTypes,
337        boolean calculateCategories,
338        Sort sort) {
339
340        super();
341        m_query = (query == null) ? "" : query;
342        if (fields == null) {
343            fields = new ArrayList<String>(2);
344            fields.add(CmsSearchIndex.DOC_META_FIELDS[0]);
345            fields.add(CmsSearchIndex.DOC_META_FIELDS[1]);
346        }
347        m_fields = fields;
348        if (roots == null) {
349            roots = new ArrayList<String>(2);
350        }
351        m_roots = roots;
352        m_categories = (categories == null) ? new ArrayList<String>() : categories;
353        m_resourceTypes = (resourceTypes == null) ? new ArrayList<String>() : resourceTypes;
354        m_calculateCategories = calculateCategories;
355        // null sort is allowed default
356        m_sort = sort;
357        m_page = 1;
358        m_queryLength = -1;
359        m_matchesPerPage = 10;
360        m_displayPages = 10;
361        m_isIgnoreQuery = false;
362
363        m_minDateCreated = Long.MIN_VALUE;
364        m_maxDateCreated = Long.MAX_VALUE;
365        m_minDateLastModified = Long.MIN_VALUE;
366        m_maxDateLastModified = Long.MAX_VALUE;
367    }
368
369    /**
370     * Adds an individual query for a search field.<p>
371     *
372     * If this is used, any setting made with {@link #setQuery(String)} and {@link #setFields(List)}
373     * will be ignored and only the individual field search settings will be used.<p>
374     *
375     * @param query the query to add
376     *
377     * @since 7.5.1
378     */
379    public void addFieldQuery(CmsSearchFieldQuery query) {
380
381        if (m_fieldQueries == null) {
382            m_fieldQueries = new ArrayList<CmsSearchFieldQuery>();
383            m_fields = new ArrayList<String>();
384        }
385        m_fieldQueries.add(query);
386        // add the used field used in the fields query to the list of fields used in the search
387        if (!m_fields.contains(query.getFieldName())) {
388            m_fields.add(query.getFieldName());
389        }
390    }
391
392    /**
393     * Adds an individual query for a search field.<p>
394     *
395     * If this is used, any setting made with {@link #setQuery(String)} and {@link #setFields(List)}
396     * will be ignored and only the individual field search settings will be used.<p>
397     *
398     * @param fieldName the field name
399     * @param searchQuery the search query
400     * @param occur the occur parameter for the query in the field
401     *
402     * @since 7.5.1
403     */
404    public void addFieldQuery(String fieldName, String searchQuery, Occur occur) {
405
406        CmsSearchFieldQuery newQuery = new CmsSearchFieldQuery(fieldName, searchQuery, occur);
407        addFieldQuery(newQuery);
408    }
409
410    /**
411     * Returns whether category counts are calculated for search results or not. <p>
412     *
413     * @return a boolean that tells whether category counts are calculated for search results or not
414     */
415    public boolean getCalculateCategories() {
416
417        return m_calculateCategories;
418    }
419
420    /**
421     * Returns the list of categories to limit the search to.<p>
422     *
423     * @return the list of categories to limit the search to
424     */
425    public List<String> getCategories() {
426
427        return m_categories;
428    }
429
430    /**
431     * Returns the maximum number of pages which should be shown.<p>
432     *
433     * @return the maximum number of pages which should be shown
434     */
435    public int getDisplayPages() {
436
437        return m_displayPages;
438    }
439
440    /**
441     * Returns the list of individual field queries.<p>
442     *
443     * @return the list of individual field queries
444     *
445     * @since 7.5.1
446     */
447    public List<CmsSearchFieldQuery> getFieldQueries() {
448
449        return m_fieldQueries;
450    }
451
452    /**
453     * Returns the list of search index field names (Strings) to search in.<p>
454     *
455     * @return the list of search index field names (Strings) to search in
456     */
457    public List<String> getFields() {
458
459        return m_fields;
460    }
461
462    /**
463     * Get the name of the index for the search.<p>
464     *
465     * @return the name of the index for the search
466     */
467    public String getIndex() {
468
469        return m_index.getName();
470    }
471
472    /**
473     * Gets the number of matches displayed on each page.<p>
474     *
475     * @return matches per result page
476     */
477    public int getMatchesPerPage() {
478
479        return m_matchesPerPage;
480    }
481
482    /**
483     * Returns the maximum creation date a resource must have to be included in the search result.<p>
484     *
485     * @return the maximum creation date a resource must have to be included in the search result
486     */
487    public long getMaxDateCreated() {
488
489        return m_maxDateCreated;
490    }
491
492    /**
493     * Returns the maximum last modification date a resource must have to be included in the search result.<p>
494     *
495     * @return the maximum last modification date a resource must have to be included in the search result
496     */
497    public long getMaxDateLastModified() {
498
499        return m_maxDateLastModified;
500    }
501
502    /**
503     * Returns the minimum creation date a resource must have to be included in the search result.<p>
504     *
505     * @return the minimum creation date a resource must have to be included in the search result
506     */
507    public long getMinDateCreated() {
508
509        return m_minDateCreated;
510    }
511
512    /**
513     * Returns the minimum last modification date a resource must have to be included in the search result.<p>
514     *
515     * @return the minimum last modification date a resource must have to be included in the search result
516     */
517    public long getMinDateLastModified() {
518
519        return m_minDateLastModified;
520    }
521
522    /**
523     * Returns the search query to use.<p>
524     *
525     * @return the search query to use
526     */
527    public String getQuery() {
528
529        return m_query;
530    }
531
532    /**
533     * Gets the minimum search query length.<p>
534     *
535     * @return the minimum search query length
536     */
537    public int getQueryLength() {
538
539        return m_queryLength;
540    }
541
542    /**
543     * Returns the list of resource types to limit the search to.<p>
544     *
545     * @return the list of resource types to limit the search to
546     *
547     * @since 7.5.1
548     */
549    public List<String> getResourceTypes() {
550
551        return m_resourceTypes;
552    }
553
554    /**
555     * Returns the list of strings of search roots to use.<p>
556     *
557     * Only resource that are sub-resource of one of the search roots are included in the search result.<p>
558     *
559     * @return the list of strings of search roots to use
560     */
561    public List<String> getRoots() {
562
563        return m_roots;
564    }
565
566    /**
567     * Returns the list of categories to limit the search to.<p>
568     *
569     * @return the list of categories to limit the search to
570     */
571    public String getSearchCategories() {
572
573        return toSeparatedString(getCategories(), ',');
574    }
575
576    /**
577     * Returns the search index to search in or null if not set before
578     * (<code>{@link #setSearchIndex(CmsSearchIndex)}</code>). <p>
579     *
580     * @return the search index to search in or null if not set before (<code>{@link #setSearchIndex(CmsSearchIndex)}</code>)
581     */
582    public CmsSearchIndex getSearchIndex() {
583
584        return m_index;
585    }
586
587    /**
588     * Returns the search page to display.<p>
589     *
590     * @return the search page to display
591     */
592    public int getSearchPage() {
593
594        return m_page;
595    }
596
597    /**
598     * Returns the comma separated lists of root folder names to restrict search to.<p>
599     *
600     * This method is a "sibling" to method <code>{@link #getRoots()}</code> but with
601     * the support of being usable with widget technology. <p>
602     *
603     * @return the comma separated lists of field names to search in
604     *
605     * @see #setSortName(String)
606     */
607    public String getSearchRoots() {
608
609        return toSeparatedString(m_roots, ',');
610    }
611
612    /**
613     * Returns the instance that defines the sort order for the results.
614     *
615     * @return the instance that defines the sort order for the results
616     */
617    public Sort getSort() {
618
619        return m_sort;
620    }
621
622    /**
623     * Returns the name of the sort option being used.<p>
624     * @return the name of the sort option being used
625     *
626     * @see #SORT_NAMES
627     * @see #setSortName(String)
628     */
629    public String getSortName() {
630
631        if (m_sort == SORT_DATE_CREATED) {
632            return SORT_NAMES[1];
633        }
634        if (m_sort == SORT_DATE_LASTMODIFIED) {
635            return SORT_NAMES[2];
636        }
637        if (m_sort == SORT_TITLE) {
638            return SORT_NAMES[3];
639        }
640        return SORT_NAMES[0];
641    }
642
643    /**
644     * Returns <code>true</code> if the category count is calculated for all search results.<p>
645     *
646     * @return <code>true</code> if the category count is calculated for all search results
647     */
648    public boolean isCalculateCategories() {
649
650        return m_calculateCategories;
651    }
652
653    /**
654     * Returns <code>true</code> if fields configured for the excerpt should be used for generating the excerpt only
655     * if they have been actually searched in.<p>
656     *
657     * The default setting is <code>false</code>, which means all text fields configured for the excerpt will
658     * be used to generate the excerpt, regardless if they have been searched in or not.<p>
659     *
660     * Please note: A field will only be included in the excerpt if it has been configured as <code>excerpt="true"</code>
661     * in <code>opencms-search.xml</code>. This method controls if so configured fields are used depending on the
662     * fields searched, see {@link #setFields(List)}.<p>
663     *
664     * @return <code>true</code> if fields configured for the excerpt should be used for generating the excerpt only
665     * if they have been actually searched in
666     */
667    public boolean isExcerptOnlySearchedFields() {
668
669        return m_excerptOnlySearchedFields;
670    }
671
672    /**
673     * Returns <code>true</code> if the query part should be ignored so that only filters are used for searching.<p>
674     *
675     * @return <code>true</code> if the query part should be ignored so that only filters are used for searching
676     *
677     * @since 8.0.0
678     */
679    public boolean isIgnoreQuery() {
680
681        return m_isIgnoreQuery;
682    }
683
684    /**
685     * Creates a merged parameter set from this parameters, restricted by the given other parameters.<p>
686     *
687     * This is mainly intended for "search in search result" functions.<p>
688     *
689     * The restricted query is build of the queries of both parameters, appended with AND.<p>
690     *
691     * The lists in the restriction for <code>{@link #getFields()}</code>, <code>{@link #getRoots()}</code> and
692     * <code>{@link #getCategories()}</code> are <b>intersected</b> with the lists of this search parameters. Only
693     * elements contained in both lists are included for the created search parameters.
694     * If a list in either the restriction or in this search parameters is <code>null</code>,
695     * the list from the other search parameters is used directly.<p>
696     *
697     * The values for
698     * <code>{@link #isCalculateCategories()}</code>
699     * and <code>{@link #getSort()}</code> of this parameters are used for the restricted parameters.<p>
700     *
701     * @param restriction the parameters to restrict this parameters with
702     * @return the restricted parameters
703     */
704    public CmsSearchParameters restrict(CmsSearchParameters restriction) {
705
706        // append queries
707        StringBuffer query = new StringBuffer(256);
708        if (getQuery() != null) {
709            // don't blow up unnecessary closures (if CmsSearch is reused and restricted several times)
710            boolean closure = !getQuery().startsWith("+(");
711            if (closure) {
712                query.append("+(");
713            }
714            query.append(getQuery());
715            if (closure) {
716                query.append(")");
717            }
718        }
719        if (restriction.getQuery() != null) {
720            // don't let Lucene max terms be exceeded in case someone reuses a CmsSearch and continuously restricts
721            // query with the same restrictions...
722            if (query.indexOf(restriction.getQuery()) < 0) {
723                query.append(" +(");
724                query.append(restriction.getQuery());
725                query.append(")");
726            }
727        }
728
729        // restrict fields
730        List<String> fields = null;
731        if ((m_fields != null) && (m_fields.size() > 0)) {
732            if ((restriction.getFields() != null) && (restriction.getFields().size() > 0)) {
733                fields = ListUtils.intersection(m_fields, restriction.getFields());
734            } else {
735                fields = m_fields;
736            }
737        } else {
738            fields = restriction.getFields();
739        }
740
741        // restrict roots
742        List<String> roots = null;
743        if ((m_roots != null) && (m_roots.size() > 0)) {
744            if ((restriction.getRoots() != null) && (restriction.getRoots().size() > 0)) {
745                roots = ListUtils.intersection(m_roots, restriction.getRoots());
746                // TODO: This only works if there are equal paths in both parameter sets - for two distinct sets
747                //       all root restrictions are dropped with an empty list.
748            } else {
749                roots = m_roots;
750            }
751        } else {
752            roots = restriction.getRoots();
753        }
754
755        // restrict categories
756        List<String> categories = null;
757        if ((m_categories != null) && (m_categories.size() > 0)) {
758            if ((restriction.getCategories() != null) && (restriction.getCategories().size() > 0)) {
759                categories = ListUtils.intersection(m_categories, restriction.getCategories());
760            } else {
761                categories = m_categories;
762            }
763        } else {
764            categories = restriction.getCategories();
765        }
766
767        // restrict resource types
768        List<String> resourceTypes = null;
769        if ((m_resourceTypes != null) && (m_resourceTypes.size() > 0)) {
770            if ((restriction.getResourceTypes() != null) && (restriction.getResourceTypes().size() > 0)) {
771                resourceTypes = ListUtils.intersection(m_resourceTypes, restriction.getResourceTypes());
772            } else {
773                resourceTypes = m_resourceTypes;
774            }
775        } else {
776            resourceTypes = restriction.getResourceTypes();
777        }
778
779        // create the new search parameters
780        CmsSearchParameters result = new CmsSearchParameters(
781            query.toString(),
782            fields,
783            roots,
784            categories,
785            resourceTypes,
786            m_calculateCategories,
787            m_sort);
788        result.setIndex(getIndex());
789        return result;
790    }
791
792    /**
793     * Set whether category counts shall be calculated for the corresponding search results or not.<p>
794     *
795     * @param flag true if category counts shall be calculated for the corresponding search results or false if not
796     */
797    public void setCalculateCategories(boolean flag) {
798
799        m_calculateCategories = flag;
800    }
801
802    /**
803     * Set the list of categories (strings) to this parameters. <p>
804     *
805     * @param categories the list of categories (strings) of this parameters
806     */
807    public void setCategories(List<String> categories) {
808
809        m_categories = categories;
810    }
811
812    /**
813     * Sets the maximum number of pages which should be shown.<p>
814     *
815     * Enter an odd value to achieve a nice, "symmetric" output.<p>
816     *
817     * @param value the maximum number of pages which should be shown
818     */
819    public void setDisplayPages(int value) {
820
821        m_displayPages = value;
822    }
823
824    /**
825     * Controls if the excerpt from a field is generated only for searched fields, or for all fields (the default).<p>
826     *
827     * @param excerptOnlySearchedFields if <code>true</code>, the excerpt is generated only from the fields actually searched in
828     *
829     * @see #isExcerptOnlySearchedFields()
830     */
831    public void setExcerptOnlySearchedFields(boolean excerptOnlySearchedFields) {
832
833        m_excerptOnlySearchedFields = excerptOnlySearchedFields;
834    }
835
836    /**
837     * Sets the list of strings of names of fields to search in. <p>
838     *
839     * @param fields the list of strings of names of fields to search in to set
840     */
841    public void setFields(List<String> fields) {
842
843        m_fields = fields;
844    }
845
846    /**
847     * Sets the flag to indicate if the query part should be ignored so that only filters are used for searching.<p>
848     *
849     * @param isIgnoreQuery the flag to indicate if the query part should be ignored
850     *
851     * @since 8.0.0
852     */
853    public void setIgnoreQuery(boolean isIgnoreQuery) {
854
855        m_isIgnoreQuery = isIgnoreQuery;
856    }
857
858    /**
859     * Set the name of the index to search.<p>
860     *
861     * @param indexName the name of the index
862     */
863    public void setIndex(String indexName) {
864
865        CmsSearchIndex index;
866        if (CmsStringUtil.isNotEmpty(indexName)) {
867            try {
868                I_CmsSearchIndex idx = OpenCms.getSearchManager().getIndex(indexName);
869                index = idx instanceof CmsSearchIndex ? (CmsSearchIndex)idx : null;
870                if (index == null) {
871                    throw new CmsException(Messages.get().container(Messages.ERR_INDEX_NOT_FOUND_1, indexName));
872                }
873                setSearchIndex(index);
874            } catch (Exception exc) {
875                if (LOG.isErrorEnabled()) {
876                    LOG.error(Messages.get().getBundle().key(Messages.LOG_INDEX_ACCESS_FAILED_1, indexName), exc);
877                }
878            }
879        }
880    }
881
882    /**
883     * Sets the number of matches per page.<p>
884     *
885     * @param matches the number of matches per page
886     */
887    public void setMatchesPerPage(int matches) {
888
889        m_matchesPerPage = matches;
890    }
891
892    /**
893     * Sets the maximum creation date a resource must have to be included in the search result.<p>
894     *
895     * @param maxDateCreated the maximum creation date to set
896     */
897    public void setMaxDateCreated(long maxDateCreated) {
898
899        m_maxDateCreated = maxDateCreated;
900    }
901
902    /**
903     * Sets the maximum last modification date a resource must have to be included in the search result.<p>
904     *
905     * @param maxDateLastModified the maximum last modification date to set
906     */
907    public void setMaxDateLastModified(long maxDateLastModified) {
908
909        m_maxDateLastModified = maxDateLastModified;
910    }
911
912    /**
913     * Sets the minimum creation date a resource must have to be included in the search result.<p>
914     *
915     * @param minDateCreated the minimum creation date to set
916     */
917    public void setMinDateCreated(long minDateCreated) {
918
919        m_minDateCreated = minDateCreated;
920    }
921
922    /**
923     * Sets the minimum last modification date a resource must have to be included in the search result.<p>
924     *
925     * @param minDateLastModified he minimum last modification date to set
926     */
927    public void setMinDateLastModified(long minDateLastModified) {
928
929        m_minDateLastModified = minDateLastModified;
930    }
931
932    /**
933     * Sets the query to search for. <p>
934     *
935     * The decoding here is tailored for query strings that are
936     * additionally manually UTF-8 encoded at client side (javascript) to get around an
937     * issue with special chars in applications that use non- UTF-8 encoding
938     * (e.g. ISO-8859-1) OpenCms applications. It is not recommended to use this with
939     * front ends that don't encode manually as characters like sole "%" (without number suffix)
940     * will cause an Exception.<p>
941     *
942     * @param query the query to search for to set
943     */
944    public void setQuery(String query) {
945
946        // for use with widgets the exception is thrown here to enforce the error message next to the widget
947        if (query.trim().length() < getQueryLength()) {
948            throw new CmsIllegalArgumentException(
949                Messages.get().container(Messages.ERR_QUERY_TOO_SHORT_1, Integer.valueOf(getQueryLength())));
950        }
951        m_query = query;
952    }
953
954    /**
955     * Sets the minimum length of the search query.<p>
956     *
957     * @param length the minimum search query length
958     */
959    public void setQueryLength(int length) {
960
961        m_queryLength = length;
962    }
963
964    /**
965     * Set the list of resource types (strings) to limit the search to. <p>
966     *
967     * @param resourceTypes the list of resource types (strings) to limit the search to
968     *
969     * @since 7.5.1
970     */
971    public void setResourceTypes(List<String> resourceTypes) {
972
973        m_resourceTypes = resourceTypes;
974    }
975
976    /**
977     * Sets the list of strings of roots to search under for the search.<p>
978     *
979     * @param roots  the list of strings of roots to search under for the search to set
980     */
981    public void setRoots(List<String> roots) {
982
983        m_roots = roots;
984    }
985
986    /**
987     * Set the comma separated search root names to  restrict search to.<p>
988     *
989     * @param categories the comma separated category names to  restrict search to
990     */
991    public void setSearchCategories(String categories) {
992
993        setCategories(CmsStringUtil.splitAsList(categories, ','));
994    }
995
996    /**
997     * Sets the search index to use for the search. <p>
998     *
999     * @param index the search index to use for the search to set.
1000     *
1001     * @throws CmsIllegalArgumentException if null is given as argument
1002     */
1003    public void setSearchIndex(CmsSearchIndex index) throws CmsIllegalArgumentException {
1004
1005        if (index == null) {
1006            throw new CmsIllegalArgumentException(Messages.get().container(Messages.ERR_INDEX_NULL_0));
1007        }
1008        m_index = index;
1009    }
1010
1011    /**
1012     * Set the search page to display. <p>
1013     *
1014     * @param page the search page to display
1015     */
1016    public void setSearchPage(int page) {
1017
1018        m_page = page;
1019    }
1020
1021    /**
1022     * Set the comma separated search root names to  restrict search to.<p>
1023     *
1024     * @param rootNameList the comma separated search root names to  restrict search to
1025     */
1026    public void setSearchRoots(String rootNameList) {
1027
1028        m_roots = CmsStringUtil.splitAsList(rootNameList, ',');
1029    }
1030
1031    /**
1032     * Set the instance that defines the sort order for search results.
1033     *
1034     * @param sortOrder the instance that defines the sort order for search results to set
1035     */
1036    public void setSort(Sort sortOrder) {
1037
1038        m_sort = sortOrder;
1039    }
1040
1041    /**
1042     * Sets the internal member of type <code>{@link Sort}</code> according to
1043     * the given sort name. <p>
1044     *
1045     * For a list of valid sort names, please see <code>{@link #SORT_NAMES}</code>.<p>
1046     *
1047     * @param sortName the name of the sort to use
1048     *
1049     * @see #SORT_NAMES
1050     */
1051    public void setSortName(String sortName) {
1052
1053        if (sortName.equals(SORT_NAMES[1])) {
1054            m_sort = SORT_DATE_CREATED;
1055        } else if (sortName.equals(SORT_NAMES[2])) {
1056            m_sort = SORT_DATE_LASTMODIFIED;
1057        } else if (sortName.equals(SORT_NAMES[3])) {
1058            m_sort = SORT_TITLE;
1059        } else {
1060            m_sort = SORT_DEFAULT;
1061        }
1062    }
1063
1064    /**
1065     * Creates a query String build from this search parameters for HTML links.<p>
1066     *
1067     * @return a query String build from this search parameters for HTML links
1068     */
1069    public String toQueryString() {
1070
1071        StringBuffer result = new StringBuffer(128);
1072        result.append("?action=search");
1073        result.append("&query=");
1074        result.append(CmsEncoder.encodeParameter(getQuery()));
1075
1076        result.append("&matchesPerPage=");
1077        result.append(getMatchesPerPage());
1078        result.append("&displayPages=");
1079        result.append(getDisplayPages());
1080        result.append("&index=");
1081        result.append(CmsEncoder.encodeParameter(getIndex()));
1082
1083        Sort sort = getSort();
1084        if (sort != CmsSearchParameters.SORT_DEFAULT) {
1085            result.append("&sort=");
1086            if (sort == CmsSearchParameters.SORT_TITLE) {
1087                result.append("title");
1088            } else if (sort == CmsSearchParameters.SORT_DATE_CREATED) {
1089                result.append("date-created");
1090            } else if (sort == CmsSearchParameters.SORT_DATE_LASTMODIFIED) {
1091                result.append("date-lastmodified");
1092            }
1093        }
1094
1095        if ((getCategories() != null) && (getCategories().size() > 0)) {
1096            result.append("&category=");
1097            Iterator<String> it = getCategories().iterator();
1098            while (it.hasNext()) {
1099                result.append(it.next());
1100                if (it.hasNext()) {
1101                    result.append(',');
1102                }
1103            }
1104        }
1105
1106        if (getMinDateCreated() > Long.MIN_VALUE) {
1107            result.append("&minDateCreated=");
1108            result.append(getMinDateCreated());
1109        }
1110        if (getMinDateLastModified() > Long.MIN_VALUE) {
1111            result.append("&minDateLastModified=");
1112            result.append(getMinDateLastModified());
1113        }
1114        if (getMaxDateCreated() < Long.MAX_VALUE) {
1115            result.append("&maxDateCreated=");
1116            result.append(getMaxDateCreated());
1117        }
1118        if (getMaxDateLastModified() < Long.MAX_VALUE) {
1119            result.append("&maxDateLastModified=");
1120            result.append(getMaxDateLastModified());
1121        }
1122
1123        if ((getRoots() != null) && (getRoots().size() > 0)) {
1124            result.append("&searchRoots=");
1125            Iterator<String> it = getRoots().iterator();
1126            while (it.hasNext()) {
1127                result.append(CmsEncoder.encode(it.next()));
1128                if (it.hasNext()) {
1129                    result.append(',');
1130                }
1131            }
1132        }
1133
1134        if (isExcerptOnlySearchedFields()) {
1135            result.append("&excerptOnlySearchedFields=true");
1136        }
1137
1138        return result.toString();
1139    }
1140
1141    /**
1142     * @see java.lang.Object#toString()
1143     */
1144    @Override
1145    public String toString() {
1146
1147        StringBuffer result = new StringBuffer();
1148        result.append("query:[");
1149        result.append(m_query);
1150        result.append("] ");
1151        if ((m_fields != null) && (m_fields.size() > 0)) {
1152            result.append("fields:[");
1153            for (int i = 0; i < m_fields.size(); i++) {
1154                result.append(m_fields.get(i));
1155                if ((i + 1) < m_fields.size()) {
1156                    result.append(", ");
1157                }
1158            }
1159            result.append("] ");
1160        }
1161        if ((m_roots != null) && (m_roots.size() > 0)) {
1162            result.append("roots:[");
1163            for (int i = 0; i < m_roots.size(); i++) {
1164                result.append(m_roots.get(i));
1165                if ((i + 1) < m_roots.size()) {
1166                    result.append(", ");
1167                }
1168            }
1169            result.append("] ");
1170        }
1171        if ((m_categories != null) && (m_categories.size() > 0)) {
1172            result.append("categories:[");
1173            for (int i = 0; i < m_categories.size(); i++) {
1174                result.append(m_categories.get(i));
1175                if ((i + 1) < m_categories.size()) {
1176                    result.append(", ");
1177                }
1178            }
1179            result.append("] ");
1180        }
1181        if ((m_resourceTypes != null) && (m_resourceTypes.size() > 0)) {
1182            result.append("resourceTypes:[");
1183            for (int i = 0; i < m_resourceTypes.size(); i++) {
1184                result.append(m_resourceTypes.get(i));
1185                if ((i + 1) < m_resourceTypes.size()) {
1186                    result.append(", ");
1187                }
1188            }
1189            result.append("] ");
1190        }
1191        if (m_calculateCategories) {
1192            result.append("calculate-categories ");
1193        }
1194        if (m_excerptOnlySearchedFields) {
1195            result.append("excerpt-searched-fields-only ");
1196        }
1197        result.append("sort:[");
1198        if (m_sort == CmsSearchParameters.SORT_DEFAULT) {
1199            result.append("default");
1200        } else if (m_sort == CmsSearchParameters.SORT_TITLE) {
1201            result.append("title");
1202        } else if (m_sort == CmsSearchParameters.SORT_DATE_CREATED) {
1203            result.append("date-created");
1204        } else if (m_sort == CmsSearchParameters.SORT_DATE_LASTMODIFIED) {
1205            result.append("date-lastmodified");
1206        } else {
1207            result.append("unknown");
1208        }
1209        result.append("]");
1210
1211        return result.toString();
1212    }
1213
1214    /**
1215     * Concatenates the elements of the string list separated by the given separator character.<p>
1216     *
1217     * @param stringList the list
1218     * @param separator the separator
1219     *
1220     * @return the concatenated string
1221     */
1222    private String toSeparatedString(List<String> stringList, char separator) {
1223
1224        StringBuffer result = new StringBuffer();
1225        Iterator<String> it = stringList.iterator();
1226        while (it.hasNext()) {
1227            result.append(it.next());
1228            if (it.hasNext()) {
1229                result.append(separator);
1230            }
1231        }
1232        return result.toString();
1233    }
1234}