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.Collections; 020import java.util.List; 021import java.util.Map; 022import java.util.Optional; 023import java.util.Set; 024 025import org.apache.avalon.framework.component.ComponentException; 026import org.apache.avalon.framework.configuration.Configurable; 027import org.apache.avalon.framework.configuration.Configuration; 028import org.apache.avalon.framework.configuration.ConfigurationException; 029import org.apache.avalon.framework.configuration.DefaultConfiguration; 030import org.apache.avalon.framework.service.ServiceException; 031import org.apache.avalon.framework.service.ServiceManager; 032 033import org.ametys.cms.content.referencetable.HierarchicalReferenceTablesHelper; 034import org.ametys.cms.contenttype.AbstractMetadataSetElement; 035import org.ametys.cms.contenttype.ContentType; 036import org.ametys.cms.contenttype.MetadataDefinition; 037import org.ametys.cms.contenttype.MetadataDefinitionReference; 038import org.ametys.cms.contenttype.MetadataSet; 039import org.ametys.cms.contenttype.MetadataType; 040import org.ametys.cms.search.query.Query.Operator; 041import org.ametys.cms.search.ui.model.impl.IndexingFieldSearchUICriterion; 042import org.ametys.cms.search.ui.model.impl.MetadataSearchUIColumn; 043import org.ametys.cms.search.ui.model.impl.SystemSearchUIColumn; 044import org.ametys.cms.search.ui.model.impl.SystemSearchUICriterion; 045import org.ametys.runtime.plugin.component.ThreadSafeComponentManager; 046 047/** 048 * Generic implementation of {@link SearchUIModel} for reference tables 049 * The search tool model automatically declares simple first level metadatas as criteria and columns. 050 */ 051public class ReferenceTableSearchUIModel extends AbstractSearchUIModel implements Configurable 052{ 053 054 /** ComponentManager for {@link SearchUICriterion}s. */ 055 protected ThreadSafeComponentManager<SearchUICriterion> _searchCriteriaManager; 056 057 /** ComponentManager for {@link SearchUIColumn}s. */ 058 protected ThreadSafeComponentManager<SearchUIColumn> _searchColumnManager; 059 060 /** The search criteria roles. */ 061 protected List<String> _searchCriteriaRoles; 062 063 /** The search column roles. */ 064 protected List<String> _searchColumnRoles; 065 066 /** The helper component for hierarchical reference tables */ 067 protected HierarchicalReferenceTablesHelper _hierarchicalReferenceTableContentsHelper; 068 069 @Override 070 public void service(ServiceManager smanager) throws ServiceException 071 { 072 super.service(smanager); 073 _hierarchicalReferenceTableContentsHelper = (HierarchicalReferenceTablesHelper) smanager.lookup(HierarchicalReferenceTablesHelper.ROLE); 074 } 075 076 @Override 077 public void configure(Configuration configuration) throws ConfigurationException 078 { 079 try 080 { 081 String cTypeId = configuration.getChild("contentType").getValue(null); 082 if (cTypeId != null) 083 { 084 setContentTypes(Collections.singleton(cTypeId)); 085 } 086 087 _searchCriteriaManager = new ThreadSafeComponentManager<>(); 088 _searchCriteriaManager.setLogger(getLogger()); 089 _searchCriteriaManager.contextualize(_context); 090 _searchCriteriaManager.service(_manager); 091 092 _searchColumnManager = new ThreadSafeComponentManager<>(); 093 _searchColumnManager.setLogger(getLogger()); 094 _searchColumnManager.contextualize(_context); 095 _searchColumnManager.service(_manager); 096 } 097 catch (Exception e) 098 { 099 throw new ConfigurationException("Unable to create local component managers.", configuration, e); 100 } 101 } 102 103 @Override 104 public Set<String> getExcludedContentTypes(Map<String, Object> contextualParameters) 105 { 106 return Collections.emptySet(); 107 } 108 109 @Override 110 public Map<String, SearchUICriterion> getCriteria(Map<String, Object> contextualParameters) 111 { 112 try 113 { 114 if (_searchCriteria == null) 115 { 116 String cTypeId = getContentTypes(contextualParameters).iterator().next(); 117 ContentType cType = _cTypeEP.getExtension(cTypeId); 118 119 _searchCriteriaRoles = new ArrayList<>(); 120 121 addCriteriaComponents(cType); 122 _searchCriteriaManager.initialize(); 123 setCriteria(getSearchUICriteria(cType)); 124 } 125 } 126 catch (Exception e) 127 { 128 throw new RuntimeException("Impossible to initialize criteria components.", e); 129 } 130 131 return _searchCriteria; 132 } 133 134 @Override 135 public Map<String, SearchUICriterion> getFacetedCriteria(Map<String, Object> contextualParameters) 136 { 137 return Collections.emptyMap(); 138 } 139 140 @Override 141 public Map<String, SearchUICriterion> getAdvancedCriteria(Map<String, Object> contextualParameters) 142 { 143 return Collections.emptyMap(); 144 } 145 146 @Override 147 public Map<String, SearchUIColumn> getResultFields(Map<String, Object> contextualParameters) 148 { 149 try 150 { 151 if (_columns == null) 152 { 153 _searchColumnRoles = new ArrayList<>(); 154 155 String cTypeId = getContentTypes(contextualParameters).iterator().next(); 156 ContentType cType = _cTypeEP.getExtension(cTypeId); 157 158 addColumnComponents(cType); 159 _searchColumnManager.initialize(); 160 setResultFields(getColumns(cType)); 161 } 162 } 163 catch (Exception e) 164 { 165 throw new RuntimeException("Impossible to initialize column components.", e); 166 } 167 168 return _columns; 169 } 170 171 /** 172 * Add criteria components to the manager. 173 * @param cType the simple content type. 174 * @throws ConfigurationException if a configuration error occurs. 175 * @throws ComponentException if a component cannot be initialized. 176 */ 177 protected void addCriteriaComponents(ContentType cType) throws ConfigurationException, ComponentException 178 { 179 MetadataSet metadataSet = cType.getMetadataSetForEdition("main"); 180 for (AbstractMetadataSetElement subMetadataSetElement : metadataSet.getElements()) 181 { 182 // Get only simple metadata (ignore composites and repeaters) 183 if (subMetadataSetElement instanceof MetadataDefinitionReference) 184 { 185 String metadataName = ((MetadataDefinitionReference) subMetadataSetElement).getMetadataName(); 186 MetadataDefinition metadataDefinition = cType.getMetadataDefinition(metadataName); 187 188 if (metadataDefinition == null) 189 { 190 getLogger().warn("The metadata '{}' defined in the metadata set '{}' does not seem to exist.", metadataName, metadataSet); 191 } 192 else if (_filterMetadata(metadataDefinition)) 193 { 194 Operator operator = metadataDefinition.getType().equals(MetadataType.STRING) || metadataDefinition.getType().equals(MetadataType.MULTILINGUAL_STRING) ? Operator.SEARCH : Operator.EQ; 195 addMetadataCriterionComponent(cType, metadataName, operator); 196 if ("title".equals(metadataName)) 197 { 198 addLikeTitleCriterionComponent(cType); 199 } 200 } 201 } 202 } 203 204 if (_hierarchicalReferenceTableContentsHelper.isHierarchical(cType) && _hierarchicalReferenceTableContentsHelper.supportCandidates(cType)) 205 { 206 addExcludeCandidateSystemCriterionComponent(cType); 207 } 208 209 addSystemCriterionComponent(cType, "contributor"); 210 211 if (!cType.isMultilingual()) 212 { 213 addSystemCriterionComponent(cType, "contentLanguage"); 214 } 215 } 216 217 /** Add criteria component to exclude the candidates 218 * @param cType the simple content type 219 */ 220 protected void addExcludeCandidateSystemCriterionComponent(ContentType cType) 221 { 222 DefaultConfiguration conf = (DefaultConfiguration) getSystemCriteriaConfiguration(cType.getId(), "mixins", null); 223 224 DefaultConfiguration defaultValueConf = new DefaultConfiguration("default-value"); 225 defaultValueConf.setValue("org.ametys.cms.referencetable.mixin.Candidate"); 226 conf.addChild(defaultValueConf); 227 228 DefaultConfiguration opConf = new DefaultConfiguration("test-operator"); 229 opConf.setValue(Operator.NE.getName()); 230 conf.addChild(opConf); 231 232 DefaultConfiguration widgetConf = new DefaultConfiguration("widget"); 233 widgetConf.setValue("edition.hidden"); 234 conf.addChild(widgetConf); 235 236 _searchCriteriaManager.addComponent("cms", null, "mixins", SystemSearchUICriterion.class, conf); 237 _searchCriteriaRoles.add("mixins"); 238 } 239 240 /** 241 * Returns true if metadata can be used as criteria and column search UI 242 * @param metadataDefinition the metadata definition 243 * @return <code>true</code> 244 */ 245 protected boolean _filterMetadata(MetadataDefinition metadataDefinition) 246 { 247 MetadataType type = metadataDefinition.getType(); 248 switch (type) 249 { 250 case STRING: 251 case MULTILINGUAL_STRING: 252 case DATE: 253 case DATETIME: 254 case LONG: 255 case DOUBLE: 256 case BOOLEAN: 257 case CONTENT: 258 return true; 259 case BINARY: 260 case RICH_TEXT: 261 case REFERENCE: 262 case FILE: 263 case GEOCODE: 264 case COMPOSITE: 265 case SUB_CONTENT: 266 default: 267 return false; 268 } 269 } 270 271 /** 272 * Add the title metadata criterion component to the manager, with a 'LIKE' operator and an hidden widget 273 * @param contentType the simple content type. 274 * @throws ConfigurationException if a configuration error occurs. 275 * @throws ComponentException if a component cannot be initialized. 276 */ 277 protected void addLikeTitleCriterionComponent(ContentType contentType) throws ConfigurationException, ComponentException 278 { 279 String metadataName = "title"; 280 DefaultConfiguration originalConf = new DefaultConfiguration("criteria"); 281 DefaultConfiguration widgetConf = new DefaultConfiguration("widget"); 282 widgetConf.setValue("edition.hidden"); 283 originalConf.addChild(widgetConf); 284 Configuration conf = getIndexingFieldCriteriaConfiguration(originalConf, contentType.getId(), metadataName, Operator.LIKE, null); 285 286 String role = metadataName + "1"; 287 _searchCriteriaManager.addComponent("cms", null, role, IndexingFieldSearchUICriterion.class, conf); 288 _searchCriteriaRoles.add(role); 289 } 290 291 /** 292 * Add a metadata criterion component to the manager. 293 * @param contentType the simple content type. 294 * @param metadataName the metadata name. 295 * @param operator the criterion operator. 296 * @throws ConfigurationException if a configuration error occurs. 297 * @throws ComponentException if a component cannot be initialized. 298 */ 299 protected void addMetadataCriterionComponent(ContentType contentType, String metadataName, Operator operator) throws ConfigurationException, ComponentException 300 { 301 DefaultConfiguration conf = (DefaultConfiguration) getIndexingFieldCriteriaConfiguration(contentType.getId(), metadataName, operator, null); 302 MetadataDefinition metadataDefinition = contentType.getMetadataDefinition(metadataName); 303 304 if ( 305 metadataDefinition.getType() == MetadataType.CONTENT 306 && contentType.isReferenceTable() 307 && Optional.ofNullable(contentType.getParentMetadata()) 308 .map(MetadataDefinition::getId) 309 .map(parent -> parent.equals(metadataName)) 310 .orElse(false) 311 ) 312 { 313 DefaultConfiguration widgetConfig = (DefaultConfiguration) conf.getChild("widget"); 314 widgetConfig.setValue("edition.select-referencetable-content"); 315 316 DefaultConfiguration widgetParamsConfig = (DefaultConfiguration) conf.getChild("widget-params"); 317 318 DefaultConfiguration allowAutopostingParamsConfig = (DefaultConfiguration) widgetParamsConfig.getChild("param"); 319 allowAutopostingParamsConfig.setAttribute("name", "allowToggleAutoposting"); 320 allowAutopostingParamsConfig.setValue(true); 321 widgetParamsConfig.addChild(allowAutopostingParamsConfig); 322 323 conf.addChild(widgetConfig); 324 conf.addChild(widgetParamsConfig); 325 } 326 _searchCriteriaManager.addComponent("cms", null, metadataName, IndexingFieldSearchUICriterion.class, conf); 327 _searchCriteriaRoles.add(metadataName); 328 } 329 330 /** 331 * Add a system criterion component to the manager. 332 * @param contentType the simple content type. 333 * @param property the system property. 334 * @throws ConfigurationException if a configuration error occurs. 335 * @throws ComponentException if a component cannot be initialized. 336 */ 337 protected void addSystemCriterionComponent(ContentType contentType, String property) throws ConfigurationException, ComponentException 338 { 339 DefaultConfiguration conf = (DefaultConfiguration) getSystemCriteriaConfiguration(contentType.getId(), property, null); 340 341 if (property.equals("contentLanguage")) 342 { 343 // FIXME Is this configuration should be provided by Language system property itself ? 344 // FIXME For now the simple contents are only created for language 'fr' 345 DefaultConfiguration widgetConf = new DefaultConfiguration("widget"); 346 widgetConf.setValue("edition.select-language"); 347 conf.addChild(widgetConf); 348 349 DefaultConfiguration defaultConf = new DefaultConfiguration("default-value"); 350 defaultConf.setValue("CURRENT"); 351 conf.addChild(defaultConf); 352 353 DefaultConfiguration validConf = new DefaultConfiguration("validation"); 354 DefaultConfiguration mandatoryConf = new DefaultConfiguration("mandatory"); 355 mandatoryConf.setValue(true); 356 validConf.addChild(mandatoryConf); 357 conf.addChild(validConf); 358 } 359 360 _searchCriteriaManager.addComponent("cms", null, property, SystemSearchUICriterion.class, conf); 361 _searchCriteriaRoles.add(property); 362 } 363 364 /** 365 * Lookup all the criteria. 366 * @param cType the simple content type. 367 * @return the search criteria list. 368 * @throws ComponentException if a component cannot be looked up. 369 */ 370 protected List<SearchUICriterion> getSearchUICriteria(ContentType cType) throws ComponentException 371 { 372 List<SearchUICriterion> criteria = new ArrayList<>(); 373 374 for (String role : _searchCriteriaRoles) 375 { 376 SearchUICriterion criterion = _searchCriteriaManager.lookup(role); 377 criteria.add(criterion); 378 } 379 380 return criteria; 381 } 382 383 /** 384 * Add column components to the manager. 385 * @param cType the simple content type. 386 * @throws ConfigurationException if a configuration error occurs. 387 * @throws ComponentException if a component cannot be initialized. 388 */ 389 protected void addColumnComponents(ContentType cType) throws ConfigurationException, ComponentException 390 { 391 MetadataSet metadataSet = cType.getMetadataSetForEdition("main"); 392 for (AbstractMetadataSetElement subMetadataSetElement : metadataSet.getElements()) 393 { 394 // Get only simple metadata (ignore composites and repeaters) 395 if (subMetadataSetElement instanceof MetadataDefinitionReference) 396 { 397 String metadataName = ((MetadataDefinitionReference) subMetadataSetElement).getMetadataName(); 398 MetadataDefinition metadataDefinition = cType.getMetadataDefinition(metadataName); 399 400 if (metadataDefinition == null) 401 { 402 getLogger().warn("The metadata '{}' defined in the metadata set '{}' does not seem to exist.", metadataName, metadataSet); 403 } 404 else if (_filterMetadata(metadataDefinition)) 405 { 406 addMetadataColumnComponent(cType, metadataName); 407 } 408 } 409 } 410 411 addSystemColumnComponent(cType, "contributor"); 412 addSystemColumnComponent(cType, "lastModified"); 413 414 if (!cType.isMultilingual()) 415 { 416 addSystemColumnComponent(cType, "contentLanguage"); 417 } 418 } 419 420 /** 421 * Add a metadata column component to the manager. 422 * @param contentType the simple content type. 423 * @param metadataName the metadata name. 424 * @throws ConfigurationException if a configuration error occurs. 425 * @throws ComponentException if a component cannot be initialized. 426 */ 427 protected void addMetadataColumnComponent(ContentType contentType, String metadataName) throws ConfigurationException, ComponentException 428 { 429 Configuration columnConf = getMetadataColumnConfiguration(contentType.getId(), metadataName); 430 _searchColumnManager.addComponent("cms", null, metadataName, MetadataSearchUIColumn.class, columnConf); 431 _searchColumnRoles.add(metadataName); 432 } 433 434 /** 435 * Add a system column component to the manager. 436 * @param contentType the simple content type. 437 * @param property the system property. 438 * @throws ConfigurationException if a configuration error occurs. 439 * @throws ComponentException if a component cannot be initialized. 440 */ 441 protected void addSystemColumnComponent(ContentType contentType, String property) throws ConfigurationException, ComponentException 442 { 443 Configuration conf = getSystemColumnConfiguration(contentType.getId(), property); 444 _searchColumnManager.addComponent("cms", null, property, SystemSearchUIColumn.class, conf); 445 _searchColumnRoles.add(property); 446 } 447 448 /** 449 * Lookup all the columns. 450 * @param cType the simple content type. 451 * @return the search column list. 452 * @throws ComponentException if a component cannot be looked up. 453 */ 454 protected List<SearchUIColumn> getColumns(ContentType cType) throws ComponentException 455 { 456 List<SearchUIColumn> columns = new ArrayList<>(); 457 458 for (String columnRole : _searchColumnRoles) 459 { 460 SearchUIColumn column = _searchColumnManager.lookup(columnRole); 461 columns.add(column); 462 } 463 464 return columns; 465 } 466 467}