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            if (Boolean.parseBoolean(m_config.getParameterValue(CmsConfigurationBean.PARAM_FILTER_MULTI_DAY))) {
351                indexField = FIELD_DATE_RANGE;
352            }
353            I_CmsSearchConfigurationFacetRange rangeFacet = new CmsSearchConfigurationFacetRange(
354                String.format(indexField, getSearchLocale().toString()),
355                "NOW/YEAR-20YEARS",
356                "NOW/MONTH+5YEARS",
357                "+1MONTHS",
358                null,
359                Boolean.FALSE,
360                FIELD_DATE_FACET_NAME,
361                Integer.valueOf(1),
362                "Date",
363                Boolean.FALSE,
364                null,
365                Boolean.TRUE,
366                null);
367
368            rangeFacets.put(rangeFacet.getName(), rangeFacet);
369            return rangeFacets;
370        }
371    }
372
373    /**
374     * Sets the 'ignore blacklist' flag.<p>
375     *
376     * If set, the search will ignore the blacklist from the list configuration.<p>
377     *
378     * @param ignoreBlacklist true if the blacklist should be ignored
379     */
380    public void setIgnoreBlacklist(boolean ignoreBlacklist) {
381
382        m_ignoreBlacklist = ignoreBlacklist;
383    }
384
385    /**
386     * Sets the pagination.<p>
387     *
388     * If this is set, parsePagination will always return the set value instead of using the default way to compute the pagination
389     *
390     * @param pagination the pagination
391     */
392    public void setPagination(I_CmsSearchConfigurationPagination pagination) {
393
394        m_pagination = pagination;
395    }
396
397    /**
398     * Sets the search locale.<p>
399     *
400     * @param locale the search locale
401     */
402    public void setSearchLocale(Locale locale) {
403
404        m_searchLocale = locale;
405    }
406
407    /**
408     * Sets the sort option.<p>
409     *
410     * @param sortOption the sort option
411     */
412    public void setSortOption(String sortOption) {
413
414        if (null != sortOption) {
415            try {
416                m_sortOrder = CmsSimpleSearchConfigurationParser.SortOption.valueOf(sortOption);
417            } catch (IllegalArgumentException e) {
418                m_sortOrder = null;
419                LOG.warn(
420                    "Setting illegal default sort option " + sortOption + " failed. Using Solr's default sort option.");
421            }
422        } else {
423            m_sortOrder = null;
424        }
425    }
426
427    /**
428     * @see org.opencms.jsp.search.config.parser.CmsJSONSearchConfigurationParser#getEscapeQueryChars()
429     */
430    @Override
431    protected Boolean getEscapeQueryChars() {
432
433        if (m_configObject.has(JSON_KEY_ESCAPE_QUERY_CHARACTERS)) {
434            return super.getEscapeQueryChars();
435        } else {
436            return Boolean.TRUE;
437        }
438    }
439
440    /**
441     * @see org.opencms.jsp.search.config.parser.CmsJSONSearchConfigurationParser#getExtraSolrParams()
442     */
443    @Override
444    protected String getExtraSolrParams() {
445
446        String params = super.getExtraSolrParams();
447        if (CmsStringUtil.isEmptyOrWhitespaceOnly(params)) {
448            params = getCategoryFolderFilter()
449                + getResourceTypeFilter()
450                + getPreconfiguredFilterQuery()
451                + getFilterQuery()
452                + getBlacklistFilter()
453                + getGeoFilterQuery();
454        }
455        return params;
456    }
457
458    /**
459     * @see org.opencms.jsp.search.config.parser.CmsJSONSearchConfigurationParser#getIgnoreExpirationDate()
460     */
461    @Override
462    protected Boolean getIgnoreExpirationDate() {
463
464        return getIgnoreReleaseAndExpiration();
465
466    }
467
468    /**
469     * @see org.opencms.jsp.search.config.parser.CmsJSONSearchConfigurationParser#getIgnoreReleaseDate()
470     */
471    @Override
472    protected Boolean getIgnoreReleaseDate() {
473
474        return getIgnoreReleaseAndExpiration();
475    }
476
477    /**
478     * @see org.opencms.jsp.search.config.parser.CmsJSONSearchConfigurationParser#getMaxReturnedResults(java.lang.String)
479     */
480    @Override
481    protected int getMaxReturnedResults(String indexName) {
482
483        return null != m_config.getMaximallyReturnedResults()
484        ? m_config.getMaximallyReturnedResults().intValue()
485        : super.getMaxReturnedResults(indexName);
486    }
487
488    /**
489     * @see org.opencms.jsp.search.config.parser.CmsJSONSearchConfigurationParser#getQueryModifier()
490     */
491    @Override
492    protected String getQueryModifier() {
493
494        String modifier = super.getQueryModifier();
495        if (CmsStringUtil.isEmptyOrWhitespaceOnly(modifier)) {
496            modifier = "{!type=edismax qf=\""
497                + CmsSearchField.FIELD_CONTENT
498                + "_"
499                + getSearchLocale().toString()
500                + " "
501                + CmsPropertyDefinition.PROPERTY_TITLE
502                + CmsSearchField.FIELD_DYNAMIC_PROPERTIES
503                + " "
504                + CmsPropertyDefinition.PROPERTY_DESCRIPTION
505                + CmsSearchField.FIELD_DYNAMIC_PROPERTIES_DIRECT
506                + " "
507                + CmsPropertyDefinition.PROPERTY_DESCRIPTION_HTML
508                + CmsSearchField.FIELD_DYNAMIC_PROPERTIES_DIRECT
509                + " "
510                + CmsSearchField.FIELD_DESCRIPTION
511                + "_"
512                + getSearchLocale().toString()
513                + " "
514                + CmsSearchField.FIELD_KEYWORDS
515                + "_"
516                + getSearchLocale().toString()
517                + "\"}%(query)";
518        }
519        return modifier;
520    }
521
522    /**
523     * @see org.opencms.jsp.search.config.parser.CmsJSONSearchConfigurationParser#getSearchForEmptyQuery()
524     */
525    @Override
526    protected Boolean getSearchForEmptyQuery() {
527
528        if (m_configObject.has(JSON_KEY_SEARCH_FOR_EMPTY_QUERY)) {
529            return super.getSearchForEmptyQuery();
530        } else {
531            return Boolean.TRUE;
532        }
533    }
534
535    /**
536     * @see org.opencms.jsp.search.config.parser.CmsJSONSearchConfigurationParser#getSortOptions()
537     */
538    @Override
539    protected List<I_CmsSearchConfigurationSortOption> getSortOptions() {
540
541        if (m_configObject.has(JSON_KEY_SORTOPTIONS)) {
542            return super.getSortOptions();
543        } else {
544            List<I_CmsSearchConfigurationSortOption> options = new LinkedList<I_CmsSearchConfigurationSortOption>();
545
546            CmsSimpleSearchConfigurationParser.SortOption currentOption = CmsSimpleSearchConfigurationParser.SortOption.valueOf(
547                m_config.getSortOrder());
548            if (m_sortOrder != null) {
549                currentOption = m_sortOrder;
550            }
551            Locale locale = getSearchLocale();
552            options.add(currentOption.getOption(locale));
553            CmsSimpleSearchConfigurationParser.SortOption[] sortOptions = CmsSimpleSearchConfigurationParser.SortOption.values();
554            for (int i = 0; i < sortOptions.length; i++) {
555                CmsSimpleSearchConfigurationParser.SortOption option = sortOptions[i];
556                if (!Objects.equals(currentOption, option)) {
557                    options.add(option.getOption(locale));
558                }
559            }
560            return options;
561        }
562    }
563
564    /**
565     * Generates the query part for the preconfigured restrictions for the type.
566     * @param type the type to generate the restriction for.
567     * @param restrictionsForType the preconfigured restrictions for the type.
568     * @return the part of the Solr query for the restriction.
569     */
570    String generatePreconfiguredRestriction(
571        String type,
572        Map<CmsRestrictionRule, Collection<FieldValues>> restrictionsForType) {
573
574        String result = "";
575        if ((null != restrictionsForType) && (restrictionsForType.size() > 0)) {
576            Collection<String> ruleRestrictions = new HashSet<>(restrictionsForType.size());
577            for (Map.Entry<CmsRestrictionRule, Collection<FieldValues>> ruleEntry : restrictionsForType.entrySet()) {
578                ruleRestrictions.add(generatePreconfiguredRestrictionForRule(ruleEntry.getKey(), ruleEntry.getValue()));
579            }
580            result = ruleRestrictions.size() > 1
581            ? ruleRestrictions.stream().reduce((r1, r2) -> (r1 + " " + CombinationMode.AND + " " + r2)).get()
582            : ruleRestrictions.iterator().next();
583            if (null != type) {
584                result = "type:\"" + type + "\" AND (" + result + ")";
585            }
586        } else if (null != type) {
587            result = "type:\"" + type + "\"";
588        }
589        return result.isEmpty() ? result : "(" + result + ")";
590    }
591
592    /**
593     * Generates the query part for the preconfigured restriction for a single rule.
594     * @param rule the rule to generate the restriction for.
595     * @param values the values provided for the rule.
596     * @return the part of the Solr query for the restriction.
597     */
598    String generatePreconfiguredRestrictionForRule(CmsRestrictionRule rule, Collection<FieldValues> values) {
599
600        Collection<String> resolvedFieldValues = values.stream().map(v -> resolveFieldValues(rule, v)).collect(
601            Collectors.toSet());
602
603        String seperator = " " + rule.getCombinationModeBetweenFields().toString() + " ";
604        return rule.getFieldForLocale(getSearchLocale())
605            + ":("
606            + resolvedFieldValues.stream().reduce((v1, v2) -> v1 + seperator + v2).get()
607            + ")";
608
609    }
610
611    /**
612     * Returns the blacklist filter.<p>
613     *
614     * @return the blacklist filter
615     */
616    String getBlacklistFilter() {
617
618        if (m_ignoreBlacklist) {
619            return "";
620        }
621        String result = "";
622        List<CmsUUID> blacklist = m_config.getBlacklist();
623        List<String> blacklistStrings = Lists.newArrayList();
624        for (CmsUUID id : blacklist) {
625            blacklistStrings.add("\"" + id.toString() + "\"");
626        }
627        if (!blacklistStrings.isEmpty()) {
628            result = "&fq=" + CmsEncoder.encode("-id:(" + CmsStringUtil.listAsString(blacklistStrings, " OR ") + ")");
629        }
630        return result;
631    }
632
633    /**
634     * Returns the category filter string.<p>
635     *
636     * @return the category filter
637     */
638    String getCategoryFilterPart() {
639
640        String result = "";
641        if (!m_config.getCategories().isEmpty()) {
642            List<String> categoryVals = Lists.newArrayList();
643            for (String path : m_config.getCategories()) {
644                try {
645                    path = CmsCategoryService.getInstance().getCategory(
646                        m_cms,
647                        m_cms.getRequestContext().addSiteRoot(path)).getPath();
648                    categoryVals.add("\"" + path + "\"");
649                } catch (CmsException e) {
650                    LOG.warn(e.getLocalizedMessage(), e);
651                }
652            }
653            if (!categoryVals.isEmpty()) {
654                String operator = " " + m_config.getCategoryMode() + " ";
655                String valueExpression = CmsStringUtil.listAsString(categoryVals, operator);
656                result = "category_exact:(" + valueExpression + ")";
657
658            }
659        }
660        return result;
661    }
662
663    /**
664     * Returns the category filter string.<p>
665     *
666     * @return the category filter
667     */
668    String getCategoryFolderFilter() {
669
670        String result = "";
671        String defaultPart = getFolderFilterPart();
672        String categoryFilterPart = getCategoryFilterPart();
673        if (!categoryFilterPart.isEmpty()) {
674            defaultPart = "((" + defaultPart + ") AND (" + categoryFilterPart + "))";
675        }
676        for (CmsCategoryFolderRestrictionBean restriction : m_config.getCategoryFolderRestrictions()) {
677            String restrictionQuery = restriction.toString();
678            if (!restrictionQuery.isEmpty()) {
679                restrictionQuery = "(" + restrictionQuery + " AND " + defaultPart + ")";
680                if (!result.isEmpty()) {
681                    result += " OR ";
682                }
683                result += restrictionQuery;
684            }
685        }
686        if (result.isEmpty()) {
687            result = defaultPart;
688        }
689        return "fq=" + CmsEncoder.encode(result);
690    }
691
692    /**
693     * The fields returned by default. Typically the output is done via display formatters and hence nearly no
694     * field is necessary. Returning all fields might cause performance problems.
695     *
696     * @return the default return fields.
697     */
698    String getDefaultReturnFields() {
699
700        StringBuffer fields = new StringBuffer("");
701        fields.append(CmsSearchField.FIELD_PATH);
702        fields.append(',');
703        fields.append(CmsSearchField.FIELD_INSTANCEDATE).append(CmsSearchField.FIELD_POSTFIX_DATE);
704        fields.append(',');
705        fields.append(CmsSearchField.FIELD_INSTANCEDATE_END).append(CmsSearchField.FIELD_POSTFIX_DATE);
706        fields.append(',');
707        fields.append(CmsSearchField.FIELD_INSTANCEDATE_CURRENT_TILL).append(CmsSearchField.FIELD_POSTFIX_DATE);
708        fields.append(',');
709        fields.append(CmsSearchField.FIELD_INSTANCEDATE).append('_').append(getSearchLocale().toString()).append(
710            CmsSearchField.FIELD_POSTFIX_DATE);
711        fields.append(',');
712        fields.append(CmsSearchField.FIELD_INSTANCEDATE_END).append('_').append(getSearchLocale().toString()).append(
713            CmsSearchField.FIELD_POSTFIX_DATE);
714        fields.append(',');
715        fields.append(CmsSearchField.FIELD_INSTANCEDATE_CURRENT_TILL).append('_').append(
716            getSearchLocale().toString()).append(CmsSearchField.FIELD_POSTFIX_DATE);
717        fields.append(',');
718        fields.append(CmsSearchField.FIELD_ID);
719        fields.append(',');
720        fields.append(CmsSearchField.FIELD_SOLR_ID);
721        fields.append(',');
722        fields.append(CmsSearchField.FIELD_DISPTITLE).append('_').append(getSearchLocale().toString()).append("_sort");
723        fields.append(',');
724        fields.append(CmsSearchField.FIELD_LINK);
725        fields.append(',');
726        fields.append(CmsSearchField.FIELD_GEOCOORDS);
727        return fields.toString();
728    }
729
730    /**
731     * Returns the filter query string.<p>
732     *
733     * @return the filter query
734     */
735    String getFilterQuery() {
736
737        String result = m_config.getFilterQuery();
738        if (result == null) {
739            result = "";
740        }
741        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(result) && !result.startsWith("&")) {
742            result = "&" + result;
743        }
744        if (!result.contains(CommonParams.FL + "=")) {
745            result += "&" + CommonParams.FL + "=" + CmsEncoder.encode(getDefaultReturnFields());
746        }
747        I_CmsDateRestriction dateRestriction = m_config.getDateRestriction();
748        if (dateRestriction != null) {
749            result += "&fq="
750                + CmsEncoder.encode(
751                    CmsSearchField.FIELD_INSTANCEDATE_CURRENT_TILL
752                        + "_"
753                        + getSearchLocale().toString()
754                        + "_dt:"
755                        + dateRestriction.getRange());
756
757        }
758        result += "&fq=con_locales:" + getSearchLocale().toString();
759        return result;
760    }
761
762    /**
763     * Returns the folder filter string.<p>
764     *
765     * @return the folder filter
766     */
767    String getFolderFilterPart() {
768
769        String result = "";
770        List<String> parentFolderVals = Lists.newArrayList();
771        if (!m_config.getFolders().isEmpty()) {
772            for (String value : m_config.getFolders()) {
773                parentFolderVals.add("\"" + value + "\"");
774            }
775        }
776        if (parentFolderVals.isEmpty()) {
777            result = "parent-folders:(\"/\")";
778        } else {
779            result = "parent-folders:(" + CmsStringUtil.listAsString(parentFolderVals, " OR ") + ")";
780        }
781        return result;
782    }
783
784    /**
785     * Returns the Geo filter query string.<p>
786     *
787     * @return the Geo filter query string
788     */
789    String getGeoFilterQuery() {
790
791        String result = "";
792        CmsGeoFilterBean geoFilterBean = m_config.getGeoFilter();
793        if (geoFilterBean != null) {
794            String fq = CmsSolrQueryUtil.composeGeoFilterQuery(
795                CmsSearchField.FIELD_GEOCOORDS,
796                geoFilterBean.getCoordinates(),
797                geoFilterBean.getRadius(),
798                "km");
799            result = "&fq=" + fq;
800        }
801        return result;
802    }
803
804    /**
805     * Returns the filter query string.<p>
806     *
807     * @return the filter query
808     */
809    String getPreconfiguredFilterQuery() {
810
811        String result = "";
812        if (m_config.hasPreconfiguredRestrictions()) {
813            CmsRestrictionsBean restrictions = m_config.getPreconfiguredRestrictions();
814            String restriction = generatePreconfiguredRestriction(null, restrictions.getRestrictionsForType(null));
815            if (!restriction.isEmpty()) {
816                result = "&fq=" + CmsEncoder.encode(restriction);
817            }
818            Collection<String> typedRestrictions = new HashSet<>();
819            for (String type : m_config.getTypes()) {
820                restriction = generatePreconfiguredRestriction(type, restrictions.getRestrictionsForType(type));
821                if (!restriction.isEmpty()) {
822                    typedRestrictions.add(restriction);
823                }
824            }
825            if (!typedRestrictions.isEmpty()) {
826                result += "&fq="
827                    + CmsEncoder.encode(
828                        "(" + typedRestrictions.stream().reduce((r1, r2) -> (r1 + " OR " + r2)).get() + ")");
829            }
830        }
831        return result;
832    }
833
834    /**
835     * Returns the resource type filter string.<p>
836     *
837     * @return the folder filter
838     */
839    String getResourceTypeFilter() {
840
841        String result = "";
842        // When we have pre-configured restrictions, we need to combine the type filter with these restrictions.
843        if (!m_config.hasPreconfiguredRestrictions()) {
844            List<String> typeVals = Lists.newArrayList();
845            for (String type : m_config.getTypes()) {
846                typeVals.add("\"" + type + "\"");
847            }
848            if (!typeVals.isEmpty()) {
849                result = "&fq=" + CmsEncoder.encode("type:(" + CmsStringUtil.listAsString(typeVals, " OR ") + ")");
850            }
851        }
852        return result;
853    }
854
855    /**
856     * Generates the search string part for one input field value.
857     * @param rule the preconfigured rule.
858     * @param fieldValues the values in the field.
859     * @return the search term part for the value in the field.
860     */
861    String resolveFieldValues(CmsRestrictionRule rule, FieldValues fieldValues) {
862
863        Collection<String> values = fieldValues.getValues();
864        Collection<String> finalValues;
865        if (FieldType.PLAIN.equals(fieldValues.getFieldType())) {
866            // We are sure that there is exactly one value in that case.
867            return "(" + values.iterator().next() + ")";
868        } else {
869            switch (rule.getMatchType()) {
870                case DEFAULT:
871                    finalValues = values;
872                    break;
873                case EXACT:
874                    finalValues = values.stream().map(v -> ("\"" + v + "\"")).collect(Collectors.toSet());
875                    break;
876                case INFIX:
877                    finalValues = values.stream().map(
878                        v -> ("(" + v + " OR *" + v + " OR *" + v + "* OR " + v + "*)")).collect(Collectors.toSet());
879                    break;
880                case POSTFIX:
881                    finalValues = values.stream().map(v -> ("(" + v + " OR *" + v + ")")).collect(Collectors.toSet());
882                    break;
883                case PREFIX:
884                    finalValues = values.stream().map(v -> ("(" + v + " OR " + v + "*)")).collect(Collectors.toSet());
885                    break;
886                default:
887                    throw new IllegalArgumentException("Unknown match type '" + rule.getMatchType() + "'.");
888            }
889            if (finalValues.size() > 1) {
890                String seperator = " " + rule.getCombinationModeInField().toString() + " ";
891                return "(" + finalValues.stream().reduce((v1, v2) -> v1 + seperator + v2).get() + ")";
892            } else {
893                return finalValues.iterator().next();
894            }
895
896        }
897    }
898
899    /**
900     * Returns a flag, indicating if the release and expiration date should be ignored.
901     * @return a flag, indicating if the release and expiration date should be ignored.
902     */
903    private Boolean getIgnoreReleaseAndExpiration() {
904
905        return Boolean.valueOf(m_config.isShowExpired());
906    }
907}