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, 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.jsp.search.config.parser;
029
030import org.opencms.file.CmsObject;
031import org.opencms.file.CmsPropertyDefinition;
032import org.opencms.i18n.CmsEncoder;
033import org.opencms.json.JSONException;
034import org.opencms.jsp.search.config.CmsSearchConfigurationFacetField;
035import org.opencms.jsp.search.config.CmsSearchConfigurationFacetRange;
036import org.opencms.jsp.search.config.CmsSearchConfigurationSortOption;
037import org.opencms.jsp.search.config.I_CmsSearchConfigurationFacet.SortOrder;
038import org.opencms.jsp.search.config.I_CmsSearchConfigurationFacetField;
039import org.opencms.jsp.search.config.I_CmsSearchConfigurationFacetRange;
040import org.opencms.jsp.search.config.I_CmsSearchConfigurationPagination;
041import org.opencms.jsp.search.config.I_CmsSearchConfigurationSortOption;
042import org.opencms.jsp.search.config.parser.simplesearch.CmsCategoryFolderRestrictionBean;
043import org.opencms.jsp.search.config.parser.simplesearch.CmsConfigurationBean;
044import org.opencms.jsp.search.config.parser.simplesearch.CmsConfigurationBean.CombinationMode;
045import org.opencms.jsp.search.config.parser.simplesearch.CmsGeoFilterBean;
046import org.opencms.jsp.search.config.parser.simplesearch.daterestrictions.I_CmsDateRestriction;
047import org.opencms.jsp.search.config.parser.simplesearch.preconfiguredrestrictions.CmsRestrictionRule;
048import org.opencms.jsp.search.config.parser.simplesearch.preconfiguredrestrictions.CmsRestrictionsBean;
049import org.opencms.jsp.search.config.parser.simplesearch.preconfiguredrestrictions.CmsRestrictionsBean.FieldValues;
050import org.opencms.jsp.search.config.parser.simplesearch.preconfiguredrestrictions.CmsRestrictionsBean.FieldValues.FieldType;
051import org.opencms.main.CmsException;
052import org.opencms.relations.CmsCategoryService;
053import org.opencms.search.fields.CmsSearchField;
054import org.opencms.search.solr.CmsSolrQuery;
055import org.opencms.search.solr.CmsSolrQueryUtil;
056import org.opencms.util.CmsStringUtil;
057import org.opencms.util.CmsUUID;
058
059import java.util.Collection;
060import java.util.Collections;
061import java.util.HashMap;
062import java.util.HashSet;
063import java.util.LinkedList;
064import java.util.List;
065import java.util.Locale;
066import java.util.Map;
067import java.util.Objects;
068import java.util.stream.Collectors;
069
070import org.apache.solr.common.params.CommonParams;
071
072import com.google.common.collect.Lists;
073
074/**
075 * Search configuration parser using a list configuration file as the base configuration with additional JSON.<p>
076 */
077public class CmsSimpleSearchConfigurationParser extends CmsJSONSearchConfigurationParser {
078
079    /** Sort options that are available by default. */
080    public static enum SortOption {
081
082        /** Sort by date ascending. */
083        DATE_ASC,
084        /** Sort by date descending. */
085        DATE_DESC,
086        /** Sort by title ascending. */
087        TITLE_ASC,
088        /** Sort by title descending. */
089        TITLE_DESC,
090        /** Sort by order ascending. */
091        ORDER_ASC,
092        /** Sort by order descending. */
093        ORDER_DESC;
094
095        /**
096         * Generates the suitable {@link I_CmsSearchConfigurationSortOption} for the option.
097         * @param l the locale for which the option should be created
098         * @return the created {@link I_CmsSearchConfigurationSortOption}
099         */
100        public I_CmsSearchConfigurationSortOption getOption(Locale l) {
101
102            switch (this) {
103                case DATE_ASC:
104                    return new CmsSearchConfigurationSortOption("date.asc", "date_asc", getSortDateField(l) + " asc");
105                case DATE_DESC:
106                    return new CmsSearchConfigurationSortOption(
107                        "date.desc",
108                        "date_desc",
109                        getSortDateField(l) + " desc");
110                case TITLE_ASC:
111                    return new CmsSearchConfigurationSortOption(
112                        "title.asc",
113                        "title_asc",
114                        getSortTitleField(l) + " asc");
115                case TITLE_DESC:
116                    return new CmsSearchConfigurationSortOption(
117                        "title.desc",
118                        "title_desc",
119                        getSortTitleField(l) + " desc");
120                case ORDER_ASC:
121                    return new CmsSearchConfigurationSortOption(
122                        "order.asc",
123                        "order_asc",
124                        getSortOrderField(l) + " asc");
125                case ORDER_DESC:
126                    return new CmsSearchConfigurationSortOption(
127                        "order.desc",
128                        "order_desc",
129                        getSortOrderField(l) + " desc");
130                default:
131                    throw new IllegalArgumentException();
132            }
133        }
134
135        /**
136         * Returns the locale specific date field to use for sorting.
137         * @param l the locale to use, can be <code>null</code>
138         * @return the locale specific date field to use for sorting.
139         */
140        protected String getSortDateField(Locale l) {
141
142            return CmsSearchField.FIELD_INSTANCEDATE
143                + (null != l ? "_" + l.toString() : "")
144                + CmsSearchField.FIELD_POSTFIX_DATE;
145        }
146
147        /**
148         * Returns the locale specific order field to use for sorting.
149         * @param l the locale to use, can be <code>null</code>
150         * @return the locale specific order field to use for sorting.
151         */
152        protected String getSortOrderField(Locale l) {
153
154            return CmsSearchField.FIELD_DISPORDER
155                + (null != l ? "_" + l.toString() : "")
156                + CmsSearchField.FIELD_POSTFIX_INT;
157        }
158
159        /**
160         * Returns the locale specific title field to use for sorting.
161         * @param l the locale to use, can be <code>null</code>
162         * @return the locale specific title field to use for sorting.
163         */
164        protected String getSortTitleField(Locale l) {
165
166            return CmsSearchField.FIELD_DISPTITLE
167                + (null != l ? "_" + l.toString() : "")
168                + CmsSearchField.FIELD_POSTFIX_SORT;
169        }
170    }
171
172    /** SOLR field name. */
173    public static final String FIELD_CATEGORIES = "category_exact";
174
175    /** SOLR field name. */
176    public static final String FIELD_DATE = "instancedate_%s_dt";
177
178    /** SOLR field name. */
179    public static final String FIELD_DATE_RANGE = "instancedaterange_%s_dr";
180
181    /** SOLR field name. */
182    public static final String FIELD_DATE_FACET_NAME = "instancedate";
183
184    /** SOLR field name. */
185    public static final String FIELD_PARENT_FOLDERS = "parent-folders";
186
187    /** Pagination which may override the default pagination. */
188    private I_CmsSearchConfigurationPagination m_pagination;
189
190    /** The current cms context. */
191    private CmsObject m_cms;
192
193    /** The list configuration bean. */
194    private CmsConfigurationBean m_config;
195
196    /** The (mutable) search locale. */
197    private Locale m_searchLocale;
198
199    /** The (mutable) sort order. */
200    private CmsSimpleSearchConfigurationParser.SortOption m_sortOrder;
201
202    /** Flag which, if true, causes the search to ignore the blacklist. */
203    private boolean m_ignoreBlacklist;
204
205    /**
206     * Constructor.<p>
207     *
208     * @param cms the cms context
209     * @param config the list configuration
210     * @param additionalParamJSON the additional JSON configuration
211     *
212     * @throws JSONException in case parsing the JSON fails
213     */
214    public CmsSimpleSearchConfigurationParser(CmsObject cms, CmsConfigurationBean config, String additionalParamJSON)
215    throws JSONException {
216
217        super(CmsStringUtil.isEmptyOrWhitespaceOnly(additionalParamJSON) ? "{}" : additionalParamJSON);
218        m_cms = cms;
219        m_config = config;
220    }
221
222    /**
223     * Creates an instance for an empty JSON configuration.<p>
224     *
225     * The point of this is that we know that passing an empty configuration makes it impossible
226     * for a JSONException to thrown.
227     *
228     * @param cms the current CMS context
229     * @param config  the search configuration
230     *
231     * @return the search config parser
232     */
233    public static CmsSimpleSearchConfigurationParser createInstanceWithNoJsonConfig(
234        CmsObject cms,
235        CmsConfigurationBean config) {
236
237        try {
238            return new CmsSimpleSearchConfigurationParser(cms, config, null);
239
240        } catch (JSONException e) {
241            return null;
242        }
243    }
244
245    /** The default field facets.
246     *
247     * @param categoryConjunction flag, indicating if category selections in the facet should be "AND" combined.
248     * @return the default field facets.
249     */
250    public static Map<String, I_CmsSearchConfigurationFacetField> getDefaultFieldFacets(boolean categoryConjunction) {
251
252        Map<String, I_CmsSearchConfigurationFacetField> fieldFacets = new HashMap<String, I_CmsSearchConfigurationFacetField>();
253        fieldFacets.put(
254            FIELD_CATEGORIES,
255            new CmsSearchConfigurationFacetField(
256                FIELD_CATEGORIES,
257                null,
258                Integer.valueOf(1),
259                Integer.valueOf(200),
260                null,
261                "Category",
262                SortOrder.index,
263                null,
264                Boolean.valueOf(categoryConjunction),
265                null,
266                Boolean.TRUE,
267                null));
268        fieldFacets.put(
269            FIELD_PARENT_FOLDERS,
270            new CmsSearchConfigurationFacetField(
271                FIELD_PARENT_FOLDERS,
272                null,
273                Integer.valueOf(1),
274                Integer.valueOf(200),
275                null,
276                "Folders",
277                SortOrder.index,
278                null,
279                Boolean.FALSE,
280                null,
281                Boolean.TRUE,
282                null));
283        return Collections.unmodifiableMap(fieldFacets);
284
285    }
286
287    /**
288     * Returns the initial SOLR query.<p>
289     *
290     * @return the SOLR query
291     */
292    public CmsSolrQuery getInitialQuery() {
293
294        Map<String, String[]> queryParams = new HashMap<String, String[]>();
295        if (!m_cms.getRequestContext().getCurrentProject().isOnlineProject() && m_config.isShowExpired()) {
296            queryParams.put("fq", new String[] {"released:[* TO *]", "expired:[* TO *]"});
297        }
298        return new CmsSolrQuery(null, queryParams);
299    }
300
301    /**
302     * Gets the search locale.<p>
303     *
304     * @return the search locale
305     */
306    public Locale getSearchLocale() {
307
308        if (m_searchLocale != null) {
309            return m_searchLocale;
310        }
311        return m_cms.getRequestContext().getLocale();
312    }
313
314    /**
315    * @see org.opencms.jsp.search.config.parser.I_CmsSearchConfigurationParser#parseFieldFacets()
316    */
317    @Override
318    public Map<String, I_CmsSearchConfigurationFacetField> parseFieldFacets() {
319
320        if (m_configObject.has(JSON_KEY_FIELD_FACETS)) {
321            return super.parseFieldFacets();
322        } else {
323            return getDefaultFieldFacets(true);
324        }
325    }
326
327    /**
328     * @see org.opencms.jsp.search.config.parser.CmsJSONSearchConfigurationParser#parsePagination()
329     */
330    @Override
331    public I_CmsSearchConfigurationPagination parsePagination() {
332
333        if (m_pagination != null) {
334            return m_pagination;
335        }
336        return super.parsePagination();
337    }
338
339    /**
340    * @see org.opencms.jsp.search.config.parser.I_CmsSearchConfigurationParser#parseRangeFacets()
341    */
342    @Override
343    public Map<String, I_CmsSearchConfigurationFacetRange> parseRangeFacets() {
344
345        if (m_configObject.has(JSON_KEY_RANGE_FACETS)) {
346            return super.parseRangeFacets();
347        } else {
348            Map<String, I_CmsSearchConfigurationFacetRange> rangeFacets = new HashMap<String, I_CmsSearchConfigurationFacetRange>();
349            String indexField = FIELD_DATE;
350            I_CmsSearchConfigurationFacetRange.Method method = I_CmsSearchConfigurationFacetRange.Method.dv;
351            if (Boolean.parseBoolean(m_config.getParameterValue(CmsConfigurationBean.PARAM_FILTER_MULTI_DAY))) {
352                indexField = FIELD_DATE_RANGE;
353                method = null;
354            }
355            I_CmsSearchConfigurationFacetRange rangeFacet = new CmsSearchConfigurationFacetRange(
356                String.format(indexField, getSearchLocale().toString()),
357                "NOW/YEAR-20YEARS",
358                "NOW/MONTH+5YEARS",
359                "+1MONTHS",
360                null,
361                Boolean.FALSE,
362                method,
363                FIELD_DATE_FACET_NAME,
364                Integer.valueOf(1),
365                "Date",
366                Boolean.FALSE,
367                null,
368                Boolean.TRUE,
369                null);
370
371            rangeFacets.put(rangeFacet.getName(), rangeFacet);
372            return rangeFacets;
373        }
374    }
375
376    /**
377     * Sets the 'ignore blacklist' flag.<p>
378     *
379     * If set, the search will ignore the blacklist from the list configuration.<p>
380     *
381     * @param ignoreBlacklist true if the blacklist should be ignored
382     */
383    public void setIgnoreBlacklist(boolean ignoreBlacklist) {
384
385        m_ignoreBlacklist = ignoreBlacklist;
386    }
387
388    /**
389     * Sets the pagination.<p>
390     *
391     * If this is set, parsePagination will always return the set value instead of using the default way to compute the pagination
392     *
393     * @param pagination the pagination
394     */
395    public void setPagination(I_CmsSearchConfigurationPagination pagination) {
396
397        m_pagination = pagination;
398    }
399
400    /**
401     * Sets the search locale.<p>
402     *
403     * @param locale the search locale
404     */
405    public void setSearchLocale(Locale locale) {
406
407        m_searchLocale = locale;
408    }
409
410    /**
411     * Sets the sort option.<p>
412     *
413     * @param sortOption the sort option
414     */
415    public void setSortOption(String sortOption) {
416
417        if (null != sortOption) {
418            try {
419                m_sortOrder = CmsSimpleSearchConfigurationParser.SortOption.valueOf(sortOption);
420            } catch (IllegalArgumentException e) {
421                m_sortOrder = null;
422                LOG.warn(
423                    "Setting illegal default sort option " + sortOption + " failed. Using Solr's default sort option.");
424            }
425        } else {
426            m_sortOrder = null;
427        }
428    }
429
430    /**
431     * @see org.opencms.jsp.search.config.parser.CmsJSONSearchConfigurationParser#getEscapeQueryChars()
432     */
433    @Override
434    protected Boolean getEscapeQueryChars() {
435
436        if (m_configObject.has(JSON_KEY_ESCAPE_QUERY_CHARACTERS)) {
437            return super.getEscapeQueryChars();
438        } else {
439            return Boolean.TRUE;
440        }
441    }
442
443    /**
444     * @see org.opencms.jsp.search.config.parser.CmsJSONSearchConfigurationParser#getExtraSolrParams()
445     */
446    @Override
447    protected String getExtraSolrParams() {
448
449        String params = super.getExtraSolrParams();
450        if (CmsStringUtil.isEmptyOrWhitespaceOnly(params)) {
451            params = getCategoryFolderFilter()
452                + getResourceTypeFilter()
453                + getPreconfiguredFilterQuery()
454                + getFilterQuery()
455                + getBlacklistFilter()
456                + getGeoFilterQuery();
457        }
458        return params;
459    }
460
461    /**
462     * @see org.opencms.jsp.search.config.parser.CmsJSONSearchConfigurationParser#getIgnoreExpirationDate()
463     */
464    @Override
465    protected Boolean getIgnoreExpirationDate() {
466
467        return getIgnoreReleaseAndExpiration();
468
469    }
470
471    /**
472     * @see org.opencms.jsp.search.config.parser.CmsJSONSearchConfigurationParser#getIgnoreReleaseDate()
473     */
474    @Override
475    protected Boolean getIgnoreReleaseDate() {
476
477        return getIgnoreReleaseAndExpiration();
478    }
479
480    /**
481     * @see org.opencms.jsp.search.config.parser.CmsJSONSearchConfigurationParser#getMaxReturnedResults(java.lang.String)
482     */
483    @Override
484    protected int getMaxReturnedResults(String indexName) {
485
486        return null != m_config.getMaximallyReturnedResults()
487        ? m_config.getMaximallyReturnedResults().intValue()
488        : super.getMaxReturnedResults(indexName);
489    }
490
491    /**
492     * @see org.opencms.jsp.search.config.parser.CmsJSONSearchConfigurationParser#getQueryModifier()
493     */
494    @Override
495    protected String getQueryModifier() {
496
497        String modifier = super.getQueryModifier();
498        if (CmsStringUtil.isEmptyOrWhitespaceOnly(modifier)) {
499            modifier = "{!type=edismax qf=\""
500                + CmsSearchField.FIELD_CONTENT
501                + "_"
502                + getSearchLocale().toString()
503                + " "
504                + CmsPropertyDefinition.PROPERTY_TITLE
505                + CmsSearchField.FIELD_DYNAMIC_PROPERTIES
506                + " "
507                + CmsPropertyDefinition.PROPERTY_DESCRIPTION
508                + CmsSearchField.FIELD_DYNAMIC_PROPERTIES_DIRECT
509                + " "
510                + CmsPropertyDefinition.PROPERTY_DESCRIPTION_HTML
511                + CmsSearchField.FIELD_DYNAMIC_PROPERTIES_DIRECT
512                + " "
513                + CmsSearchField.FIELD_DESCRIPTION
514                + "_"
515                + getSearchLocale().toString()
516                + " "
517                + CmsSearchField.FIELD_KEYWORDS
518                + "_"
519                + getSearchLocale().toString()
520                + "\"}%(query)";
521        }
522        return modifier;
523    }
524
525    /**
526     * @see org.opencms.jsp.search.config.parser.CmsJSONSearchConfigurationParser#getSearchForEmptyQuery()
527     */
528    @Override
529    protected Boolean getSearchForEmptyQuery() {
530
531        if (m_configObject.has(JSON_KEY_SEARCH_FOR_EMPTY_QUERY)) {
532            return super.getSearchForEmptyQuery();
533        } else {
534            return Boolean.TRUE;
535        }
536    }
537
538    /**
539     * @see org.opencms.jsp.search.config.parser.CmsJSONSearchConfigurationParser#getSortOptions()
540     */
541    @Override
542    protected List<I_CmsSearchConfigurationSortOption> getSortOptions() {
543
544        if (m_configObject.has(JSON_KEY_SORTOPTIONS)) {
545            return super.getSortOptions();
546        } else {
547            List<I_CmsSearchConfigurationSortOption> options = new LinkedList<I_CmsSearchConfigurationSortOption>();
548
549            CmsSimpleSearchConfigurationParser.SortOption currentOption = CmsSimpleSearchConfigurationParser.SortOption.valueOf(
550                m_config.getSortOrder());
551            if (m_sortOrder != null) {
552                currentOption = m_sortOrder;
553            }
554            Locale locale = getSearchLocale();
555            options.add(currentOption.getOption(locale));
556            CmsSimpleSearchConfigurationParser.SortOption[] sortOptions = CmsSimpleSearchConfigurationParser.SortOption.values();
557            for (int i = 0; i < sortOptions.length; i++) {
558                CmsSimpleSearchConfigurationParser.SortOption option = sortOptions[i];
559                if (!Objects.equals(currentOption, option)) {
560                    options.add(option.getOption(locale));
561                }
562            }
563            return options;
564        }
565    }
566
567    /**
568     * Generates the query part for the preconfigured restrictions for the type.
569     * @param type the type to generate the restriction for.
570     * @param restrictionsForType the preconfigured restrictions for the type.
571     * @return the part of the Solr query for the restriction.
572     */
573    String generatePreconfiguredRestriction(
574        String type,
575        Map<CmsRestrictionRule, Collection<FieldValues>> restrictionsForType) {
576
577        String result = "";
578        if ((null != restrictionsForType) && (restrictionsForType.size() > 0)) {
579            Collection<String> ruleRestrictions = new HashSet<>(restrictionsForType.size());
580            for (Map.Entry<CmsRestrictionRule, Collection<FieldValues>> ruleEntry : restrictionsForType.entrySet()) {
581                ruleRestrictions.add(generatePreconfiguredRestrictionForRule(ruleEntry.getKey(), ruleEntry.getValue()));
582            }
583            result = ruleRestrictions.size() > 1
584            ? ruleRestrictions.stream().reduce((r1, r2) -> (r1 + " " + CombinationMode.AND + " " + r2)).get()
585            : ruleRestrictions.iterator().next();
586            if (null != type) {
587                result = "type:\"" + type + "\" AND (" + result + ")";
588            }
589        } else if (null != type) {
590            result = "type:\"" + type + "\"";
591        }
592        return result.isEmpty() ? result : "(" + result + ")";
593    }
594
595    /**
596     * Generates the query part for the preconfigured restriction for a single rule.
597     * @param rule the rule to generate the restriction for.
598     * @param values the values provided for the rule.
599     * @return the part of the Solr query for the restriction.
600     */
601    String generatePreconfiguredRestrictionForRule(CmsRestrictionRule rule, Collection<FieldValues> values) {
602
603        Collection<String> resolvedFieldValues = values.stream().map(v -> resolveFieldValues(rule, v)).collect(
604            Collectors.toSet());
605
606        String seperator = " " + rule.getCombinationModeBetweenFields().toString() + " ";
607        return rule.getFieldForLocale(getSearchLocale())
608            + ":("
609            + resolvedFieldValues.stream().reduce((v1, v2) -> v1 + seperator + v2).get()
610            + ")";
611
612    }
613
614    /**
615     * Returns the blacklist filter.<p>
616     *
617     * @return the blacklist filter
618     */
619    String getBlacklistFilter() {
620
621        if (m_ignoreBlacklist) {
622            return "";
623        }
624        String result = "";
625        List<CmsUUID> blacklist = m_config.getBlacklist();
626        List<String> blacklistStrings = Lists.newArrayList();
627        for (CmsUUID id : blacklist) {
628            blacklistStrings.add("\"" + id.toString() + "\"");
629        }
630        if (!blacklistStrings.isEmpty()) {
631            result = "&fq=" + CmsEncoder.encode("-id:(" + CmsStringUtil.listAsString(blacklistStrings, " OR ") + ")");
632        }
633        return result;
634    }
635
636    /**
637     * Returns the category filter string.<p>
638     *
639     * @return the category filter
640     */
641    String getCategoryFilterPart() {
642
643        String result = "";
644        if (!m_config.getCategories().isEmpty()) {
645            List<String> categoryVals = Lists.newArrayList();
646            for (String path : m_config.getCategories()) {
647                try {
648                    path = CmsCategoryService.getInstance().getCategory(
649                        m_cms,
650                        m_cms.getRequestContext().addSiteRoot(path)).getPath();
651                    categoryVals.add("\"" + path + "\"");
652                } catch (CmsException e) {
653                    LOG.warn(e.getLocalizedMessage(), e);
654                }
655            }
656            if (!categoryVals.isEmpty()) {
657                String operator = " " + m_config.getCategoryMode() + " ";
658                String valueExpression = CmsStringUtil.listAsString(categoryVals, operator);
659                result = "category_exact:(" + valueExpression + ")";
660
661            }
662        }
663        return result;
664    }
665
666    /**
667     * Returns the category filter string.<p>
668     *
669     * @return the category filter
670     */
671    String getCategoryFolderFilter() {
672
673        String result = "";
674        String defaultPart = getFolderFilterPart();
675        String categoryFilterPart = getCategoryFilterPart();
676        if (!categoryFilterPart.isEmpty()) {
677            defaultPart = "((" + defaultPart + ") AND (" + categoryFilterPart + "))";
678        }
679        for (CmsCategoryFolderRestrictionBean restriction : m_config.getCategoryFolderRestrictions()) {
680            String restrictionQuery = restriction.toString();
681            if (!restrictionQuery.isEmpty()) {
682                restrictionQuery = "(" + restrictionQuery + " AND " + defaultPart + ")";
683                if (!result.isEmpty()) {
684                    result += " OR ";
685                }
686                result += restrictionQuery;
687            }
688        }
689        if (result.isEmpty()) {
690            result = defaultPart;
691        }
692        return "fq=" + CmsEncoder.encode(result);
693    }
694
695    /**
696     * The fields returned by default. Typically the output is done via display formatters and hence nearly no
697     * field is necessary. Returning all fields might cause performance problems.
698     *
699     * @return the default return fields.
700     */
701    String getDefaultReturnFields() {
702
703        StringBuffer fields = new StringBuffer("");
704        fields.append(CmsSearchField.FIELD_PATH);
705        fields.append(',');
706        fields.append(CmsSearchField.FIELD_INSTANCEDATE).append(CmsSearchField.FIELD_POSTFIX_DATE);
707        fields.append(',');
708        fields.append(CmsSearchField.FIELD_INSTANCEDATE_END).append(CmsSearchField.FIELD_POSTFIX_DATE);
709        fields.append(',');
710        fields.append(CmsSearchField.FIELD_INSTANCEDATE_CURRENT_TILL).append(CmsSearchField.FIELD_POSTFIX_DATE);
711        fields.append(',');
712        fields.append(CmsSearchField.FIELD_INSTANCEDATE).append('_').append(getSearchLocale().toString()).append(
713            CmsSearchField.FIELD_POSTFIX_DATE);
714        fields.append(',');
715        fields.append(CmsSearchField.FIELD_INSTANCEDATE_END).append('_').append(getSearchLocale().toString()).append(
716            CmsSearchField.FIELD_POSTFIX_DATE);
717        fields.append(',');
718        fields.append(CmsSearchField.FIELD_INSTANCEDATE_CURRENT_TILL).append('_').append(
719            getSearchLocale().toString()).append(CmsSearchField.FIELD_POSTFIX_DATE);
720        fields.append(',');
721        fields.append(CmsSearchField.FIELD_ID);
722        fields.append(',');
723        fields.append(CmsSearchField.FIELD_SOLR_ID);
724        fields.append(',');
725        fields.append(CmsSearchField.FIELD_DISPTITLE).append('_').append(getSearchLocale().toString()).append("_sort");
726        fields.append(',');
727        fields.append(CmsSearchField.FIELD_LINK);
728        fields.append(',');
729        fields.append(CmsSearchField.FIELD_GEOCOORDS);
730        return fields.toString();
731    }
732
733    /**
734     * Returns the filter query string.<p>
735     *
736     * @return the filter query
737     */
738    String getFilterQuery() {
739
740        String result = m_config.getFilterQuery();
741        if (result == null) {
742            result = "";
743        }
744        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(result) && !result.startsWith("&")) {
745            result = "&" + result;
746        }
747        if (!result.contains(CommonParams.FL + "=")) {
748            result += "&" + CommonParams.FL + "=" + CmsEncoder.encode(getDefaultReturnFields());
749        }
750        I_CmsDateRestriction dateRestriction = m_config.getDateRestriction();
751        if (dateRestriction != null) {
752            result += "&fq="
753                + CmsEncoder.encode(
754                    CmsSearchField.FIELD_INSTANCEDATE_CURRENT_TILL
755                        + "_"
756                        + getSearchLocale().toString()
757                        + "_dt:"
758                        + dateRestriction.getRange());
759
760        }
761        result += "&fq=con_locales:" + getSearchLocale().toString();
762        return result;
763    }
764
765    /**
766     * Returns the folder filter string.<p>
767     *
768     * @return the folder filter
769     */
770    String getFolderFilterPart() {
771
772        String result = "";
773        List<String> parentFolderVals = Lists.newArrayList();
774        if (!m_config.getFolders().isEmpty()) {
775            for (String value : m_config.getFolders()) {
776                parentFolderVals.add("\"" + value + "\"");
777            }
778        }
779        if (parentFolderVals.isEmpty()) {
780            result = "parent-folders:(\"/\")";
781        } else {
782            result = "parent-folders:(" + CmsStringUtil.listAsString(parentFolderVals, " OR ") + ")";
783        }
784        return result;
785    }
786
787    /**
788     * Returns the Geo filter query string.<p>
789     *
790     * @return the Geo filter query string
791     */
792    String getGeoFilterQuery() {
793
794        String result = "";
795        CmsGeoFilterBean geoFilterBean = m_config.getGeoFilter();
796        if (geoFilterBean != null) {
797            String fq = CmsSolrQueryUtil.composeGeoFilterQuery(
798                CmsSearchField.FIELD_GEOCOORDS,
799                geoFilterBean.getCoordinates(),
800                geoFilterBean.getRadius(),
801                "km");
802            result = "&fq=" + fq;
803        }
804        return result;
805    }
806
807    /**
808     * Returns the filter query string.<p>
809     *
810     * @return the filter query
811     */
812    String getPreconfiguredFilterQuery() {
813
814        String result = "";
815        if (m_config.hasPreconfiguredRestrictions()) {
816            CmsRestrictionsBean restrictions = m_config.getPreconfiguredRestrictions();
817            String restriction = generatePreconfiguredRestriction(null, restrictions.getRestrictionsForType(null));
818            if (!restriction.isEmpty()) {
819                result = "&fq=" + CmsEncoder.encode(restriction);
820            }
821            Collection<String> typedRestrictions = new HashSet<>();
822            for (String type : m_config.getTypes()) {
823                restriction = generatePreconfiguredRestriction(type, restrictions.getRestrictionsForType(type));
824                if (!restriction.isEmpty()) {
825                    typedRestrictions.add(restriction);
826                }
827            }
828            if (!typedRestrictions.isEmpty()) {
829                result += "&fq="
830                    + CmsEncoder.encode(
831                        "(" + typedRestrictions.stream().reduce((r1, r2) -> (r1 + " OR " + r2)).get() + ")");
832            }
833        }
834        return result;
835    }
836
837    /**
838     * Returns the resource type filter string.<p>
839     *
840     * @return the folder filter
841     */
842    String getResourceTypeFilter() {
843
844        String result = "";
845        // When we have pre-configured restrictions, we need to combine the type filter with these restrictions.
846        if (!m_config.hasPreconfiguredRestrictions()) {
847            List<String> typeVals = Lists.newArrayList();
848            for (String type : m_config.getTypes()) {
849                typeVals.add("\"" + type + "\"");
850            }
851            if (!typeVals.isEmpty()) {
852                result = "&fq=" + CmsEncoder.encode("type:(" + CmsStringUtil.listAsString(typeVals, " OR ") + ")");
853            }
854        }
855        return result;
856    }
857
858    /**
859     * Generates the search string part for one input field value.
860     * @param rule the preconfigured rule.
861     * @param fieldValues the values in the field.
862     * @return the search term part for the value in the field.
863     */
864    String resolveFieldValues(CmsRestrictionRule rule, FieldValues fieldValues) {
865
866        Collection<String> values = fieldValues.getValues();
867        Collection<String> finalValues;
868        if (FieldType.PLAIN.equals(fieldValues.getFieldType())) {
869            // We are sure that there is exactly one value in that case.
870            return "(" + values.iterator().next() + ")";
871        } else {
872            switch (rule.getMatchType()) {
873                case DEFAULT:
874                    finalValues = values;
875                    break;
876                case EXACT:
877                    finalValues = values.stream().map(v -> ("\"" + v + "\"")).collect(Collectors.toSet());
878                    break;
879                case INFIX:
880                    finalValues = values.stream().map(
881                        v -> ("(" + v + " OR *" + v + " OR *" + v + "* OR " + v + "*)")).collect(Collectors.toSet());
882                    break;
883                case POSTFIX:
884                    finalValues = values.stream().map(v -> ("(" + v + " OR *" + v + ")")).collect(Collectors.toSet());
885                    break;
886                case PREFIX:
887                    finalValues = values.stream().map(v -> ("(" + v + " OR " + v + "*)")).collect(Collectors.toSet());
888                    break;
889                default:
890                    throw new IllegalArgumentException("Unknown match type '" + rule.getMatchType() + "'.");
891            }
892            if (finalValues.size() > 1) {
893                String seperator = " " + rule.getCombinationModeInField().toString() + " ";
894                return "(" + finalValues.stream().reduce((v1, v2) -> v1 + seperator + v2).get() + ")";
895            } else {
896                return finalValues.iterator().next();
897            }
898
899        }
900    }
901
902    /**
903     * Returns a flag, indicating if the release and expiration date should be ignored.
904     * @return a flag, indicating if the release and expiration date should be ignored.
905     */
906    private Boolean getIgnoreReleaseAndExpiration() {
907
908        return Boolean.valueOf(m_config.isShowExpired());
909    }
910}