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