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.json.JSONArray;
032import org.opencms.json.JSONException;
033import org.opencms.json.JSONObject;
034import org.opencms.jsp.search.config.CmsSearchConfigurationCommon;
035import org.opencms.jsp.search.config.CmsSearchConfigurationDidYouMean;
036import org.opencms.jsp.search.config.CmsSearchConfigurationFacetField;
037import org.opencms.jsp.search.config.CmsSearchConfigurationFacetQuery;
038import org.opencms.jsp.search.config.CmsSearchConfigurationFacetQuery.CmsFacetQueryItem;
039import org.opencms.jsp.search.config.CmsSearchConfigurationFacetRange;
040import org.opencms.jsp.search.config.CmsSearchConfigurationGeoFilter;
041import org.opencms.jsp.search.config.CmsSearchConfigurationHighlighting;
042import org.opencms.jsp.search.config.CmsSearchConfigurationPagination;
043import org.opencms.jsp.search.config.CmsSearchConfigurationSortOption;
044import org.opencms.jsp.search.config.CmsSearchConfigurationSorting;
045import org.opencms.jsp.search.config.I_CmsSearchConfiguration;
046import org.opencms.jsp.search.config.I_CmsSearchConfigurationCommon;
047import org.opencms.jsp.search.config.I_CmsSearchConfigurationDidYouMean;
048import org.opencms.jsp.search.config.I_CmsSearchConfigurationFacet;
049import org.opencms.jsp.search.config.I_CmsSearchConfigurationFacetField;
050import org.opencms.jsp.search.config.I_CmsSearchConfigurationFacetQuery;
051import org.opencms.jsp.search.config.I_CmsSearchConfigurationFacetQuery.I_CmsFacetQueryItem;
052import org.opencms.jsp.search.config.I_CmsSearchConfigurationFacetRange;
053import org.opencms.jsp.search.config.I_CmsSearchConfigurationGeoFilter;
054import org.opencms.jsp.search.config.I_CmsSearchConfigurationHighlighting;
055import org.opencms.jsp.search.config.I_CmsSearchConfigurationPagination;
056import org.opencms.jsp.search.config.I_CmsSearchConfigurationSortOption;
057import org.opencms.jsp.search.config.I_CmsSearchConfigurationSorting;
058import org.opencms.main.CmsLog;
059import org.opencms.main.OpenCms;
060import org.opencms.search.solr.CmsSolrIndex;
061
062import java.util.ArrayList;
063import java.util.Collections;
064import java.util.HashMap;
065import java.util.Iterator;
066import java.util.LinkedHashMap;
067import java.util.LinkedList;
068import java.util.List;
069import java.util.Map;
070import java.util.Objects;
071
072import org.apache.commons.logging.Log;
073
074/** Search configuration parser reading JSON. */
075public class CmsJSONSearchConfigurationParser implements I_CmsSearchConfigurationParser {
076
077    /** Logger for the class. */
078    protected static final Log LOG = CmsLog.getLog(CmsJSONSearchConfigurationParser.class);
079
080    /** The keys that can be used in the JSON object */
081    /** JSON keys for common options. */
082    /** A JSON key. */
083    public static final String JSON_KEY_QUERYPARAM = "queryparam";
084    /** A JSON key. */
085    public static final String JSON_KEY_LAST_QUERYPARAM = "lastqueryparam";
086    /** A JSON key. */
087    public static final String JSON_KEY_ESCAPE_QUERY_CHARACTERS = "escapequerychars";
088    /** A JSON key. */
089    public static final String JSON_KEY_RELOADED_PARAM = "reloadedparam";
090    /** A JSON key. */
091    public static final String JSON_KEY_SEARCH_FOR_EMPTY_QUERY = "searchforemptyquery";
092    /** A JSON key. */
093    public static final String JSON_KEY_IGNORE_QUERY = "ignorequery";
094    /** A JSON key. */
095    public static final String JSON_KEY_IGNORE_RELEASE_DATE = "ignoreReleaseDate";
096    /** A JSON key. */
097    public static final String JSON_KEY_MAX_RETURNED_RESULTS = "maxReturnedResults";
098    /** A JSON key. */
099    public static final String JSON_KEY_IGNORE_EXPIRATION_DATE = "ignoreExpirationDate";
100    /** A JSON key. */
101    public static final String JSON_KEY_QUERY_MODIFIER = "querymodifier";
102    /** A JSON key. */
103    public static final String JSON_KEY_PAGEPARAM = "pageparam";
104    /** A JSON key. */
105    public static final String JSON_KEY_INDEX = "index";
106    /** A JSON key. */
107    public static final String JSON_KEY_CORE = "core";
108    /** A JSON key. */
109    public static final String JSON_KEY_EXTRASOLRPARAMS = "extrasolrparams";
110    /** A JSON key. */
111    public static final String JSON_KEY_ADDITIONAL_PARAMETERS = "additionalrequestparams";
112    /** A JSON key. */
113    public static final String JSON_KEY_ADDITIONAL_PARAMETERS_PARAM = "param";
114    /** A JSON key. */
115    public static final String JSON_KEY_ADDITIONAL_PARAMETERS_SOLRQUERY = "solrquery";
116    /** A JSON key. */
117    public static final String JSON_KEY_PAGESIZE = "pagesize";
118    /** A JSON key. */
119    public static final String JSON_KEY_PAGENAVLENGTH = "pagenavlength";
120    /** JSON keys for facet configuration. */
121    /** The JSON key for the sub-node with all field facet configurations. */
122    public static final String JSON_KEY_FIELD_FACETS = "fieldfacets";
123    /** The JSON key for the sub-node with all field facet configurations. */
124    public static final String JSON_KEY_RANGE_FACETS = "rangefacets";
125    /** The JSON key for the sub-node with the query facet configuration. */
126    public static final String JSON_KEY_QUERY_FACET = "queryfacet";
127    /** JSON keys for a single facet. */
128    /** A JSON key. */
129    public static final String JSON_KEY_FACET_LIMIT = "limit";
130    /** A JSON key. */
131    public static final String JSON_KEY_FACET_MINCOUNT = "mincount";
132    /** A JSON key. */
133    public static final String JSON_KEY_FACET_LABEL = "label";
134    /** A JSON key. */
135    public static final String JSON_KEY_FACET_FIELD = "field";
136    /** A JSON key. */
137    public static final String JSON_KEY_FACET_NAME = "name";
138    /** A JSON key. */
139    public static final String JSON_KEY_FACET_PREFIX = "prefix";
140    /** A JSON key. */
141    public static final String JSON_KEY_FACET_ORDER = "order";
142    /** A JSON key. */
143    public static final String JSON_KEY_FACET_FILTERQUERYMODIFIER = "filterquerymodifier";
144    /** A JSON key. */
145    public static final String JSON_KEY_FACET_ISANDFACET = "isAndFacet";
146    /** A JSON key. */
147    public static final String JSON_KEY_FACET_IGNOREALLFACETFILTERS = "ignoreAllFacetFilters";
148    /** A JSON key. */
149    public static final String JSON_KEY_FACET_EXCLUDETAGS = "excludeTags";
150    /** A JSON key. */
151    public static final String JSON_KEY_FACET_PRESELECTION = "preselection";
152    /** A JSON key. */
153    public static final String JSON_KEY_RANGE_FACET_RANGE = "range";
154    /** A JSON key. */
155    public static final String JSON_KEY_RANGE_FACET_START = "start";
156    /** A JSON key. */
157    public static final String JSON_KEY_RANGE_FACET_END = "end";
158    /** A JSON key. */
159    public static final String JSON_KEY_RANGE_FACET_GAP = "gap";
160    /** A JSON key. */
161    public static final String JSON_KEY_RANGE_FACET_OTHER = "other";
162    /** A JSON key. */
163    public static final String JSON_KEY_RANGE_FACET_HARDEND = "hardend";
164    /** A JSON key. */
165    public static final String JSON_KEY_QUERY_FACET_QUERY = "queryitems";
166    /** A JSON key. */
167    public static final String JSON_KEY_QUERY_FACET_QUERY_QUERY = "query";
168    /** A JSON key. */
169    public static final String JSON_KEY_QUERY_FACET_QUERY_LABEL = "label";
170
171    /** JSON keys for sort options. */
172    /** A JSON key. */
173    public static final String JSON_KEY_SORTPARAM = "sortby";
174    /** The JSON key for the default sort option, should hold the name paramvalue for the default option. */
175    public static final String JSON_KEY_DEFAULT_SORT_OPTION = "defaultSortOption";
176    /** The JSON key for the sub-node with all search option configurations. */
177    public static final String JSON_KEY_SORTOPTIONS = "sortoptions";
178    /** JSON keys for a single search option. */
179    /** A JSON key. */
180    public static final String JSON_KEY_SORTOPTION_LABEL = "label";
181    /** A JSON key. */
182    public static final String JSON_KEY_SORTOPTION_PARAMVALUE = "paramvalue";
183    /** A JSON key. */
184    public static final String JSON_KEY_SORTOPTION_SOLRVALUE = "solrvalue";
185    /** JSON keys for the highlighting configuration. */
186    /** The JSON key for the subnode of all highlighting configuration. */
187    public static final String JSON_KEY_HIGHLIGHTER = "highlighter";
188    /** A JSON key. */
189    public static final String JSON_KEY_HIGHLIGHTER_FIELD = "field";
190    /** A JSON key. */
191    public static final String JSON_KEY_HIGHLIGHTER_SNIPPETS = "snippets";
192    /** A JSON key. */
193    public static final String JSON_KEY_HIGHLIGHTER_FRAGSIZE = "fragsize";
194    /** A JSON key. */
195    public static final String JSON_KEY_HIGHLIGHTER_ALTERNATE_FIELD = "alternateField";
196    /** A JSON key. */
197    public static final String JSON_KEY_HIGHLIGHTER_MAX_LENGTH_ALTERNATE_FIELD = "maxAlternateFieldLength";
198    /** A JSON key. */
199    public static final String JSON_KEY_HIGHLIGHTER_SIMPLE_PRE = "simple.pre";
200    /** A JSON key. */
201    public static final String JSON_KEY_HIGHLIGHTER_SIMPLE_POST = "simple.post";
202    /** A JSON key. */
203    public static final String JSON_KEY_HIGHLIGHTER_FORMATTER = "formatter";
204    /** A JSON key. */
205    public static final String JSON_KEY_HIGHLIGHTER_FRAGMENTER = "fragmenter";
206    /** A JSON key. */
207    public static final String JSON_KEY_HIGHLIGHTER_FASTVECTORHIGHLIGHTING = "useFastVectorHighlighting";
208
209    /** JSON keys for "Did you mean ...?" */
210    /** A JSON key. */
211    public static final String JSON_KEY_DIDYOUMEAN = "didYouMean";
212    /** The JSON key for the subnode of all "Did you mean?" configuration. */
213    /** A JSON key. */
214    public static final String JSON_KEY_DIDYOUMEAN_QUERYPARAM = "didYouMeanQueryParam";
215    /** A JSON key. */
216    public static final String JSON_KEY_DIDYOUMEAN_COLLATE = "didYouMeanCollate";
217    /** A JSON key. */
218    public static final String JSON_KEY_DIDYOUMEAN_COUNT = "didYouMeanCount";
219
220    /** JSON keys for the Geo filter. */
221    public static final String JSON_KEY_GEO_FILTER = "geofilter";
222    /** A JSON key. */
223    public static final String JSON_KEY_GEO_FILTER_COORDINATES = "coordinates";
224    /** A JSON key. */
225    public static final String JSON_KEY_GEO_FILTER_COORDINATES_PARAM = "coordinatesparam";
226    /** A JSON key. */
227    public static final String JSON_KEY_GEO_FILTER_FIELD_NAME = "fieldName";
228    /** A JSON key. */
229    public static final String JSON_KEY_GEO_FILTER_RADIUS = "radius";
230    /** A JSON key. */
231    public static final String JSON_KEY_GEO_FILTER_RADIUS_PARAM = "radiusparam";
232    /** A JSON key. */
233    public static final String JSON_KEY_GEO_FILTER_UNITS = "units";
234    /** A JSON key. */
235    public static final String JSON_KEY_GEO_FILTER_UNITS_PARAM = "unitsparam";
236
237    /** The default values. */
238    /** A JSON key. */
239    public static final String DEFAULT_QUERY_PARAM = "q";
240    /** A JSON key. */
241    public static final String DEFAULT_LAST_QUERY_PARAM = "lq";
242    /** A JSON key. */
243    public static final String DEFAULT_RELOADED_PARAM = "reloaded";
244
245    /** The whole JSON file. */
246    protected JSONObject m_configObject;
247
248    /** The optional base configuration that should be changed by the JSON configuration. */
249    private I_CmsSearchConfiguration m_baseConfig;
250
251    /** Constructor taking the JSON as String.
252     * @param json The JSON that should be parsed as String.
253     * @throws JSONException Thrown if parsing fails.
254     */
255    public CmsJSONSearchConfigurationParser(String json)
256    throws JSONException {
257
258        init(json, null);
259    }
260
261    /** Constructor taking the JSON as String.
262     * @param json The JSON that should be parsed as String.
263     * @param baseConfig A base configuration that is adjusted by the JSON configuration string.
264     * @throws JSONException Thrown if parsing fails.
265     */
266    public CmsJSONSearchConfigurationParser(String json, I_CmsSearchConfiguration baseConfig)
267    throws JSONException {
268
269        init(json, baseConfig);
270    }
271
272    /** Helper for reading a mandatory String value list - throwing an Exception if parsing fails.
273     * @param json The JSON object where the list should be read from.
274     * @param key The key of the value to read.
275     * @return The value from the JSON.
276     * @throws JSONException thrown when parsing fails.
277     */
278    protected static List<String> parseMandatoryStringValues(JSONObject json, String key) throws JSONException {
279
280        List<String> list = null;
281        JSONArray array = json.getJSONArray(key);
282        list = new ArrayList<String>(array.length());
283        for (int i = 0; i < array.length(); i++) {
284            try {
285                String entry = array.getString(i);
286                list.add(entry);
287            } catch (JSONException e) {
288                LOG.info(Messages.get().getBundle().key(Messages.LOG_OPTIONAL_STRING_ENTRY_UNPARSABLE_1, key), e);
289            }
290        }
291        return list;
292    }
293
294    /** Helper for reading an optional Boolean value - returning <code>null</code> if parsing fails.
295     * @param json The JSON object where the value should be read from.
296     * @param key The key of the value to read.
297     * @return The value from the JSON, or <code>null</code> if the value does not exist, or is no Boolean.
298     */
299    protected static Boolean parseOptionalBooleanValue(JSONObject json, String key) {
300
301        try {
302            return Boolean.valueOf(json.getBoolean(key));
303        } catch (JSONException e) {
304            LOG.info(Messages.get().getBundle().key(Messages.LOG_OPTIONAL_BOOLEAN_MISSING_1, key), e);
305            return null;
306        }
307    }
308
309    /** Helper for reading an optional Integer value - returning <code>null</code> if parsing fails.
310     * @param json The JSON object where the value should be read from.
311     * @param key The key of the value to read.
312     * @return The value from the JSON, or <code>null</code> if the value does not exist, or is no Integer.
313     */
314    protected static Integer parseOptionalIntValue(JSONObject json, String key) {
315
316        try {
317            return Integer.valueOf(json.getInt(key));
318        } catch (JSONException e) {
319            LOG.info(Messages.get().getBundle().key(Messages.LOG_OPTIONAL_INTEGER_MISSING_1, key), e);
320            return null;
321        }
322    }
323
324    /** Helper for reading an optional String value - returning <code>null</code> if parsing fails.
325     * @param json The JSON object where the value should be read from.
326     * @param key The key of the value to read.
327     * @return The value from the JSON, or <code>null</code> if the value does not exist.
328     */
329    protected static String parseOptionalStringValue(JSONObject json, String key) {
330
331        try {
332            return json.getString(key);
333        } catch (JSONException e) {
334            LOG.info(Messages.get().getBundle().key(Messages.LOG_OPTIONAL_STRING_MISSING_1, key), e);
335            return null;
336        }
337    }
338
339    /** Helper for reading an optional String value list - returning <code>null</code> if parsing fails for the whole list, otherwise just skipping unparsable entries.
340     * @param json The JSON object where the list should be read from.
341     * @param key The key of the value to read.
342     * @return The value from the JSON, or <code>null</code> if the value does not exist.
343     */
344    protected static List<String> parseOptionalStringValues(JSONObject json, String key) {
345
346        List<String> list = null;
347        try {
348            list = parseMandatoryStringValues(json, key);
349        } catch (JSONException e) {
350            LOG.info(Messages.get().getBundle().key(Messages.LOG_OPTIONAL_STRING_LIST_MISSING_1, key), e);
351            return null;
352        }
353        return list;
354    }
355
356    /**
357     * @see org.opencms.jsp.search.config.parser.I_CmsSearchConfigurationParser#parseCommon(CmsObject)
358     */
359    @Override
360    public I_CmsSearchConfigurationCommon parseCommon(CmsObject cms) {
361
362        String indexName = getIndex(cms);
363
364        return new CmsSearchConfigurationCommon(
365            getQueryParam(),
366            getLastQueryParam(),
367            getEscapeQueryChars(),
368            getFirstCallParam(),
369            getSearchForEmptyQuery(),
370            getIgnoreQuery(),
371            getQueryModifier(),
372            indexName,
373            getCore(),
374            getExtraSolrParams(),
375            getAdditionalParameters(),
376            getIgnoreReleaseDate(),
377            getIgnoreExpirationDate(),
378            getMaxReturnedResults(indexName));
379    }
380
381    /**
382     * @see org.opencms.jsp.search.config.parser.I_CmsSearchConfigurationParser#parseDidYouMean()
383     */
384    public I_CmsSearchConfigurationDidYouMean parseDidYouMean() {
385
386        try {
387            JSONObject didYouMean = m_configObject.getJSONObject(JSON_KEY_DIDYOUMEAN);
388            String param = parseOptionalStringValue(didYouMean, JSON_KEY_DIDYOUMEAN_QUERYPARAM);
389            // default to the normal query param
390            if (null == param) {
391                param = getQueryParam();
392            }
393            Boolean collate = parseOptionalBooleanValue(didYouMean, JSON_KEY_DIDYOUMEAN_COLLATE);
394            Integer count = parseOptionalIntValue(didYouMean, JSON_KEY_DIDYOUMEAN_COUNT);
395            return new CmsSearchConfigurationDidYouMean(param, collate, count);
396
397        } catch (JSONException e) {
398            if (null == m_baseConfig) {
399                if (LOG.isInfoEnabled()) {
400                    LOG.info(Messages.get().getBundle().key(Messages.LOG_NO_HIGHLIGHTING_CONFIG_0), e);
401                }
402                return null;
403            } else {
404                return m_baseConfig.getDidYouMeanConfig();
405            }
406        }
407
408    }
409
410    /**
411     * @see org.opencms.jsp.search.config.parser.I_CmsSearchConfigurationParser#parseFieldFacets()
412     */
413    @Override
414    public Map<String, I_CmsSearchConfigurationFacetField> parseFieldFacets() {
415
416        Map<String, I_CmsSearchConfigurationFacetField> facetConfigs = new LinkedHashMap<String, I_CmsSearchConfigurationFacetField>();
417        try {
418            JSONArray fieldFacets = m_configObject.getJSONArray(JSON_KEY_FIELD_FACETS);
419            for (int i = 0; i < fieldFacets.length(); i++) {
420
421                I_CmsSearchConfigurationFacetField config = parseFieldFacet(fieldFacets.getJSONObject(i));
422                if (config != null) {
423                    facetConfigs.put(config.getName(), config);
424                }
425            }
426        } catch (JSONException e) {
427            if (null == m_baseConfig) {
428                if (LOG.isInfoEnabled()) {
429                    LOG.info(Messages.get().getBundle().key(Messages.LOG_NO_FACET_CONFIG_0), e);
430                }
431            } else {
432                facetConfigs = m_baseConfig.getFieldFacetConfigs();
433            }
434        }
435        return facetConfigs;
436    }
437
438    /**
439     * @see org.opencms.jsp.search.config.parser.I_CmsSearchConfigurationParser#parseGeoFilter()
440     */
441    @Override
442    public I_CmsSearchConfigurationGeoFilter parseGeoFilter() {
443
444        try {
445            JSONObject geoFilter = m_configObject.getJSONObject(JSON_KEY_GEO_FILTER);
446            String coordinates = parseOptionalStringValue(geoFilter, JSON_KEY_GEO_FILTER_COORDINATES);
447            String coordinatesParam = parseOptionalStringValue(geoFilter, JSON_KEY_GEO_FILTER_COORDINATES_PARAM);
448            String fieldName = parseOptionalStringValue(geoFilter, JSON_KEY_GEO_FILTER_FIELD_NAME);
449            String radius = parseOptionalStringValue(geoFilter, JSON_KEY_GEO_FILTER_RADIUS);
450            String radiusParam = parseOptionalStringValue(geoFilter, JSON_KEY_GEO_FILTER_RADIUS_PARAM);
451            String units = parseOptionalStringValue(geoFilter, JSON_KEY_GEO_FILTER_UNITS);
452            String unitsParam = parseOptionalStringValue(geoFilter, JSON_KEY_GEO_FILTER_UNITS_PARAM);
453            return new CmsSearchConfigurationGeoFilter(
454                coordinates,
455                coordinatesParam,
456                fieldName,
457                radius,
458                radiusParam,
459                units,
460                unitsParam);
461        } catch (JSONException e) {
462            if (null == m_baseConfig) {
463                if (LOG.isInfoEnabled()) {
464                    LOG.info(Messages.get().getBundle().key(Messages.LOG_NO_GEOFILTER_CONFIG_0), e);
465                }
466                return null;
467            } else {
468                return m_baseConfig.getGeoFilterConfig();
469            }
470        }
471    }
472
473    /**
474     * @see org.opencms.jsp.search.config.parser.I_CmsSearchConfigurationParser#parseHighlighter()
475     */
476    @Override
477    public I_CmsSearchConfigurationHighlighting parseHighlighter() {
478
479        try {
480            JSONObject highlighter = m_configObject.getJSONObject(JSON_KEY_HIGHLIGHTER);
481            String field = highlighter.getString(JSON_KEY_HIGHLIGHTER_FIELD);
482            Integer snippets = parseOptionalIntValue(highlighter, JSON_KEY_HIGHLIGHTER_SNIPPETS);
483            Integer fragsize = parseOptionalIntValue(highlighter, JSON_KEY_HIGHLIGHTER_FRAGSIZE);
484            String alternateField = parseOptionalStringValue(highlighter, JSON_KEY_HIGHLIGHTER_ALTERNATE_FIELD);
485            Integer maxAlternateFieldLength = parseOptionalIntValue(
486                highlighter,
487                JSON_KEY_HIGHLIGHTER_MAX_LENGTH_ALTERNATE_FIELD);
488            String pre = parseOptionalStringValue(highlighter, JSON_KEY_HIGHLIGHTER_SIMPLE_PRE);
489            String post = parseOptionalStringValue(highlighter, JSON_KEY_HIGHLIGHTER_SIMPLE_POST);
490            String formatter = parseOptionalStringValue(highlighter, JSON_KEY_HIGHLIGHTER_FORMATTER);
491            String fragmenter = parseOptionalStringValue(highlighter, JSON_KEY_HIGHLIGHTER_FRAGMENTER);
492            Boolean useFastVectorHighlighting = parseOptionalBooleanValue(
493                highlighter,
494                JSON_KEY_HIGHLIGHTER_FASTVECTORHIGHLIGHTING);
495            return new CmsSearchConfigurationHighlighting(
496                field,
497                snippets,
498                fragsize,
499                alternateField,
500                maxAlternateFieldLength,
501                pre,
502                post,
503                formatter,
504                fragmenter,
505                useFastVectorHighlighting);
506        } catch (JSONException e) {
507            if (null == m_baseConfig) {
508                if (LOG.isInfoEnabled()) {
509                    LOG.info(Messages.get().getBundle().key(Messages.LOG_NO_HIGHLIGHTING_CONFIG_0), e);
510                }
511                return null;
512            } else {
513                return m_baseConfig.getHighlighterConfig();
514            }
515        }
516    }
517
518    /**
519     * @see org.opencms.jsp.search.config.parser.I_CmsSearchConfigurationParser#parsePagination()
520     */
521    @Override
522    public I_CmsSearchConfigurationPagination parsePagination() {
523
524        return CmsSearchConfigurationPagination.create(getPageParam(), getPageSizes(), getPageNavLength());
525    }
526
527    /**
528     * @see org.opencms.jsp.search.config.parser.I_CmsSearchConfigurationParser#parseQueryFacet()
529     */
530    @Override
531    public I_CmsSearchConfigurationFacetQuery parseQueryFacet() {
532
533        try {
534            JSONObject queryFacetObject = m_configObject.getJSONObject(JSON_KEY_QUERY_FACET);
535            try {
536                List<I_CmsFacetQueryItem> queries = parseFacetQueryItems(queryFacetObject);
537                String label = parseOptionalStringValue(queryFacetObject, JSON_KEY_FACET_LABEL);
538                Boolean isAndFacet = parseOptionalBooleanValue(queryFacetObject, JSON_KEY_FACET_ISANDFACET);
539                List<String> preselection = parseOptionalStringValues(queryFacetObject, JSON_KEY_FACET_PRESELECTION);
540                Boolean ignoreAllFacetFilters = parseOptionalBooleanValue(
541                    queryFacetObject,
542                    JSON_KEY_FACET_IGNOREALLFACETFILTERS);
543                List<String> excludeTags = parseOptionalStringValues(queryFacetObject, JSON_KEY_FACET_EXCLUDETAGS);
544                return new CmsSearchConfigurationFacetQuery(
545                    queries,
546                    label,
547                    isAndFacet,
548                    preselection,
549                    ignoreAllFacetFilters,
550                    excludeTags);
551            } catch (JSONException e) {
552                LOG.error(
553                    Messages.get().getBundle().key(
554                        Messages.ERR_QUERY_FACET_MANDATORY_KEY_MISSING_1,
555                        JSON_KEY_QUERY_FACET_QUERY),
556                    e);
557                return null;
558            }
559        } catch (JSONException e) {
560            // nothing to do, configuration is optional
561            return null != m_baseConfig ? m_baseConfig.getQueryFacetConfig() : null;
562        }
563    }
564
565    /**
566     * @see org.opencms.jsp.search.config.parser.I_CmsSearchConfigurationParser#parseRangeFacets()
567     */
568    public Map<String, I_CmsSearchConfigurationFacetRange> parseRangeFacets() {
569
570        Map<String, I_CmsSearchConfigurationFacetRange> facetConfigs = new LinkedHashMap<String, I_CmsSearchConfigurationFacetRange>();
571        try {
572            JSONArray rangeFacets = m_configObject.getJSONArray(JSON_KEY_RANGE_FACETS);
573            for (int i = 0; i < rangeFacets.length(); i++) {
574
575                I_CmsSearchConfigurationFacetRange config = parseRangeFacet(rangeFacets.getJSONObject(i));
576                if (config != null) {
577                    facetConfigs.put(config.getName(), config);
578                }
579            }
580        } catch (JSONException e) {
581            if (null == m_baseConfig) {
582                if (LOG.isInfoEnabled()) {
583                    LOG.info(Messages.get().getBundle().key(Messages.LOG_NO_FACET_CONFIG_0), e);
584                }
585            } else {
586                facetConfigs = m_baseConfig.getRangeFacetConfigs();
587            }
588        }
589        return facetConfigs;
590
591    }
592
593    /**
594     * @see org.opencms.jsp.search.config.parser.I_CmsSearchConfigurationParser#parseSorting()
595     */
596    @Override
597    public I_CmsSearchConfigurationSorting parseSorting() {
598
599        List<I_CmsSearchConfigurationSortOption> options = getSortOptions();
600        String defaultOptionParamValue = parseOptionalStringValue(m_configObject, JSON_KEY_DEFAULT_SORT_OPTION);
601        I_CmsSearchConfigurationSortOption defaultSortOption = null;
602        if (null != defaultOptionParamValue) {
603            Iterator<I_CmsSearchConfigurationSortOption> optIterator = options.iterator();
604            while ((null == defaultSortOption) && optIterator.hasNext()) {
605                I_CmsSearchConfigurationSortOption opt = optIterator.next();
606                if (Objects.equals(opt.getParamValue(), defaultOptionParamValue)) {
607                    defaultSortOption = opt;
608                }
609            }
610        }
611        if ((null == defaultSortOption) && !options.isEmpty()) {
612            defaultSortOption = options.get(0);
613        }
614        return CmsSearchConfigurationSorting.create(getSortParam(), options, defaultSortOption);
615    }
616
617    /** Returns a map with additional request parameters, mapping the parameter names to Solr query parts.
618     * @return A map with additional request parameters, mapping the parameter names to Solr query parts.
619     */
620    protected Map<String, String> getAdditionalParameters() {
621
622        Map<String, String> result;
623        try {
624            JSONArray additionalParams = m_configObject.getJSONArray(JSON_KEY_ADDITIONAL_PARAMETERS);
625            result = new HashMap<String, String>(additionalParams.length());
626            for (int i = 0; i < additionalParams.length(); i++) {
627                try {
628                    JSONObject currentParam = additionalParams.getJSONObject(i);
629                    String param = currentParam.getString(JSON_KEY_ADDITIONAL_PARAMETERS_PARAM);
630                    String solrQuery = parseOptionalStringValue(currentParam, JSON_KEY_ADDITIONAL_PARAMETERS_SOLRQUERY);
631                    result.put(param, solrQuery);
632                } catch (JSONException e) {
633                    LOG.error(Messages.get().getBundle().key(Messages.ERR_ADDITIONAL_PARAMETER_CONFIG_WRONG_0), e);
634                    continue;
635                }
636            }
637        } catch (JSONException e) {
638            LOG.info(Messages.get().getBundle().key(Messages.LOG_ADDITIONAL_PARAMETER_CONFIG_NOT_PARSED_0), e);
639            return null != m_baseConfig
640            ? m_baseConfig.getGeneralConfig().getAdditionalParameters()
641            : new HashMap<String, String>();
642        }
643        return result;
644    }
645
646    /** Returns the configured Solr core, or <code>null</code> if no core is configured.
647     * @return The configured Solr core, or <code>null</code> if no core is configured.
648     */
649    protected String getCore() {
650
651        try {
652            return m_configObject.getString(JSON_KEY_CORE);
653        } catch (JSONException e) {
654            if (null == m_baseConfig) {
655                if (LOG.isInfoEnabled()) {
656                    LOG.info(Messages.get().getBundle().key(Messages.LOG_NO_CORE_SPECIFIED_0), e);
657                }
658                return null;
659            } else {
660                return m_baseConfig.getGeneralConfig().getSolrCore();
661            }
662        }
663    }
664
665    /**
666     * Returns the flag, indicating if the characters in the query string that are commands to Solr should be escaped.
667     * @return the flag, indicating if the characters in the query string that are commands to Solr should be escaped.
668     */
669    protected Boolean getEscapeQueryChars() {
670
671        Boolean isEscape = parseOptionalBooleanValue(m_configObject, JSON_KEY_ESCAPE_QUERY_CHARACTERS);
672        return (null == isEscape) && (m_baseConfig != null)
673        ? Boolean.valueOf(m_baseConfig.getGeneralConfig().getEscapeQueryChars())
674        : isEscape;
675    }
676
677    /** Returns the configured extra parameters that should be given to Solr, or the empty string if no parameters are configured.
678     * @return The configured extra parameters that should be given to Solr, or the empty string if no parameters are configured.
679     */
680    protected String getExtraSolrParams() {
681
682        try {
683            return m_configObject.getString(JSON_KEY_EXTRASOLRPARAMS);
684        } catch (JSONException e) {
685            if (null == m_baseConfig) {
686                if (LOG.isInfoEnabled()) {
687                    LOG.info(Messages.get().getBundle().key(Messages.LOG_NO_EXTRA_PARAMETERS_0), e);
688                }
689                return "";
690            } else {
691                return m_baseConfig.getGeneralConfig().getExtraSolrParams();
692            }
693        }
694    }
695
696    /** Returns the configured request parameter for the last query, or the default parameter if no core is configured.
697    * @return The configured request parameter for the last query, or the default parameter if no core is configured.
698    */
699    protected String getFirstCallParam() {
700
701        String param = parseOptionalStringValue(m_configObject, JSON_KEY_RELOADED_PARAM);
702        if (param == null) {
703            return null != m_baseConfig ? m_baseConfig.getGeneralConfig().getReloadedParam() : DEFAULT_RELOADED_PARAM;
704        } else {
705            return param;
706        }
707    }
708
709    /** Returns a flag indicating if also expired resources should be found.
710     * @return A flag indicating if also expired resources should be found.
711     */
712    protected Boolean getIgnoreExpirationDate() {
713
714        Boolean isIgnoreExpirationDate = parseOptionalBooleanValue(m_configObject, JSON_KEY_IGNORE_EXPIRATION_DATE);
715        return (null == isIgnoreExpirationDate) && (m_baseConfig != null)
716        ? Boolean.valueOf(m_baseConfig.getGeneralConfig().getIgnoreExpirationDate())
717        : isIgnoreExpirationDate;
718    }
719
720    /** Returns a flag indicating if the query given by the parameters should be ignored.
721     * @return A flag indicating if the query given by the parameters should be ignored.
722     */
723    protected Boolean getIgnoreQuery() {
724
725        Boolean isIgnoreQuery = parseOptionalBooleanValue(m_configObject, JSON_KEY_IGNORE_QUERY);
726        return (null == isIgnoreQuery) && (m_baseConfig != null)
727        ? Boolean.valueOf(m_baseConfig.getGeneralConfig().getIgnoreQueryParam())
728        : isIgnoreQuery;
729    }
730
731    /** Returns a flag indicating if also unreleased resources should be found.
732     * @return A flag indicating if also unreleased resources should be found.
733     */
734    protected Boolean getIgnoreReleaseDate() {
735
736        Boolean isIgnoreReleaseDate = parseOptionalBooleanValue(m_configObject, JSON_KEY_IGNORE_RELEASE_DATE);
737        return (null == isIgnoreReleaseDate) && (m_baseConfig != null)
738        ? Boolean.valueOf(m_baseConfig.getGeneralConfig().getIgnoreReleaseDate())
739        : isIgnoreReleaseDate;
740    }
741
742    /** Returns the configured Solr index, or <code>null</code> if no core is configured.
743     * @param cms the current context.
744     * @return The configured Solr index, or <code>null</code> if no core is configured.
745     */
746    protected String getIndex(CmsObject cms) {
747
748        String indexName = null;
749        try {
750            indexName = m_configObject.getString(JSON_KEY_INDEX);
751        } catch (JSONException e) {
752            if (null == m_baseConfig) {
753                if (LOG.isInfoEnabled()) {
754                    LOG.info(Messages.get().getBundle().key(Messages.LOG_NO_INDEX_SPECIFIED_0), e);
755                }
756            } else {
757                indexName = m_baseConfig.getGeneralConfig().getSolrIndex();
758            }
759        }
760        try {
761            if (null != OpenCms.getSearchManager().getIndexSolr(indexName)) {
762                return indexName;
763            }
764        } catch (Throwable t) {
765            if ((indexName != null) && LOG.isErrorEnabled()) {
766                LOG.error(
767                    Messages.get().getBundle().key(
768                        Messages.ERR_REQUESTED_INDEX_NOT_CONFIGURED_USING_DEFAULT_1,
769                        indexName));
770            }
771        }
772        return cms.getRequestContext().getCurrentProject().isOnlineProject()
773        ? CmsSolrIndex.DEFAULT_INDEX_NAME_ONLINE
774        : CmsSolrIndex.DEFAULT_INDEX_NAME_OFFLINE;
775    }
776
777    /** Returns the configured request parameter for the last query, or the default parameter if no core is configured.
778    * @return The configured request parameter for the last query, or the default parameter if no core is configured.
779    */
780    protected String getLastQueryParam() {
781
782        String param = parseOptionalStringValue(m_configObject, JSON_KEY_LAST_QUERYPARAM);
783        if (param == null) {
784            return null != m_baseConfig
785            ? m_baseConfig.getGeneralConfig().getLastQueryParam()
786            : DEFAULT_LAST_QUERY_PARAM;
787        } else {
788            return param;
789        }
790    }
791
792    /** Returns the number of maximally returned results, or <code>null</code> if the indexes default should be used.
793     * @param indexName the name of the index to search in.
794     * @return The number of maximally returned results, or <code>null</code> if the indexes default should be used.
795     */
796    protected int getMaxReturnedResults(String indexName) {
797
798        Integer maxReturnedResults = parseOptionalIntValue(m_configObject, JSON_KEY_MAX_RETURNED_RESULTS);
799        if (null != maxReturnedResults) {
800            return maxReturnedResults.intValue();
801        } else if (m_baseConfig != null) {
802            return m_baseConfig.getGeneralConfig().getMaxReturnedResults();
803        } else {
804            return OpenCms.getSearchManager().getIndexSolr(indexName).getMaxProcessedResults();
805        }
806    }
807
808    /** Returns the configured length of the "Google"-like page navigation, or the default parameter if no core is configured.
809     * @return The configured length of the "Google"-like page navigation, or the default parameter if no core is configured.
810     */
811    protected Integer getPageNavLength() {
812
813        return parseOptionalIntValue(m_configObject, JSON_KEY_PAGENAVLENGTH);
814    }
815
816    /** Returns the configured request parameter for the current page, or the default parameter if no core is configured.
817     * @return The configured request parameter for the current page, or the default parameter if no core is configured.
818     */
819    protected String getPageParam() {
820
821        return parseOptionalStringValue(m_configObject, JSON_KEY_PAGEPARAM);
822    }
823
824    /** Returns the configured page sizes, or the default page size if no core is configured.
825     * @return The configured page sizes, or the default page size if no core is configured.
826     */
827    protected List<Integer> getPageSizes() {
828
829        try {
830            return Collections.singletonList(Integer.valueOf(m_configObject.getInt(JSON_KEY_PAGESIZE)));
831        } catch (JSONException e) {
832            List<Integer> result = null;
833            String pageSizesString = null;
834            try {
835                pageSizesString = m_configObject.getString(JSON_KEY_PAGESIZE);
836                String[] pageSizesArray = pageSizesString.split("-");
837                if (pageSizesArray.length > 0) {
838                    result = new ArrayList<>(pageSizesArray.length);
839                    for (int i = 0; i < pageSizesArray.length; i++) {
840                        result.add(Integer.valueOf(pageSizesArray[i]));
841                    }
842                }
843                return result;
844            } catch (NumberFormatException | JSONException e1) {
845                LOG.warn(Messages.get().getBundle().key(Messages.LOG_PARSING_PAGE_SIZES_FAILED_1, pageSizesString), e);
846            }
847            if (null == m_baseConfig) {
848                if (LOG.isInfoEnabled()) {
849                    LOG.info(Messages.get().getBundle().key(Messages.LOG_NO_PAGESIZE_SPECIFIED_0), e);
850                }
851                return null;
852            } else {
853                return m_baseConfig.getPaginationConfig().getPageSizes();
854            }
855        }
856    }
857
858    /** Returns the optional query modifier.
859     * @return the optional query modifier.
860     */
861    protected String getQueryModifier() {
862
863        String queryModifier = parseOptionalStringValue(m_configObject, JSON_KEY_QUERY_MODIFIER);
864        return (null == queryModifier) && (null != m_baseConfig)
865        ? m_baseConfig.getGeneralConfig().getQueryModifier()
866        : queryModifier;
867    }
868
869    /** Returns the configured request parameter for the query string, or the default parameter if no core is configured.
870     * @return The configured request parameter for the query string, or the default parameter if no core is configured.
871     */
872    protected String getQueryParam() {
873
874        String param = parseOptionalStringValue(m_configObject, JSON_KEY_QUERYPARAM);
875        if (param == null) {
876            return null != m_baseConfig ? m_baseConfig.getGeneralConfig().getQueryParam() : DEFAULT_QUERY_PARAM;
877        } else {
878            return param;
879        }
880    }
881
882    /** Returns a flag, indicating if search should be performed using a wildcard if the empty query is given.
883     * @return A flag, indicating if search should be performed using a wildcard if the empty query is given.
884     */
885    protected Boolean getSearchForEmptyQuery() {
886
887        Boolean isSearchForEmptyQuery = parseOptionalBooleanValue(m_configObject, JSON_KEY_SEARCH_FOR_EMPTY_QUERY);
888        return (isSearchForEmptyQuery == null) && (null != m_baseConfig)
889        ? Boolean.valueOf(m_baseConfig.getGeneralConfig().getSearchForEmptyQueryParam())
890        : isSearchForEmptyQuery;
891    }
892
893    /** Returns the list of the configured sort options, or the empty list if no sort options are configured.
894     * @return The list of the configured sort options, or the empty list if no sort options are configured.
895     */
896    protected List<I_CmsSearchConfigurationSortOption> getSortOptions() {
897
898        List<I_CmsSearchConfigurationSortOption> options = new LinkedList<I_CmsSearchConfigurationSortOption>();
899        try {
900            JSONArray sortOptions = m_configObject.getJSONArray(JSON_KEY_SORTOPTIONS);
901            for (int i = 0; i < sortOptions.length(); i++) {
902                I_CmsSearchConfigurationSortOption option = parseSortOption(sortOptions.getJSONObject(i));
903                if (option != null) {
904                    options.add(option);
905                }
906            }
907        } catch (JSONException e) {
908            if (null == m_baseConfig) {
909                if (LOG.isInfoEnabled()) {
910                    LOG.info(Messages.get().getBundle().key(Messages.LOG_NO_SORT_CONFIG_0), e);
911                }
912            } else {
913                options = m_baseConfig.getSortConfig().getSortOptions();
914            }
915        }
916        return options;
917    }
918
919    /** Returns the configured request parameter for the sort option, or the default parameter if no core is configured.
920     * @return The configured request parameter for the sort option, or the default parameter if no core is configured.
921     */
922    protected String getSortParam() {
923
924        return parseOptionalStringValue(m_configObject, JSON_KEY_SORTPARAM);
925    }
926
927    /** Initialization that parses the String to a JSON object.
928     * @param configString The JSON as string.
929     * @param baseConfig The optional basic search configuration to overwrite (partly) by the JSON configuration.
930     * @throws JSONException thrown if parsing fails.
931     */
932    protected void init(String configString, I_CmsSearchConfiguration baseConfig) throws JSONException {
933
934        m_configObject = new JSONObject(configString);
935        m_baseConfig = baseConfig;
936    }
937
938    /** Parses a single query item for the query facet.
939     * @param item JSON object of the query item.
940     * @return the parsed query item, or <code>null</code> if parsing failed.
941     */
942    protected I_CmsFacetQueryItem parseFacetQueryItem(JSONObject item) {
943
944        String query;
945        try {
946            query = item.getString(JSON_KEY_QUERY_FACET_QUERY_QUERY);
947        } catch (JSONException e) {
948            // TODO: Log
949            return null;
950        }
951        String label = parseOptionalStringValue(item, JSON_KEY_QUERY_FACET_QUERY_LABEL);
952        return new CmsFacetQueryItem(query, label);
953    }
954
955    /** Parses the list of query items for the query facet.
956     * @param queryFacetObject JSON object representing the node with the query facet.
957     * @return list of query options
958     * @throws JSONException if the list cannot be parsed.
959     */
960    protected List<I_CmsFacetQueryItem> parseFacetQueryItems(JSONObject queryFacetObject) throws JSONException {
961
962        JSONArray items = queryFacetObject.getJSONArray(JSON_KEY_QUERY_FACET_QUERY);
963        List<I_CmsFacetQueryItem> result = new ArrayList<I_CmsFacetQueryItem>(items.length());
964        for (int i = 0; i < items.length(); i++) {
965            I_CmsFacetQueryItem item = parseFacetQueryItem(items.getJSONObject(i));
966            if (item != null) {
967                result.add(item);
968            }
969        }
970        return result;
971    }
972
973    /** Parses the field facet configurations.
974     * @param fieldFacetObject The JSON sub-node with the field facet configurations.
975     * @return The field facet configurations.
976     */
977    protected I_CmsSearchConfigurationFacetField parseFieldFacet(JSONObject fieldFacetObject) {
978
979        try {
980            String field = fieldFacetObject.getString(JSON_KEY_FACET_FIELD);
981            String name = parseOptionalStringValue(fieldFacetObject, JSON_KEY_FACET_NAME);
982            String label = parseOptionalStringValue(fieldFacetObject, JSON_KEY_FACET_LABEL);
983            Integer minCount = parseOptionalIntValue(fieldFacetObject, JSON_KEY_FACET_MINCOUNT);
984            Integer limit = parseOptionalIntValue(fieldFacetObject, JSON_KEY_FACET_LIMIT);
985            String prefix = parseOptionalStringValue(fieldFacetObject, JSON_KEY_FACET_PREFIX);
986            String sorder = parseOptionalStringValue(fieldFacetObject, JSON_KEY_FACET_ORDER);
987            I_CmsSearchConfigurationFacet.SortOrder order;
988            try {
989                order = I_CmsSearchConfigurationFacet.SortOrder.valueOf(sorder);
990            } catch (Exception e) {
991                order = null;
992            }
993            String filterQueryModifier = parseOptionalStringValue(fieldFacetObject, JSON_KEY_FACET_FILTERQUERYMODIFIER);
994            Boolean isAndFacet = parseOptionalBooleanValue(fieldFacetObject, JSON_KEY_FACET_ISANDFACET);
995            List<String> preselection = parseOptionalStringValues(fieldFacetObject, JSON_KEY_FACET_PRESELECTION);
996            Boolean ignoreFilterAllFacetFilters = parseOptionalBooleanValue(
997                fieldFacetObject,
998                JSON_KEY_FACET_IGNOREALLFACETFILTERS);
999            List<String> excludeTags = parseOptionalStringValues(fieldFacetObject, JSON_KEY_FACET_EXCLUDETAGS);
1000            return new CmsSearchConfigurationFacetField(
1001                field,
1002                name,
1003                minCount,
1004                limit,
1005                prefix,
1006                label,
1007                order,
1008                filterQueryModifier,
1009                isAndFacet,
1010                preselection,
1011                ignoreFilterAllFacetFilters,
1012                excludeTags);
1013        } catch (JSONException e) {
1014            LOG.error(
1015                Messages.get().getBundle().key(Messages.ERR_FIELD_FACET_MANDATORY_KEY_MISSING_1, JSON_KEY_FACET_FIELD),
1016                e);
1017            return null;
1018        }
1019    }
1020
1021    /** Parses the query facet configurations.
1022     * @param rangeFacetObject The JSON sub-node with the query facet configurations.
1023     * @return The query facet configurations.
1024     */
1025    protected I_CmsSearchConfigurationFacetRange parseRangeFacet(JSONObject rangeFacetObject) {
1026
1027        try {
1028            String range = rangeFacetObject.getString(JSON_KEY_RANGE_FACET_RANGE);
1029            String name = parseOptionalStringValue(rangeFacetObject, JSON_KEY_FACET_NAME);
1030            String label = parseOptionalStringValue(rangeFacetObject, JSON_KEY_FACET_LABEL);
1031            Integer minCount = parseOptionalIntValue(rangeFacetObject, JSON_KEY_FACET_MINCOUNT);
1032            String start = rangeFacetObject.getString(JSON_KEY_RANGE_FACET_START);
1033            String end = rangeFacetObject.getString(JSON_KEY_RANGE_FACET_END);
1034            String gap = rangeFacetObject.getString(JSON_KEY_RANGE_FACET_GAP);
1035            List<String> sother = parseOptionalStringValues(rangeFacetObject, JSON_KEY_RANGE_FACET_OTHER);
1036            Boolean hardEnd = parseOptionalBooleanValue(rangeFacetObject, JSON_KEY_RANGE_FACET_HARDEND);
1037            List<I_CmsSearchConfigurationFacetRange.Other> other = null;
1038            if (sother != null) {
1039                other = new ArrayList<I_CmsSearchConfigurationFacetRange.Other>(sother.size());
1040                for (String so : sother) {
1041                    try {
1042                        I_CmsSearchConfigurationFacetRange.Other o = I_CmsSearchConfigurationFacetRange.Other.valueOf(
1043                            so);
1044                        other.add(o);
1045                    } catch (Exception e) {
1046                        LOG.error(Messages.get().getBundle().key(Messages.ERR_INVALID_OTHER_OPTION_1, so), e);
1047                    }
1048                }
1049            }
1050            Boolean isAndFacet = parseOptionalBooleanValue(rangeFacetObject, JSON_KEY_FACET_ISANDFACET);
1051            List<String> preselection = parseOptionalStringValues(rangeFacetObject, JSON_KEY_FACET_PRESELECTION);
1052            Boolean ignoreAllFacetFilters = parseOptionalBooleanValue(
1053                rangeFacetObject,
1054                JSON_KEY_FACET_IGNOREALLFACETFILTERS);
1055            List<String> excludeTags = parseOptionalStringValues(rangeFacetObject, JSON_KEY_FACET_EXCLUDETAGS);
1056            return new CmsSearchConfigurationFacetRange(
1057                range,
1058                start,
1059                end,
1060                gap,
1061                other,
1062                hardEnd,
1063                name,
1064                minCount,
1065                label,
1066                isAndFacet,
1067                preselection,
1068                ignoreAllFacetFilters,
1069                excludeTags);
1070        } catch (JSONException e) {
1071            LOG.error(
1072                Messages.get().getBundle().key(
1073                    Messages.ERR_RANGE_FACET_MANDATORY_KEY_MISSING_1,
1074                    JSON_KEY_RANGE_FACET_RANGE
1075                        + ", "
1076                        + JSON_KEY_RANGE_FACET_START
1077                        + ", "
1078                        + JSON_KEY_RANGE_FACET_END
1079                        + ", "
1080                        + JSON_KEY_RANGE_FACET_GAP),
1081                e);
1082            return null;
1083        }
1084
1085    }
1086
1087    /** Returns a single sort option configuration as configured via the methods parameter, or null if the parameter does not specify a sort option.
1088     * @param json The JSON sort option configuration.
1089     * @return The sort option configuration, or null if the JSON could not be read.
1090     */
1091    protected I_CmsSearchConfigurationSortOption parseSortOption(JSONObject json) {
1092
1093        try {
1094            String solrValue = json.getString(JSON_KEY_SORTOPTION_SOLRVALUE);
1095            String paramValue = parseOptionalStringValue(json, JSON_KEY_SORTOPTION_PARAMVALUE);
1096            paramValue = (paramValue == null) ? solrValue : paramValue;
1097            String label = parseOptionalStringValue(json, JSON_KEY_SORTOPTION_LABEL);
1098            label = (label == null) ? paramValue : label;
1099            return new CmsSearchConfigurationSortOption(label, paramValue, solrValue);
1100        } catch (JSONException e) {
1101            LOG.error(
1102                Messages.get().getBundle().key(Messages.ERR_SORT_OPTION_NOT_PARSABLE_1, JSON_KEY_SORTOPTION_SOLRVALUE),
1103                e);
1104            return null;
1105        }
1106    }
1107}