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.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 pre-parsed query. */
274    private String m_parsedQuery;
275
276    /** The search query to use. */
277    private String m_query;
278
279    /** The minimum length of the search query. */
280    private int m_queryLength;
281
282    /** The list of resource types to limit the search to. */
283    private List<String> m_resourceTypes;
284
285    /** Only resource that are sub-resource of one of the search roots are included in the search result. */
286    private List<String> m_roots;
287
288    /** The sort order for the search. */
289    private Sort m_sort;
290
291    /**
292     * Creates a new search parameter instance with no search query and
293     * default values for the remaining parameters. <p>
294     *
295     * Before using this search parameters for a search method
296     * <code>{@link #setQuery(String)}</code> has to be invoked. <p>
297     *
298     */
299    public CmsSearchParameters() {
300
301        this("");
302    }
303
304    /**
305     * Creates a new search parameter instance with the provided search query and
306     * default values for the remaining parameters. <p>
307     *
308     * Only the "meta" field (combination of content and title) will be used for search.
309     * No search root restriction is chosen.
310     * No category restriction is used.
311     * No category counts are calculated for the result.
312     * Sorting is turned off. This is a simple but fast setup. <p>
313     *
314     * @param query the query to search for
315     */
316    public CmsSearchParameters(String query) {
317
318        this(query, null, null, null, null, false, null);
319
320    }
321
322    /**
323     * Creates a new search parameter instance with the provided parameter values.<p>
324     *
325     * @param query the search term to search the index
326     * @param fields the list of fields to search
327     * @param roots only resource that are sub-resource of one of the search roots are included in the search result
328     * @param categories the list of categories to limit the search to
329     * @param resourceTypes the list of resource types to limit the search to
330     * @param calculateCategories if <code>true</code>, the category count is calculated for all search results
331     *      (use with caution, this option uses much performance)
332     * @param sort the sort order for the search
333     */
334    public CmsSearchParameters(
335        String query,
336        List<String> fields,
337        List<String> roots,
338        List<String> categories,
339        List<String> resourceTypes,
340        boolean calculateCategories,
341        Sort sort) {
342
343        super();
344        m_query = (query == null) ? "" : query;
345        if (fields == null) {
346            fields = new ArrayList<String>(2);
347            fields.add(CmsSearchIndex.DOC_META_FIELDS[0]);
348            fields.add(CmsSearchIndex.DOC_META_FIELDS[1]);
349        }
350        m_fields = fields;
351        if (roots == null) {
352            roots = new ArrayList<String>(2);
353        }
354        m_roots = roots;
355        m_categories = (categories == null) ? new ArrayList<String>() : categories;
356        m_resourceTypes = (resourceTypes == null) ? new ArrayList<String>() : resourceTypes;
357        m_calculateCategories = calculateCategories;
358        // null sort is allowed default
359        m_sort = sort;
360        m_page = 1;
361        m_queryLength = -1;
362        m_matchesPerPage = 10;
363        m_displayPages = 10;
364        m_isIgnoreQuery = false;
365
366        m_minDateCreated = Long.MIN_VALUE;
367        m_maxDateCreated = Long.MAX_VALUE;
368        m_minDateLastModified = Long.MIN_VALUE;
369        m_maxDateLastModified = Long.MAX_VALUE;
370    }
371
372    /**
373     * Adds an individual query for a search field.<p>
374     *
375     * If this is used, any setting made with {@link #setQuery(String)} and {@link #setFields(List)}
376     * will be ignored and only the individual field search settings will be used.<p>
377     *
378     * @param query the query to add
379     *
380     * @since 7.5.1
381     */
382    public void addFieldQuery(CmsSearchFieldQuery query) {
383
384        if (m_fieldQueries == null) {
385            m_fieldQueries = new ArrayList<CmsSearchFieldQuery>();
386            m_fields = new ArrayList<String>();
387        }
388        m_fieldQueries.add(query);
389        // add the used field used in the fields query to the list of fields used in the search
390        if (!m_fields.contains(query.getFieldName())) {
391            m_fields.add(query.getFieldName());
392        }
393    }
394
395    /**
396     * Adds an individual query for a search field.<p>
397     *
398     * If this is used, any setting made with {@link #setQuery(String)} and {@link #setFields(List)}
399     * will be ignored and only the individual field search settings will be used.<p>
400     *
401     * @param fieldName the field name
402     * @param searchQuery the search query
403     * @param occur the occur parameter for the query in the field
404     *
405     * @since 7.5.1
406     */
407    public void addFieldQuery(String fieldName, String searchQuery, Occur occur) {
408
409        CmsSearchFieldQuery newQuery = new CmsSearchFieldQuery(fieldName, searchQuery, occur);
410        addFieldQuery(newQuery);
411    }
412
413    /**
414     * Returns whether category counts are calculated for search results or not. <p>
415     *
416     * @return a boolean that tells whether category counts are calculated for search results or not
417     */
418    public boolean getCalculateCategories() {
419
420        return m_calculateCategories;
421    }
422
423    /**
424     * Returns the list of categories to limit the search to.<p>
425     *
426     * @return the list of categories to limit the search to
427     */
428    public List<String> getCategories() {
429
430        return m_categories;
431    }
432
433    /**
434     * Returns the maximum number of pages which should be shown.<p>
435     *
436     * @return the maximum number of pages which should be shown
437     */
438    public int getDisplayPages() {
439
440        return m_displayPages;
441    }
442
443    /**
444     * Returns the list of individual field queries.<p>
445     *
446     * @return the list of individual field queries
447     *
448     * @since 7.5.1
449     */
450    public List<CmsSearchFieldQuery> getFieldQueries() {
451
452        return m_fieldQueries;
453    }
454
455    /**
456     * Returns the list of search index field names (Strings) to search in.<p>
457     *
458     * @return the list of search index field names (Strings) to search in
459     */
460    public List<String> getFields() {
461
462        return m_fields;
463    }
464
465    /**
466     * Get the name of the index for the search.<p>
467     *
468     * @return the name of the index for the search
469     */
470    public String getIndex() {
471
472        return m_index.getName();
473    }
474
475    /**
476     * Gets the number of matches displayed on each page.<p>
477     *
478     * @return matches per result page
479     */
480    public int getMatchesPerPage() {
481
482        return m_matchesPerPage;
483    }
484
485    /**
486     * Returns the maximum creation date a resource must have to be included in the search result.<p>
487     *
488     * @return the maximum creation date a resource must have to be included in the search result
489     */
490    public long getMaxDateCreated() {
491
492        return m_maxDateCreated;
493    }
494
495    /**
496     * Returns the maximum last modification date a resource must have to be included in the search result.<p>
497     *
498     * @return the maximum last modification date a resource must have to be included in the search result
499     */
500    public long getMaxDateLastModified() {
501
502        return m_maxDateLastModified;
503    }
504
505    /**
506     * Returns the minimum creation date a resource must have to be included in the search result.<p>
507     *
508     * @return the minimum creation date a resource must have to be included in the search result
509     */
510    public long getMinDateCreated() {
511
512        return m_minDateCreated;
513    }
514
515    /**
516     * Returns the minimum last modification date a resource must have to be included in the search result.<p>
517     *
518     * @return the minimum last modification date a resource must have to be included in the search result
519     */
520    public long getMinDateLastModified() {
521
522        return m_minDateLastModified;
523    }
524
525    /**
526     * Returns the parsed query.<p>
527     *
528     * The parsed query is automatically set by the OpenCms search index when a query is created
529     * with either {@link #setQuery(String)} or {@link #addFieldQuery(CmsSearchFieldQuery)}.
530     * The Lucene query build from the parameters is stored here and can be later used
531     * for paging through the results.<p>
532     *
533     * Please note that this returns only to the query part, not the filter part of the search.<p>
534     *
535     * @return the parsed query
536     */
537    public String getParsedQuery() {
538
539        return m_parsedQuery;
540    }
541
542    /**
543     * Returns the search query to use.<p>
544     *
545     * @return the search query to use
546     */
547    public String getQuery() {
548
549        return m_query;
550    }
551
552    /**
553     * Gets the minimum search query length.<p>
554     *
555     * @return the minimum search query length
556     */
557    public int getQueryLength() {
558
559        return m_queryLength;
560    }
561
562    /**
563     * Returns the list of resource types to limit the search to.<p>
564     *
565     * @return the list of resource types to limit the search to
566     *
567     * @since 7.5.1
568     */
569    public List<String> getResourceTypes() {
570
571        return m_resourceTypes;
572    }
573
574    /**
575     * Returns the list of strings of search roots to use.<p>
576     *
577     * Only resource that are sub-resource of one of the search roots are included in the search result.<p>
578     *
579     * @return the list of strings of search roots to use
580     */
581    public List<String> getRoots() {
582
583        return m_roots;
584    }
585
586    /**
587     * Returns the list of categories to limit the search to.<p>
588     *
589     * @return the list of categories to limit the search to
590     */
591    public String getSearchCategories() {
592
593        return toSeparatedString(getCategories(), ',');
594    }
595
596    /**
597     * Returns the search index to search in or null if not set before
598     * (<code>{@link #setSearchIndex(CmsSearchIndex)}</code>). <p>
599     *
600     * @return the search index to search in or null if not set before (<code>{@link #setSearchIndex(CmsSearchIndex)}</code>)
601     */
602    public CmsSearchIndex getSearchIndex() {
603
604        return m_index;
605    }
606
607    /**
608     * Returns the search page to display.<p>
609     *
610     * @return the search page to display
611     */
612    public int getSearchPage() {
613
614        return m_page;
615    }
616
617    /**
618     * Returns the comma separated lists of root folder names to restrict search to.<p>
619     *
620     * This method is a "sibling" to method <code>{@link #getRoots()}</code> but with
621     * the support of being usable with widget technology. <p>
622     *
623     * @return the comma separated lists of field names to search in
624     *
625     * @see #setSortName(String)
626     */
627    public String getSearchRoots() {
628
629        return toSeparatedString(m_roots, ',');
630    }
631
632    /**
633     * Returns the instance that defines the sort order for the results.
634     *
635     * @return the instance that defines the sort order for the results
636     */
637    public Sort getSort() {
638
639        return m_sort;
640    }
641
642    /**
643     * Returns the name of the sort option being used.<p>
644     * @return the name of the sort option being used
645     *
646     * @see #SORT_NAMES
647     * @see #setSortName(String)
648     */
649    public String getSortName() {
650
651        if (m_sort == SORT_DATE_CREATED) {
652            return SORT_NAMES[1];
653        }
654        if (m_sort == SORT_DATE_LASTMODIFIED) {
655            return SORT_NAMES[2];
656        }
657        if (m_sort == SORT_TITLE) {
658            return SORT_NAMES[3];
659        }
660        return SORT_NAMES[0];
661    }
662
663    /**
664     * Returns <code>true</code> if the category count is calculated for all search results.<p>
665     *
666     * @return <code>true</code> if the category count is calculated for all search results
667     */
668    public boolean isCalculateCategories() {
669
670        return m_calculateCategories;
671    }
672
673    /**
674     * Returns <code>true</code> if fields configured for the excerpt should be used for generating the excerpt only
675     * if they have been actually searched in.<p>
676     *
677     * The default setting is <code>false</code>, which means all text fields configured for the excerpt will
678     * be used to generate the excerpt, regardless if they have been searched in or not.<p>
679     *
680     * Please note: A field will only be included in the excerpt if it has been configured as <code>excerpt="true"</code>
681     * in <code>opencms-search.xml</code>. This method controls if so configured fields are used depending on the
682     * fields searched, see {@link #setFields(List)}.<p>
683     *
684     * @return <code>true</code> if fields configured for the excerpt should be used for generating the excerpt only
685     * if they have been actually searched in
686     */
687    public boolean isExcerptOnlySearchedFields() {
688
689        return m_excerptOnlySearchedFields;
690    }
691
692    /**
693     * Returns <code>true</code> if the query part should be ignored so that only filters are used for searching.<p>
694     *
695     * @return <code>true</code> if the query part should be ignored so that only filters are used for searching
696     *
697     * @since 8.0.0
698     */
699    public boolean isIgnoreQuery() {
700
701        return m_isIgnoreQuery;
702    }
703
704    /**
705     * Creates a merged parameter set from this parameters, restricted by the given other parameters.<p>
706     *
707     * This is mainly intended for "search in search result" functions.<p>
708     *
709     * The restricted query is build of the queries of both parameters, appended with AND.<p>
710     *
711     * The lists in the restriction for <code>{@link #getFields()}</code>, <code>{@link #getRoots()}</code> and
712     * <code>{@link #getCategories()}</code> are <b>intersected</b> with the lists of this search parameters. Only
713     * elements contained in both lists are included for the created search parameters.
714     * If a list in either the restriction or in this search parameters is <code>null</code>,
715     * the list from the other search parameters is used directly.<p>
716     *
717     * The values for
718     * <code>{@link #isCalculateCategories()}</code>
719     * and <code>{@link #getSort()}</code> of this parameters are used for the restricted parameters.<p>
720     *
721     * @param restriction the parameters to restrict this parameters with
722     * @return the restricted parameters
723     */
724    public CmsSearchParameters restrict(CmsSearchParameters restriction) {
725
726        // append queries
727        StringBuffer query = new StringBuffer(256);
728        if (getQuery() != null) {
729            // don't blow up unnecessary closures (if CmsSearch is reused and restricted several times)
730            boolean closure = !getQuery().startsWith("+(");
731            if (closure) {
732                query.append("+(");
733            }
734            query.append(getQuery());
735            if (closure) {
736                query.append(")");
737            }
738        }
739        if (restriction.getQuery() != null) {
740            // don't let Lucene max terms be exceeded in case someone reuses a CmsSearch and continuously restricts
741            // query with the same restrictions...
742            if (query.indexOf(restriction.getQuery()) < 0) {
743                query.append(" +(");
744                query.append(restriction.getQuery());
745                query.append(")");
746            }
747        }
748
749        // restrict fields
750        List<String> fields = null;
751        if ((m_fields != null) && (m_fields.size() > 0)) {
752            if ((restriction.getFields() != null) && (restriction.getFields().size() > 0)) {
753                fields = ListUtils.intersection(m_fields, restriction.getFields());
754            } else {
755                fields = m_fields;
756            }
757        } else {
758            fields = restriction.getFields();
759        }
760
761        // restrict roots
762        List<String> roots = null;
763        if ((m_roots != null) && (m_roots.size() > 0)) {
764            if ((restriction.getRoots() != null) && (restriction.getRoots().size() > 0)) {
765                roots = ListUtils.intersection(m_roots, restriction.getRoots());
766                // TODO: This only works if there are equal paths in both parameter sets - for two distinct sets
767                //       all root restrictions are dropped with an empty list.
768            } else {
769                roots = m_roots;
770            }
771        } else {
772            roots = restriction.getRoots();
773        }
774
775        // restrict categories
776        List<String> categories = null;
777        if ((m_categories != null) && (m_categories.size() > 0)) {
778            if ((restriction.getCategories() != null) && (restriction.getCategories().size() > 0)) {
779                categories = ListUtils.intersection(m_categories, restriction.getCategories());
780            } else {
781                categories = m_categories;
782            }
783        } else {
784            categories = restriction.getCategories();
785        }
786
787        // restrict resource types
788        List<String> resourceTypes = null;
789        if ((m_resourceTypes != null) && (m_resourceTypes.size() > 0)) {
790            if ((restriction.getResourceTypes() != null) && (restriction.getResourceTypes().size() > 0)) {
791                resourceTypes = ListUtils.intersection(m_resourceTypes, restriction.getResourceTypes());
792            } else {
793                resourceTypes = m_resourceTypes;
794            }
795        } else {
796            resourceTypes = restriction.getResourceTypes();
797        }
798
799        // create the new search parameters
800        CmsSearchParameters result = new CmsSearchParameters(
801            query.toString(),
802            fields,
803            roots,
804            categories,
805            resourceTypes,
806            m_calculateCategories,
807            m_sort);
808        result.setIndex(getIndex());
809        return result;
810    }
811
812    /**
813     * Set whether category counts shall be calculated for the corresponding search results or not.<p>
814     *
815     * @param flag true if category counts shall be calculated for the corresponding search results or false if not
816     */
817    public void setCalculateCategories(boolean flag) {
818
819        m_calculateCategories = flag;
820    }
821
822    /**
823     * Set the list of categories (strings) to this parameters. <p>
824     *
825     * @param categories the list of categories (strings) of this parameters
826     */
827    public void setCategories(List<String> categories) {
828
829        m_categories = categories;
830    }
831
832    /**
833     * Sets the maximum number of pages which should be shown.<p>
834     *
835     * Enter an odd value to achieve a nice, "symmetric" output.<p>
836     *
837     * @param value the maximum number of pages which should be shown
838     */
839    public void setDisplayPages(int value) {
840
841        m_displayPages = value;
842    }
843
844    /**
845     * Controls if the excerpt from a field is generated only for searched fields, or for all fields (the default).<p>
846     *
847     * @param excerptOnlySearchedFields if <code>true</code>, the excerpt is generated only from the fields actually searched in
848     *
849     * @see #isExcerptOnlySearchedFields()
850     */
851    public void setExcerptOnlySearchedFields(boolean excerptOnlySearchedFields) {
852
853        m_excerptOnlySearchedFields = excerptOnlySearchedFields;
854    }
855
856    /**
857     * Sets the list of strings of names of fields to search in. <p>
858     *
859     * @param fields the list of strings of names of fields to search in to set
860     */
861    public void setFields(List<String> fields) {
862
863        m_fields = fields;
864    }
865
866    /**
867     * Sets the flag to indicate if the query part should be ignored so that only filters are used for searching.<p>
868     *
869     * @param isIgnoreQuery the flag to indicate if the query part should be ignored
870     *
871     * @since 8.0.0
872     */
873    public void setIgnoreQuery(boolean isIgnoreQuery) {
874
875        m_isIgnoreQuery = isIgnoreQuery;
876    }
877
878    /**
879     * Set the name of the index to search.<p>
880     *
881     * @param indexName the name of the index
882     */
883    public void setIndex(String indexName) {
884
885        CmsSearchIndex index;
886        if (CmsStringUtil.isNotEmpty(indexName)) {
887            try {
888                I_CmsSearchIndex idx = OpenCms.getSearchManager().getIndex(indexName);
889                index = idx instanceof CmsSearchIndex ? (CmsSearchIndex)idx : null;
890                if (index == null) {
891                    throw new CmsException(Messages.get().container(Messages.ERR_INDEX_NOT_FOUND_1, indexName));
892                }
893                setSearchIndex(index);
894            } catch (Exception exc) {
895                if (LOG.isErrorEnabled()) {
896                    LOG.error(Messages.get().getBundle().key(Messages.LOG_INDEX_ACCESS_FAILED_1, indexName), exc);
897                }
898            }
899        }
900    }
901
902    /**
903     * Sets the number of matches per page.<p>
904     *
905     * @param matches the number of matches per page
906     */
907    public void setMatchesPerPage(int matches) {
908
909        m_matchesPerPage = matches;
910    }
911
912    /**
913     * Sets the maximum creation date a resource must have to be included in the search result.<p>
914     *
915     * @param maxDateCreated the maximum creation date to set
916     */
917    public void setMaxDateCreated(long maxDateCreated) {
918
919        m_maxDateCreated = maxDateCreated;
920    }
921
922    /**
923     * Sets the maximum last modification date a resource must have to be included in the search result.<p>
924     *
925     * @param maxDateLastModified the maximum last modification date to set
926     */
927    public void setMaxDateLastModified(long maxDateLastModified) {
928
929        m_maxDateLastModified = maxDateLastModified;
930    }
931
932    /**
933     * Sets the minimum creation date a resource must have to be included in the search result.<p>
934     *
935     * @param minDateCreated the minimum creation date to set
936     */
937    public void setMinDateCreated(long minDateCreated) {
938
939        m_minDateCreated = minDateCreated;
940    }
941
942    /**
943     * Sets the minimum last modification date a resource must have to be included in the search result.<p>
944     *
945     * @param minDateLastModified he minimum last modification date to set
946     */
947    public void setMinDateLastModified(long minDateLastModified) {
948
949        m_minDateLastModified = minDateLastModified;
950    }
951
952    /**
953     * Sets the parsed query.<p>
954     *
955     * The parsed query is automatically set by the OpenCms search index when a query is created
956     * with either {@link #setQuery(String)} or {@link #addFieldQuery(CmsSearchFieldQuery)}.
957     * The Lucene query build from the parameters is stored here and can be later used
958     * for paging through the results.<p>
959     *
960     * Please note that this applies only to the query part, not the filter part of the search.<p>
961     *
962     * @param parsedQuery the parsed query to set
963     */
964    public void setParsedQuery(String parsedQuery) {
965
966        m_parsedQuery = parsedQuery;
967    }
968
969    /**
970     * Sets the query to search for. <p>
971     *
972     * The decoding here is tailored for query strings that are
973     * additionally manually UTF-8 encoded at client side (javascript) to get around an
974     * issue with special chars in applications that use non- UTF-8 encoding
975     * (e.g. ISO-8859-1) OpenCms applications. It is not recommended to use this with
976     * front ends that don't encode manually as characters like sole "%" (without number suffix)
977     * will cause an Exception.<p>
978     *
979     * @param query the query to search for to set
980     */
981    public void setQuery(String query) {
982
983        // for use with widgets the exception is thrown here to enforce the error message next to the widget
984        if (query.trim().length() < getQueryLength()) {
985            throw new CmsIllegalArgumentException(
986                Messages.get().container(Messages.ERR_QUERY_TOO_SHORT_1, new Integer(getQueryLength())));
987        }
988        m_query = query;
989    }
990
991    /**
992     * Sets the minimum length of the search query.<p>
993     *
994     * @param length the minimum search query length
995     */
996    public void setQueryLength(int length) {
997
998        m_queryLength = length;
999    }
1000
1001    /**
1002     * Set the list of resource types (strings) to limit the search to. <p>
1003     *
1004     * @param resourceTypes the list of resource types (strings) to limit the search to
1005     *
1006     * @since 7.5.1
1007     */
1008    public void setResourceTypes(List<String> resourceTypes) {
1009
1010        m_resourceTypes = resourceTypes;
1011    }
1012
1013    /**
1014     * Sets the list of strings of roots to search under for the search.<p>
1015     *
1016     * @param roots  the list of strings of roots to search under for the search to set
1017     */
1018    public void setRoots(List<String> roots) {
1019
1020        m_roots = roots;
1021    }
1022
1023    /**
1024     * Set the comma separated search root names to  restrict search to.<p>
1025     *
1026     * @param categories the comma separated category names to  restrict search to
1027     */
1028    public void setSearchCategories(String categories) {
1029
1030        setCategories(CmsStringUtil.splitAsList(categories, ','));
1031    }
1032
1033    /**
1034     * Sets the search index to use for the search. <p>
1035     *
1036     * @param index the search index to use for the search to set.
1037     *
1038     * @throws CmsIllegalArgumentException if null is given as argument
1039     */
1040    public void setSearchIndex(CmsSearchIndex index) throws CmsIllegalArgumentException {
1041
1042        if (index == null) {
1043            throw new CmsIllegalArgumentException(Messages.get().container(Messages.ERR_INDEX_NULL_0));
1044        }
1045        m_index = index;
1046    }
1047
1048    /**
1049     * Set the search page to display. <p>
1050     *
1051     * @param page the search page to display
1052     */
1053    public void setSearchPage(int page) {
1054
1055        m_page = page;
1056    }
1057
1058    /**
1059     * Set the comma separated search root names to  restrict search to.<p>
1060     *
1061     * @param rootNameList the comma separated search root names to  restrict search to
1062     */
1063    public void setSearchRoots(String rootNameList) {
1064
1065        m_roots = CmsStringUtil.splitAsList(rootNameList, ',');
1066    }
1067
1068    /**
1069     * Set the instance that defines the sort order for search results.
1070     *
1071     * @param sortOrder the instance that defines the sort order for search results to set
1072     */
1073    public void setSort(Sort sortOrder) {
1074
1075        m_sort = sortOrder;
1076    }
1077
1078    /**
1079     * Sets the internal member of type <code>{@link Sort}</code> according to
1080     * the given sort name. <p>
1081     *
1082     * For a list of valid sort names, please see <code>{@link #SORT_NAMES}</code>.<p>
1083     *
1084     * @param sortName the name of the sort to use
1085     *
1086     * @see #SORT_NAMES
1087     */
1088    public void setSortName(String sortName) {
1089
1090        if (sortName.equals(SORT_NAMES[1])) {
1091            m_sort = SORT_DATE_CREATED;
1092        } else if (sortName.equals(SORT_NAMES[2])) {
1093            m_sort = SORT_DATE_LASTMODIFIED;
1094        } else if (sortName.equals(SORT_NAMES[3])) {
1095            m_sort = SORT_TITLE;
1096        } else {
1097            m_sort = SORT_DEFAULT;
1098        }
1099    }
1100
1101    /**
1102     * Creates a query String build from this search parameters for HTML links.<p>
1103     *
1104     * @return a query String build from this search parameters for HTML links
1105     */
1106    public String toQueryString() {
1107
1108        StringBuffer result = new StringBuffer(128);
1109        result.append("?action=search");
1110        if (getParsedQuery() != null) {
1111            result.append("&parsedQuery=");
1112            result.append(CmsEncoder.encodeParameter(getParsedQuery()));
1113        } else {
1114            result.append("&query=");
1115            result.append(CmsEncoder.encodeParameter(getQuery()));
1116        }
1117
1118        result.append("&matchesPerPage=");
1119        result.append(getMatchesPerPage());
1120        result.append("&displayPages=");
1121        result.append(getDisplayPages());
1122        result.append("&index=");
1123        result.append(CmsEncoder.encodeParameter(getIndex()));
1124
1125        Sort sort = getSort();
1126        if (sort != CmsSearchParameters.SORT_DEFAULT) {
1127            result.append("&sort=");
1128            if (sort == CmsSearchParameters.SORT_TITLE) {
1129                result.append("title");
1130            } else if (sort == CmsSearchParameters.SORT_DATE_CREATED) {
1131                result.append("date-created");
1132            } else if (sort == CmsSearchParameters.SORT_DATE_LASTMODIFIED) {
1133                result.append("date-lastmodified");
1134            }
1135        }
1136
1137        if ((getCategories() != null) && (getCategories().size() > 0)) {
1138            result.append("&category=");
1139            Iterator<String> it = getCategories().iterator();
1140            while (it.hasNext()) {
1141                result.append(it.next());
1142                if (it.hasNext()) {
1143                    result.append(',');
1144                }
1145            }
1146        }
1147
1148        if (getMinDateCreated() > Long.MIN_VALUE) {
1149            result.append("&minDateCreated=");
1150            result.append(getMinDateCreated());
1151        }
1152        if (getMinDateLastModified() > Long.MIN_VALUE) {
1153            result.append("&minDateLastModified=");
1154            result.append(getMinDateLastModified());
1155        }
1156        if (getMaxDateCreated() < Long.MAX_VALUE) {
1157            result.append("&maxDateCreated=");
1158            result.append(getMaxDateCreated());
1159        }
1160        if (getMaxDateLastModified() < Long.MAX_VALUE) {
1161            result.append("&maxDateLastModified=");
1162            result.append(getMaxDateLastModified());
1163        }
1164
1165        if ((getRoots() != null) && (getRoots().size() > 0)) {
1166            result.append("&searchRoots=");
1167            Iterator<String> it = getRoots().iterator();
1168            while (it.hasNext()) {
1169                result.append(CmsEncoder.encode(it.next()));
1170                if (it.hasNext()) {
1171                    result.append(',');
1172                }
1173            }
1174        }
1175
1176        if (isExcerptOnlySearchedFields()) {
1177            result.append("&excerptOnlySearchedFields=true");
1178        }
1179
1180        return result.toString();
1181    }
1182
1183    /**
1184     * @see java.lang.Object#toString()
1185     */
1186    @Override
1187    public String toString() {
1188
1189        StringBuffer result = new StringBuffer();
1190        result.append("query:[");
1191        result.append(m_query);
1192        result.append("] ");
1193        if ((m_fields != null) && (m_fields.size() > 0)) {
1194            result.append("fields:[");
1195            for (int i = 0; i < m_fields.size(); i++) {
1196                result.append(m_fields.get(i));
1197                if ((i + 1) < m_fields.size()) {
1198                    result.append(", ");
1199                }
1200            }
1201            result.append("] ");
1202        }
1203        if ((m_roots != null) && (m_roots.size() > 0)) {
1204            result.append("roots:[");
1205            for (int i = 0; i < m_roots.size(); i++) {
1206                result.append(m_roots.get(i));
1207                if ((i + 1) < m_roots.size()) {
1208                    result.append(", ");
1209                }
1210            }
1211            result.append("] ");
1212        }
1213        if ((m_categories != null) && (m_categories.size() > 0)) {
1214            result.append("categories:[");
1215            for (int i = 0; i < m_categories.size(); i++) {
1216                result.append(m_categories.get(i));
1217                if ((i + 1) < m_categories.size()) {
1218                    result.append(", ");
1219                }
1220            }
1221            result.append("] ");
1222        }
1223        if ((m_resourceTypes != null) && (m_resourceTypes.size() > 0)) {
1224            result.append("resourceTypes:[");
1225            for (int i = 0; i < m_resourceTypes.size(); i++) {
1226                result.append(m_resourceTypes.get(i));
1227                if ((i + 1) < m_resourceTypes.size()) {
1228                    result.append(", ");
1229                }
1230            }
1231            result.append("] ");
1232        }
1233        if (m_calculateCategories) {
1234            result.append("calculate-categories ");
1235        }
1236        if (m_excerptOnlySearchedFields) {
1237            result.append("excerpt-searched-fields-only ");
1238        }
1239        result.append("sort:[");
1240        if (m_sort == CmsSearchParameters.SORT_DEFAULT) {
1241            result.append("default");
1242        } else if (m_sort == CmsSearchParameters.SORT_TITLE) {
1243            result.append("title");
1244        } else if (m_sort == CmsSearchParameters.SORT_DATE_CREATED) {
1245            result.append("date-created");
1246        } else if (m_sort == CmsSearchParameters.SORT_DATE_LASTMODIFIED) {
1247            result.append("date-lastmodified");
1248        } else {
1249            result.append("unknown");
1250        }
1251        result.append("]");
1252
1253        return result.toString();
1254    }
1255
1256    /**
1257     * Concatenates the elements of the string list separated by the given separator character.<p>
1258     *
1259     * @param stringList the list
1260     * @param separator the separator
1261     *
1262     * @return the concatenated string
1263     */
1264    private String toSeparatedString(List<String> stringList, char separator) {
1265
1266        StringBuffer result = new StringBuffer();
1267        Iterator<String> it = stringList.iterator();
1268        while (it.hasNext()) {
1269            result.append(it.next());
1270            if (it.hasNext()) {
1271                result.append(separator);
1272            }
1273        }
1274        return result.toString();
1275    }
1276}