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