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