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