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