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.json.JSONArray; 031import org.opencms.json.JSONException; 032import org.opencms.json.JSONObject; 033import org.opencms.jsp.search.config.CmsSearchConfigurationCommon; 034import org.opencms.jsp.search.config.CmsSearchConfigurationDidYouMean; 035import org.opencms.jsp.search.config.CmsSearchConfigurationFacetField; 036import org.opencms.jsp.search.config.CmsSearchConfigurationFacetQuery; 037import org.opencms.jsp.search.config.CmsSearchConfigurationFacetQuery.CmsFacetQueryItem; 038import org.opencms.jsp.search.config.CmsSearchConfigurationFacetRange; 039import org.opencms.jsp.search.config.CmsSearchConfigurationHighlighting; 040import org.opencms.jsp.search.config.CmsSearchConfigurationPagination; 041import org.opencms.jsp.search.config.CmsSearchConfigurationSortOption; 042import org.opencms.jsp.search.config.CmsSearchConfigurationSorting; 043import org.opencms.jsp.search.config.I_CmsSearchConfiguration; 044import org.opencms.jsp.search.config.I_CmsSearchConfigurationCommon; 045import org.opencms.jsp.search.config.I_CmsSearchConfigurationDidYouMean; 046import org.opencms.jsp.search.config.I_CmsSearchConfigurationFacet; 047import org.opencms.jsp.search.config.I_CmsSearchConfigurationFacetField; 048import org.opencms.jsp.search.config.I_CmsSearchConfigurationFacetQuery; 049import org.opencms.jsp.search.config.I_CmsSearchConfigurationFacetQuery.I_CmsFacetQueryItem; 050import org.opencms.jsp.search.config.I_CmsSearchConfigurationFacetRange; 051import org.opencms.jsp.search.config.I_CmsSearchConfigurationHighlighting; 052import org.opencms.jsp.search.config.I_CmsSearchConfigurationPagination; 053import org.opencms.jsp.search.config.I_CmsSearchConfigurationSortOption; 054import org.opencms.jsp.search.config.I_CmsSearchConfigurationSorting; 055import org.opencms.main.CmsLog; 056 057import java.util.ArrayList; 058import java.util.Collections; 059import java.util.HashMap; 060import java.util.LinkedHashMap; 061import java.util.LinkedList; 062import java.util.List; 063import java.util.Map; 064 065import org.apache.commons.logging.Log; 066 067/** Search configuration parser reading JSON. */ 068public class CmsJSONSearchConfigurationParser implements I_CmsSearchConfigurationParser { 069 070 /** Logger for the class. */ 071 protected static final Log LOG = CmsLog.getLog(CmsJSONSearchConfigurationParser.class); 072 073 /** The keys that can be used in the JSON object */ 074 /** JSON keys for common options. */ 075 /** A JSON key. */ 076 public static final String JSON_KEY_QUERYPARAM = "queryparam"; 077 /** A JSON key. */ 078 public static final String JSON_KEY_LAST_QUERYPARAM = "lastqueryparam"; 079 /** A JSON key. */ 080 public static final String JSON_KEY_ESCAPE_QUERY_CHARACTERS = "escapequerychars"; 081 /** A JSON key. */ 082 public static final String JSON_KEY_RELOADED_PARAM = "reloadedparam"; 083 /** A JSON key. */ 084 public static final String JSON_KEY_SEARCH_FOR_EMPTY_QUERY = "searchforemptyquery"; 085 /** A JSON key. */ 086 public static final String JSON_KEY_IGNORE_QUERY = "ignorequery"; 087 /** A JSON key. */ 088 public static final String JSON_KEY_IGNORE_RELEASE_DATE = "ignoreReleaseDate"; 089 /** A JSON key. */ 090 public static final String JSON_KEY_IGNORE_EXPIRATION_DATE = "ignoreExpirationDate"; 091 /** A JSON key. */ 092 public static final String JSON_KEY_QUERY_MODIFIER = "querymodifier"; 093 /** A JSON key. */ 094 public static final String JSON_KEY_PAGEPARAM = "pageparam"; 095 /** A JSON key. */ 096 public static final String JSON_KEY_INDEX = "index"; 097 /** A JSON key. */ 098 public static final String JSON_KEY_CORE = "core"; 099 /** A JSON key. */ 100 public static final String JSON_KEY_EXTRASOLRPARAMS = "extrasolrparams"; 101 /** A JSON key. */ 102 public static final String JSON_KEY_ADDITIONAL_PARAMETERS = "additionalrequestparams"; 103 /** A JSON key. */ 104 public static final String JSON_KEY_ADDITIONAL_PARAMETERS_PARAM = "param"; 105 /** A JSON key. */ 106 public static final String JSON_KEY_ADDITIONAL_PARAMETERS_SOLRQUERY = "solrquery"; 107 /** A JSON key. */ 108 public static final String JSON_KEY_PAGESIZE = "pagesize"; 109 /** A JSON key. */ 110 public static final String JSON_KEY_PAGENAVLENGTH = "pagenavlength"; 111 /** JSON keys for facet configuration. */ 112 /** The JSON key for the sub-node with all field facet configurations. */ 113 public static final String JSON_KEY_FIELD_FACETS = "fieldfacets"; 114 /** The JSON key for the sub-node with all field facet configurations. */ 115 public static final String JSON_KEY_RANGE_FACETS = "rangefacets"; 116 /** The JSON key for the sub-node with the query facet configuration. */ 117 public static final String JSON_KEY_QUERY_FACET = "queryfacet"; 118 /** JSON keys for a single facet. */ 119 /** A JSON key. */ 120 public static final String JSON_KEY_FACET_LIMIT = "limit"; 121 /** A JSON key. */ 122 public static final String JSON_KEY_FACET_MINCOUNT = "mincount"; 123 /** A JSON key. */ 124 public static final String JSON_KEY_FACET_LABEL = "label"; 125 /** A JSON key. */ 126 public static final String JSON_KEY_FACET_FIELD = "field"; 127 /** A JSON key. */ 128 public static final String JSON_KEY_FACET_NAME = "name"; 129 /** A JSON key. */ 130 public static final String JSON_KEY_FACET_PREFIX = "prefix"; 131 /** A JSON key. */ 132 public static final String JSON_KEY_FACET_ORDER = "order"; 133 /** A JSON key. */ 134 public static final String JSON_KEY_FACET_FILTERQUERYMODIFIER = "filterquerymodifier"; 135 /** A JSON key. */ 136 public static final String JSON_KEY_FACET_ISANDFACET = "isAndFacet"; 137 /** A JSON key. */ 138 public static final String JSON_KEY_FACET_IGNOREALLFACETFILTERS = "ignoreAllFacetFilters"; 139 /** A JSON key. */ 140 public static final String JSON_KEY_FACET_PRESELECTION = "preselection"; 141 /** A JSON key. */ 142 public static final String JSON_KEY_RANGE_FACET_RANGE = "range"; 143 /** A JSON key. */ 144 public static final String JSON_KEY_RANGE_FACET_START = "start"; 145 /** A JSON key. */ 146 public static final String JSON_KEY_RANGE_FACET_END = "end"; 147 /** A JSON key. */ 148 public static final String JSON_KEY_RANGE_FACET_GAP = "gap"; 149 /** A JSON key. */ 150 public static final String JSON_KEY_RANGE_FACET_OTHER = "other"; 151 /** A JSON key. */ 152 public static final String JSON_KEY_RANGE_FACET_HARDEND = "hardend"; 153 /** A JSON key. */ 154 public static final String JSON_KEY_QUERY_FACET_QUERY = "queryitems"; 155 /** A JSON key. */ 156 public static final String JSON_KEY_QUERY_FACET_QUERY_QUERY = "query"; 157 /** A JSON key. */ 158 public static final String JSON_KEY_QUERY_FACET_QUERY_LABEL = "label"; 159 160 /** JSON keys for sort options. */ 161 /** A JSON key. */ 162 public static final String JSON_KEY_SORTPARAM = "sortby"; 163 /** The JSON key for the sub-node with all search option configurations. */ 164 public static final String JSON_KEY_SORTOPTIONS = "sortoptions"; 165 /** JSON keys for a single search option. */ 166 /** A JSON key. */ 167 public static final String JSON_KEY_SORTOPTION_LABEL = "label"; 168 /** A JSON key. */ 169 public static final String JSON_KEY_SORTOPTION_PARAMVALUE = "paramvalue"; 170 /** A JSON key. */ 171 public static final String JSON_KEY_SORTOPTION_SOLRVALUE = "solrvalue"; 172 /** JSON keys for the highlighting configuration. */ 173 /** The JSON key for the subnode of all highlighting configuration. */ 174 public static final String JSON_KEY_HIGHLIGHTER = "highlighter"; 175 /** A JSON key. */ 176 public static final String JSON_KEY_HIGHLIGHTER_FIELD = "field"; 177 /** A JSON key. */ 178 public static final String JSON_KEY_HIGHLIGHTER_SNIPPETS = "snippets"; 179 /** A JSON key. */ 180 public static final String JSON_KEY_HIGHLIGHTER_FRAGSIZE = "fragsize"; 181 /** A JSON key. */ 182 public static final String JSON_KEY_HIGHLIGHTER_ALTERNATE_FIELD = "alternateField"; 183 /** A JSON key. */ 184 public static final String JSON_KEY_HIGHLIGHTER_MAX_LENGTH_ALTERNATE_FIELD = "maxAlternateFieldLength"; 185 /** A JSON key. */ 186 public static final String JSON_KEY_HIGHLIGHTER_SIMPLE_PRE = "simple.pre"; 187 /** A JSON key. */ 188 public static final String JSON_KEY_HIGHLIGHTER_SIMPLE_POST = "simple.post"; 189 /** A JSON key. */ 190 public static final String JSON_KEY_HIGHLIGHTER_FORMATTER = "formatter"; 191 /** A JSON key. */ 192 public static final String JSON_KEY_HIGHLIGHTER_FRAGMENTER = "fragmenter"; 193 /** A JSON key. */ 194 public static final String JSON_KEY_HIGHLIGHTER_FASTVECTORHIGHLIGHTING = "useFastVectorHighlighting"; 195 196 /** JSON keys for "Did you mean ...?" */ 197 /** A JSON key. */ 198 public static final String JSON_KEY_DIDYOUMEAN = "didYouMean"; 199 /** The JSON key for the subnode of all "Did you mean?" configuration. */ 200 /** A JSON key. */ 201 public static final String JSON_KEY_DIDYOUMEAN_QUERYPARAM = "didYouMeanQueryParam"; 202 /** A JSON key. */ 203 public static final String JSON_KEY_DIDYOUMEAN_COLLATE = "didYouMeanCollate"; 204 /** A JSON key. */ 205 public static final String JSON_KEY_DIDYOUMEAN_COUNT = "didYouMeanCount"; 206 207 /** The default values. */ 208 /** A JSON key. */ 209 public static final String DEFAULT_QUERY_PARAM = "q"; 210 /** A JSON key. */ 211 public static final String DEFAULT_LAST_QUERY_PARAM = "lq"; 212 /** A JSON key. */ 213 public static final String DEFAULT_RELOADED_PARAM = "reloaded"; 214 215 /** The whole JSON file. */ 216 protected JSONObject m_configObject; 217 218 /** The optional base configuration that should be changed by the JSON configuration. */ 219 private I_CmsSearchConfiguration m_baseConfig; 220 221 /** Constructor taking the JSON as String. 222 * @param json The JSON that should be parsed as String. 223 * @throws JSONException Thrown if parsing fails. 224 */ 225 public CmsJSONSearchConfigurationParser(String json) 226 throws JSONException { 227 228 init(json, null); 229 } 230 231 /** Constructor taking the JSON as String. 232 * @param json The JSON that should be parsed as String. 233 * @param baseConfig A base configuration that is adjusted by the JSON configuration string. 234 * @throws JSONException Thrown if parsing fails. 235 */ 236 public CmsJSONSearchConfigurationParser(String json, I_CmsSearchConfiguration baseConfig) 237 throws JSONException { 238 239 init(json, baseConfig); 240 } 241 242 /** Helper for reading a mandatory String value list - throwing an Exception if parsing fails. 243 * @param json The JSON object where the list should be read from. 244 * @param key The key of the value to read. 245 * @return The value from the JSON. 246 * @throws JSONException thrown when parsing fails. 247 */ 248 protected static List<String> parseMandatoryStringValues(JSONObject json, String key) throws JSONException { 249 250 List<String> list = null; 251 JSONArray array = json.getJSONArray(key); 252 list = new ArrayList<String>(array.length()); 253 for (int i = 0; i < array.length(); i++) { 254 try { 255 String entry = array.getString(i); 256 list.add(entry); 257 } catch (JSONException e) { 258 LOG.info(Messages.get().getBundle().key(Messages.LOG_OPTIONAL_STRING_ENTRY_UNPARSABLE_1, key), e); 259 } 260 } 261 return list; 262 } 263 264 /** Helper for reading an optional Boolean value - returning <code>null</code> if parsing fails. 265 * @param json The JSON object where the value should be read from. 266 * @param key The key of the value to read. 267 * @return The value from the JSON, or <code>null</code> if the value does not exist, or is no Boolean. 268 */ 269 protected static Boolean parseOptionalBooleanValue(JSONObject json, String key) { 270 271 try { 272 return Boolean.valueOf(json.getBoolean(key)); 273 } catch (JSONException e) { 274 LOG.info(Messages.get().getBundle().key(Messages.LOG_OPTIONAL_BOOLEAN_MISSING_1, key), e); 275 return null; 276 } 277 } 278 279 /** Helper for reading an optional Integer value - returning <code>null</code> if parsing fails. 280 * @param json The JSON object where the value should be read from. 281 * @param key The key of the value to read. 282 * @return The value from the JSON, or <code>null</code> if the value does not exist, or is no Integer. 283 */ 284 protected static Integer parseOptionalIntValue(JSONObject json, String key) { 285 286 try { 287 return Integer.valueOf(json.getInt(key)); 288 } catch (JSONException e) { 289 LOG.info(Messages.get().getBundle().key(Messages.LOG_OPTIONAL_INTEGER_MISSING_1, key), e); 290 return null; 291 } 292 } 293 294 /** Helper for reading an optional String value - returning <code>null</code> if parsing fails. 295 * @param json The JSON object where the value should be read from. 296 * @param key The key of the value to read. 297 * @return The value from the JSON, or <code>null</code> if the value does not exist. 298 */ 299 protected static String parseOptionalStringValue(JSONObject json, String key) { 300 301 try { 302 return json.getString(key); 303 } catch (JSONException e) { 304 LOG.info(Messages.get().getBundle().key(Messages.LOG_OPTIONAL_STRING_MISSING_1, key), e); 305 return null; 306 } 307 } 308 309 /** Helper for reading an optional String value list - returning <code>null</code> if parsing fails for the whole list, otherwise just skipping unparsable entries. 310 * @param json The JSON object where the list should be read from. 311 * @param key The key of the value to read. 312 * @return The value from the JSON, or <code>null</code> if the value does not exist. 313 */ 314 protected static List<String> parseOptionalStringValues(JSONObject json, String key) { 315 316 List<String> list = null; 317 try { 318 list = parseMandatoryStringValues(json, key); 319 } catch (JSONException e) { 320 LOG.info(Messages.get().getBundle().key(Messages.LOG_OPTIONAL_STRING_LIST_MISSING_1, key), e); 321 return null; 322 } 323 return list; 324 } 325 326 /** 327 * @see org.opencms.jsp.search.config.parser.I_CmsSearchConfigurationParser#parseCommon() 328 */ 329 @Override 330 public I_CmsSearchConfigurationCommon parseCommon() { 331 332 return new CmsSearchConfigurationCommon( 333 getQueryParam(), 334 getLastQueryParam(), 335 getEscapeQueryChars(), 336 getFirstCallParam(), 337 getSearchForEmptyQuery(), 338 getIgnoreQuery(), 339 getQueryModifier(), 340 getIndex(), 341 getCore(), 342 getExtraSolrParams(), 343 getAdditionalParameters(), 344 getIgnoreReleaseDate(), 345 getIgnoreExpirationDate()); 346 } 347 348 /** 349 * @see org.opencms.jsp.search.config.parser.I_CmsSearchConfigurationParser#parseDidYouMean() 350 */ 351 public I_CmsSearchConfigurationDidYouMean parseDidYouMean() { 352 353 try { 354 JSONObject didYouMean = m_configObject.getJSONObject(JSON_KEY_DIDYOUMEAN); 355 String param = parseOptionalStringValue(didYouMean, JSON_KEY_DIDYOUMEAN_QUERYPARAM); 356 // default to the normal query param 357 if (null == param) { 358 param = getQueryParam(); 359 } 360 Boolean collate = parseOptionalBooleanValue(didYouMean, JSON_KEY_DIDYOUMEAN_COLLATE); 361 Integer count = parseOptionalIntValue(didYouMean, JSON_KEY_DIDYOUMEAN_COUNT); 362 return new CmsSearchConfigurationDidYouMean(param, collate, count); 363 364 } catch (JSONException e) { 365 if (null == m_baseConfig) { 366 if (LOG.isInfoEnabled()) { 367 LOG.info(Messages.get().getBundle().key(Messages.LOG_NO_HIGHLIGHTING_CONFIG_0), e); 368 } 369 return null; 370 } else { 371 return m_baseConfig.getDidYouMeanConfig(); 372 } 373 } 374 375 } 376 377 /** 378 * @see org.opencms.jsp.search.config.parser.I_CmsSearchConfigurationParser#parseFieldFacets() 379 */ 380 @Override 381 public Map<String, I_CmsSearchConfigurationFacetField> parseFieldFacets() { 382 383 Map<String, I_CmsSearchConfigurationFacetField> facetConfigs = new LinkedHashMap<String, I_CmsSearchConfigurationFacetField>(); 384 try { 385 JSONArray fieldFacets = m_configObject.getJSONArray(JSON_KEY_FIELD_FACETS); 386 for (int i = 0; i < fieldFacets.length(); i++) { 387 388 I_CmsSearchConfigurationFacetField config = parseFieldFacet(fieldFacets.getJSONObject(i)); 389 if (config != null) { 390 facetConfigs.put(config.getName(), config); 391 } 392 } 393 } catch (JSONException e) { 394 if (null == m_baseConfig) { 395 if (LOG.isInfoEnabled()) { 396 LOG.info(Messages.get().getBundle().key(Messages.LOG_NO_FACET_CONFIG_0), e); 397 } 398 } else { 399 facetConfigs = m_baseConfig.getFieldFacetConfigs(); 400 } 401 } 402 return facetConfigs; 403 } 404 405 /** 406 * @see org.opencms.jsp.search.config.parser.I_CmsSearchConfigurationParser#parseHighlighter() 407 */ 408 @Override 409 public I_CmsSearchConfigurationHighlighting parseHighlighter() { 410 411 try { 412 JSONObject highlighter = m_configObject.getJSONObject(JSON_KEY_HIGHLIGHTER); 413 String field = highlighter.getString(JSON_KEY_HIGHLIGHTER_FIELD); 414 Integer snippets = parseOptionalIntValue(highlighter, JSON_KEY_HIGHLIGHTER_SNIPPETS); 415 Integer fragsize = parseOptionalIntValue(highlighter, JSON_KEY_HIGHLIGHTER_FRAGSIZE); 416 String alternateField = parseOptionalStringValue(highlighter, JSON_KEY_HIGHLIGHTER_ALTERNATE_FIELD); 417 Integer maxAlternateFieldLength = parseOptionalIntValue( 418 highlighter, 419 JSON_KEY_HIGHLIGHTER_MAX_LENGTH_ALTERNATE_FIELD); 420 String pre = parseOptionalStringValue(highlighter, JSON_KEY_HIGHLIGHTER_SIMPLE_PRE); 421 String post = parseOptionalStringValue(highlighter, JSON_KEY_HIGHLIGHTER_SIMPLE_POST); 422 String formatter = parseOptionalStringValue(highlighter, JSON_KEY_HIGHLIGHTER_FORMATTER); 423 String fragmenter = parseOptionalStringValue(highlighter, JSON_KEY_HIGHLIGHTER_FRAGMENTER); 424 Boolean useFastVectorHighlighting = parseOptionalBooleanValue( 425 highlighter, 426 JSON_KEY_HIGHLIGHTER_FASTVECTORHIGHLIGHTING); 427 return new CmsSearchConfigurationHighlighting( 428 field, 429 snippets, 430 fragsize, 431 alternateField, 432 maxAlternateFieldLength, 433 pre, 434 post, 435 formatter, 436 fragmenter, 437 useFastVectorHighlighting); 438 } catch (JSONException e) { 439 if (null == m_baseConfig) { 440 if (LOG.isInfoEnabled()) { 441 LOG.info(Messages.get().getBundle().key(Messages.LOG_NO_HIGHLIGHTING_CONFIG_0), e); 442 } 443 return null; 444 } else { 445 return m_baseConfig.getHighlighterConfig(); 446 } 447 } 448 } 449 450 /** 451 * @see org.opencms.jsp.search.config.parser.I_CmsSearchConfigurationParser#parsePagination() 452 */ 453 @Override 454 public I_CmsSearchConfigurationPagination parsePagination() { 455 456 return CmsSearchConfigurationPagination.create(getPageParam(), getPageSizes(), getPageNavLength()); 457 } 458 459 /** 460 * @see org.opencms.jsp.search.config.parser.I_CmsSearchConfigurationParser#parseQueryFacet() 461 */ 462 @Override 463 public I_CmsSearchConfigurationFacetQuery parseQueryFacet() { 464 465 try { 466 JSONObject queryFacetObject = m_configObject.getJSONObject(JSON_KEY_QUERY_FACET); 467 try { 468 List<I_CmsFacetQueryItem> queries = parseFacetQueryItems(queryFacetObject); 469 String label = parseOptionalStringValue(queryFacetObject, JSON_KEY_FACET_LABEL); 470 Boolean isAndFacet = parseOptionalBooleanValue(queryFacetObject, JSON_KEY_FACET_ISANDFACET); 471 List<String> preselection = parseOptionalStringValues(queryFacetObject, JSON_KEY_FACET_PRESELECTION); 472 Boolean ignoreAllFacetFilters = parseOptionalBooleanValue( 473 queryFacetObject, 474 JSON_KEY_FACET_IGNOREALLFACETFILTERS); 475 return new CmsSearchConfigurationFacetQuery( 476 queries, 477 label, 478 isAndFacet, 479 preselection, 480 ignoreAllFacetFilters); 481 } catch (JSONException e) { 482 LOG.error( 483 Messages.get().getBundle().key( 484 Messages.ERR_QUERY_FACET_MANDATORY_KEY_MISSING_1, 485 JSON_KEY_QUERY_FACET_QUERY), 486 e); 487 return null; 488 } 489 } catch (JSONException e) { 490 // nothing to do, configuration is optional 491 return null != m_baseConfig ? m_baseConfig.getQueryFacetConfig() : null; 492 } 493 } 494 495 /** 496 * @see org.opencms.jsp.search.config.parser.I_CmsSearchConfigurationParser#parseRangeFacets() 497 */ 498 public Map<String, I_CmsSearchConfigurationFacetRange> parseRangeFacets() { 499 500 Map<String, I_CmsSearchConfigurationFacetRange> facetConfigs = new LinkedHashMap<String, I_CmsSearchConfigurationFacetRange>(); 501 try { 502 JSONArray rangeFacets = m_configObject.getJSONArray(JSON_KEY_RANGE_FACETS); 503 for (int i = 0; i < rangeFacets.length(); i++) { 504 505 I_CmsSearchConfigurationFacetRange config = parseRangeFacet(rangeFacets.getJSONObject(i)); 506 if (config != null) { 507 facetConfigs.put(config.getName(), config); 508 } 509 } 510 } catch (JSONException e) { 511 if (null == m_baseConfig) { 512 if (LOG.isInfoEnabled()) { 513 LOG.info(Messages.get().getBundle().key(Messages.LOG_NO_FACET_CONFIG_0), e); 514 } 515 } else { 516 facetConfigs = m_baseConfig.getRangeFacetConfigs(); 517 } 518 } 519 return facetConfigs; 520 521 } 522 523 /** 524 * @see org.opencms.jsp.search.config.parser.I_CmsSearchConfigurationParser#parseSorting() 525 */ 526 @Override 527 public I_CmsSearchConfigurationSorting parseSorting() { 528 529 List<I_CmsSearchConfigurationSortOption> options = getSortOptions(); 530 I_CmsSearchConfigurationSortOption defaultOption = (options != null) && !options.isEmpty() 531 ? options.get(0) 532 : null; 533 return CmsSearchConfigurationSorting.create(getSortParam(), options, defaultOption); 534 } 535 536 /** Returns a map with additional request parameters, mapping the parameter names to Solr query parts. 537 * @return A map with additional request parameters, mapping the parameter names to Solr query parts. 538 */ 539 protected Map<String, String> getAdditionalParameters() { 540 541 Map<String, String> result; 542 try { 543 JSONArray additionalParams = m_configObject.getJSONArray(JSON_KEY_ADDITIONAL_PARAMETERS); 544 result = new HashMap<String, String>(additionalParams.length()); 545 for (int i = 0; i < additionalParams.length(); i++) { 546 try { 547 JSONObject currentParam = additionalParams.getJSONObject(i); 548 String param = currentParam.getString(JSON_KEY_ADDITIONAL_PARAMETERS_PARAM); 549 String solrQuery = currentParam.getString(JSON_KEY_ADDITIONAL_PARAMETERS_SOLRQUERY); 550 result.put(param, solrQuery); 551 } catch (JSONException e) { 552 LOG.error(Messages.get().getBundle().key(Messages.ERR_ADDITIONAL_PARAMETER_CONFIG_WRONG_0), e); 553 continue; 554 } 555 } 556 } catch (JSONException e) { 557 LOG.info(Messages.get().getBundle().key(Messages.LOG_ADDITIONAL_PARAMETER_CONFIG_NOT_PARSED_0), e); 558 return null != m_baseConfig 559 ? m_baseConfig.getGeneralConfig().getAdditionalParameters() 560 : new HashMap<String, String>(); 561 } 562 return result; 563 } 564 565 /** Returns the configured Solr core, or <code>null</code> if no core is configured. 566 * @return The configured Solr core, or <code>null</code> if no core is configured. 567 */ 568 protected String getCore() { 569 570 try { 571 return m_configObject.getString(JSON_KEY_CORE); 572 } catch (JSONException e) { 573 if (null == m_baseConfig) { 574 if (LOG.isInfoEnabled()) { 575 LOG.info(Messages.get().getBundle().key(Messages.LOG_NO_CORE_SPECIFIED_0), e); 576 } 577 return null; 578 } else { 579 return m_baseConfig.getGeneralConfig().getSolrCore(); 580 } 581 } 582 } 583 584 /** 585 * Returns the flag, indicating if the characters in the query string that are commands to Solr should be escaped. 586 * @return the flag, indicating if the characters in the query string that are commands to Solr should be escaped. 587 */ 588 protected Boolean getEscapeQueryChars() { 589 590 Boolean isEscape = parseOptionalBooleanValue(m_configObject, JSON_KEY_ESCAPE_QUERY_CHARACTERS); 591 return (null == isEscape) && (m_baseConfig != null) 592 ? Boolean.valueOf(m_baseConfig.getGeneralConfig().getEscapeQueryChars()) 593 : isEscape; 594 } 595 596 /** Returns the configured extra parameters that should be given to Solr, or the empty string if no parameters are configured. 597 * @return The configured extra parameters that should be given to Solr, or the empty string if no parameters are configured. 598 */ 599 protected String getExtraSolrParams() { 600 601 try { 602 return m_configObject.getString(JSON_KEY_EXTRASOLRPARAMS); 603 } catch (JSONException e) { 604 if (null == m_baseConfig) { 605 if (LOG.isInfoEnabled()) { 606 LOG.info(Messages.get().getBundle().key(Messages.LOG_NO_EXTRA_PARAMETERS_0), e); 607 } 608 return ""; 609 } else { 610 return m_baseConfig.getGeneralConfig().getExtraSolrParams(); 611 } 612 } 613 } 614 615 /** Returns the configured request parameter for the last query, or the default parameter if no core is configured. 616 * @return The configured request parameter for the last query, or the default parameter if no core is configured. 617 */ 618 protected String getFirstCallParam() { 619 620 String param = parseOptionalStringValue(m_configObject, JSON_KEY_RELOADED_PARAM); 621 if (param == null) { 622 return null != m_baseConfig ? m_baseConfig.getGeneralConfig().getReloadedParam() : DEFAULT_RELOADED_PARAM; 623 } else { 624 return param; 625 } 626 } 627 628 /** Returns a flag indicating if also expired resources should be found. 629 * @return A flag indicating if also expired resources should be found. 630 */ 631 protected Boolean getIgnoreExpirationDate() { 632 633 Boolean isIgnoreExpirationDate = parseOptionalBooleanValue(m_configObject, JSON_KEY_IGNORE_EXPIRATION_DATE); 634 return (null == isIgnoreExpirationDate) && (m_baseConfig != null) 635 ? Boolean.valueOf(m_baseConfig.getGeneralConfig().getIgnoreExpirationDate()) 636 : isIgnoreExpirationDate; 637 } 638 639 /** Returns a flag indicating if the query given by the parameters should be ignored. 640 * @return A flag indicating if the query given by the parameters should be ignored. 641 */ 642 protected Boolean getIgnoreQuery() { 643 644 Boolean isIgnoreQuery = parseOptionalBooleanValue(m_configObject, JSON_KEY_IGNORE_QUERY); 645 return (null == isIgnoreQuery) && (m_baseConfig != null) 646 ? Boolean.valueOf(m_baseConfig.getGeneralConfig().getIgnoreQueryParam()) 647 : isIgnoreQuery; 648 } 649 650 /** Returns a flag indicating if also unreleased resources should be found. 651 * @return A flag indicating if also unreleased resources should be found. 652 */ 653 protected Boolean getIgnoreReleaseDate() { 654 655 Boolean isIgnoreReleaseDate = parseOptionalBooleanValue(m_configObject, JSON_KEY_IGNORE_RELEASE_DATE); 656 return (null == isIgnoreReleaseDate) && (m_baseConfig != null) 657 ? Boolean.valueOf(m_baseConfig.getGeneralConfig().getIgnoreReleaseDate()) 658 : isIgnoreReleaseDate; 659 } 660 661 /** Returns the configured Solr index, or <code>null</code> if no core is configured. 662 * @return The configured Solr index, or <code>null</code> if no core is configured. 663 */ 664 protected String getIndex() { 665 666 try { 667 return m_configObject.getString(JSON_KEY_INDEX); 668 } catch (JSONException e) { 669 if (null == m_baseConfig) { 670 if (LOG.isInfoEnabled()) { 671 LOG.info(Messages.get().getBundle().key(Messages.LOG_NO_INDEX_SPECIFIED_0), e); 672 } 673 return null; 674 } else { 675 return m_baseConfig.getGeneralConfig().getSolrIndex(); 676 } 677 } 678 } 679 680 /** Returns the configured request parameter for the last query, or the default parameter if no core is configured. 681 * @return The configured request parameter for the last query, or the default parameter if no core is configured. 682 */ 683 protected String getLastQueryParam() { 684 685 String param = parseOptionalStringValue(m_configObject, JSON_KEY_LAST_QUERYPARAM); 686 if (param == null) { 687 return null != m_baseConfig 688 ? m_baseConfig.getGeneralConfig().getLastQueryParam() 689 : DEFAULT_LAST_QUERY_PARAM; 690 } else { 691 return param; 692 } 693 } 694 695 /** Returns the configured length of the "Google"-like page navigation, or the default parameter if no core is configured. 696 * @return The configured length of the "Google"-like page navigation, or the default parameter if no core is configured. 697 */ 698 protected Integer getPageNavLength() { 699 700 return parseOptionalIntValue(m_configObject, JSON_KEY_PAGENAVLENGTH); 701 } 702 703 /** Returns the configured request parameter for the current page, or the default parameter if no core is configured. 704 * @return The configured request parameter for the current page, or the default parameter if no core is configured. 705 */ 706 protected String getPageParam() { 707 708 return parseOptionalStringValue(m_configObject, JSON_KEY_PAGEPARAM); 709 } 710 711 /** Returns the configured page sizes, or the default page size if no core is configured. 712 * @return The configured page sizes, or the default page size if no core is configured. 713 */ 714 protected List<Integer> getPageSizes() { 715 716 try { 717 return Collections.singletonList(Integer.valueOf(m_configObject.getInt(JSON_KEY_PAGESIZE))); 718 } catch (JSONException e) { 719 List<Integer> result = null; 720 String pageSizesString = null; 721 try { 722 pageSizesString = m_configObject.getString(JSON_KEY_PAGESIZE); 723 String[] pageSizesArray = pageSizesString.split("-"); 724 if (pageSizesArray.length > 0) { 725 result = new ArrayList<>(pageSizesArray.length); 726 for (int i = 0; i < pageSizesArray.length; i++) { 727 result.add(Integer.valueOf(pageSizesArray[i])); 728 } 729 } 730 return result; 731 } catch (NumberFormatException | JSONException e1) { 732 LOG.warn(Messages.get().getBundle().key(Messages.LOG_PARSING_PAGE_SIZES_FAILED_1, pageSizesString), e); 733 } 734 if (null == m_baseConfig) { 735 if (LOG.isInfoEnabled()) { 736 LOG.info(Messages.get().getBundle().key(Messages.LOG_NO_PAGESIZE_SPECIFIED_0), e); 737 } 738 return null; 739 } else { 740 return m_baseConfig.getPaginationConfig().getPageSizes(); 741 } 742 } 743 } 744 745 /** Returns the optional query modifier. 746 * @return the optional query modifier. 747 */ 748 protected String getQueryModifier() { 749 750 String queryModifier = parseOptionalStringValue(m_configObject, JSON_KEY_QUERY_MODIFIER); 751 return (null == queryModifier) && (null != m_baseConfig) 752 ? m_baseConfig.getGeneralConfig().getQueryModifier() 753 : queryModifier; 754 } 755 756 /** Returns the configured request parameter for the query string, or the default parameter if no core is configured. 757 * @return The configured request parameter for the query string, or the default parameter if no core is configured. 758 */ 759 protected String getQueryParam() { 760 761 String param = parseOptionalStringValue(m_configObject, JSON_KEY_QUERYPARAM); 762 if (param == null) { 763 return null != m_baseConfig ? m_baseConfig.getGeneralConfig().getQueryParam() : DEFAULT_QUERY_PARAM; 764 } else { 765 return param; 766 } 767 } 768 769 /** Returns a flag, indicating if search should be performed using a wildcard if the empty query is given. 770 * @return A flag, indicating if search should be performed using a wildcard if the empty query is given. 771 */ 772 protected Boolean getSearchForEmptyQuery() { 773 774 Boolean isSearchForEmptyQuery = parseOptionalBooleanValue(m_configObject, JSON_KEY_SEARCH_FOR_EMPTY_QUERY); 775 return (isSearchForEmptyQuery == null) && (null != m_baseConfig) 776 ? Boolean.valueOf(m_baseConfig.getGeneralConfig().getSearchForEmptyQueryParam()) 777 : isSearchForEmptyQuery; 778 } 779 780 /** Returns the list of the configured sort options, or the empty list if no sort options are configured. 781 * @return The list of the configured sort options, or the empty list if no sort options are configured. 782 */ 783 protected List<I_CmsSearchConfigurationSortOption> getSortOptions() { 784 785 List<I_CmsSearchConfigurationSortOption> options = new LinkedList<I_CmsSearchConfigurationSortOption>(); 786 try { 787 JSONArray sortOptions = m_configObject.getJSONArray(JSON_KEY_SORTOPTIONS); 788 for (int i = 0; i < sortOptions.length(); i++) { 789 I_CmsSearchConfigurationSortOption option = parseSortOption(sortOptions.getJSONObject(i)); 790 if (option != null) { 791 options.add(option); 792 } 793 } 794 } catch (JSONException e) { 795 if (null == m_baseConfig) { 796 if (LOG.isInfoEnabled()) { 797 LOG.info(Messages.get().getBundle().key(Messages.LOG_NO_SORT_CONFIG_0), e); 798 } 799 } else { 800 options = m_baseConfig.getSortConfig().getSortOptions(); 801 } 802 } 803 return options; 804 } 805 806 /** Returns the configured request parameter for the sort option, or the default parameter if no core is configured. 807 * @return The configured request parameter for the sort option, or the default parameter if no core is configured. 808 */ 809 protected String getSortParam() { 810 811 return parseOptionalStringValue(m_configObject, JSON_KEY_SORTPARAM); 812 } 813 814 /** Initialization that parses the String to a JSON object. 815 * @param configString The JSON as string. 816 * @param baseConfig The optional basic search configuration to overwrite (partly) by the JSON configuration. 817 * @throws JSONException thrown if parsing fails. 818 */ 819 protected void init(String configString, I_CmsSearchConfiguration baseConfig) throws JSONException { 820 821 m_configObject = new JSONObject(configString); 822 m_baseConfig = baseConfig; 823 } 824 825 /** Parses a single query item for the query facet. 826 * @param item JSON object of the query item. 827 * @return the parsed query item, or <code>null</code> if parsing failed. 828 */ 829 protected I_CmsFacetQueryItem parseFacetQueryItem(JSONObject item) { 830 831 String query; 832 try { 833 query = item.getString(JSON_KEY_QUERY_FACET_QUERY_QUERY); 834 } catch (JSONException e) { 835 // TODO: Log 836 return null; 837 } 838 String label = parseOptionalStringValue(item, JSON_KEY_QUERY_FACET_QUERY_LABEL); 839 return new CmsFacetQueryItem(query, label); 840 } 841 842 /** Parses the list of query items for the query facet. 843 * @param queryFacetObject JSON object representing the node with the query facet. 844 * @return list of query options 845 * @throws JSONException if the list cannot be parsed. 846 */ 847 protected List<I_CmsFacetQueryItem> parseFacetQueryItems(JSONObject queryFacetObject) throws JSONException { 848 849 JSONArray items = queryFacetObject.getJSONArray(JSON_KEY_QUERY_FACET_QUERY); 850 List<I_CmsFacetQueryItem> result = new ArrayList<I_CmsFacetQueryItem>(items.length()); 851 for (int i = 0; i < items.length(); i++) { 852 I_CmsFacetQueryItem item = parseFacetQueryItem(items.getJSONObject(i)); 853 if (item != null) { 854 result.add(item); 855 } 856 } 857 return result; 858 } 859 860 /** Parses the field facet configurations. 861 * @param fieldFacetObject The JSON sub-node with the field facet configurations. 862 * @return The field facet configurations. 863 */ 864 protected I_CmsSearchConfigurationFacetField parseFieldFacet(JSONObject fieldFacetObject) { 865 866 try { 867 String field = fieldFacetObject.getString(JSON_KEY_FACET_FIELD); 868 String name = parseOptionalStringValue(fieldFacetObject, JSON_KEY_FACET_NAME); 869 String label = parseOptionalStringValue(fieldFacetObject, JSON_KEY_FACET_LABEL); 870 Integer minCount = parseOptionalIntValue(fieldFacetObject, JSON_KEY_FACET_MINCOUNT); 871 Integer limit = parseOptionalIntValue(fieldFacetObject, JSON_KEY_FACET_LIMIT); 872 String prefix = parseOptionalStringValue(fieldFacetObject, JSON_KEY_FACET_PREFIX); 873 String sorder = parseOptionalStringValue(fieldFacetObject, JSON_KEY_FACET_ORDER); 874 I_CmsSearchConfigurationFacet.SortOrder order; 875 try { 876 order = I_CmsSearchConfigurationFacet.SortOrder.valueOf(sorder); 877 } catch (Exception e) { 878 order = null; 879 } 880 String filterQueryModifier = parseOptionalStringValue(fieldFacetObject, JSON_KEY_FACET_FILTERQUERYMODIFIER); 881 Boolean isAndFacet = parseOptionalBooleanValue(fieldFacetObject, JSON_KEY_FACET_ISANDFACET); 882 List<String> preselection = parseOptionalStringValues(fieldFacetObject, JSON_KEY_FACET_PRESELECTION); 883 Boolean ignoreFilterAllFacetFilters = parseOptionalBooleanValue( 884 fieldFacetObject, 885 JSON_KEY_FACET_IGNOREALLFACETFILTERS); 886 return new CmsSearchConfigurationFacetField( 887 field, 888 name, 889 minCount, 890 limit, 891 prefix, 892 label, 893 order, 894 filterQueryModifier, 895 isAndFacet, 896 preselection, 897 ignoreFilterAllFacetFilters); 898 } catch (JSONException e) { 899 LOG.error( 900 Messages.get().getBundle().key(Messages.ERR_FIELD_FACET_MANDATORY_KEY_MISSING_1, JSON_KEY_FACET_FIELD), 901 e); 902 return null; 903 } 904 } 905 906 /** Parses the query facet configurations. 907 * @param rangeFacetObject The JSON sub-node with the query facet configurations. 908 * @return The query facet configurations. 909 */ 910 protected I_CmsSearchConfigurationFacetRange parseRangeFacet(JSONObject rangeFacetObject) { 911 912 try { 913 String range = rangeFacetObject.getString(JSON_KEY_RANGE_FACET_RANGE); 914 String name = parseOptionalStringValue(rangeFacetObject, JSON_KEY_FACET_NAME); 915 String label = parseOptionalStringValue(rangeFacetObject, JSON_KEY_FACET_LABEL); 916 Integer minCount = parseOptionalIntValue(rangeFacetObject, JSON_KEY_FACET_MINCOUNT); 917 String start = rangeFacetObject.getString(JSON_KEY_RANGE_FACET_START); 918 String end = rangeFacetObject.getString(JSON_KEY_RANGE_FACET_END); 919 String gap = rangeFacetObject.getString(JSON_KEY_RANGE_FACET_GAP); 920 List<String> sother = parseOptionalStringValues(rangeFacetObject, JSON_KEY_RANGE_FACET_OTHER); 921 Boolean hardEnd = parseOptionalBooleanValue(rangeFacetObject, JSON_KEY_RANGE_FACET_HARDEND); 922 List<I_CmsSearchConfigurationFacetRange.Other> other = null; 923 if (sother != null) { 924 other = new ArrayList<I_CmsSearchConfigurationFacetRange.Other>(sother.size()); 925 for (String so : sother) { 926 try { 927 I_CmsSearchConfigurationFacetRange.Other o = I_CmsSearchConfigurationFacetRange.Other.valueOf( 928 so); 929 other.add(o); 930 } catch (Exception e) { 931 LOG.error(Messages.get().getBundle().key(Messages.ERR_INVALID_OTHER_OPTION_1, so), e); 932 } 933 } 934 } 935 Boolean isAndFacet = parseOptionalBooleanValue(rangeFacetObject, JSON_KEY_FACET_ISANDFACET); 936 List<String> preselection = parseOptionalStringValues(rangeFacetObject, JSON_KEY_FACET_PRESELECTION); 937 Boolean ignoreAllFacetFilters = parseOptionalBooleanValue( 938 rangeFacetObject, 939 JSON_KEY_FACET_IGNOREALLFACETFILTERS); 940 return new CmsSearchConfigurationFacetRange( 941 range, 942 start, 943 end, 944 gap, 945 other, 946 hardEnd, 947 name, 948 minCount, 949 label, 950 isAndFacet, 951 preselection, 952 ignoreAllFacetFilters); 953 } catch (JSONException e) { 954 LOG.error( 955 Messages.get().getBundle().key( 956 Messages.ERR_RANGE_FACET_MANDATORY_KEY_MISSING_1, 957 JSON_KEY_RANGE_FACET_RANGE 958 + ", " 959 + JSON_KEY_RANGE_FACET_START 960 + ", " 961 + JSON_KEY_RANGE_FACET_END 962 + ", " 963 + JSON_KEY_RANGE_FACET_GAP), 964 e); 965 return null; 966 } 967 968 } 969 970 /** Returns a single sort option configuration as configured via the methods parameter, or null if the parameter does not specify a sort option. 971 * @param json The JSON sort option configuration. 972 * @return The sort option configuration, or null if the JSON could not be read. 973 */ 974 protected I_CmsSearchConfigurationSortOption parseSortOption(JSONObject json) { 975 976 try { 977 String solrValue = json.getString(JSON_KEY_SORTOPTION_SOLRVALUE); 978 String paramValue = parseOptionalStringValue(json, JSON_KEY_SORTOPTION_PARAMVALUE); 979 paramValue = (paramValue == null) ? solrValue : paramValue; 980 String label = parseOptionalStringValue(json, JSON_KEY_SORTOPTION_LABEL); 981 label = (label == null) ? paramValue : label; 982 return new CmsSearchConfigurationSortOption(label, paramValue, solrValue); 983 } catch (JSONException e) { 984 LOG.error( 985 Messages.get().getBundle().key(Messages.ERR_SORT_OPTION_NOT_PARSABLE_1, JSON_KEY_SORTOPTION_SOLRVALUE), 986 e); 987 return null; 988 } 989 } 990}