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