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.Set; 023 024import org.apache.avalon.framework.component.ComponentException; 025import org.apache.avalon.framework.configuration.Configurable; 026import org.apache.avalon.framework.configuration.Configuration; 027import org.apache.avalon.framework.configuration.ConfigurationException; 028import org.apache.avalon.framework.configuration.DefaultConfiguration; 029import org.apache.avalon.framework.service.ServiceException; 030import org.apache.avalon.framework.service.ServiceManager; 031 032import org.ametys.cms.content.referencetable.HierarchicalReferenceTablesHelper; 033import org.ametys.cms.contenttype.ContentType; 034import org.ametys.cms.data.type.ModelItemTypeConstants; 035import org.ametys.cms.repository.Content; 036import org.ametys.cms.search.query.Query.Operator; 037import org.ametys.cms.search.ui.model.impl.IndexingFieldSearchUICriterion; 038import org.ametys.cms.search.ui.model.impl.MetadataSearchUIColumn; 039import org.ametys.cms.search.ui.model.impl.SystemSearchUIColumn; 040import org.ametys.cms.search.ui.model.impl.SystemSearchUICriterion; 041import org.ametys.runtime.model.ModelItem; 042import org.ametys.runtime.model.ModelViewItem; 043import org.ametys.runtime.model.View; 044import org.ametys.runtime.model.ViewItem; 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 View view = cType.getView("main"); 180 for (ViewItem viewItem : view.getViewItems()) 181 { 182 if (viewItem instanceof ModelViewItem) 183 { 184 ModelItem modelItem = ((ModelViewItem) viewItem).getDefinition(); 185 if (_filterModelItemForCriteria(modelItem)) 186 { 187 Operator operator = org.ametys.runtime.model.type.ModelItemTypeConstants.STRING_TYPE_ID.equals(modelItem.getType().getId()) || ModelItemTypeConstants.MULTILINGUAL_STRING_ELEMENT_TYPE_ID.equals(modelItem.getType().getId()) ? Operator.SEARCH : Operator.EQ; 188 addAttributeCriterionComponent(cType, modelItem.getName(), operator); 189 190 if (Content.ATTRIBUTE_TITLE.equals(modelItem.getName())) 191 { 192 addLikeTitleCriterionComponent(cType); 193 } 194 } 195 } 196 } 197 198 if (_hierarchicalReferenceTableContentsHelper.isHierarchical(cType) && _hierarchicalReferenceTableContentsHelper.supportCandidates(cType)) 199 { 200 addExcludeCandidateSystemCriterionComponent(cType); 201 } 202 203 addSystemCriterionComponent(cType, "contributor"); 204 205 if (!cType.isMultilingual()) 206 { 207 addSystemCriterionComponent(cType, "contentLanguage"); 208 } 209 } 210 211 /** Add criteria component to exclude the candidates 212 * @param cType the simple content type 213 */ 214 protected void addExcludeCandidateSystemCriterionComponent(ContentType cType) 215 { 216 DefaultConfiguration conf = (DefaultConfiguration) getSystemCriteriaConfiguration(Set.of(cType.getId()), "mixins", null); 217 218 DefaultConfiguration defaultValueConf = new DefaultConfiguration("default-value"); 219 defaultValueConf.setValue("org.ametys.cms.referencetable.mixin.Candidate"); 220 conf.addChild(defaultValueConf); 221 222 DefaultConfiguration opConf = new DefaultConfiguration("test-operator"); 223 opConf.setValue(Operator.NE.getName()); 224 conf.addChild(opConf); 225 226 DefaultConfiguration widgetConf = new DefaultConfiguration("widget"); 227 widgetConf.setValue("edition.hidden"); 228 conf.addChild(widgetConf); 229 230 _searchCriteriaManager.addComponent("cms", null, "mixins", SystemSearchUICriterion.class, conf); 231 _searchCriteriaRoles.add("mixins"); 232 } 233 234 /** 235 * Returns <code>true</code> if the model item can be used as criteria 236 * @param modelItem the model item 237 * @return <code>true</code> if the model item can be used as criteria 238 */ 239 @SuppressWarnings("static-access") 240 protected boolean _filterModelItemForCriteria(ModelItem modelItem) 241 { 242 String typeId = modelItem.getType().getId(); 243 switch (typeId) 244 { 245 case ModelItemTypeConstants.STRING_TYPE_ID: 246 case ModelItemTypeConstants.MULTILINGUAL_STRING_ELEMENT_TYPE_ID: 247 case ModelItemTypeConstants.DATE_TYPE_ID: 248 case ModelItemTypeConstants.DATETIME_TYPE_ID: 249 case ModelItemTypeConstants.LONG_TYPE_ID: 250 case ModelItemTypeConstants.DOUBLE_TYPE_ID: 251 case ModelItemTypeConstants.BOOLEAN_TYPE_ID: 252 case ModelItemTypeConstants.CONTENT_ELEMENT_TYPE_ID: 253 case ModelItemTypeConstants.REFERENCE_ELEMENT_TYPE_ID: 254 return true; 255 case ModelItemTypeConstants.BINARY_ELEMENT_TYPE_ID: 256 case ModelItemTypeConstants.RICH_TEXT_ELEMENT_TYPE_ID: 257 case ModelItemTypeConstants.FILE_ELEMENT_TYPE_ID: 258 case ModelItemTypeConstants.GEOCODE_ELEMENT_TYPE_ID: 259 case ModelItemTypeConstants.COMPOSITE_TYPE_ID: 260 case ModelItemTypeConstants.REPEATER_TYPE_ID: 261 default: 262 return false; 263 } 264 } 265 266 /** 267 * Returns <code>true</code> if model item can be used as column search UI 268 * @param modelItem the model item 269 * @return <code>true</code> if model item can be used as column search UI 270 */ 271 @SuppressWarnings("static-access") 272 protected boolean _filterModelItemForColumn(ModelItem modelItem) 273 { 274 String typeId = modelItem.getType().getId(); 275 switch (typeId) 276 { 277 case ModelItemTypeConstants.STRING_TYPE_ID: 278 case ModelItemTypeConstants.MULTILINGUAL_STRING_ELEMENT_TYPE_ID: 279 case ModelItemTypeConstants.DATE_TYPE_ID: 280 case ModelItemTypeConstants.DATETIME_TYPE_ID: 281 case ModelItemTypeConstants.LONG_TYPE_ID: 282 case ModelItemTypeConstants.DOUBLE_TYPE_ID: 283 case ModelItemTypeConstants.BOOLEAN_TYPE_ID: 284 case ModelItemTypeConstants.CONTENT_ELEMENT_TYPE_ID: 285 case ModelItemTypeConstants.GEOCODE_ELEMENT_TYPE_ID: 286 case ModelItemTypeConstants.REFERENCE_ELEMENT_TYPE_ID: 287 return true; 288 case ModelItemTypeConstants.BINARY_ELEMENT_TYPE_ID: 289 case ModelItemTypeConstants.RICH_TEXT_ELEMENT_TYPE_ID: 290 case ModelItemTypeConstants.FILE_ELEMENT_TYPE_ID: 291 case ModelItemTypeConstants.COMPOSITE_TYPE_ID: 292 case ModelItemTypeConstants.REPEATER_TYPE_ID: 293 default: 294 return false; 295 } 296 } 297 298 /** 299 * Add the title attribute criterion component to the manager, with a 'LIKE' operator and an hidden widget 300 * @param contentType the simple content type. 301 * @throws ConfigurationException if a configuration error occurs. 302 * @throws ComponentException if a component cannot be initialized. 303 */ 304 protected void addLikeTitleCriterionComponent(ContentType contentType) throws ConfigurationException, ComponentException 305 { 306 DefaultConfiguration originalConf = new DefaultConfiguration("criteria"); 307 DefaultConfiguration widgetConf = new DefaultConfiguration("widget"); 308 widgetConf.setValue("edition.hidden"); 309 originalConf.addChild(widgetConf); 310 Configuration conf = getIndexingFieldCriteriaConfiguration(originalConf, Set.of(contentType.getId()), Content.ATTRIBUTE_TITLE, Operator.LIKE, null); 311 312 String role = Content.ATTRIBUTE_TITLE + "1"; 313 _searchCriteriaManager.addComponent("cms", null, role, IndexingFieldSearchUICriterion.class, conf); 314 _searchCriteriaRoles.add(role); 315 } 316 317 /** 318 * Add an attribute criterion component to the manager. 319 * @param contentType the simple content type. 320 * @param attributeName the attribute name. 321 * @param operator the criterion operator. 322 * @throws ConfigurationException if a configuration error occurs. 323 * @throws ComponentException if a component cannot be initialized. 324 */ 325 protected void addAttributeCriterionComponent(ContentType contentType, String attributeName, Operator operator) throws ConfigurationException, ComponentException 326 { 327 DefaultConfiguration conf = (DefaultConfiguration) getIndexingFieldCriteriaConfiguration(Set.of(contentType.getId()), attributeName, operator, null); 328 ModelItem metadataDefinition = contentType.getModelItem(attributeName); 329 330 if ( 331 ModelItemTypeConstants.CONTENT_ELEMENT_TYPE_ID.equals(metadataDefinition.getType().getId()) 332 && contentType.isReferenceTable() 333 && contentType.getParentAttributeDefinition() 334 .map(ModelItem::getPath) 335 .map(parent -> parent.equals(attributeName)) 336 .orElse(false) 337 ) 338 { 339 DefaultConfiguration widgetConfig = (DefaultConfiguration) conf.getChild("widget"); 340 widgetConfig.setValue("edition.select-referencetable-content"); 341 342 DefaultConfiguration widgetParamsConfig = (DefaultConfiguration) conf.getChild("widget-params"); 343 344 DefaultConfiguration allowAutopostingParamsConfig = (DefaultConfiguration) widgetParamsConfig.getChild("param"); 345 allowAutopostingParamsConfig.setAttribute("name", "allowToggleAutoposting"); 346 allowAutopostingParamsConfig.setValue(true); 347 widgetParamsConfig.addChild(allowAutopostingParamsConfig); 348 349 conf.addChild(widgetConfig); 350 conf.addChild(widgetParamsConfig); 351 } 352 _searchCriteriaManager.addComponent("cms", null, attributeName, IndexingFieldSearchUICriterion.class, conf); 353 _searchCriteriaRoles.add(attributeName); 354 } 355 356 /** 357 * Add a system criterion component to the manager. 358 * @param contentType the simple content type. 359 * @param property the system property. 360 * @throws ConfigurationException if a configuration error occurs. 361 * @throws ComponentException if a component cannot be initialized. 362 */ 363 protected void addSystemCriterionComponent(ContentType contentType, String property) throws ConfigurationException, ComponentException 364 { 365 DefaultConfiguration conf = (DefaultConfiguration) getSystemCriteriaConfiguration(Set.of(contentType.getId()), property, null); 366 367 if (property.equals("contentLanguage")) 368 { 369 // FIXME Is this configuration should be provided by Language system property itself ? 370 // FIXME For now the simple contents are only created for language 'fr' 371 DefaultConfiguration widgetConf = new DefaultConfiguration("widget"); 372 widgetConf.setValue("edition.select-language"); 373 conf.addChild(widgetConf); 374 375 DefaultConfiguration defaultConf = new DefaultConfiguration("default-value"); 376 defaultConf.setValue("CURRENT"); 377 conf.addChild(defaultConf); 378 379 DefaultConfiguration validConf = new DefaultConfiguration("validation"); 380 DefaultConfiguration mandatoryConf = new DefaultConfiguration("mandatory"); 381 mandatoryConf.setValue(true); 382 validConf.addChild(mandatoryConf); 383 conf.addChild(validConf); 384 } 385 386 _searchCriteriaManager.addComponent("cms", null, property, SystemSearchUICriterion.class, conf); 387 _searchCriteriaRoles.add(property); 388 } 389 390 /** 391 * Lookup all the criteria. 392 * @param cType the simple content type. 393 * @return the search criteria list. 394 * @throws ComponentException if a component cannot be looked up. 395 */ 396 protected List<SearchUICriterion> getSearchUICriteria(ContentType cType) throws ComponentException 397 { 398 List<SearchUICriterion> criteria = new ArrayList<>(); 399 400 for (String role : _searchCriteriaRoles) 401 { 402 SearchUICriterion criterion = _searchCriteriaManager.lookup(role); 403 criteria.add(criterion); 404 } 405 406 return criteria; 407 } 408 409 /** 410 * Add column components to the manager. 411 * @param cType the simple content type. 412 * @throws ConfigurationException if a configuration error occurs. 413 * @throws ComponentException if a component cannot be initialized. 414 */ 415 protected void addColumnComponents(ContentType cType) throws ConfigurationException, ComponentException 416 { 417 View view = cType.getView("main"); 418 for (ViewItem viewItem : view.getViewItems()) 419 { 420 if (viewItem instanceof ModelViewItem) 421 { 422 ModelItem modelItem = ((ModelViewItem) viewItem).getDefinition(); 423 424 if (_filterModelItemForColumn(modelItem)) 425 { 426 addAttributeColumnComponent(cType, modelItem.getName()); 427 } 428 } 429 } 430 431 addSystemColumnComponent(cType, "contributor"); 432 addSystemColumnComponent(cType, "lastModified"); 433 434 if (!cType.isMultilingual()) 435 { 436 addSystemColumnComponent(cType, "contentLanguage"); 437 } 438 } 439 440 /** 441 * Add an attribute column component to the manager. 442 * @param contentType the simple content type. 443 * @param attributeName the attribute name. 444 * @throws ConfigurationException if a configuration error occurs. 445 * @throws ComponentException if a component cannot be initialized. 446 */ 447 protected void addAttributeColumnComponent(ContentType contentType, String attributeName) throws ConfigurationException, ComponentException 448 { 449 Configuration columnConf = getMetadataColumnConfiguration(Set.of(contentType.getId()), attributeName); 450 _searchColumnManager.addComponent("cms", null, attributeName, MetadataSearchUIColumn.class, columnConf); 451 _searchColumnRoles.add(attributeName); 452 } 453 454 /** 455 * Add a system column component to the manager. 456 * @param contentType the simple content type. 457 * @param property the system property. 458 * @throws ConfigurationException if a configuration error occurs. 459 * @throws ComponentException if a component cannot be initialized. 460 */ 461 protected void addSystemColumnComponent(ContentType contentType, String property) throws ConfigurationException, ComponentException 462 { 463 Configuration conf = getSystemColumnConfiguration(Set.of(contentType.getId()), property); 464 _searchColumnManager.addComponent("cms", null, property, SystemSearchUIColumn.class, conf); 465 _searchColumnRoles.add(property); 466 } 467 468 /** 469 * Lookup all the columns. 470 * @param cType the simple content type. 471 * @return the search column list. 472 * @throws ComponentException if a component cannot be looked up. 473 */ 474 protected List<SearchUIColumn> getColumns(ContentType cType) throws ComponentException 475 { 476 List<SearchUIColumn> columns = new ArrayList<>(); 477 478 for (String columnRole : _searchColumnRoles) 479 { 480 SearchUIColumn column = _searchColumnManager.lookup(columnRole); 481 columns.add(column); 482 } 483 484 return columns; 485 } 486 487}