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                + "\"}%(query)";
510        }
511        return modifier;
512    }
513
514    /**
515     * @see org.opencms.jsp.search.config.parser.CmsJSONSearchConfigurationParser#getSearchForEmptyQuery()
516     */
517    @Override
518    protected Boolean getSearchForEmptyQuery() {
519
520        if (m_configObject.has(JSON_KEY_SEARCH_FOR_EMPTY_QUERY)) {
521            return super.getSearchForEmptyQuery();
522        } else {
523            return Boolean.TRUE;
524        }
525    }
526
527    /**
528     * @see org.opencms.jsp.search.config.parser.CmsJSONSearchConfigurationParser#getSortOptions()
529     */
530    @Override
531    protected List<I_CmsSearchConfigurationSortOption> getSortOptions() {
532
533        if (m_configObject.has(JSON_KEY_SORTOPTIONS)) {
534            return super.getSortOptions();
535        } else {
536            List<I_CmsSearchConfigurationSortOption> options = new LinkedList<I_CmsSearchConfigurationSortOption>();
537
538            CmsSimpleSearchConfigurationParser.SortOption currentOption = CmsSimpleSearchConfigurationParser.SortOption.valueOf(
539                m_config.getSortOrder());
540            if (m_sortOrder != null) {
541                currentOption = m_sortOrder;
542            }
543            Locale locale = getSearchLocale();
544            options.add(currentOption.getOption(locale));
545            CmsSimpleSearchConfigurationParser.SortOption[] sortOptions = CmsSimpleSearchConfigurationParser.SortOption.values();
546            for (int i = 0; i < sortOptions.length; i++) {
547                CmsSimpleSearchConfigurationParser.SortOption option = sortOptions[i];
548                if (!Objects.equals(currentOption, option)) {
549                    options.add(option.getOption(locale));
550                }
551            }
552            return options;
553        }
554    }
555
556    /**
557     * Generates the query part for the preconfigured restrictions for the type.
558     * @param type the type to generate the restriction for.
559     * @param restrictionsForType the preconfigured restrictions for the type.
560     * @return the part of the Solr query for the restriction.
561     */
562    String generatePreconfiguredRestriction(
563        String type,
564        Map<CmsRestrictionRule, Collection<FieldValues>> restrictionsForType) {
565
566        String result = "";
567        if ((null != restrictionsForType) && (restrictionsForType.size() > 0)) {
568            Collection<String> ruleRestrictions = new HashSet<>(restrictionsForType.size());
569            for (Map.Entry<CmsRestrictionRule, Collection<FieldValues>> ruleEntry : restrictionsForType.entrySet()) {
570                ruleRestrictions.add(generatePreconfiguredRestrictionForRule(ruleEntry.getKey(), ruleEntry.getValue()));
571            }
572            result = ruleRestrictions.size() > 1
573            ? ruleRestrictions.stream().reduce((r1, r2) -> (r1 + " " + CombinationMode.AND + " " + r2)).get()
574            : ruleRestrictions.iterator().next();
575            if (null != type) {
576                result = "type:\"" + type + "\" AND (" + result + ")";
577            }
578        } else if (null != type) {
579            result = "type:\"" + type + "\"";
580        }
581        return result.isEmpty() ? result : "(" + result + ")";
582    }
583
584    /**
585     * Generates the query part for the preconfigured restriction for a single rule.
586     * @param rule the rule to generate the restriction for.
587     * @param values the values provided for the rule.
588     * @return the part of the Solr query for the restriction.
589     */
590    String generatePreconfiguredRestrictionForRule(CmsRestrictionRule rule, Collection<FieldValues> values) {
591
592        Collection<String> resolvedFieldValues = values.stream().map(v -> resolveFieldValues(rule, v)).collect(
593            Collectors.toSet());
594
595        String seperator = " " + rule.getCombinationModeBetweenFields().toString() + " ";
596        return rule.getFieldForLocale(getSearchLocale())
597            + ":("
598            + resolvedFieldValues.stream().reduce((v1, v2) -> v1 + seperator + v2).get()
599            + ")";
600
601    }
602
603    /**
604     * Returns the blacklist filter.<p>
605     *
606     * @return the blacklist filter
607     */
608    String getBlacklistFilter() {
609
610        if (m_ignoreBlacklist) {
611            return "";
612        }
613        String result = "";
614        List<CmsUUID> blacklist = m_config.getBlacklist();
615        List<String> blacklistStrings = Lists.newArrayList();
616        for (CmsUUID id : blacklist) {
617            blacklistStrings.add("\"" + id.toString() + "\"");
618        }
619        if (!blacklistStrings.isEmpty()) {
620            result = "&fq=" + CmsEncoder.encode("-id:(" + CmsStringUtil.listAsString(blacklistStrings, " OR ") + ")");
621        }
622        return result;
623    }
624
625    /**
626     * Returns the category filter string.<p>
627     *
628     * @return the category filter
629     */
630    String getCategoryFilterPart() {
631
632        String result = "";
633        if (!m_config.getCategories().isEmpty()) {
634            List<String> categoryVals = Lists.newArrayList();
635            for (String path : m_config.getCategories()) {
636                try {
637                    path = CmsCategoryService.getInstance().getCategory(
638                        m_cms,
639                        m_cms.getRequestContext().addSiteRoot(path)).getPath();
640                    categoryVals.add("\"" + path + "\"");
641                } catch (CmsException e) {
642                    LOG.warn(e.getLocalizedMessage(), e);
643                }
644            }
645            if (!categoryVals.isEmpty()) {
646                String operator = " " + m_config.getCategoryMode() + " ";
647                String valueExpression = CmsStringUtil.listAsString(categoryVals, operator);
648                result = "category_exact:(" + valueExpression + ")";
649
650            }
651        }
652        return result;
653    }
654
655    /**
656     * Returns the category filter string.<p>
657     *
658     * @return the category filter
659     */
660    String getCategoryFolderFilter() {
661
662        String result = "";
663        String defaultPart = getFolderFilterPart();
664        String categoryFilterPart = getCategoryFilterPart();
665        if (!categoryFilterPart.isEmpty()) {
666            defaultPart = "((" + defaultPart + ") AND (" + categoryFilterPart + "))";
667        }
668        for (CmsCategoryFolderRestrictionBean restriction : m_config.getCategoryFolderRestrictions()) {
669            String restrictionQuery = restriction.toString();
670            if (!restrictionQuery.isEmpty()) {
671                restrictionQuery = "(" + restrictionQuery + " AND " + defaultPart + ")";
672                if (!result.isEmpty()) {
673                    result += " OR ";
674                }
675                result += restrictionQuery;
676            }
677        }
678        if (result.isEmpty()) {
679            result = defaultPart;
680        }
681        return "fq=" + CmsEncoder.encode(result);
682    }
683
684    /**
685     * The fields returned by default. Typically the output is done via display formatters and hence nearly no
686     * field is necessary. Returning all fields might cause performance problems.
687     *
688     * @return the default return fields.
689     */
690    String getDefaultReturnFields() {
691
692        StringBuffer fields = new StringBuffer("");
693        fields.append(CmsSearchField.FIELD_PATH);
694        fields.append(',');
695        fields.append(CmsSearchField.FIELD_INSTANCEDATE).append(CmsSearchField.FIELD_POSTFIX_DATE);
696        fields.append(',');
697        fields.append(CmsSearchField.FIELD_INSTANCEDATE_END).append(CmsSearchField.FIELD_POSTFIX_DATE);
698        fields.append(',');
699        fields.append(CmsSearchField.FIELD_INSTANCEDATE_CURRENT_TILL).append(CmsSearchField.FIELD_POSTFIX_DATE);
700        fields.append(',');
701        fields.append(CmsSearchField.FIELD_INSTANCEDATE).append('_').append(getSearchLocale().toString()).append(
702            CmsSearchField.FIELD_POSTFIX_DATE);
703        fields.append(',');
704        fields.append(CmsSearchField.FIELD_INSTANCEDATE_END).append('_').append(getSearchLocale().toString()).append(
705            CmsSearchField.FIELD_POSTFIX_DATE);
706        fields.append(',');
707        fields.append(CmsSearchField.FIELD_INSTANCEDATE_CURRENT_TILL).append('_').append(
708            getSearchLocale().toString()).append(CmsSearchField.FIELD_POSTFIX_DATE);
709        fields.append(',');
710        fields.append(CmsSearchField.FIELD_ID);
711        fields.append(',');
712        fields.append(CmsSearchField.FIELD_SOLR_ID);
713        fields.append(',');
714        fields.append(CmsSearchField.FIELD_DISPTITLE).append('_').append(getSearchLocale().toString()).append("_sort");
715        fields.append(',');
716        fields.append(CmsSearchField.FIELD_LINK);
717        fields.append(',');
718        fields.append(CmsSearchField.FIELD_GEOCOORDS);
719        return fields.toString();
720    }
721
722    /**
723     * Returns the filter query string.<p>
724     *
725     * @return the filter query
726     */
727    String getFilterQuery() {
728
729        String result = m_config.getFilterQuery();
730        if (result == null) {
731            result = "";
732        }
733        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(result) && !result.startsWith("&")) {
734            result = "&" + result;
735        }
736        if (!result.contains(CommonParams.FL + "=")) {
737            result += "&" + CommonParams.FL + "=" + CmsEncoder.encode(getDefaultReturnFields());
738        }
739        I_CmsDateRestriction dateRestriction = m_config.getDateRestriction();
740        if (dateRestriction != null) {
741            result += "&fq="
742                + CmsEncoder.encode(
743                    CmsSearchField.FIELD_INSTANCEDATE_CURRENT_TILL
744                        + "_"
745                        + getSearchLocale().toString()
746                        + "_dt:"
747                        + dateRestriction.getRange());
748
749        }
750        result += "&fq=con_locales:" + getSearchLocale().toString();
751        return result;
752    }
753
754    /**
755     * Returns the folder filter string.<p>
756     *
757     * @return the folder filter
758     */
759    String getFolderFilterPart() {
760
761        String result = "";
762        List<String> parentFolderVals = Lists.newArrayList();
763        if (!m_config.getFolders().isEmpty()) {
764            for (String value : m_config.getFolders()) {
765                parentFolderVals.add("\"" + value + "\"");
766            }
767        }
768        if (parentFolderVals.isEmpty()) {
769            result = "parent-folders:(\"/\")";
770        } else {
771            result = "parent-folders:(" + CmsStringUtil.listAsString(parentFolderVals, " OR ") + ")";
772        }
773        return result;
774    }
775
776    /**
777     * Returns the Geo filter query string.<p>
778     *
779     * @return the Geo filter query string
780     */
781    String getGeoFilterQuery() {
782
783        String result = "";
784        CmsGeoFilterBean geoFilterBean = m_config.getGeoFilter();
785        if (geoFilterBean != null) {
786            String fq = CmsSolrQueryUtil.composeGeoFilterQuery(
787                CmsSearchField.FIELD_GEOCOORDS,
788                geoFilterBean.getCoordinates(),
789                geoFilterBean.getRadius(),
790                "km");
791            result = "&fq=" + fq;
792        }
793        return result;
794    }
795
796    /**
797     * Returns the filter query string.<p>
798     *
799     * @return the filter query
800     */
801    String getPreconfiguredFilterQuery() {
802
803        String result = "";
804        if (m_config.hasPreconfiguredRestrictions()) {
805            CmsRestrictionsBean restrictions = m_config.getPreconfiguredRestrictions();
806            String restriction = generatePreconfiguredRestriction(null, restrictions.getRestrictionsForType(null));
807            if (!restriction.isEmpty()) {
808                result = "&fq=" + CmsEncoder.encode(restriction);
809            }
810            Collection<String> typedRestrictions = new HashSet<>();
811            for (String type : m_config.getTypes()) {
812                restriction = generatePreconfiguredRestriction(type, restrictions.getRestrictionsForType(type));
813                if (!restriction.isEmpty()) {
814                    typedRestrictions.add(restriction);
815                }
816            }
817            if (!typedRestrictions.isEmpty()) {
818                result += "&fq="
819                    + CmsEncoder.encode(
820                        "(" + typedRestrictions.stream().reduce((r1, r2) -> (r1 + " OR " + r2)).get() + ")");
821            }
822        }
823        return result;
824    }
825
826    /**
827     * Returns the resource type filter string.<p>
828     *
829     * @return the folder filter
830     */
831    String getResourceTypeFilter() {
832
833        String result = "";
834        // When we have pre-configured restrictions, we need to combine the type filter with these restrictions.
835        if (!m_config.hasPreconfiguredRestrictions()) {
836            List<String> typeVals = Lists.newArrayList();
837            for (String type : m_config.getTypes()) {
838                typeVals.add("\"" + type + "\"");
839            }
840            if (!typeVals.isEmpty()) {
841                result = "&fq=" + CmsEncoder.encode("type:(" + CmsStringUtil.listAsString(typeVals, " OR ") + ")");
842            }
843        }
844        return result;
845    }
846
847    /**
848     * Generates the search string part for one input field value.
849     * @param rule the preconfigured rule.
850     * @param fieldValues the values in the field.
851     * @return the search term part for the value in the field.
852     */
853    String resolveFieldValues(CmsRestrictionRule rule, FieldValues fieldValues) {
854
855        Collection<String> values = fieldValues.getValues();
856        Collection<String> finalValues;
857        if (FieldType.PLAIN.equals(fieldValues.getFieldType())) {
858            // We are sure that there is exactly one value in that case.
859            return "(" + values.iterator().next() + ")";
860        } else {
861            switch (rule.getMatchType()) {
862                case DEFAULT:
863                    finalValues = values;
864                    break;
865                case EXACT:
866                    finalValues = values.stream().map(v -> ("\"" + v + "\"")).collect(Collectors.toSet());
867                    break;
868                case INFIX:
869                    finalValues = values.stream().map(
870                        v -> ("(" + v + " OR *" + v + " OR *" + v + "* OR " + v + "*)")).collect(Collectors.toSet());
871                    break;
872                case POSTFIX:
873                    finalValues = values.stream().map(v -> ("(" + v + " OR *" + v + ")")).collect(Collectors.toSet());
874                    break;
875                case PREFIX:
876                    finalValues = values.stream().map(v -> ("(" + v + " OR " + v + "*)")).collect(Collectors.toSet());
877                    break;
878                default:
879                    throw new IllegalArgumentException("Unknown match type '" + rule.getMatchType() + "'.");
880            }
881            if (finalValues.size() > 1) {
882                String seperator = " " + rule.getCombinationModeInField().toString() + " ";
883                return "(" + finalValues.stream().reduce((v1, v2) -> v1 + seperator + v2).get() + ")";
884            } else {
885                return finalValues.iterator().next();
886            }
887
888        }
889    }
890
891    /**
892     * Returns a flag, indicating if the release and expiration date should be ignored.
893     * @return a flag, indicating if the release and expiration date should be ignored.
894     */
895    private Boolean getIgnoreReleaseAndExpiration() {
896
897        return Boolean.valueOf(m_config.isShowExpired());
898    }
899}