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