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