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.jsp.search.config.CmsSearchConfigurationCommon;
031import org.opencms.jsp.search.config.CmsSearchConfigurationDidYouMean;
032import org.opencms.jsp.search.config.CmsSearchConfigurationFacetField;
033import org.opencms.jsp.search.config.CmsSearchConfigurationFacetQuery;
034import org.opencms.jsp.search.config.CmsSearchConfigurationFacetQuery.CmsFacetQueryItem;
035import org.opencms.jsp.search.config.CmsSearchConfigurationFacetRange;
036import org.opencms.jsp.search.config.CmsSearchConfigurationHighlighting;
037import org.opencms.jsp.search.config.CmsSearchConfigurationPagination;
038import org.opencms.jsp.search.config.CmsSearchConfigurationSortOption;
039import org.opencms.jsp.search.config.CmsSearchConfigurationSorting;
040import org.opencms.jsp.search.config.I_CmsSearchConfigurationCommon;
041import org.opencms.jsp.search.config.I_CmsSearchConfigurationDidYouMean;
042import org.opencms.jsp.search.config.I_CmsSearchConfigurationFacet;
043import org.opencms.jsp.search.config.I_CmsSearchConfigurationFacetField;
044import org.opencms.jsp.search.config.I_CmsSearchConfigurationFacetQuery;
045import org.opencms.jsp.search.config.I_CmsSearchConfigurationFacetQuery.I_CmsFacetQueryItem;
046import org.opencms.jsp.search.config.I_CmsSearchConfigurationFacetRange;
047import org.opencms.jsp.search.config.I_CmsSearchConfigurationHighlighting;
048import org.opencms.jsp.search.config.I_CmsSearchConfigurationPagination;
049import org.opencms.jsp.search.config.I_CmsSearchConfigurationSortOption;
050import org.opencms.jsp.search.config.I_CmsSearchConfigurationSorting;
051import org.opencms.main.CmsLog;
052import org.opencms.xml.content.CmsXmlContent;
053import org.opencms.xml.content.CmsXmlContentValueSequence;
054import org.opencms.xml.types.I_CmsXmlContentValue;
055
056import java.util.ArrayList;
057import java.util.Arrays;
058import java.util.HashMap;
059import java.util.LinkedHashMap;
060import java.util.List;
061import java.util.Locale;
062import java.util.Map;
063
064import org.apache.commons.logging.Log;
065
066/** Search configuration parser reading XML. */
067public class CmsXMLSearchConfigurationParser implements I_CmsSearchConfigurationParser {
068
069    /** Logger for the class. */
070    protected static final Log LOG = CmsLog.getLog(CmsXMLSearchConfigurationParser.class);
071
072    /** The element names of the xml content. */
073    /** Elements for common options. */
074    /** XML element name. */
075    private static final String XML_ELEMENT_QUERYPARAM = "QueryParam";
076    /** XML element name. */
077    private static final String XML_ELEMENT_LAST_QUERYPARAM = "LastQueryParam";
078    /** XML element name. */
079    private static final String XML_ELEMENT_ESCAPE_QUERY_CHARACTERS = "EscapeQueryCharacters";
080    /** XML element name. */
081    private static final String XML_ELEMENT_RELOADED_PARAM = "ReloadedParam";
082    /** XML element name. */
083    private static final String XML_ELEMENT_SEARCH_FOR_EMPTY_QUERY = "SearchForEmptyQuery";
084    /** XML element name. */
085    private static final String XML_ELEMENT_IGNORE_QUERY = "IgnoreQuery";
086    /** XML element name. */
087    private static final String XML_ELEMENT_IGNORE_RELEASE_DATE = "IgnoreReleaseDate";
088    /** XML element name. */
089    private static final String XML_ELEMENT_IGNORE_EXPIRATION_DATE = "IgnoreExpirationDate";
090    /** XML element name. */
091    private static final String XML_ELEMENT_QUERY_MODIFIER = "QueryModifier";
092    /** XML element name. */
093    private static final String XML_ELEMENT_PAGEPARAM = "PageParam";
094    /** XML element name. */
095    private static final String XML_ELEMENT_INDEX = "Index";
096    /** XML element name. */
097    private static final String XML_ELEMENT_CORE = "Core";
098    /** XML element name. */
099    private static final String XML_ELEMENT_EXTRASOLRPARAMS = "ExtraSolrParams";
100    /** XML element name. */
101    private static final String XML_ELEMENT_ADDITIONAL_PARAMETERS = "AdditionalRequestParams";
102    /** XML element name. */
103    private static final String XML_ELEMENT_ADDITIONAL_PARAMETERS_PARAM = "Param";
104    /** XML element name. */
105    private static final String XML_ELEMENT_ADDITIONAL_PARAMETERS_SOLRQUERY = "SolrQuery";
106    /** XML element name. */
107    private static final String XML_ELEMENT_PAGESIZE = "PageSize";
108    /** XML element name. */
109    private static final String XML_ELEMENT_PAGENAVLENGTH = "PageNavLength";
110
111    /** XML element names for facet configuration. */
112    /** XML element name for the root element of a field facet configuration. */
113    private static final String XML_ELEMENT_FIELD_FACETS = "FieldFacet";
114    /** XML element name for the root element of the query facet configuration. */
115    private static final String XML_ELEMENT_QUERY_FACET = "QueryFacet";
116    /** XML element names for facet options. */
117    /** XML element name. */
118    private static final String XML_ELEMENT_FACET_LIMIT = "Limit";
119    /** XML element name. */
120    private static final String XML_ELEMENT_FACET_MINCOUNT = "MinCount";
121    /** XML element name. */
122    private static final String XML_ELEMENT_FACET_LABEL = "Label";
123    /** XML element name. */
124    private static final String XML_ELEMENT_FACET_FIELD = "Field";
125    /** XML element name. */
126    private static final String XML_ELEMENT_FACET_NAME = "Name";
127    /** XML element name. */
128    private static final String XML_ELEMENT_FACET_PREFIX = "Prefix";
129    /** XML element name. */
130    private static final String XML_ELEMENT_FACET_ORDER = "Order";
131    /** XML element name. */
132    private static final String XML_ELEMENT_FACET_FILTERQUERYMODIFIER = "FilterQueryModifier";
133    /** XML element name. */
134    private static final String XML_ELEMENT_FACET_ISANDFACET = "IsAndFacet";
135    /** XML element name. */
136    private static final String XML_ELEMENT_FACET_PRESELECTION = "PreSelection";
137    /** XML element name. */
138    private static final String XML_ELEMENT_FACET_IGNOREALLFACETFILTERS = "IgnoreAllFacetFilters";
139    /** XML element name. */
140    private static final String XML_ELEMENT_QUERY_FACET_QUERY = "QueryItem";
141    /** XML element name. */
142    private static final String XML_ELEMENT_QUERY_FACET_QUERY_QUERY = "Query";
143    /** XML element name. */
144    private static final String XML_ELEMENT_QUERY_FACET_QUERY_LABEL = "Label";
145
146    /** XML element names for sort options. */
147    /** XML element name. */
148    private static final String XML_ELEMENT_SORTPARAM = "SortParam";
149    /** XML element name for the root element for sort options. */
150    private static final String XML_ELEMENT_SORTOPTIONS = "SortOption";
151    /** XML element names for a single search option. */
152    private static final String XML_ELEMENT_SORTOPTION_LABEL = "Label";
153    /** XML element name. */
154    private static final String XML_ELEMENT_SORTOPTION_PARAMVALUE = "ParamValue";
155    /** XML element name. */
156    private static final String XML_ELEMENT_SORTOPTION_SOLRVALUE = "SolrValue";
157    /** XML element name for the root element for the highlighting configuration. */
158    private static final String XML_ELEMENT_HIGHLIGHTER = "Highlighting";
159    /** XML elements for the highlighting configuration. */
160    /** XML element name. */
161    private static final String XML_ELEMENT_HIGHLIGHTER_FIELD = "Field";
162    /** XML element name. */
163    private static final String XML_ELEMENT_HIGHLIGHTER_SNIPPETS = "Snippets";
164    /** XML element name. */
165    private static final String XML_ELEMENT_HIGHLIGHTER_FRAGSIZE = "FragSize";
166    /** XML element name. */
167    private static final String XML_ELEMENT_HIGHLIGHTER_ALTERNATE_FIELD = "AlternateField";
168    /** XML element name. */
169    private static final String XML_ELEMENT_HIGHLIGHTER_MAX_LENGTH_ALTERNATE_FIELD = "MaxAlternateFieldLength";
170    /** XML element name. */
171    private static final String XML_ELEMENT_HIGHLIGHTER_SIMPLE_PRE = "SimplePre";
172    /** XML element name. */
173    private static final String XML_ELEMENT_HIGHLIGHTER_SIMPLE_POST = "SimplePost";
174    /** XML element name. */
175    private static final String XML_ELEMENT_HIGHLIGHTER_FORMATTER = "Formatter";
176    /** XML element name. */
177    private static final String XML_ELEMENT_HIGHLIGHTER_FRAGMENTER = "Fragmenter";
178    /** XML element name. */
179    private static final String XML_ELEMENT_HIGHLIGHTER_FASTVECTORHIGHLIGHTING = "UseFastVectorHighlighting";
180
181    /** XML element names for "Did you mean ...?". */
182    /** XML element name. */
183    private static final String XML_ELEMENT_DIDYOUMEAN = "DidYouMean";
184    /** XML elements for the "Did you mean ...?" configuration. */
185    /** XML element name. */
186    private static final String XML_ELEMENT_DIDYOUMEAN_QUERYPARAM = "QueryParam";
187    /** XML element name. */
188    private static final String XML_ELEMENT_DIDYOUMEAN_COLLATE = "Collate";
189    /** XML element name. */
190    private static final String XML_ELEMENT_DIDYOUMEAN_COUNT = "Count";
191    /** XML element name. */
192    private static final String XML_ELEMENT_RANGE_FACETS = "RangeFacet";
193    /** XML element name. */
194    private static final String XML_ELEMENT_RANGE_FACET_RANGE = "Range";
195    /** XML element name. */
196    private static final String XML_ELEMENT_RANGE_FACET_START = "Start";
197    /** XML element name. */
198    private static final String XML_ELEMENT_RANGE_FACET_END = "End";
199    /** XML element name. */
200    private static final String XML_ELEMENT_RANGE_FACET_GAP = "Gap";
201    /** XML element name. */
202    private static final String XML_ELEMENT_RANGE_FACET_OTHER = "Other";
203    /** XML element name. */
204    private static final String XML_ELEMENT_RANGE_FACET_HARDEND = "HardEnd";
205
206    /** Default value. */
207    private static final String DEFAULT_QUERY_PARAM = "q";
208    /** Default value. */
209    private static final String DEFAULT_LAST_QUERY_PARAM = "lq";
210    /** Default value. */
211    private static final String DEFAULT_RELOADED_PARAM = "reloaded";
212
213    /** The XML content that contains the configuration. */
214    CmsXmlContent m_xml;
215    /** The locale in which the configuration should be read. */
216    Locale m_locale;
217
218    /** Constructor taking the XML content that should be read and the locale in which it should be read.
219     * @param xml The XML content that should be read for the configuration.
220     * @param locale The locale in which the content should be read.
221     */
222    public CmsXMLSearchConfigurationParser(final CmsXmlContent xml, final Locale locale) {
223
224        m_xml = xml;
225        m_locale = locale;
226    }
227
228    /**
229     * @see org.opencms.jsp.search.config.parser.I_CmsSearchConfigurationParser#parseCommon()
230     */
231    @Override
232    public I_CmsSearchConfigurationCommon parseCommon() {
233
234        return new CmsSearchConfigurationCommon(
235            getQueryParam(),
236            getLastQueryParam(),
237            parseOptionalBooleanValue(XML_ELEMENT_ESCAPE_QUERY_CHARACTERS),
238            getFirstCallParam(),
239            getSearchForEmtpyQuery(),
240            getIgnoreQuery(),
241            getQueryModifier(),
242            getIndex(),
243            getCore(),
244            getExtraSolrParams(),
245            getAdditionalRequestParameters(),
246            getIgnoreReleaseDate(),
247            getIgnoreExpirationDate());
248    }
249
250    /**
251     * @see org.opencms.jsp.search.config.parser.I_CmsSearchConfigurationParser#parseDidYouMean()
252     */
253    public I_CmsSearchConfigurationDidYouMean parseDidYouMean() {
254
255        final I_CmsXmlContentValue didYouMean = m_xml.getValue(XML_ELEMENT_DIDYOUMEAN, m_locale);
256        if (didYouMean == null) {
257            return null;
258        } else {
259            final String pathPrefix = didYouMean.getPath() + "/";
260            String param = parseOptionalStringValue(pathPrefix + XML_ELEMENT_DIDYOUMEAN_QUERYPARAM);
261            if (null == param) {
262                param = getQueryParam();
263            }
264            Boolean collate = parseOptionalBooleanValue(pathPrefix + XML_ELEMENT_DIDYOUMEAN_COLLATE);
265            Integer count = parseOptionalIntValue(pathPrefix + XML_ELEMENT_DIDYOUMEAN_COUNT);
266            return new CmsSearchConfigurationDidYouMean(param, collate, count);
267        }
268    }
269
270    /**
271     * @see org.opencms.jsp.search.config.parser.I_CmsSearchConfigurationParser#parseFieldFacets()
272     */
273    @Override
274    public Map<String, I_CmsSearchConfigurationFacetField> parseFieldFacets() {
275
276        final Map<String, I_CmsSearchConfigurationFacetField> facetConfigs = new LinkedHashMap<String, I_CmsSearchConfigurationFacetField>();
277        final CmsXmlContentValueSequence fieldFacets = m_xml.getValueSequence(XML_ELEMENT_FIELD_FACETS, m_locale);
278        if (fieldFacets != null) {
279            for (int i = 0; i < fieldFacets.getElementCount(); i++) {
280
281                final I_CmsSearchConfigurationFacetField config = parseFieldFacet(
282                    fieldFacets.getValue(i).getPath() + "/");
283                if (config != null) {
284                    facetConfigs.put(config.getName(), config);
285                }
286            }
287        }
288        return facetConfigs;
289    }
290
291    /**
292     * @see org.opencms.jsp.search.config.parser.I_CmsSearchConfigurationParser#parseHighlighter()
293     */
294    @Override
295    public I_CmsSearchConfigurationHighlighting parseHighlighter() {
296
297        final I_CmsXmlContentValue highlighter = m_xml.getValue(XML_ELEMENT_HIGHLIGHTER, m_locale);
298        if (highlighter == null) {
299            return null;
300        } else {
301            try {
302                final String pathPrefix = highlighter.getPath() + "/";
303                final String field = parseMandatoryStringValue(pathPrefix + XML_ELEMENT_HIGHLIGHTER_FIELD);
304                final Integer snippets = parseOptionalIntValue(pathPrefix + XML_ELEMENT_HIGHLIGHTER_SNIPPETS);
305                final Integer fragsize = parseOptionalIntValue(pathPrefix + XML_ELEMENT_HIGHLIGHTER_FRAGSIZE);
306                final String alternateField = parseOptionalStringValue(
307                    pathPrefix + XML_ELEMENT_HIGHLIGHTER_ALTERNATE_FIELD);
308                final Integer maxAlternateFieldLength = parseOptionalIntValue(
309                    pathPrefix + XML_ELEMENT_HIGHLIGHTER_MAX_LENGTH_ALTERNATE_FIELD);
310                final String pre = parseOptionalStringValue(pathPrefix + XML_ELEMENT_HIGHLIGHTER_SIMPLE_PRE);
311                final String post = parseOptionalStringValue(pathPrefix + XML_ELEMENT_HIGHLIGHTER_SIMPLE_POST);
312                final String formatter = parseOptionalStringValue(pathPrefix + XML_ELEMENT_HIGHLIGHTER_FORMATTER);
313                final String fragmenter = parseOptionalStringValue(pathPrefix + XML_ELEMENT_HIGHLIGHTER_FRAGMENTER);
314                final Boolean useFastVectorHighlighting = parseOptionalBooleanValue(
315                    pathPrefix + XML_ELEMENT_HIGHLIGHTER_FASTVECTORHIGHLIGHTING);
316                return new CmsSearchConfigurationHighlighting(
317                    field,
318                    snippets,
319                    fragsize,
320                    alternateField,
321                    maxAlternateFieldLength,
322                    pre,
323                    post,
324                    formatter,
325                    fragmenter,
326                    useFastVectorHighlighting);
327            } catch (final Exception e) {
328                LOG.error(Messages.get().getBundle().key(Messages.ERR_MANDATORY_HIGHLIGHTING_FIELD_MISSING_0), e);
329                return null;
330            }
331        }
332    }
333
334    /**
335     * @see org.opencms.jsp.search.config.parser.I_CmsSearchConfigurationParser#parsePagination()
336     */
337    @Override
338    public I_CmsSearchConfigurationPagination parsePagination() {
339
340        return CmsSearchConfigurationPagination.create(getPageParam(), getPageSizes(), getPageNavLength());
341    }
342
343    /**
344     * @see org.opencms.jsp.search.config.parser.I_CmsSearchConfigurationParser#parseQueryFacet()
345     */
346    @Override
347    public I_CmsSearchConfigurationFacetQuery parseQueryFacet() {
348
349        final String pathPrefix = XML_ELEMENT_QUERY_FACET + "/";
350        try {
351            final List<I_CmsFacetQueryItem> queries = parseFacetQueryItems(pathPrefix + XML_ELEMENT_QUERY_FACET_QUERY);
352            final String label = parseOptionalStringValue(pathPrefix + XML_ELEMENT_FACET_LABEL);
353            final Boolean isAndFacet = parseOptionalBooleanValue(pathPrefix + XML_ELEMENT_FACET_ISANDFACET);
354            final List<String> preselection = parseOptionalStringValues(pathPrefix + XML_ELEMENT_FACET_PRESELECTION);
355            final Boolean ignoreAllFacetFilters = parseOptionalBooleanValue(
356                pathPrefix + XML_ELEMENT_FACET_IGNOREALLFACETFILTERS);
357            return new CmsSearchConfigurationFacetQuery(
358                queries,
359                label,
360                isAndFacet,
361                preselection,
362                ignoreAllFacetFilters);
363        } catch (final Exception e) {
364            LOG.error(
365                Messages.get().getBundle().key(
366                    Messages.ERR_QUERY_FACET_MANDATORY_KEY_MISSING_1,
367                    XML_ELEMENT_QUERY_FACET_QUERY),
368                e);
369            return null;
370        }
371    }
372
373    /**
374     * @see org.opencms.jsp.search.config.parser.I_CmsSearchConfigurationParser#parseRangeFacets()
375     */
376    public Map<String, I_CmsSearchConfigurationFacetRange> parseRangeFacets() {
377
378        final Map<String, I_CmsSearchConfigurationFacetRange> facetConfigs = new LinkedHashMap<String, I_CmsSearchConfigurationFacetRange>();
379        final CmsXmlContentValueSequence rangeFacets = m_xml.getValueSequence(XML_ELEMENT_RANGE_FACETS, m_locale);
380        if (rangeFacets != null) {
381            for (int i = 0; i < rangeFacets.getElementCount(); i++) {
382
383                final I_CmsSearchConfigurationFacetRange config = parseRangeFacet(
384                    rangeFacets.getValue(i).getPath() + "/");
385                if (config != null) {
386                    facetConfigs.put(config.getName(), config);
387                }
388            }
389        }
390        return facetConfigs;
391    }
392
393    /**
394     * @see org.opencms.jsp.search.config.parser.I_CmsSearchConfigurationParser#parseSorting()
395     */
396    @Override
397    public I_CmsSearchConfigurationSorting parseSorting() {
398
399        List<I_CmsSearchConfigurationSortOption> options = getSortOptions();
400        I_CmsSearchConfigurationSortOption defaultOption = (options != null) && !options.isEmpty()
401        ? options.get(0)
402        : null;
403        return CmsSearchConfigurationSorting.create(getSortParam(), options, defaultOption);
404    }
405
406    /** Helper to read a mandatory String value list.
407     * @param path The XML path of the element to read.
408     * @return The String list stored in the XML, or <code>null</code> if the value could not be read.
409     * @throws Exception thrown if the list of String values can not be read.
410     */
411    protected List<I_CmsFacetQueryItem> parseFacetQueryItems(final String path) throws Exception {
412
413        final List<I_CmsXmlContentValue> values = m_xml.getValues(path, m_locale);
414        if (values == null) {
415            return null;
416        } else {
417            List<I_CmsFacetQueryItem> parsedItems = new ArrayList<I_CmsFacetQueryItem>(values.size());
418            for (I_CmsXmlContentValue value : values) {
419                I_CmsFacetQueryItem item = parseFacetQueryItem(value.getPath() + "/");
420                if (null != item) {
421                    parsedItems.add(item);
422                } else {
423                    // TODO: log
424                }
425            }
426            return parsedItems;
427        }
428    }
429
430    /** Reads the configuration of a field facet.
431     * @param pathPrefix The XML Path that leads to the field facet configuration, or <code>null</code> if the XML was not correctly structured.
432     * @return The read configuration, or <code>null</code> if the XML was not correctly structured.
433     */
434    protected I_CmsSearchConfigurationFacetField parseFieldFacet(final String pathPrefix) {
435
436        try {
437            final String field = parseMandatoryStringValue(pathPrefix + XML_ELEMENT_FACET_FIELD);
438            final String name = parseOptionalStringValue(pathPrefix + XML_ELEMENT_FACET_NAME);
439            final String label = parseOptionalStringValue(pathPrefix + XML_ELEMENT_FACET_LABEL);
440            final Integer minCount = parseOptionalIntValue(pathPrefix + XML_ELEMENT_FACET_MINCOUNT);
441            final Integer limit = parseOptionalIntValue(pathPrefix + XML_ELEMENT_FACET_LIMIT);
442            final String prefix = parseOptionalStringValue(pathPrefix + XML_ELEMENT_FACET_PREFIX);
443            final String sorder = parseOptionalStringValue(pathPrefix + XML_ELEMENT_FACET_ORDER);
444            I_CmsSearchConfigurationFacet.SortOrder order;
445            try {
446                order = I_CmsSearchConfigurationFacet.SortOrder.valueOf(sorder);
447            } catch (@SuppressWarnings("unused") final Exception e) {
448                order = null;
449            }
450            final String filterQueryModifier = parseOptionalStringValue(
451                pathPrefix + XML_ELEMENT_FACET_FILTERQUERYMODIFIER);
452            final Boolean isAndFacet = parseOptionalBooleanValue(pathPrefix + XML_ELEMENT_FACET_ISANDFACET);
453            final List<String> preselection = parseOptionalStringValues(pathPrefix + XML_ELEMENT_FACET_PRESELECTION);
454            final Boolean ignoreAllFacetFilters = parseOptionalBooleanValue(
455                pathPrefix + XML_ELEMENT_FACET_IGNOREALLFACETFILTERS);
456            return new CmsSearchConfigurationFacetField(
457                field,
458                name,
459                minCount,
460                limit,
461                prefix,
462                label,
463                order,
464                filterQueryModifier,
465                isAndFacet,
466                preselection,
467                ignoreAllFacetFilters);
468        } catch (final Exception e) {
469            LOG.error(
470                Messages.get().getBundle().key(
471                    Messages.ERR_FIELD_FACET_MANDATORY_KEY_MISSING_1,
472                    XML_ELEMENT_FACET_FIELD),
473                e);
474            return null;
475        }
476    }
477
478    /** Helper to read an optional Boolean value.
479     * @param path The XML path of the element to read.
480     * @return The Boolean value stored in the XML, or <code>null</code> if the value could not be read.
481     */
482    protected Boolean parseOptionalBooleanValue(final String path) {
483
484        final I_CmsXmlContentValue value = m_xml.getValue(path, m_locale);
485        if (value == null) {
486            return null;
487        } else {
488            final String stringValue = value.getStringValue(null);
489            try {
490                final Boolean boolValue = Boolean.valueOf(stringValue);
491                return boolValue;
492            } catch (final NumberFormatException e) {
493                LOG.info(Messages.get().getBundle().key(Messages.LOG_OPTIONAL_BOOLEAN_MISSING_1, path), e);
494                return null;
495            }
496        }
497    }
498
499    /** Helper to read an optional Integer value.
500     * @param path The XML path of the element to read.
501     * @return The Integer value stored in the XML, or <code>null</code> if the value could not be read.
502     */
503    protected Integer parseOptionalIntValue(final String path) {
504
505        final I_CmsXmlContentValue value = m_xml.getValue(path, m_locale);
506        if (value == null) {
507            return null;
508        } else {
509            final String stringValue = value.getStringValue(null);
510            try {
511                final Integer intValue = Integer.valueOf(stringValue);
512                return intValue;
513            } catch (final NumberFormatException e) {
514                LOG.info(Messages.get().getBundle().key(Messages.LOG_OPTIONAL_INTEGER_MISSING_1, path), e);
515                return null;
516            }
517        }
518    }
519
520    /** Helper to read an optional String value.
521     * @param path The XML path of the element to read.
522     * @return The String value stored in the XML, or <code>null</code> if the value could not be read.
523     */
524    protected String parseOptionalStringValue(final String path) {
525
526        final I_CmsXmlContentValue value = m_xml.getValue(path, m_locale);
527        if (value == null) {
528            return null;
529        } else {
530            return value.getStringValue(null);
531        }
532    }
533
534    /** Helper to read an optional String value list.
535     * @param path The XML path of the element to read.
536     * @return The String list stored in the XML, or <code>null</code> if the value could not be read.
537     */
538    protected List<String> parseOptionalStringValues(final String path) {
539
540        final List<I_CmsXmlContentValue> values = m_xml.getValues(path, m_locale);
541        if (values == null) {
542            return null;
543        } else {
544            List<String> stringValues = new ArrayList<String>(values.size());
545            for (I_CmsXmlContentValue value : values) {
546                stringValues.add(value.getStringValue(null));
547            }
548            return stringValues;
549        }
550    }
551
552    /** Reads the configuration of a range facet.
553     * @param pathPrefix The XML Path that leads to the range facet configuration, or <code>null</code> if the XML was not correctly structured.
554     * @return The read configuration, or <code>null</code> if the XML was not correctly structured.
555     */
556    protected I_CmsSearchConfigurationFacetRange parseRangeFacet(String pathPrefix) {
557
558        try {
559            final String range = parseMandatoryStringValue(pathPrefix + XML_ELEMENT_RANGE_FACET_RANGE);
560            final String name = parseOptionalStringValue(pathPrefix + XML_ELEMENT_FACET_NAME);
561            final String label = parseOptionalStringValue(pathPrefix + XML_ELEMENT_FACET_LABEL);
562            final Integer minCount = parseOptionalIntValue(pathPrefix + XML_ELEMENT_FACET_MINCOUNT);
563            final String start = parseMandatoryStringValue(pathPrefix + XML_ELEMENT_RANGE_FACET_START);
564            final String end = parseMandatoryStringValue(pathPrefix + XML_ELEMENT_RANGE_FACET_END);
565            final String gap = parseMandatoryStringValue(pathPrefix + XML_ELEMENT_RANGE_FACET_GAP);
566            final String sother = parseOptionalStringValue(pathPrefix + XML_ELEMENT_RANGE_FACET_OTHER);
567            List<I_CmsSearchConfigurationFacetRange.Other> other = null;
568            if (sother != null) {
569                final List<String> sothers = Arrays.asList(sother.split(","));
570                other = new ArrayList<I_CmsSearchConfigurationFacetRange.Other>(sothers.size());
571                for (String so : sothers) {
572                    try {
573                        I_CmsSearchConfigurationFacetRange.Other o = I_CmsSearchConfigurationFacetRange.Other.valueOf(
574                            so);
575                        other.add(o);
576                    } catch (final Exception e) {
577                        LOG.error(Messages.get().getBundle().key(Messages.ERR_INVALID_OTHER_OPTION_1, so), e);
578                    }
579                }
580            }
581            final Boolean hardEnd = parseOptionalBooleanValue(pathPrefix + XML_ELEMENT_RANGE_FACET_HARDEND);
582            final Boolean isAndFacet = parseOptionalBooleanValue(pathPrefix + XML_ELEMENT_FACET_ISANDFACET);
583            final List<String> preselection = parseOptionalStringValues(pathPrefix + XML_ELEMENT_FACET_PRESELECTION);
584            final Boolean ignoreAllFacetFilters = parseOptionalBooleanValue(
585                pathPrefix + XML_ELEMENT_FACET_IGNOREALLFACETFILTERS);
586            return new CmsSearchConfigurationFacetRange(
587                range,
588                start,
589                end,
590                gap,
591                other,
592                hardEnd,
593                name,
594                minCount,
595                label,
596                isAndFacet,
597                preselection,
598                ignoreAllFacetFilters);
599        } catch (final Exception e) {
600            LOG.error(
601                Messages.get().getBundle().key(
602                    Messages.ERR_RANGE_FACET_MANDATORY_KEY_MISSING_1,
603                    XML_ELEMENT_RANGE_FACET_RANGE
604                        + ", "
605                        + XML_ELEMENT_RANGE_FACET_START
606                        + ", "
607                        + XML_ELEMENT_RANGE_FACET_END
608                        + ", "
609                        + XML_ELEMENT_RANGE_FACET_GAP),
610                e);
611            return null;
612        }
613
614    }
615
616    /** Returns a map with additional request parameters, mapping the parameter names to Solr query parts.
617     * @return A map with additional request parameters, mapping the parameter names to Solr query parts.
618     */
619    private Map<String, String> getAdditionalRequestParameters() {
620
621        List<I_CmsXmlContentValue> parametersToParse = m_xml.getValues(XML_ELEMENT_ADDITIONAL_PARAMETERS, m_locale);
622        Map<String, String> result = new HashMap<String, String>(parametersToParse.size());
623        for (I_CmsXmlContentValue additionalParam : parametersToParse) {
624            String param = m_xml.getValue(
625                additionalParam.getPath() + "/" + XML_ELEMENT_ADDITIONAL_PARAMETERS_PARAM,
626                m_locale).getStringValue(null);
627            String solrQuery = m_xml.hasValue(
628                additionalParam.getPath() + "/" + XML_ELEMENT_ADDITIONAL_PARAMETERS_SOLRQUERY,
629                m_locale)
630                ? m_xml.getValue(
631                    additionalParam.getPath() + "/" + XML_ELEMENT_ADDITIONAL_PARAMETERS_SOLRQUERY,
632                    m_locale).getStringValue(null)
633                : null;
634            result.put(param, solrQuery);
635        }
636        return result;
637    }
638
639    /** Returns the configured Solr core, or <code>null</code> if the core is not specified.
640     * @return The configured Solr core, or <code>null</code> if the core is not specified.
641     */
642    private String getCore() {
643
644        try {
645            return parseMandatoryStringValue(XML_ELEMENT_CORE);
646        } catch (final Exception e) {
647            LOG.info(Messages.get().getBundle().key(Messages.LOG_NO_CORE_SPECIFIED_0), e);
648            return null;
649        }
650    }
651
652    /** Returns the extra Solr parameters specified in the configuration, or the empty string if no extra parameters are configured.
653     * @return The extra Solr parameters specified in the configuration, or the empty string if no extra parameters are configured.
654     */
655    private String getExtraSolrParams() {
656
657        return parseOptionalStringValue(XML_ELEMENT_EXTRASOLRPARAMS);
658    }
659
660    /** Returns the configured request parameter for the last query, or the default parameter if the core is not specified.
661     * @return The configured request parameter for the last query, or the default parameter if the core is not specified.
662     */
663    private String getFirstCallParam() {
664
665        final String param = parseOptionalStringValue(XML_ELEMENT_RELOADED_PARAM);
666        if (param == null) {
667            return DEFAULT_RELOADED_PARAM;
668        } else {
669            return param;
670        }
671    }
672
673    /** Returns a flag indicating if also expired resources should be found.
674     * @return A flag indicating if also expired resources should be found.
675     */
676    private Boolean getIgnoreExpirationDate() {
677
678        return parseOptionalBooleanValue(XML_ELEMENT_IGNORE_EXPIRATION_DATE);
679    }
680
681    /** Returns a flag, indicating if the query and lastquery parameters should be ignored. E.g., if only the additional parameters should be used for the search.
682     * @return A flag, indicating if the query and lastquery parameters should be ignored.
683     */
684    private Boolean getIgnoreQuery() {
685
686        return parseOptionalBooleanValue(XML_ELEMENT_IGNORE_QUERY);
687    }
688
689    /** Returns a flag indicating if also unreleased resources should be found.
690     * @return A flag indicating if also unreleased resources should be found.
691     */
692    private Boolean getIgnoreReleaseDate() {
693
694        return parseOptionalBooleanValue(XML_ELEMENT_IGNORE_RELEASE_DATE);
695    }
696
697    /** Returns the configured Solr index, or <code>null</code> if the core is not specified.
698     * @return The configured Solr index, or <code>null</code> if the core is not specified.
699     */
700    private String getIndex() {
701
702        try {
703            return parseMandatoryStringValue(XML_ELEMENT_INDEX);
704        } catch (final Exception e) {
705            LOG.info(Messages.get().getBundle().key(Messages.LOG_NO_INDEX_SPECIFIED_0), e);
706            return null;
707        }
708    }
709
710    /** Returns the configured request parameter for the last query, or the default parameter if the core is not specified.
711     * @return The configured request parameter for the last query, or the default parameter if the core is not specified.
712     */
713    private String getLastQueryParam() {
714
715        final String param = parseOptionalStringValue(XML_ELEMENT_LAST_QUERYPARAM);
716        if (param == null) {
717            return DEFAULT_LAST_QUERY_PARAM;
718        } else {
719            return param;
720        }
721    }
722
723    /** Returns the configured length of the "Google"-like page navigation, or the default length if it is not configured.
724     * @return The configured length of the "Google"-like page navigation, or the default length if it is not configured.
725     */
726    private Integer getPageNavLength() {
727
728        return parseOptionalIntValue(XML_ELEMENT_PAGENAVLENGTH);
729    }
730
731    /** Returns the configured request parameter for the current page, or the default parameter if the core is not specified.
732     * @return The configured request parameter for the current page, or the default parameter if the core is not specified.
733     */
734    private String getPageParam() {
735
736        return parseOptionalStringValue(XML_ELEMENT_PAGEPARAM);
737    }
738
739    /** Returns the configured page size, or the default page size if it is not configured.
740     * @return The configured page size, or the default page size if it is not configured.
741     */
742    private List<Integer> getPageSizes() {
743
744        final String pageSizes = parseOptionalStringValue(XML_ELEMENT_PAGESIZE);
745        if (pageSizes != null) {
746            String[] pageSizesArray = pageSizes.split("-");
747            if (pageSizesArray.length > 0) {
748                try {
749                    List<Integer> result = new ArrayList<>(pageSizesArray.length);
750                    for (int i = 0; i < pageSizesArray.length; i++) {
751                        result.add(Integer.valueOf(pageSizesArray[i]));
752                    }
753                    return result;
754                } catch (NumberFormatException e) {
755                    LOG.warn(Messages.get().getBundle().key(Messages.LOG_PARSING_PAGE_SIZES_FAILED_1, pageSizes), e);
756                }
757            }
758        }
759        return null;
760    }
761
762    /** Returns the optional query modifier.
763     * @return the optional query modifier.
764     */
765    private String getQueryModifier() {
766
767        return parseOptionalStringValue(XML_ELEMENT_QUERY_MODIFIER);
768    }
769
770    /** Returns the configured request parameter for the current query string, or the default parameter if the core is not specified.
771     * @return The configured request parameter for the current query string, or the default parameter if the core is not specified.
772     */
773    private String getQueryParam() {
774
775        final String param = parseOptionalStringValue(XML_ELEMENT_QUERYPARAM);
776        if (param == null) {
777            return DEFAULT_QUERY_PARAM;
778        } else {
779            return param;
780        }
781    }
782
783    /** Returns a flag, indicating if search should be performed using a wildcard if the empty query is given.
784     * @return A flag, indicating if search should be performed using a wildcard if the empty query is given.
785     */
786    private Boolean getSearchForEmtpyQuery() {
787
788        return parseOptionalBooleanValue(XML_ELEMENT_SEARCH_FOR_EMPTY_QUERY);
789    }
790
791    /** Returns the configured sort options, or the empty list if no such options are configured.
792     * @return The configured sort options, or the empty list if no such options are configured.
793     */
794    private List<I_CmsSearchConfigurationSortOption> getSortOptions() {
795
796        final List<I_CmsSearchConfigurationSortOption> options = new ArrayList<I_CmsSearchConfigurationSortOption>();
797        final CmsXmlContentValueSequence sortOptions = m_xml.getValueSequence(XML_ELEMENT_SORTOPTIONS, m_locale);
798        if (sortOptions == null) {
799            return null;
800        } else {
801            for (int i = 0; i < sortOptions.getElementCount(); i++) {
802                final I_CmsSearchConfigurationSortOption option = parseSortOption(
803                    sortOptions.getValue(i).getPath() + "/");
804                if (option != null) {
805                    options.add(option);
806                }
807            }
808            return options;
809        }
810    }
811
812    /** Returns the configured request parameter for the current sort option, or the default parameter if the core is not specified.
813     * @return The configured request parameter for the current sort option, or the default parameter if the core is not specified.
814     */
815    private String getSortParam() {
816
817        return parseOptionalStringValue(XML_ELEMENT_SORTPARAM);
818    }
819
820    /** Parses a single query facet item with query and label.
821     * @param prefix path to the query facet item (with trailing '/').
822     * @return the query facet item.
823     */
824    private I_CmsFacetQueryItem parseFacetQueryItem(final String prefix) {
825
826        I_CmsXmlContentValue query = m_xml.getValue(prefix + XML_ELEMENT_QUERY_FACET_QUERY_QUERY, m_locale);
827        if (null != query) {
828            String queryString = query.getStringValue(null);
829            I_CmsXmlContentValue label = m_xml.getValue(prefix + XML_ELEMENT_QUERY_FACET_QUERY_LABEL, m_locale);
830            String labelString = null != label ? label.getStringValue(null) : null;
831            return new CmsFacetQueryItem(queryString, labelString);
832        } else {
833            return null;
834        }
835    }
836
837    /** Helper to read a mandatory String value.
838     * @param path The XML path of the element to read.
839     * @return The String value stored in the XML.
840     * @throws Exception thrown if the value could not be read.
841     */
842    private String parseMandatoryStringValue(final String path) throws Exception {
843
844        final String value = parseOptionalStringValue(path);
845        if (value == null) {
846            throw new Exception();
847        }
848        return value;
849    }
850
851    /** Returns the configuration of a single sort option, or <code>null</code> if the XML cannot be read.
852     * @param pathPrefix The XML path to the root node of the sort option's configuration.
853     * @return The configuration of a single sort option, or <code>null</code> if the XML cannot be read.
854     */
855    private I_CmsSearchConfigurationSortOption parseSortOption(final String pathPrefix) {
856
857        try {
858            final String solrValue = parseMandatoryStringValue(pathPrefix + XML_ELEMENT_SORTOPTION_SOLRVALUE);
859            String paramValue = parseOptionalStringValue(pathPrefix + XML_ELEMENT_SORTOPTION_PARAMVALUE);
860            paramValue = (paramValue == null) ? solrValue : paramValue;
861            String label = parseOptionalStringValue(pathPrefix + XML_ELEMENT_SORTOPTION_LABEL);
862            label = (label == null) ? paramValue : label;
863            return new CmsSearchConfigurationSortOption(label, paramValue, solrValue);
864        } catch (final Exception e) {
865            LOG.error(
866                Messages.get().getBundle().key(
867                    Messages.ERR_SORT_OPTION_NOT_PARSABLE_1,
868                    XML_ELEMENT_SORTOPTION_SOLRVALUE),
869                e);
870            return null;
871        }
872    }
873}