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