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