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