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