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