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        return null != indexName
761        ? indexName
762        : (cms.getRequestContext().getCurrentProject().isOnlineProject()
763        ? CmsSolrIndex.DEFAULT_INDEX_NAME_ONLINE
764        : CmsSolrIndex.DEFAULT_INDEX_NAME_OFFLINE);
765    }
766
767    /** Returns the configured request parameter for the last query, or the default parameter if no core is configured.
768    * @return The configured request parameter for the last query, or the default parameter if no core is configured.
769    */
770    protected String getLastQueryParam() {
771
772        String param = parseOptionalStringValue(m_configObject, JSON_KEY_LAST_QUERYPARAM);
773        if (param == null) {
774            return null != m_baseConfig
775            ? m_baseConfig.getGeneralConfig().getLastQueryParam()
776            : DEFAULT_LAST_QUERY_PARAM;
777        } else {
778            return param;
779        }
780    }
781
782    /** Returns the number of maximally returned results, or <code>null</code> if the indexes default should be used.
783     * @param indexName the name of the index to search in.
784     * @return The number of maximally returned results, or <code>null</code> if the indexes default should be used.
785     */
786    protected int getMaxReturnedResults(String indexName) {
787
788        Integer maxReturnedResults = parseOptionalIntValue(m_configObject, JSON_KEY_MAX_RETURNED_RESULTS);
789        if (null != maxReturnedResults) {
790            return maxReturnedResults.intValue();
791        } else if (m_baseConfig != null) {
792            return m_baseConfig.getGeneralConfig().getMaxReturnedResults();
793        } else {
794            try {
795                CmsSolrIndex idx = OpenCms.getSearchManager().getIndexSolr(indexName);
796                if (null != idx) {
797                    return idx.getMaxProcessedResults();
798                }
799            } catch (Throwable t) {
800                // This is ok, it's allowed to have an external other index here.
801                LOG.debug(
802                    "Parsing JSON search configuration for none-CmsSolrIndex "
803                        + indexName
804                        + ". Setting max processed results to unlimited.");
805            }
806            return CmsSolrIndex.MAX_RESULTS_UNLIMITED;
807        }
808    }
809
810    /** Returns the configured length of the "Google"-like page navigation, or the default parameter if no core is configured.
811     * @return The configured length of the "Google"-like page navigation, or the default parameter if no core is configured.
812     */
813    protected Integer getPageNavLength() {
814
815        return parseOptionalIntValue(m_configObject, JSON_KEY_PAGENAVLENGTH);
816    }
817
818    /** Returns the configured request parameter for the current page, or the default parameter if no core is configured.
819     * @return The configured request parameter for the current page, or the default parameter if no core is configured.
820     */
821    protected String getPageParam() {
822
823        return parseOptionalStringValue(m_configObject, JSON_KEY_PAGEPARAM);
824    }
825
826    /** Returns the configured page sizes, or the default page size if no core is configured.
827     * @return The configured page sizes, or the default page size if no core is configured.
828     */
829    protected List<Integer> getPageSizes() {
830
831        try {
832            return Collections.singletonList(Integer.valueOf(m_configObject.getInt(JSON_KEY_PAGESIZE)));
833        } catch (JSONException e) {
834            List<Integer> result = null;
835            String pageSizesString = null;
836            try {
837                pageSizesString = m_configObject.getString(JSON_KEY_PAGESIZE);
838                String[] pageSizesArray = pageSizesString.split("-");
839                if (pageSizesArray.length > 0) {
840                    result = new ArrayList<>(pageSizesArray.length);
841                    for (int i = 0; i < pageSizesArray.length; i++) {
842                        result.add(Integer.valueOf(pageSizesArray[i]));
843                    }
844                }
845                return result;
846            } catch (NumberFormatException | JSONException e1) {
847                LOG.warn(Messages.get().getBundle().key(Messages.LOG_PARSING_PAGE_SIZES_FAILED_1, pageSizesString), e);
848            }
849            if (null == m_baseConfig) {
850                if (LOG.isInfoEnabled()) {
851                    LOG.info(Messages.get().getBundle().key(Messages.LOG_NO_PAGESIZE_SPECIFIED_0), e);
852                }
853                return null;
854            } else {
855                return m_baseConfig.getPaginationConfig().getPageSizes();
856            }
857        }
858    }
859
860    /** Returns the optional query modifier.
861     * @return the optional query modifier.
862     */
863    protected String getQueryModifier() {
864
865        String queryModifier = parseOptionalStringValue(m_configObject, JSON_KEY_QUERY_MODIFIER);
866        return (null == queryModifier) && (null != m_baseConfig)
867        ? m_baseConfig.getGeneralConfig().getQueryModifier()
868        : queryModifier;
869    }
870
871    /** Returns the configured request parameter for the query string, or the default parameter if no core is configured.
872     * @return The configured request parameter for the query string, or the default parameter if no core is configured.
873     */
874    protected String getQueryParam() {
875
876        String param = parseOptionalStringValue(m_configObject, JSON_KEY_QUERYPARAM);
877        if (param == null) {
878            return null != m_baseConfig ? m_baseConfig.getGeneralConfig().getQueryParam() : DEFAULT_QUERY_PARAM;
879        } else {
880            return param;
881        }
882    }
883
884    /** Returns a flag, indicating if search should be performed using a wildcard if the empty query is given.
885     * @return A flag, indicating if search should be performed using a wildcard if the empty query is given.
886     */
887    protected Boolean getSearchForEmptyQuery() {
888
889        Boolean isSearchForEmptyQuery = parseOptionalBooleanValue(m_configObject, JSON_KEY_SEARCH_FOR_EMPTY_QUERY);
890        return (isSearchForEmptyQuery == null) && (null != m_baseConfig)
891        ? Boolean.valueOf(m_baseConfig.getGeneralConfig().getSearchForEmptyQueryParam())
892        : isSearchForEmptyQuery;
893    }
894
895    /** Returns the list of the configured sort options, or the empty list if no sort options are configured.
896     * @return The list of the configured sort options, or the empty list if no sort options are configured.
897     */
898    protected List<I_CmsSearchConfigurationSortOption> getSortOptions() {
899
900        List<I_CmsSearchConfigurationSortOption> options = new LinkedList<I_CmsSearchConfigurationSortOption>();
901        try {
902            JSONArray sortOptions = m_configObject.getJSONArray(JSON_KEY_SORTOPTIONS);
903            for (int i = 0; i < sortOptions.length(); i++) {
904                I_CmsSearchConfigurationSortOption option = parseSortOption(sortOptions.getJSONObject(i));
905                if (option != null) {
906                    options.add(option);
907                }
908            }
909        } catch (JSONException e) {
910            if (null == m_baseConfig) {
911                if (LOG.isInfoEnabled()) {
912                    LOG.info(Messages.get().getBundle().key(Messages.LOG_NO_SORT_CONFIG_0), e);
913                }
914            } else {
915                options = m_baseConfig.getSortConfig().getSortOptions();
916            }
917        }
918        return options;
919    }
920
921    /** Returns the configured request parameter for the sort option, or the default parameter if no core is configured.
922     * @return The configured request parameter for the sort option, or the default parameter if no core is configured.
923     */
924    protected String getSortParam() {
925
926        return parseOptionalStringValue(m_configObject, JSON_KEY_SORTPARAM);
927    }
928
929    /** Initialization that parses the String to a JSON object.
930     * @param configString The JSON as string.
931     * @param baseConfig The optional basic search configuration to overwrite (partly) by the JSON configuration.
932     * @throws JSONException thrown if parsing fails.
933     */
934    protected void init(String configString, I_CmsSearchConfiguration baseConfig) throws JSONException {
935
936        m_configObject = new JSONObject(configString);
937        m_baseConfig = baseConfig;
938    }
939
940    /** Parses a single query item for the query facet.
941     * @param item JSON object of the query item.
942     * @return the parsed query item, or <code>null</code> if parsing failed.
943     */
944    protected I_CmsFacetQueryItem parseFacetQueryItem(JSONObject item) {
945
946        String query;
947        try {
948            query = item.getString(JSON_KEY_QUERY_FACET_QUERY_QUERY);
949        } catch (JSONException e) {
950            // TODO: Log
951            return null;
952        }
953        String label = parseOptionalStringValue(item, JSON_KEY_QUERY_FACET_QUERY_LABEL);
954        return new CmsFacetQueryItem(query, label);
955    }
956
957    /** Parses the list of query items for the query facet.
958     * @param queryFacetObject JSON object representing the node with the query facet.
959     * @return list of query options
960     * @throws JSONException if the list cannot be parsed.
961     */
962    protected List<I_CmsFacetQueryItem> parseFacetQueryItems(JSONObject queryFacetObject) throws JSONException {
963
964        JSONArray items = queryFacetObject.getJSONArray(JSON_KEY_QUERY_FACET_QUERY);
965        List<I_CmsFacetQueryItem> result = new ArrayList<I_CmsFacetQueryItem>(items.length());
966        for (int i = 0; i < items.length(); i++) {
967            I_CmsFacetQueryItem item = parseFacetQueryItem(items.getJSONObject(i));
968            if (item != null) {
969                result.add(item);
970            }
971        }
972        return result;
973    }
974
975    /** Parses the field facet configurations.
976     * @param fieldFacetObject The JSON sub-node with the field facet configurations.
977     * @return The field facet configurations.
978     */
979    protected I_CmsSearchConfigurationFacetField parseFieldFacet(JSONObject fieldFacetObject) {
980
981        try {
982            String field = fieldFacetObject.getString(JSON_KEY_FACET_FIELD);
983            String name = parseOptionalStringValue(fieldFacetObject, JSON_KEY_FACET_NAME);
984            String label = parseOptionalStringValue(fieldFacetObject, JSON_KEY_FACET_LABEL);
985            Integer minCount = parseOptionalIntValue(fieldFacetObject, JSON_KEY_FACET_MINCOUNT);
986            Integer limit = parseOptionalIntValue(fieldFacetObject, JSON_KEY_FACET_LIMIT);
987            String prefix = parseOptionalStringValue(fieldFacetObject, JSON_KEY_FACET_PREFIX);
988            String sorder = parseOptionalStringValue(fieldFacetObject, JSON_KEY_FACET_ORDER);
989            I_CmsSearchConfigurationFacet.SortOrder order;
990            try {
991                order = I_CmsSearchConfigurationFacet.SortOrder.valueOf(sorder);
992            } catch (Exception e) {
993                order = null;
994            }
995            String filterQueryModifier = parseOptionalStringValue(fieldFacetObject, JSON_KEY_FACET_FILTERQUERYMODIFIER);
996            Boolean isAndFacet = parseOptionalBooleanValue(fieldFacetObject, JSON_KEY_FACET_ISANDFACET);
997            List<String> preselection = parseOptionalStringValues(fieldFacetObject, JSON_KEY_FACET_PRESELECTION);
998            Boolean ignoreFilterAllFacetFilters = parseOptionalBooleanValue(
999                fieldFacetObject,
1000                JSON_KEY_FACET_IGNOREALLFACETFILTERS);
1001            List<String> excludeTags = parseOptionalStringValues(fieldFacetObject, JSON_KEY_FACET_EXCLUDETAGS);
1002            return new CmsSearchConfigurationFacetField(
1003                field,
1004                name,
1005                minCount,
1006                limit,
1007                prefix,
1008                label,
1009                order,
1010                filterQueryModifier,
1011                isAndFacet,
1012                preselection,
1013                ignoreFilterAllFacetFilters,
1014                excludeTags);
1015        } catch (JSONException e) {
1016            LOG.error(
1017                Messages.get().getBundle().key(Messages.ERR_FIELD_FACET_MANDATORY_KEY_MISSING_1, JSON_KEY_FACET_FIELD),
1018                e);
1019            return null;
1020        }
1021    }
1022
1023    /** Parses the query facet configurations.
1024     * @param rangeFacetObject The JSON sub-node with the query facet configurations.
1025     * @return The query facet configurations.
1026     */
1027    protected I_CmsSearchConfigurationFacetRange parseRangeFacet(JSONObject rangeFacetObject) {
1028
1029        try {
1030            String range = rangeFacetObject.getString(JSON_KEY_RANGE_FACET_RANGE);
1031            String name = parseOptionalStringValue(rangeFacetObject, JSON_KEY_FACET_NAME);
1032            String label = parseOptionalStringValue(rangeFacetObject, JSON_KEY_FACET_LABEL);
1033            Integer minCount = parseOptionalIntValue(rangeFacetObject, JSON_KEY_FACET_MINCOUNT);
1034            String start = rangeFacetObject.getString(JSON_KEY_RANGE_FACET_START);
1035            String end = rangeFacetObject.getString(JSON_KEY_RANGE_FACET_END);
1036            String gap = rangeFacetObject.getString(JSON_KEY_RANGE_FACET_GAP);
1037            List<String> sother = parseOptionalStringValues(rangeFacetObject, JSON_KEY_RANGE_FACET_OTHER);
1038            Boolean hardEnd = parseOptionalBooleanValue(rangeFacetObject, JSON_KEY_RANGE_FACET_HARDEND);
1039            List<I_CmsSearchConfigurationFacetRange.Other> other = null;
1040            if (sother != null) {
1041                other = new ArrayList<I_CmsSearchConfigurationFacetRange.Other>(sother.size());
1042                for (String so : sother) {
1043                    try {
1044                        I_CmsSearchConfigurationFacetRange.Other o = I_CmsSearchConfigurationFacetRange.Other.valueOf(
1045                            so);
1046                        other.add(o);
1047                    } catch (Exception e) {
1048                        LOG.error(Messages.get().getBundle().key(Messages.ERR_INVALID_OTHER_OPTION_1, so), e);
1049                    }
1050                }
1051            }
1052            Boolean isAndFacet = parseOptionalBooleanValue(rangeFacetObject, JSON_KEY_FACET_ISANDFACET);
1053            List<String> preselection = parseOptionalStringValues(rangeFacetObject, JSON_KEY_FACET_PRESELECTION);
1054            Boolean ignoreAllFacetFilters = parseOptionalBooleanValue(
1055                rangeFacetObject,
1056                JSON_KEY_FACET_IGNOREALLFACETFILTERS);
1057            List<String> excludeTags = parseOptionalStringValues(rangeFacetObject, JSON_KEY_FACET_EXCLUDETAGS);
1058            return new CmsSearchConfigurationFacetRange(
1059                range,
1060                start,
1061                end,
1062                gap,
1063                other,
1064                hardEnd,
1065                name,
1066                minCount,
1067                label,
1068                isAndFacet,
1069                preselection,
1070                ignoreAllFacetFilters,
1071                excludeTags);
1072        } catch (JSONException e) {
1073            LOG.error(
1074                Messages.get().getBundle().key(
1075                    Messages.ERR_RANGE_FACET_MANDATORY_KEY_MISSING_1,
1076                    JSON_KEY_RANGE_FACET_RANGE
1077                        + ", "
1078                        + JSON_KEY_RANGE_FACET_START
1079                        + ", "
1080                        + JSON_KEY_RANGE_FACET_END
1081                        + ", "
1082                        + JSON_KEY_RANGE_FACET_GAP),
1083                e);
1084            return null;
1085        }
1086
1087    }
1088
1089    /** Returns a single sort option configuration as configured via the methods parameter, or null if the parameter does not specify a sort option.
1090     * @param json The JSON sort option configuration.
1091     * @return The sort option configuration, or null if the JSON could not be read.
1092     */
1093    protected I_CmsSearchConfigurationSortOption parseSortOption(JSONObject json) {
1094
1095        try {
1096            String solrValue = json.getString(JSON_KEY_SORTOPTION_SOLRVALUE);
1097            String paramValue = parseOptionalStringValue(json, JSON_KEY_SORTOPTION_PARAMVALUE);
1098            paramValue = (paramValue == null) ? solrValue : paramValue;
1099            String label = parseOptionalStringValue(json, JSON_KEY_SORTOPTION_LABEL);
1100            label = (label == null) ? paramValue : label;
1101            return new CmsSearchConfigurationSortOption(label, paramValue, solrValue);
1102        } catch (JSONException e) {
1103            LOG.error(
1104                Messages.get().getBundle().key(Messages.ERR_SORT_OPTION_NOT_PARSABLE_1, JSON_KEY_SORTOPTION_SOLRVALUE),
1105                e);
1106            return null;
1107        }
1108    }
1109}