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.file.CmsPropertyDefinition; 032import org.opencms.i18n.CmsEncoder; 033import org.opencms.json.JSONException; 034import org.opencms.jsp.search.config.CmsSearchConfigurationFacetField; 035import org.opencms.jsp.search.config.CmsSearchConfigurationFacetRange; 036import org.opencms.jsp.search.config.CmsSearchConfigurationSortOption; 037import org.opencms.jsp.search.config.I_CmsSearchConfigurationFacet.SortOrder; 038import org.opencms.jsp.search.config.I_CmsSearchConfigurationFacetField; 039import org.opencms.jsp.search.config.I_CmsSearchConfigurationFacetRange; 040import org.opencms.jsp.search.config.I_CmsSearchConfigurationPagination; 041import org.opencms.jsp.search.config.I_CmsSearchConfigurationSortOption; 042import org.opencms.jsp.search.config.parser.simplesearch.CmsCategoryFolderRestrictionBean; 043import org.opencms.jsp.search.config.parser.simplesearch.CmsConfigurationBean; 044import org.opencms.jsp.search.config.parser.simplesearch.CmsConfigurationBean.CombinationMode; 045import org.opencms.jsp.search.config.parser.simplesearch.CmsGeoFilterBean; 046import org.opencms.jsp.search.config.parser.simplesearch.daterestrictions.I_CmsDateRestriction; 047import org.opencms.jsp.search.config.parser.simplesearch.preconfiguredrestrictions.CmsRestrictionRule; 048import org.opencms.jsp.search.config.parser.simplesearch.preconfiguredrestrictions.CmsRestrictionsBean; 049import org.opencms.jsp.search.config.parser.simplesearch.preconfiguredrestrictions.CmsRestrictionsBean.FieldValues; 050import org.opencms.jsp.search.config.parser.simplesearch.preconfiguredrestrictions.CmsRestrictionsBean.FieldValues.FieldType; 051import org.opencms.main.CmsException; 052import org.opencms.relations.CmsCategoryService; 053import org.opencms.search.fields.CmsSearchField; 054import org.opencms.search.solr.CmsSolrQuery; 055import org.opencms.search.solr.CmsSolrQueryUtil; 056import org.opencms.util.CmsStringUtil; 057import org.opencms.util.CmsUUID; 058 059import java.util.Collection; 060import java.util.Collections; 061import java.util.HashMap; 062import java.util.HashSet; 063import java.util.LinkedList; 064import java.util.List; 065import java.util.Locale; 066import java.util.Map; 067import java.util.Objects; 068import java.util.stream.Collectors; 069 070import org.apache.solr.common.params.CommonParams; 071 072import com.google.common.collect.Lists; 073 074/** 075 * Search configuration parser using a list configuration file as the base configuration with additional JSON.<p> 076 */ 077public class CmsSimpleSearchConfigurationParser extends CmsJSONSearchConfigurationParser { 078 079 /** Sort options that are available by default. */ 080 public static enum SortOption { 081 082 /** Sort by date ascending. */ 083 DATE_ASC, 084 /** Sort by date descending. */ 085 DATE_DESC, 086 /** Sort by title ascending. */ 087 TITLE_ASC, 088 /** Sort by title descending. */ 089 TITLE_DESC, 090 /** Sort by order ascending. */ 091 ORDER_ASC, 092 /** Sort by order descending. */ 093 ORDER_DESC; 094 095 /** 096 * Generates the suitable {@link I_CmsSearchConfigurationSortOption} for the option. 097 * @param l the locale for which the option should be created 098 * @return the created {@link I_CmsSearchConfigurationSortOption} 099 */ 100 public I_CmsSearchConfigurationSortOption getOption(Locale l) { 101 102 switch (this) { 103 case DATE_ASC: 104 return new CmsSearchConfigurationSortOption("date.asc", "date_asc", getSortDateField(l) + " asc"); 105 case DATE_DESC: 106 return new CmsSearchConfigurationSortOption( 107 "date.desc", 108 "date_desc", 109 getSortDateField(l) + " desc"); 110 case TITLE_ASC: 111 return new CmsSearchConfigurationSortOption( 112 "title.asc", 113 "title_asc", 114 getSortTitleField(l) + " asc"); 115 case TITLE_DESC: 116 return new CmsSearchConfigurationSortOption( 117 "title.desc", 118 "title_desc", 119 getSortTitleField(l) + " desc"); 120 case ORDER_ASC: 121 return new CmsSearchConfigurationSortOption( 122 "order.asc", 123 "order_asc", 124 getSortOrderField(l) + " asc"); 125 case ORDER_DESC: 126 return new CmsSearchConfigurationSortOption( 127 "order.desc", 128 "order_desc", 129 getSortOrderField(l) + " desc"); 130 default: 131 throw new IllegalArgumentException(); 132 } 133 } 134 135 /** 136 * Returns the locale specific date field to use for sorting. 137 * @param l the locale to use, can be <code>null</code> 138 * @return the locale specific date field to use for sorting. 139 */ 140 protected String getSortDateField(Locale l) { 141 142 return CmsSearchField.FIELD_INSTANCEDATE 143 + (null != l ? "_" + l.toString() : "") 144 + CmsSearchField.FIELD_POSTFIX_DATE; 145 } 146 147 /** 148 * Returns the locale specific order field to use for sorting. 149 * @param l the locale to use, can be <code>null</code> 150 * @return the locale specific order field to use for sorting. 151 */ 152 protected String getSortOrderField(Locale l) { 153 154 return CmsSearchField.FIELD_DISPORDER 155 + (null != l ? "_" + l.toString() : "") 156 + CmsSearchField.FIELD_POSTFIX_INT; 157 } 158 159 /** 160 * Returns the locale specific title field to use for sorting. 161 * @param l the locale to use, can be <code>null</code> 162 * @return the locale specific title field to use for sorting. 163 */ 164 protected String getSortTitleField(Locale l) { 165 166 return CmsSearchField.FIELD_DISPTITLE 167 + (null != l ? "_" + l.toString() : "") 168 + CmsSearchField.FIELD_POSTFIX_SORT; 169 } 170 } 171 172 /** SOLR field name. */ 173 public static final String FIELD_CATEGORIES = "category_exact"; 174 175 /** SOLR field name. */ 176 public static final String FIELD_DATE = "instancedate_%s_dt"; 177 178 /** SOLR field name. */ 179 public static final String FIELD_DATE_RANGE = "instancedaterange_%s_dr"; 180 181 /** SOLR field name. */ 182 public static final String FIELD_DATE_FACET_NAME = "instancedate"; 183 184 /** SOLR field name. */ 185 public static final String FIELD_PARENT_FOLDERS = "parent-folders"; 186 187 /** Pagination which may override the default pagination. */ 188 private I_CmsSearchConfigurationPagination m_pagination; 189 190 /** The current cms context. */ 191 private CmsObject m_cms; 192 193 /** The list configuration bean. */ 194 private CmsConfigurationBean m_config; 195 196 /** The (mutable) search locale. */ 197 private Locale m_searchLocale; 198 199 /** The (mutable) sort order. */ 200 private CmsSimpleSearchConfigurationParser.SortOption m_sortOrder; 201 202 /** Flag which, if true, causes the search to ignore the blacklist. */ 203 private boolean m_ignoreBlacklist; 204 205 /** 206 * Constructor.<p> 207 * 208 * @param cms the cms context 209 * @param config the list configuration 210 * @param additionalParamJSON the additional JSON configuration 211 * 212 * @throws JSONException in case parsing the JSON fails 213 */ 214 public CmsSimpleSearchConfigurationParser(CmsObject cms, CmsConfigurationBean config, String additionalParamJSON) 215 throws JSONException { 216 217 super(CmsStringUtil.isEmptyOrWhitespaceOnly(additionalParamJSON) ? "{}" : additionalParamJSON); 218 m_cms = cms; 219 m_config = config; 220 } 221 222 /** 223 * Creates an instance for an empty JSON configuration.<p> 224 * 225 * The point of this is that we know that passing an empty configuration makes it impossible 226 * for a JSONException to thrown. 227 * 228 * @param cms the current CMS context 229 * @param config the search configuration 230 * 231 * @return the search config parser 232 */ 233 public static CmsSimpleSearchConfigurationParser createInstanceWithNoJsonConfig( 234 CmsObject cms, 235 CmsConfigurationBean config) { 236 237 try { 238 return new CmsSimpleSearchConfigurationParser(cms, config, null); 239 240 } catch (JSONException e) { 241 return null; 242 } 243 } 244 245 /** The default field facets. 246 * 247 * @param categoryConjunction flag, indicating if category selections in the facet should be "AND" combined. 248 * @return the default field facets. 249 */ 250 public static Map<String, I_CmsSearchConfigurationFacetField> getDefaultFieldFacets(boolean categoryConjunction) { 251 252 Map<String, I_CmsSearchConfigurationFacetField> fieldFacets = new HashMap<String, I_CmsSearchConfigurationFacetField>(); 253 fieldFacets.put( 254 FIELD_CATEGORIES, 255 new CmsSearchConfigurationFacetField( 256 FIELD_CATEGORIES, 257 null, 258 Integer.valueOf(1), 259 Integer.valueOf(200), 260 null, 261 "Category", 262 SortOrder.index, 263 null, 264 Boolean.valueOf(categoryConjunction), 265 null, 266 Boolean.TRUE, 267 null)); 268 fieldFacets.put( 269 FIELD_PARENT_FOLDERS, 270 new CmsSearchConfigurationFacetField( 271 FIELD_PARENT_FOLDERS, 272 null, 273 Integer.valueOf(1), 274 Integer.valueOf(200), 275 null, 276 "Folders", 277 SortOrder.index, 278 null, 279 Boolean.FALSE, 280 null, 281 Boolean.TRUE, 282 null)); 283 return Collections.unmodifiableMap(fieldFacets); 284 285 } 286 287 /** 288 * Returns the initial SOLR query.<p> 289 * 290 * @return the SOLR query 291 */ 292 public CmsSolrQuery getInitialQuery() { 293 294 Map<String, String[]> queryParams = new HashMap<String, String[]>(); 295 if (!m_cms.getRequestContext().getCurrentProject().isOnlineProject() && m_config.isShowExpired()) { 296 queryParams.put("fq", new String[] {"released:[* TO *]", "expired:[* TO *]"}); 297 } 298 return new CmsSolrQuery(null, queryParams); 299 } 300 301 /** 302 * Gets the search locale.<p> 303 * 304 * @return the search locale 305 */ 306 public Locale getSearchLocale() { 307 308 if (m_searchLocale != null) { 309 return m_searchLocale; 310 } 311 return m_cms.getRequestContext().getLocale(); 312 } 313 314 /** 315 * @see org.opencms.jsp.search.config.parser.I_CmsSearchConfigurationParser#parseFieldFacets() 316 */ 317 @Override 318 public Map<String, I_CmsSearchConfigurationFacetField> parseFieldFacets() { 319 320 if (m_configObject.has(JSON_KEY_FIELD_FACETS)) { 321 return super.parseFieldFacets(); 322 } else { 323 return getDefaultFieldFacets(true); 324 } 325 } 326 327 /** 328 * @see org.opencms.jsp.search.config.parser.CmsJSONSearchConfigurationParser#parsePagination() 329 */ 330 @Override 331 public I_CmsSearchConfigurationPagination parsePagination() { 332 333 if (m_pagination != null) { 334 return m_pagination; 335 } 336 return super.parsePagination(); 337 } 338 339 /** 340 * @see org.opencms.jsp.search.config.parser.I_CmsSearchConfigurationParser#parseRangeFacets() 341 */ 342 @Override 343 public Map<String, I_CmsSearchConfigurationFacetRange> parseRangeFacets() { 344 345 if (m_configObject.has(JSON_KEY_RANGE_FACETS)) { 346 return super.parseRangeFacets(); 347 } else { 348 Map<String, I_CmsSearchConfigurationFacetRange> rangeFacets = new HashMap<String, I_CmsSearchConfigurationFacetRange>(); 349 String indexField = FIELD_DATE; 350 if (Boolean.parseBoolean(m_config.getParameterValue(CmsConfigurationBean.PARAM_FILTER_MULTI_DAY))) { 351 indexField = FIELD_DATE_RANGE; 352 } 353 I_CmsSearchConfigurationFacetRange rangeFacet = new CmsSearchConfigurationFacetRange( 354 String.format(indexField, getSearchLocale().toString()), 355 "NOW/YEAR-20YEARS", 356 "NOW/MONTH+5YEARS", 357 "+1MONTHS", 358 null, 359 Boolean.FALSE, 360 FIELD_DATE_FACET_NAME, 361 Integer.valueOf(1), 362 "Date", 363 Boolean.FALSE, 364 null, 365 Boolean.TRUE, 366 null); 367 368 rangeFacets.put(rangeFacet.getName(), rangeFacet); 369 return rangeFacets; 370 } 371 } 372 373 /** 374 * Sets the 'ignore blacklist' flag.<p> 375 * 376 * If set, the search will ignore the blacklist from the list configuration.<p> 377 * 378 * @param ignoreBlacklist true if the blacklist should be ignored 379 */ 380 public void setIgnoreBlacklist(boolean ignoreBlacklist) { 381 382 m_ignoreBlacklist = ignoreBlacklist; 383 } 384 385 /** 386 * Sets the pagination.<p> 387 * 388 * If this is set, parsePagination will always return the set value instead of using the default way to compute the pagination 389 * 390 * @param pagination the pagination 391 */ 392 public void setPagination(I_CmsSearchConfigurationPagination pagination) { 393 394 m_pagination = pagination; 395 } 396 397 /** 398 * Sets the search locale.<p> 399 * 400 * @param locale the search locale 401 */ 402 public void setSearchLocale(Locale locale) { 403 404 m_searchLocale = locale; 405 } 406 407 /** 408 * Sets the sort option.<p> 409 * 410 * @param sortOption the sort option 411 */ 412 public void setSortOption(String sortOption) { 413 414 if (null != sortOption) { 415 try { 416 m_sortOrder = CmsSimpleSearchConfigurationParser.SortOption.valueOf(sortOption); 417 } catch (IllegalArgumentException e) { 418 m_sortOrder = null; 419 LOG.warn( 420 "Setting illegal default sort option " + sortOption + " failed. Using Solr's default sort option."); 421 } 422 } else { 423 m_sortOrder = null; 424 } 425 } 426 427 /** 428 * @see org.opencms.jsp.search.config.parser.CmsJSONSearchConfigurationParser#getEscapeQueryChars() 429 */ 430 @Override 431 protected Boolean getEscapeQueryChars() { 432 433 if (m_configObject.has(JSON_KEY_ESCAPE_QUERY_CHARACTERS)) { 434 return super.getEscapeQueryChars(); 435 } else { 436 return Boolean.TRUE; 437 } 438 } 439 440 /** 441 * @see org.opencms.jsp.search.config.parser.CmsJSONSearchConfigurationParser#getExtraSolrParams() 442 */ 443 @Override 444 protected String getExtraSolrParams() { 445 446 String params = super.getExtraSolrParams(); 447 if (CmsStringUtil.isEmptyOrWhitespaceOnly(params)) { 448 params = getCategoryFolderFilter() 449 + getResourceTypeFilter() 450 + getPreconfiguredFilterQuery() 451 + getFilterQuery() 452 + getBlacklistFilter() 453 + getGeoFilterQuery(); 454 } 455 return params; 456 } 457 458 /** 459 * @see org.opencms.jsp.search.config.parser.CmsJSONSearchConfigurationParser#getIgnoreExpirationDate() 460 */ 461 @Override 462 protected Boolean getIgnoreExpirationDate() { 463 464 return getIgnoreReleaseAndExpiration(); 465 466 } 467 468 /** 469 * @see org.opencms.jsp.search.config.parser.CmsJSONSearchConfigurationParser#getIgnoreReleaseDate() 470 */ 471 @Override 472 protected Boolean getIgnoreReleaseDate() { 473 474 return getIgnoreReleaseAndExpiration(); 475 } 476 477 /** 478 * @see org.opencms.jsp.search.config.parser.CmsJSONSearchConfigurationParser#getMaxReturnedResults(java.lang.String) 479 */ 480 @Override 481 protected int getMaxReturnedResults(String indexName) { 482 483 return null != m_config.getMaximallyReturnedResults() 484 ? m_config.getMaximallyReturnedResults().intValue() 485 : super.getMaxReturnedResults(indexName); 486 } 487 488 /** 489 * @see org.opencms.jsp.search.config.parser.CmsJSONSearchConfigurationParser#getQueryModifier() 490 */ 491 @Override 492 protected String getQueryModifier() { 493 494 String modifier = super.getQueryModifier(); 495 if (CmsStringUtil.isEmptyOrWhitespaceOnly(modifier)) { 496 modifier = "{!type=edismax qf=\"" 497 + CmsSearchField.FIELD_CONTENT 498 + "_" 499 + getSearchLocale().toString() 500 + " " 501 + CmsPropertyDefinition.PROPERTY_TITLE 502 + CmsSearchField.FIELD_DYNAMIC_PROPERTIES 503 + " " 504 + CmsPropertyDefinition.PROPERTY_DESCRIPTION 505 + CmsSearchField.FIELD_DYNAMIC_PROPERTIES_DIRECT 506 + " " 507 + CmsPropertyDefinition.PROPERTY_DESCRIPTION_HTML 508 + CmsSearchField.FIELD_DYNAMIC_PROPERTIES_DIRECT 509 + " " 510 + CmsSearchField.FIELD_DESCRIPTION 511 + "_" 512 + getSearchLocale().toString() 513 + " " 514 + CmsSearchField.FIELD_KEYWORDS 515 + "_" 516 + getSearchLocale().toString() 517 + "\"}%(query)"; 518 } 519 return modifier; 520 } 521 522 /** 523 * @see org.opencms.jsp.search.config.parser.CmsJSONSearchConfigurationParser#getSearchForEmptyQuery() 524 */ 525 @Override 526 protected Boolean getSearchForEmptyQuery() { 527 528 if (m_configObject.has(JSON_KEY_SEARCH_FOR_EMPTY_QUERY)) { 529 return super.getSearchForEmptyQuery(); 530 } else { 531 return Boolean.TRUE; 532 } 533 } 534 535 /** 536 * @see org.opencms.jsp.search.config.parser.CmsJSONSearchConfigurationParser#getSortOptions() 537 */ 538 @Override 539 protected List<I_CmsSearchConfigurationSortOption> getSortOptions() { 540 541 if (m_configObject.has(JSON_KEY_SORTOPTIONS)) { 542 return super.getSortOptions(); 543 } else { 544 List<I_CmsSearchConfigurationSortOption> options = new LinkedList<I_CmsSearchConfigurationSortOption>(); 545 546 CmsSimpleSearchConfigurationParser.SortOption currentOption = CmsSimpleSearchConfigurationParser.SortOption.valueOf( 547 m_config.getSortOrder()); 548 if (m_sortOrder != null) { 549 currentOption = m_sortOrder; 550 } 551 Locale locale = getSearchLocale(); 552 options.add(currentOption.getOption(locale)); 553 CmsSimpleSearchConfigurationParser.SortOption[] sortOptions = CmsSimpleSearchConfigurationParser.SortOption.values(); 554 for (int i = 0; i < sortOptions.length; i++) { 555 CmsSimpleSearchConfigurationParser.SortOption option = sortOptions[i]; 556 if (!Objects.equals(currentOption, option)) { 557 options.add(option.getOption(locale)); 558 } 559 } 560 return options; 561 } 562 } 563 564 /** 565 * Generates the query part for the preconfigured restrictions for the type. 566 * @param type the type to generate the restriction for. 567 * @param restrictionsForType the preconfigured restrictions for the type. 568 * @return the part of the Solr query for the restriction. 569 */ 570 String generatePreconfiguredRestriction( 571 String type, 572 Map<CmsRestrictionRule, Collection<FieldValues>> restrictionsForType) { 573 574 String result = ""; 575 if ((null != restrictionsForType) && (restrictionsForType.size() > 0)) { 576 Collection<String> ruleRestrictions = new HashSet<>(restrictionsForType.size()); 577 for (Map.Entry<CmsRestrictionRule, Collection<FieldValues>> ruleEntry : restrictionsForType.entrySet()) { 578 ruleRestrictions.add(generatePreconfiguredRestrictionForRule(ruleEntry.getKey(), ruleEntry.getValue())); 579 } 580 result = ruleRestrictions.size() > 1 581 ? ruleRestrictions.stream().reduce((r1, r2) -> (r1 + " " + CombinationMode.AND + " " + r2)).get() 582 : ruleRestrictions.iterator().next(); 583 if (null != type) { 584 result = "type:\"" + type + "\" AND (" + result + ")"; 585 } 586 } else if (null != type) { 587 result = "type:\"" + type + "\""; 588 } 589 return result.isEmpty() ? result : "(" + result + ")"; 590 } 591 592 /** 593 * Generates the query part for the preconfigured restriction for a single rule. 594 * @param rule the rule to generate the restriction for. 595 * @param values the values provided for the rule. 596 * @return the part of the Solr query for the restriction. 597 */ 598 String generatePreconfiguredRestrictionForRule(CmsRestrictionRule rule, Collection<FieldValues> values) { 599 600 Collection<String> resolvedFieldValues = values.stream().map(v -> resolveFieldValues(rule, v)).collect( 601 Collectors.toSet()); 602 603 String seperator = " " + rule.getCombinationModeBetweenFields().toString() + " "; 604 return rule.getFieldForLocale(getSearchLocale()) 605 + ":(" 606 + resolvedFieldValues.stream().reduce((v1, v2) -> v1 + seperator + v2).get() 607 + ")"; 608 609 } 610 611 /** 612 * Returns the blacklist filter.<p> 613 * 614 * @return the blacklist filter 615 */ 616 String getBlacklistFilter() { 617 618 if (m_ignoreBlacklist) { 619 return ""; 620 } 621 String result = ""; 622 List<CmsUUID> blacklist = m_config.getBlacklist(); 623 List<String> blacklistStrings = Lists.newArrayList(); 624 for (CmsUUID id : blacklist) { 625 blacklistStrings.add("\"" + id.toString() + "\""); 626 } 627 if (!blacklistStrings.isEmpty()) { 628 result = "&fq=" + CmsEncoder.encode("-id:(" + CmsStringUtil.listAsString(blacklistStrings, " OR ") + ")"); 629 } 630 return result; 631 } 632 633 /** 634 * Returns the category filter string.<p> 635 * 636 * @return the category filter 637 */ 638 String getCategoryFilterPart() { 639 640 String result = ""; 641 if (!m_config.getCategories().isEmpty()) { 642 List<String> categoryVals = Lists.newArrayList(); 643 for (String path : m_config.getCategories()) { 644 try { 645 path = CmsCategoryService.getInstance().getCategory( 646 m_cms, 647 m_cms.getRequestContext().addSiteRoot(path)).getPath(); 648 categoryVals.add("\"" + path + "\""); 649 } catch (CmsException e) { 650 LOG.warn(e.getLocalizedMessage(), e); 651 } 652 } 653 if (!categoryVals.isEmpty()) { 654 String operator = " " + m_config.getCategoryMode() + " "; 655 String valueExpression = CmsStringUtil.listAsString(categoryVals, operator); 656 result = "category_exact:(" + valueExpression + ")"; 657 658 } 659 } 660 return result; 661 } 662 663 /** 664 * Returns the category filter string.<p> 665 * 666 * @return the category filter 667 */ 668 String getCategoryFolderFilter() { 669 670 String result = ""; 671 String defaultPart = getFolderFilterPart(); 672 String categoryFilterPart = getCategoryFilterPart(); 673 if (!categoryFilterPart.isEmpty()) { 674 defaultPart = "((" + defaultPart + ") AND (" + categoryFilterPart + "))"; 675 } 676 for (CmsCategoryFolderRestrictionBean restriction : m_config.getCategoryFolderRestrictions()) { 677 String restrictionQuery = restriction.toString(); 678 if (!restrictionQuery.isEmpty()) { 679 restrictionQuery = "(" + restrictionQuery + " AND " + defaultPart + ")"; 680 if (!result.isEmpty()) { 681 result += " OR "; 682 } 683 result += restrictionQuery; 684 } 685 } 686 if (result.isEmpty()) { 687 result = defaultPart; 688 } 689 return "fq=" + CmsEncoder.encode(result); 690 } 691 692 /** 693 * The fields returned by default. Typically the output is done via display formatters and hence nearly no 694 * field is necessary. Returning all fields might cause performance problems. 695 * 696 * @return the default return fields. 697 */ 698 String getDefaultReturnFields() { 699 700 StringBuffer fields = new StringBuffer(""); 701 fields.append(CmsSearchField.FIELD_PATH); 702 fields.append(','); 703 fields.append(CmsSearchField.FIELD_INSTANCEDATE).append(CmsSearchField.FIELD_POSTFIX_DATE); 704 fields.append(','); 705 fields.append(CmsSearchField.FIELD_INSTANCEDATE_END).append(CmsSearchField.FIELD_POSTFIX_DATE); 706 fields.append(','); 707 fields.append(CmsSearchField.FIELD_INSTANCEDATE_CURRENT_TILL).append(CmsSearchField.FIELD_POSTFIX_DATE); 708 fields.append(','); 709 fields.append(CmsSearchField.FIELD_INSTANCEDATE).append('_').append(getSearchLocale().toString()).append( 710 CmsSearchField.FIELD_POSTFIX_DATE); 711 fields.append(','); 712 fields.append(CmsSearchField.FIELD_INSTANCEDATE_END).append('_').append(getSearchLocale().toString()).append( 713 CmsSearchField.FIELD_POSTFIX_DATE); 714 fields.append(','); 715 fields.append(CmsSearchField.FIELD_INSTANCEDATE_CURRENT_TILL).append('_').append( 716 getSearchLocale().toString()).append(CmsSearchField.FIELD_POSTFIX_DATE); 717 fields.append(','); 718 fields.append(CmsSearchField.FIELD_ID); 719 fields.append(','); 720 fields.append(CmsSearchField.FIELD_SOLR_ID); 721 fields.append(','); 722 fields.append(CmsSearchField.FIELD_DISPTITLE).append('_').append(getSearchLocale().toString()).append("_sort"); 723 fields.append(','); 724 fields.append(CmsSearchField.FIELD_LINK); 725 fields.append(','); 726 fields.append(CmsSearchField.FIELD_GEOCOORDS); 727 return fields.toString(); 728 } 729 730 /** 731 * Returns the filter query string.<p> 732 * 733 * @return the filter query 734 */ 735 String getFilterQuery() { 736 737 String result = m_config.getFilterQuery(); 738 if (result == null) { 739 result = ""; 740 } 741 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(result) && !result.startsWith("&")) { 742 result = "&" + result; 743 } 744 if (!result.contains(CommonParams.FL + "=")) { 745 result += "&" + CommonParams.FL + "=" + CmsEncoder.encode(getDefaultReturnFields()); 746 } 747 I_CmsDateRestriction dateRestriction = m_config.getDateRestriction(); 748 if (dateRestriction != null) { 749 result += "&fq=" 750 + CmsEncoder.encode( 751 CmsSearchField.FIELD_INSTANCEDATE_CURRENT_TILL 752 + "_" 753 + getSearchLocale().toString() 754 + "_dt:" 755 + dateRestriction.getRange()); 756 757 } 758 result += "&fq=con_locales:" + getSearchLocale().toString(); 759 return result; 760 } 761 762 /** 763 * Returns the folder filter string.<p> 764 * 765 * @return the folder filter 766 */ 767 String getFolderFilterPart() { 768 769 String result = ""; 770 List<String> parentFolderVals = Lists.newArrayList(); 771 if (!m_config.getFolders().isEmpty()) { 772 for (String value : m_config.getFolders()) { 773 parentFolderVals.add("\"" + value + "\""); 774 } 775 } 776 if (parentFolderVals.isEmpty()) { 777 result = "parent-folders:(\"/\")"; 778 } else { 779 result = "parent-folders:(" + CmsStringUtil.listAsString(parentFolderVals, " OR ") + ")"; 780 } 781 return result; 782 } 783 784 /** 785 * Returns the Geo filter query string.<p> 786 * 787 * @return the Geo filter query string 788 */ 789 String getGeoFilterQuery() { 790 791 String result = ""; 792 CmsGeoFilterBean geoFilterBean = m_config.getGeoFilter(); 793 if (geoFilterBean != null) { 794 String fq = CmsSolrQueryUtil.composeGeoFilterQuery( 795 CmsSearchField.FIELD_GEOCOORDS, 796 geoFilterBean.getCoordinates(), 797 geoFilterBean.getRadius(), 798 "km"); 799 result = "&fq=" + fq; 800 } 801 return result; 802 } 803 804 /** 805 * Returns the filter query string.<p> 806 * 807 * @return the filter query 808 */ 809 String getPreconfiguredFilterQuery() { 810 811 String result = ""; 812 if (m_config.hasPreconfiguredRestrictions()) { 813 CmsRestrictionsBean restrictions = m_config.getPreconfiguredRestrictions(); 814 String restriction = generatePreconfiguredRestriction(null, restrictions.getRestrictionsForType(null)); 815 if (!restriction.isEmpty()) { 816 result = "&fq=" + CmsEncoder.encode(restriction); 817 } 818 Collection<String> typedRestrictions = new HashSet<>(); 819 for (String type : m_config.getTypes()) { 820 restriction = generatePreconfiguredRestriction(type, restrictions.getRestrictionsForType(type)); 821 if (!restriction.isEmpty()) { 822 typedRestrictions.add(restriction); 823 } 824 } 825 if (!typedRestrictions.isEmpty()) { 826 result += "&fq=" 827 + CmsEncoder.encode( 828 "(" + typedRestrictions.stream().reduce((r1, r2) -> (r1 + " OR " + r2)).get() + ")"); 829 } 830 } 831 return result; 832 } 833 834 /** 835 * Returns the resource type filter string.<p> 836 * 837 * @return the folder filter 838 */ 839 String getResourceTypeFilter() { 840 841 String result = ""; 842 // When we have pre-configured restrictions, we need to combine the type filter with these restrictions. 843 if (!m_config.hasPreconfiguredRestrictions()) { 844 List<String> typeVals = Lists.newArrayList(); 845 for (String type : m_config.getTypes()) { 846 typeVals.add("\"" + type + "\""); 847 } 848 if (!typeVals.isEmpty()) { 849 result = "&fq=" + CmsEncoder.encode("type:(" + CmsStringUtil.listAsString(typeVals, " OR ") + ")"); 850 } 851 } 852 return result; 853 } 854 855 /** 856 * Generates the search string part for one input field value. 857 * @param rule the preconfigured rule. 858 * @param fieldValues the values in the field. 859 * @return the search term part for the value in the field. 860 */ 861 String resolveFieldValues(CmsRestrictionRule rule, FieldValues fieldValues) { 862 863 Collection<String> values = fieldValues.getValues(); 864 Collection<String> finalValues; 865 if (FieldType.PLAIN.equals(fieldValues.getFieldType())) { 866 // We are sure that there is exactly one value in that case. 867 return "(" + values.iterator().next() + ")"; 868 } else { 869 switch (rule.getMatchType()) { 870 case DEFAULT: 871 finalValues = values; 872 break; 873 case EXACT: 874 finalValues = values.stream().map(v -> ("\"" + v + "\"")).collect(Collectors.toSet()); 875 break; 876 case INFIX: 877 finalValues = values.stream().map( 878 v -> ("(" + v + " OR *" + v + " OR *" + v + "* OR " + v + "*)")).collect(Collectors.toSet()); 879 break; 880 case POSTFIX: 881 finalValues = values.stream().map(v -> ("(" + v + " OR *" + v + ")")).collect(Collectors.toSet()); 882 break; 883 case PREFIX: 884 finalValues = values.stream().map(v -> ("(" + v + " OR " + v + "*)")).collect(Collectors.toSet()); 885 break; 886 default: 887 throw new IllegalArgumentException("Unknown match type '" + rule.getMatchType() + "'."); 888 } 889 if (finalValues.size() > 1) { 890 String seperator = " " + rule.getCombinationModeInField().toString() + " "; 891 return "(" + finalValues.stream().reduce((v1, v2) -> v1 + seperator + v2).get() + ")"; 892 } else { 893 return finalValues.iterator().next(); 894 } 895 896 } 897 } 898 899 /** 900 * Returns a flag, indicating if the release and expiration date should be ignored. 901 * @return a flag, indicating if the release and expiration date should be ignored. 902 */ 903 private Boolean getIgnoreReleaseAndExpiration() { 904 905 return Boolean.valueOf(m_config.isShowExpired()); 906 } 907}