001/* 002 * Copyright 2013 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.cms.search.ui.model; 017 018import java.util.ArrayList; 019import java.util.Collection; 020import java.util.Collections; 021import java.util.HashSet; 022import java.util.LinkedHashMap; 023import java.util.List; 024import java.util.Map; 025import java.util.Optional; 026import java.util.Set; 027import java.util.stream.Collectors; 028 029import org.apache.avalon.framework.activity.Disposable; 030import org.apache.avalon.framework.component.ComponentException; 031import org.apache.avalon.framework.configuration.Configurable; 032import org.apache.avalon.framework.configuration.Configuration; 033import org.apache.avalon.framework.configuration.ConfigurationException; 034import org.apache.avalon.framework.context.Context; 035import org.apache.avalon.framework.context.ContextException; 036import org.apache.avalon.framework.context.Contextualizable; 037import org.apache.avalon.framework.service.ServiceException; 038import org.apache.avalon.framework.service.ServiceManager; 039import org.apache.avalon.framework.service.Serviceable; 040import org.apache.cocoon.components.LifecycleHelper; 041import org.apache.cocoon.util.log.SLF4JLoggerAdapter; 042import org.apache.commons.lang3.StringUtils; 043 044import org.ametys.cms.contenttype.ContentType; 045import org.ametys.cms.contenttype.ContentTypeExtensionPoint; 046import org.ametys.cms.contenttype.ContentTypesHelper; 047import org.ametys.cms.repository.Content; 048import org.ametys.cms.search.model.SearchCriterionHelper; 049import org.ametys.cms.search.model.SystemPropertyExtensionPoint; 050import org.ametys.cms.search.query.Query.Operator; 051import org.ametys.cms.search.ui.model.impl.IndexingFieldSearchUICriterion; 052import org.ametys.cms.search.ui.model.impl.SystemSearchUICriterion; 053import org.ametys.runtime.i18n.I18nizableText; 054import org.ametys.runtime.model.ElementDefinition; 055import org.ametys.runtime.model.ModelHelper.ConfigurationAndPluginName; 056import org.ametys.runtime.model.ModelItem; 057import org.ametys.runtime.model.ViewParser; 058import org.ametys.runtime.plugin.component.ThreadSafeComponentManager; 059 060/** 061 * Static implementation of a {@link AbstractSearchUIModel} 062 */ 063public class StaticSearchUIModel extends AbstractSearchUIModel implements Serviceable, Contextualizable, Configurable, Disposable 064{ 065 /** ComponentManager for {@link SearchUICriterion}s. */ 066 protected ThreadSafeComponentManager<SearchUICriterion> _searchCriterionManager; 067 068 /** The context. */ 069 protected Context _context; 070 071 /** The service manager */ 072 protected ServiceManager _manager; 073 074 /** The content type helper. */ 075 protected ContentTypesHelper _contentTypesHelper; 076 077 /** The helper for columns */ 078 protected ColumnHelper _columnHelper; 079 080 /** The content type extension point */ 081 protected ContentTypeExtensionPoint _contentTypeExtensionPoint; 082 083 /** The system property extension point. */ 084 protected SystemPropertyExtensionPoint _systemPropertyExtensionPoint; 085 086 /** The search criterion helper */ 087 protected SearchCriterionHelper _searchCriterionHelper; 088 089 private int _criteriaIndex; 090 091 private int _pageSize; 092 private String _workspace; 093 private String _searchUrl; 094 private String _searchUrlPlugin; 095 private String _exportCSVUrl; 096 private String _exportCSVUrlPlugin; 097 private String _exportDOCUrl; 098 private String _exportDOCUrlPlugin; 099 private String _exportXMLUrl; 100 private String _exportXMLUrlPlugin; 101 private String _exportPDFUrl; 102 private String _exportPDFUrlPlugin; 103 private String _printUrl; 104 private String _printUrlPlugin; 105 private String _summaryView; 106 private boolean _sortOnMultipleJoin; 107 108 @Override 109 public void contextualize(Context context) throws ContextException 110 { 111 _context = context; 112 } 113 114 public void service(ServiceManager manager) throws ServiceException 115 { 116 _manager = manager; 117 118 _contentTypesHelper = (ContentTypesHelper) manager.lookup(ContentTypesHelper.ROLE); 119 _columnHelper = (ColumnHelper) manager.lookup(ColumnHelper.ROLE); 120 _contentTypeExtensionPoint = (ContentTypeExtensionPoint) manager.lookup(ContentTypeExtensionPoint.ROLE); 121 _systemPropertyExtensionPoint = (SystemPropertyExtensionPoint) manager.lookup(SystemPropertyExtensionPoint.ROLE); 122 _searchCriterionHelper = (SearchCriterionHelper) manager.lookup(SearchCriterionHelper.ROLE); 123 } 124 125 @Override 126 public void configure(Configuration configuration) throws ConfigurationException 127 { 128 try 129 { 130 _searchCriterionManager = new ThreadSafeComponentManager<>(); 131 _searchCriterionManager.setLogger(getLogger()); 132 _searchCriterionManager.contextualize(_context); 133 _searchCriterionManager.service(_manager); 134 135 Configuration searchConfig = configuration.getChild("SearchModel"); 136// _cTypes = _configureContentTypes(searchConfig.getChild("content-types", false)); 137 _configureContentTypes(searchConfig.getChild("content-types", false)); 138 139 _summaryView = searchConfig.getChild("summary-view").getValue(null); 140 _sortOnMultipleJoin = searchConfig.getChild("allow-sort-on-multiple-join").getValueAsBoolean(super.allowSortOnMultipleJoin()); 141 142 // Get the base content types and try to find common ancestors. 143 Set<String> baseCTypeIds = _configureBaseContentTypes(searchConfig.getChild("content-types")); 144 Set<ContentType> commonContentTypes = _contentTypesHelper.getCommonAncestors(baseCTypeIds) 145 .stream() 146 .map(_contentTypeExtensionPoint::getExtension) 147 .collect(Collectors.toSet()); 148 149 _pageSize = searchConfig.getChild("page-size").getValueAsInteger(50); 150 if (_pageSize < 0) 151 { 152 _pageSize = 50; 153 } 154 _workspace = searchConfig.getChild("workspace").getValue(null); 155 156 _configureSearchUrl(searchConfig); 157 _configureExportCSVUrl(searchConfig); 158 _configureExportDOCUrl(searchConfig); 159 _configureExportXMLUrl(searchConfig); 160 _configureExportPDFUrl(searchConfig); 161 _configurePrintUrl(searchConfig); 162 163 _criteriaIndex = 0; 164 List<String> searchCriteriaRoles = new ArrayList<>(); 165 List<String> facetedSearchUICriterionRoles = new ArrayList<>(); 166 List<String> advancedSearchUICriterionRoles = new ArrayList<>(); 167 168 _addCriteriaComponents(commonContentTypes, searchConfig.getChild("simple-search-criteria"), searchCriteriaRoles); 169 170 Configuration advancedCriteriaConf = searchConfig.getChild("advanced-search-criteria", false); 171 if (advancedCriteriaConf != null) 172 { 173 _addCriteriaComponents(commonContentTypes, advancedCriteriaConf, advancedSearchUICriterionRoles); 174 } 175 176 Configuration facetsConf = searchConfig.getChild("facets", false); 177 if (facetsConf != null) 178 { 179 _addFacetCriteriaComponents(commonContentTypes, facetsConf, facetedSearchUICriterionRoles); 180 } 181 182 _searchCriterionManager.initialize(); 183 184 _searchCriteria = new LinkedHashMap<>(); 185 _facetedCriteria = new LinkedHashMap<>(); 186 _advancedSearchCriteria = new LinkedHashMap<>(); 187 188 _configureCriteria(_searchCriteria, searchCriteriaRoles, false); 189 if (advancedCriteriaConf != null) 190 { 191 if (advancedSearchUICriterionRoles.isEmpty()) 192 { 193 // The facet root configuration is present but has no criteria configuration: 194 // copy the simple search criteria. 195 _copyAdvancedCriteria(_advancedSearchCriteria, _searchCriteria.values()); 196 } 197 else 198 { 199 _configureCriteria(_advancedSearchCriteria, advancedSearchUICriterionRoles, false); 200 } 201 } 202 203 if (facetsConf != null) 204 { 205 if (facetedSearchUICriterionRoles.isEmpty()) 206 { 207 // The facet root configuration is present but has no criteria configuration: 208 // copy the simple search criteria which are facetable. 209 _copyFacetableCriteria(_facetedCriteria, _searchCriteria.values()); 210 } 211 else 212 { 213 _configureCriteria(_facetedCriteria, facetedSearchUICriterionRoles, true); 214 } 215 } 216 217 Configuration columnConfs = searchConfig.getChild("columns").getChild("default"); 218 ViewParser parser = new StaticSearchUIModelColumnsParser(commonContentTypes); 219 try 220 { 221 LifecycleHelper.setupComponent(parser, new SLF4JLoggerAdapter(getLogger()), _context, _manager, null); 222 _resultItems = parser.parseView(new ConfigurationAndPluginName(columnConfs, "")); 223 } 224 catch (Exception e) 225 { 226 throw new ConfigurationException("Unable to parse columns of search model", columnConfs, e); 227 } 228 finally 229 { 230 LifecycleHelper.dispose(parser); 231 } 232 } 233 catch (Exception e) 234 { 235 throw new ConfigurationException("Unable to create local component managers.", configuration, e); 236 } 237 } 238 239 @Override 240 public void dispose() 241 { 242 _searchCriterionManager.dispose(); 243 _searchCriterionManager = null; 244 } 245 246 @Override 247 public Set<String> getContentTypes(Map<String, Object> contextualParameters) 248 { 249 return Collections.unmodifiableSet(_cTypes); 250 } 251 252 @Override 253 public Set<String> getExcludedContentTypes(Map<String, Object> contextualParameters) 254 { 255 return Collections.unmodifiableSet(_excludedCTypes); 256 } 257 258 @Override 259 public int getPageSize(Map<String, Object> contextualParameters) 260 { 261 return _pageSize; 262 } 263 264 @Override 265 public String getWorkspace(Map<String, Object> contextualParameters) 266 { 267 return _workspace; 268 } 269 270 @Override 271 public String getSearchUrl(Map<String, Object> contextualParameters) 272 { 273 return _searchUrl; 274 } 275 276 @Override 277 public String getSearchUrlPlugin(Map<String, Object> contextualParameters) 278 { 279 return _searchUrlPlugin; 280 } 281 282 @Override 283 public String getExportCSVUrl(Map<String, Object> contextualParameters) 284 { 285 return _exportCSVUrl; 286 } 287 288 @Override 289 public String getExportCSVUrlPlugin(Map<String, Object> contextualParameters) 290 { 291 return _exportCSVUrlPlugin; 292 } 293 294 @Override 295 public String getExportDOCUrl(Map<String, Object> contextualParameters) 296 { 297 return _exportDOCUrl; 298 } 299 300 @Override 301 public String getExportDOCUrlPlugin(Map<String, Object> contextualParameters) 302 { 303 return _exportDOCUrlPlugin; 304 } 305 306 @Override 307 public String getExportXMLUrl(Map<String, Object> contextualParameters) 308 { 309 return _exportXMLUrl; 310 } 311 312 @Override 313 public String getExportXMLUrlPlugin(Map<String, Object> contextualParameters) 314 { 315 return _exportXMLUrlPlugin; 316 } 317 318 @Override 319 public String getExportPDFUrl(Map<String, Object> contextualParameters) 320 { 321 return _exportPDFUrl; 322 } 323 324 @Override 325 public String getExportPDFUrlPlugin(Map<String, Object> contextualParameters) 326 { 327 return _exportPDFUrlPlugin; 328 } 329 330 @Override 331 public String getPrintUrl(Map<String, Object> contextualParameters) 332 { 333 return _printUrl; 334 } 335 336 @Override 337 public String getPrintUrlPlugin(Map<String, Object> contextualParameters) 338 { 339 return _printUrlPlugin; 340 } 341 342 @Override 343 public String getSummaryView() 344 { 345 return _summaryView; 346 } 347 348 @Override 349 public boolean allowSortOnMultipleJoin() 350 { 351 return _sortOnMultipleJoin; 352 } 353 354// @Override 355// public Map<String, SearchUICriterion> getCriteria(Map<String, Object> contextualParameters) 356// { 357// return Collections.unmodifiableMap(_searchCriteria); 358// } 359// 360// @Override 361// public Map<String, SearchUICriterion> getFacetedCriteria(Map<String, Object> contextualParameters) 362// { 363// return Collections.unmodifiableMap(_facetedCriteria); 364// } 365// 366// @Override 367// public Map<String, SearchUICriterion> getAdvancedCriteria(Map<String, Object> contextualParameters) 368// { 369// return Collections.unmodifiableMap(_advancedSearchCriteria); 370// } 371 372 /** 373 * Configure the content type ids 374 * @param configuration The content types configuration 375 * @throws ConfigurationException If an error occurs 376 */ 377// protected Set<String> _configureContentTypes(Configuration configuration) throws ConfigurationException 378 protected void _configureContentTypes(Configuration configuration) throws ConfigurationException 379 { 380 _cTypes = new HashSet<>(); 381 _excludedCTypes = new HashSet<>(); 382 383 if (configuration != null) 384 { 385 Configuration excludeConf = configuration.getChild("exclude"); 386 387 List<String> excludedTags = new ArrayList<>(); 388 for (Configuration tagCong : excludeConf.getChildren("tag")) 389 { 390 excludedTags.add(tagCong.getValue()); 391 } 392 393 List<String> excludedCTypes = new ArrayList<>(); 394 for (Configuration cType : excludeConf.getChildren("content-type")) 395 { 396 excludedCTypes.add(cType.getValue()); 397 } 398 399 Configuration[] cTypesConfiguration = configuration.getChildren("content-type"); 400 if (cTypesConfiguration.length == 0) 401 { 402 // Keep "content types" empty. 403 for (String id : _contentTypeExtensionPoint.getExtensionsIds()) 404 { 405 if (!_isValidContentType(id, excludedTags, excludedCTypes)) 406 { 407 _excludedCTypes.add(id); 408 } 409 } 410 } 411 else 412 { 413 for (Configuration conf : configuration.getChildren("content-type")) 414 { 415 String id = conf.getAttribute("id"); 416 _cTypes.add(id); 417 if (!_isValidContentType(id, excludedTags, excludedCTypes)) 418 { 419 _excludedCTypes.add(id); 420 } 421 422 for (String subTypeId : _contentTypeExtensionPoint.getSubTypes(id)) 423 { 424 if (!_isValidContentType(subTypeId, excludedTags, excludedCTypes)) 425 { 426 _excludedCTypes.add(subTypeId); 427 } 428 } 429 } 430 } 431 } 432 } 433 434 /** 435 * Configure the base content type ids. 436 * @param configuration The content types configuration 437 * @return The set of base content type ids 438 * @throws ConfigurationException If an error occurs 439 */ 440 protected Set<String> _configureBaseContentTypes(Configuration configuration) throws ConfigurationException 441 { 442 Set<String> cTypes = new HashSet<>(); 443 444 Configuration[] cTypesConfiguration = configuration.getChildren("content-type"); 445 if (cTypesConfiguration.length == 0) 446 { 447 cTypes.addAll(_contentTypeExtensionPoint.getExtensionsIds()); 448 } 449 else 450 { 451 for (Configuration conf : cTypesConfiguration) 452 { 453 cTypes.add(conf.getAttribute("id")); 454 } 455 } 456 457 return cTypes; 458 } 459 460 /** 461 * Determines if the content type is a valid content type in current configuration 462 * @param id The content type id 463 * @param excludedTags The tags to exclude 464 * @param excludedContentTypes The content types to exclude 465 * @return <code>true</code> if the content type is a valid content type 466 */ 467 protected boolean _isValidContentType (String id, List<String> excludedTags, List<String> excludedContentTypes) 468 { 469 if (excludedContentTypes.contains(id)) 470 { 471 return false; 472 } 473 474 ContentType cType = _contentTypeExtensionPoint.getExtension(id); 475 for (String tag : excludedTags) 476 { 477 if (cType.hasTag(tag)) 478 { 479 return false; 480 } 481 } 482 483 return true; 484 } 485 486 private void _configureSearchUrl(Configuration configuration) 487 { 488 _searchUrlPlugin = configuration.getChild("search-url").getAttribute("plugin", DEFAULT_URL_PLUGIN); 489 _searchUrl = configuration.getChild("search-url").getValue(DEFAULT_SEARCH_URL); 490 } 491 492 private void _configureExportCSVUrl(Configuration configuration) 493 { 494 _exportCSVUrlPlugin = configuration.getChild("export-csv-url").getAttribute("plugin", DEFAULT_URL_PLUGIN); 495 _exportCSVUrl = configuration.getChild("export-csv-url").getValue(DEFAULT_EXPORT_CSV_URL); 496 } 497 498 private void _configureExportDOCUrl(Configuration configuration) 499 { 500 _exportDOCUrlPlugin = configuration.getChild("export-doc-url").getAttribute("plugin", DEFAULT_URL_PLUGIN); 501 _exportDOCUrl = configuration.getChild("export-doc-url").getValue(DEFAULT_EXPORT_DOC_URL); 502 } 503 504 private void _configureExportXMLUrl(Configuration configuration) 505 { 506 _exportXMLUrlPlugin = configuration.getChild("export-xml-url").getAttribute("plugin", DEFAULT_URL_PLUGIN); 507 _exportXMLUrl = configuration.getChild("export-xml-url").getValue(DEFAULT_EXPORT_XML_URL); 508 } 509 510 private void _configureExportPDFUrl(Configuration configuration) 511 { 512 _exportPDFUrlPlugin = configuration.getChild("export-pdf-url").getAttribute("plugin", DEFAULT_URL_PLUGIN); 513 _exportPDFUrl = configuration.getChild("export-pdf-url").getValue(DEFAULT_EXPORT_PDF_URL); 514 } 515 516 private void _configurePrintUrl(Configuration configuration) 517 { 518 _printUrlPlugin = configuration.getChild("print-url").getAttribute("plugin", DEFAULT_URL_PLUGIN); 519 _printUrl = configuration.getChild("print-url").getValue(DEFAULT_PRINT_URL); 520 } 521 522 /** 523 * Add criteria components to the search criteria manager. 524 * @param commonContentTypes the model's common content types. 525 * @param configuration the model configuration. 526 * @param searchCriteriaRoles the criteria role list to fill. 527 * @throws ConfigurationException if an error occurs. 528 */ 529 protected void _addCriteriaComponents(Set<ContentType> commonContentTypes, Configuration configuration, List<String> searchCriteriaRoles) throws ConfigurationException 530 { 531 for (Configuration groupConf : configuration.getChildren("group")) 532 { 533 I18nizableText group = _configureI18nizableText(groupConf.getChild("label", false), null); 534 535 _addCriteriaComponents (commonContentTypes, groupConf, searchCriteriaRoles, group); 536 } 537 538 // Criteria without groups 539 _addCriteriaComponents (commonContentTypes, configuration, searchCriteriaRoles, null); 540 } 541 542 /** 543 * Add standard criteria components to the search criteria manager. 544 * @param commonContentTypes the model's common content types. 545 * @param configuration the model configuration. 546 * @param searchCriteriaRoles the criteria role list to fill. 547 * @param group the criteria group. 548 * @throws ConfigurationException if an error occurs. 549 */ 550 protected void _addCriteriaComponents(Set<ContentType> commonContentTypes, Configuration configuration, List<String> searchCriteriaRoles, I18nizableText group) throws ConfigurationException 551 { 552 for (Configuration conf : configuration.getChildren("criteria")) 553 { 554 String fieldRef = conf.getAttribute("field-ref", null); 555 String systemProperty = conf.getAttribute("system-ref", null); 556 String customId = conf.getAttribute("custom-ref", null); 557 558 if (StringUtils.isNotEmpty(fieldRef)) 559 { 560 _addIndexingFieldCriteriaComponents(commonContentTypes, conf, searchCriteriaRoles, fieldRef, group); 561 } 562 else if (systemProperty != null) 563 { 564 _addSystemCriteriaComponents(commonContentTypes, conf, searchCriteriaRoles, systemProperty, group); 565 } 566 else if (customId != null) 567 { 568 _addCustomCriteriaComponents(commonContentTypes, conf, searchCriteriaRoles, customId, group); 569 } 570 } 571 } 572 573 /** 574 * Add facet criteria components to the search criteria manager. 575 * @param commonContentTypes the model's common content types. 576 * @param configuration the model configuration. 577 * @param searchCriteriaRoles the criteria role list to fill. 578 * @throws ConfigurationException if an error occurs. 579 */ 580 protected void _addFacetCriteriaComponents(Set<ContentType> commonContentTypes, Configuration configuration, List<String> searchCriteriaRoles) throws ConfigurationException 581 { 582 for (Configuration conf : configuration.getChildren("criteria")) 583 { 584 String fieldRef = conf.getAttribute("field-ref", null); 585 String systemProperty = conf.getAttribute("system-ref", null); 586 String customId = conf.getAttribute("custom-ref", null); 587 588 if (StringUtils.isNotEmpty(fieldRef)) 589 { 590 _addIndexingFieldCriteriaComponents(commonContentTypes, conf, searchCriteriaRoles, fieldRef, null); 591 } 592 else if (systemProperty != null) 593 { 594 _addSystemCriteriaComponents(commonContentTypes, conf, searchCriteriaRoles, systemProperty, null); 595 } 596 else if (customId != null) 597 { 598 _addCustomCriteriaComponents(commonContentTypes, conf, searchCriteriaRoles, customId, null); 599 } 600 } 601 } 602 603 /** 604 * Add a indexing field criteria component to the manager. 605 * @param commonContentTypes the model common content types. 606 * @param conf the criteria configuration. 607 * @param searchCriteriaRoles the criteria role list to fill. 608 * @param fieldRef the field path. 609 * @param group The group. 610 * @throws ConfigurationException if an error occurs. 611 */ 612 protected void _addIndexingFieldCriteriaComponents(Set<ContentType> commonContentTypes, Configuration conf, List<String> searchCriteriaRoles, String fieldRef, I18nizableText group) throws ConfigurationException 613 { 614 try 615 { 616 if (commonContentTypes.isEmpty() && (fieldRef.equals("*") || fieldRef.equals(Content.ATTRIBUTE_TITLE))) 617 { 618 // If no common ancestors, only title attribute is allowed 619 String role = Content.ATTRIBUTE_TITLE + _criteriaIndex; 620 _criteriaIndex++; 621 Configuration criteriaConf = _searchCriterionHelper.getIndexingFieldCriteriaConfiguration(this, Optional.ofNullable(conf), Set.of(), Content.ATTRIBUTE_TITLE, Optional.of(Operator.EQ), Optional.ofNullable(group)); 622 623 _searchCriterionManager.addComponent("cms", null, role, IndexingFieldSearchUICriterion.class, criteriaConf); 624 625 searchCriteriaRoles.add(role); 626 } 627 else if (!commonContentTypes.isEmpty() && fieldRef.equals("*")) 628 { 629 Set<String> commonContentTypeIds = commonContentTypes.stream() 630 .map(ContentType::getId) 631 .collect(Collectors.toSet()); 632 633 for (ContentType commonContentType : commonContentTypes) 634 { 635 for (ModelItem modelItem : commonContentType.getModelItems()) 636 { 637 // Get only first-level field (ignore composites and repeaters) 638 if (modelItem instanceof ElementDefinition definition) 639 { 640 String role = definition.getName() + _criteriaIndex; 641 _criteriaIndex++; 642 Configuration criteriaConf = _searchCriterionHelper.getIndexingFieldCriteriaConfiguration(this, Optional.ofNullable(conf), commonContentTypeIds, definition.getName(), Optional.empty(), Optional.ofNullable(group)); 643 644 _searchCriterionManager.addComponent("cms", null, role, IndexingFieldSearchUICriterion.class, criteriaConf); 645 646 searchCriteriaRoles.add(role); 647 } 648 } 649 } 650 } 651 else if (!commonContentTypes.isEmpty()) 652 { 653 Set<String> commonContentTypeIds = commonContentTypes.stream() 654 .map(ContentType::getId) 655 .collect(Collectors.toSet()); 656 657 // The field ref is the indexing field path. 658 String role = fieldRef + _criteriaIndex; 659 _criteriaIndex++; 660 Configuration criteriaConf = _searchCriterionHelper.getIndexingFieldCriteriaConfiguration(this, Optional.ofNullable(conf), commonContentTypeIds, fieldRef, Optional.empty(), Optional.ofNullable(group)); 661 662 _searchCriterionManager.addComponent("cms", null, role, IndexingFieldSearchUICriterion.class, criteriaConf); 663 664 searchCriteriaRoles.add(role); 665 } 666 } 667 catch (Exception e) 668 { 669 throw new ConfigurationException("Unable to instanciate IndexingFieldSearchUICriterion for field " + fieldRef, conf, e); 670 } 671 } 672 673 /** 674 * Add a system criteria component to the manager. 675 * @param commonContentTypes the model common content types. 676 * @param originalConf the criteria configuration. 677 * @param searchCriteriaRoles the criteria role list to fill. 678 * @param property the system property id. 679 * @param group The group. 680 * @throws ConfigurationException if an error occurs. 681 */ 682 protected void _addSystemCriteriaComponents(Set<ContentType> commonContentTypes, Configuration originalConf, List<String> searchCriteriaRoles, String property, I18nizableText group) throws ConfigurationException 683 { 684 try 685 { 686 Set<String> commonContentTypeIds = commonContentTypes.stream() 687 .map(ContentType::getId) 688 .collect(Collectors.toSet()); 689 690 if (property.equals("*")) 691 { 692 for (String propId : _systemPropertyExtensionPoint.getSearchProperties()) 693 { 694 String role = propId + _criteriaIndex; 695 _criteriaIndex++; 696 697 Configuration criteriaConf = _searchCriterionHelper.getSystemCriteriaConfiguration(this, Optional.ofNullable(originalConf), commonContentTypeIds, propId, Optional.ofNullable(group)); 698 _searchCriterionManager.addComponent("cms", null, role, SystemSearchUICriterion.class, criteriaConf); 699 700 searchCriteriaRoles.add(role); 701 } 702 } 703 else 704 { 705 String role = property + _criteriaIndex; 706 _criteriaIndex++; 707 708 Configuration criteriaConf = _searchCriterionHelper.getSystemCriteriaConfiguration(this, Optional.ofNullable(originalConf), commonContentTypeIds, property, Optional.ofNullable(group)); 709 _searchCriterionManager.addComponent("cms", null, role, SystemSearchUICriterion.class, criteriaConf); 710 711 searchCriteriaRoles.add(role); 712 } 713 } 714 catch (Exception e) 715 { 716 throw new ConfigurationException("Unable to instanciate SystemSearchUICriterion for property " + property, originalConf, e); 717 } 718 } 719 720 /** 721 * Add a custom criteria component to the manager. 722 * @param commonContentTypes the model common content types. 723 * @param conf the criteria configuration. 724 * @param searchCriteriaRoles the criteria role list to fill. 725 * @param searchCriterionId the custom criteria id. 726 * @param group The group. Can be null. 727 * @throws ConfigurationException if an error occurs. 728 */ 729 protected void _addCustomCriteriaComponents(Set<ContentType> commonContentTypes, Configuration conf, List<String> searchCriteriaRoles, String searchCriterionId, I18nizableText group) throws ConfigurationException 730 { 731 Configuration classConf = conf.getChild("class"); 732 String className = classConf.getAttribute("name", null); 733 734 if (className == null) 735 { 736 throw new ConfigurationException("The custom search criterion '" + searchCriterionId + "' does not specifiy a class.", conf); 737 } 738 739 try 740 { 741 String role = searchCriterionId + _criteriaIndex; 742 _criteriaIndex++; 743 744 // Common content type or first content type 745 String defaultContentTypeId = _searchCriterionHelper.getAllContentTypes(this, Map.of()).iterator().next(); 746 Set<String> contentTypeIds = commonContentTypes.isEmpty() ? Set.of(defaultContentTypeId) : commonContentTypes.stream() 747 .map(ContentType::getId) 748 .collect(Collectors.toSet()); 749 750 Configuration criteriaConf = _searchCriterionHelper.getCustomCriteriaConfiguration(this, Optional.ofNullable(conf), contentTypeIds, searchCriterionId, Optional.ofNullable(group)); 751 752 @SuppressWarnings("unchecked") 753 Class<SearchUICriterion> searchCriteriaClass = (Class<SearchUICriterion>) Class.forName(className); 754 _searchCriterionManager.addComponent("cms", null, role, searchCriteriaClass, criteriaConf); 755 756 searchCriteriaRoles.add(role); 757 } 758 catch (Exception e) 759 { 760 throw new ConfigurationException("Unable to instanciate custom SearchUICriterion for class: " + className, conf, e); 761 } 762 } 763 764 /** 765 * Lookup the previously initialized criteria components and fill the given map with them. 766 * @param criteriaMap the criteria map to fill. 767 * @param criteriaRoles the roles of the criteria components to lookup. 768 * @param checkFacetable true to check if the criteria are facetable. 769 * @throws ConfigurationException if an error occurs. 770 */ 771 protected void _configureCriteria(Map<String, SearchUICriterion> criteriaMap, List<String> criteriaRoles, boolean checkFacetable) throws ConfigurationException 772 { 773 for (String role : criteriaRoles) 774 { 775 try 776 { 777 SearchUICriterion criterion = _searchCriterionManager.lookup(role); 778 779 if (checkFacetable && !criterion.isFacetable()) 780 { 781 throw new ConfigurationException("The search criteria of id '" + criterion.getId() + "' is not facetable."); 782 } 783 784 criteriaMap.put(criterion.getId(), criterion); 785 } 786 catch (ComponentException e) 787 { 788 throw new ConfigurationException("Impossible to lookup the search criteria of role: " + role, e); 789 } 790 } 791 } 792 793 /** 794 * Copy all the allowed search criteria to the given criteria map. 795 * @param advancedCriteria the criteria map to fill. 796 * @param criteria the source criteria collection. 797 * @throws ConfigurationException if an error occurs. 798 */ 799 protected void _copyAdvancedCriteria(Map<String, SearchUICriterion> advancedCriteria, Collection<SearchUICriterion> criteria) throws ConfigurationException 800 { 801 for (SearchUICriterion criterion : criteria) 802 { 803 if (_isAdvanced(criterion)) 804 { 805 advancedCriteria.put(criterion.getId(), criterion); 806 } 807 } 808 } 809 810 /** 811 * Copy all the facetable search criteria to the given criteria map. 812 * @param facetedCriteria the criteria map to fill. 813 * @param criteria the source criteria collection. 814 * @throws ConfigurationException if an error occurs. 815 */ 816 protected void _copyFacetableCriteria(Map<String, SearchUICriterion> facetedCriteria, Collection<SearchUICriterion> criteria) throws ConfigurationException 817 { 818 for (SearchUICriterion criterion : criteria) 819 { 820 if (criterion.isFacetable()) 821 { 822 facetedCriteria.put(criterion.getId(), criterion); 823 } 824 } 825 } 826 827 /** 828 * Test if a search criterion can be used in advanced search mode. 829 * For instance: geocode, rich-text, file-typed criterion are not allowed. 830 * @param criterion the search criterion to test. 831 * @return <code>true</code> if the criterion can be used in advanced search mode, <code>false</code> otherwise. 832 */ 833 protected boolean _isAdvanced(SearchUICriterion criterion) 834 { 835 boolean isAdvanced = false; 836 837 switch (criterion.getType()) 838 { 839 case STRING: 840 case MULTILINGUAL_STRING: 841 case LONG: 842 case DOUBLE: 843 case DATE: 844 case DATETIME: 845 case BOOLEAN: 846 case CONTENT: 847 case USER: 848 case RICH_TEXT: 849 isAdvanced = true; 850 break; 851 case COMPOSITE: 852 case FILE: 853 case BINARY: 854 case REFERENCE: 855 case GEOCODE: 856 default: 857 // Do nothing. 858 break; 859 } 860 861 return isAdvanced; 862 } 863 864 private I18nizableText _configureI18nizableText(Configuration config, I18nizableText defaultValue) throws ConfigurationException 865 { 866 if (config != null) 867 { 868 return I18nizableText.parseI18nizableText(config, null); 869 } 870 else 871 { 872 return defaultValue; 873 } 874 } 875 876}