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.Collection; 019import java.util.Collections; 020import java.util.HashSet; 021import java.util.LinkedHashMap; 022import java.util.Map; 023import java.util.Map.Entry; 024import java.util.Set; 025 026import org.apache.avalon.framework.configuration.DefaultConfiguration; 027import org.apache.avalon.framework.context.Context; 028import org.apache.avalon.framework.service.ServiceManager; 029import org.apache.commons.lang3.StringUtils; 030import org.slf4j.Logger; 031 032import org.ametys.cms.contenttype.ContentType; 033import org.ametys.cms.contenttype.ContentTypeEnumerator; 034import org.ametys.cms.contenttype.ContentTypeExtensionPoint; 035import org.ametys.cms.contenttype.MetadataType; 036import org.ametys.cms.search.query.Query; 037import org.ametys.cms.search.query.Query.Operator; 038import org.ametys.cms.search.ui.model.impl.AbstractSearchUICriterion; 039import org.ametys.cms.search.ui.model.impl.SystemSearchUICriterion; 040import org.ametys.runtime.i18n.I18nizableText; 041import org.ametys.runtime.model.ModelViewItem; 042import org.ametys.runtime.model.ViewItemContainer; 043import org.ametys.runtime.parameter.Enumerator; 044import org.ametys.runtime.parameter.Validator; 045import org.ametys.runtime.plugin.component.ThreadSafeComponentManager; 046 047/** 048 * Dynamic search model wrapping a search model with a restriction on content types 049 * 050 */ 051public class DynamicWrappedSearchUIModel implements SearchUIModel 052{ 053 private ThreadSafeComponentManager<Enumerator> _enumeratorManager; 054 055 private Logger _logger; 056 private SearchUIModel _wrappedModel; 057 private Set<String> _restrictedContentTypes; 058 private Set<String> _restrictedMixins; 059 private boolean _containsReferenceTable; 060 061 /** 062 * Constructor 063 * @param model The wrapped search model 064 * @param restrictedContentTypes The restricted content types 065 * @param cTypeEP The content type extension point 066 * @param logger The logger 067 * @param context The context 068 * @param manager The service manager 069 */ 070 public DynamicWrappedSearchUIModel(SearchUIModel model, Collection<String> restrictedContentTypes, ContentTypeExtensionPoint cTypeEP, Logger logger, Context context, ServiceManager manager) 071 { 072 _wrappedModel = model; 073 074 _restrictedContentTypes = new HashSet<>(); 075 _restrictedMixins = new HashSet<>(); 076 077 for (String cTypeId : restrictedContentTypes) 078 { 079 ContentType cType = cTypeEP.getExtension(cTypeId); 080 if (cType.isMixin()) 081 { 082 _restrictedMixins.add(cTypeId); 083 } 084 else 085 { 086 _restrictedContentTypes.add(cTypeId); 087 } 088 089 _containsReferenceTable = cType.isReferenceTable() || _containsReferenceTable; 090 091 for (String subTypeId : cTypeEP.getSubTypes(cTypeId)) 092 { 093 ContentType subCType = cTypeEP.getExtension(subTypeId); 094 if (subCType.isMixin()) 095 { 096 _restrictedMixins.add(subTypeId); 097 } 098 else 099 { 100 _restrictedContentTypes.add(subTypeId); 101 } 102 103 _containsReferenceTable = subCType.isReferenceTable() || _containsReferenceTable; 104 } 105 } 106 107 try 108 { 109 _logger = logger; 110 _enumeratorManager = new ThreadSafeComponentManager<>(); 111 _enumeratorManager.setLogger(logger); 112 _enumeratorManager.contextualize(context); 113 _enumeratorManager.service(manager); 114 115 _addContentTypeEnumeratorComponent(); 116 _addContentTypeAndMixinEnumeratorComponent(); 117 _addMixinEnumeratorComponent(); 118 119 _enumeratorManager.initialize(); 120 } 121 catch (Exception e) 122 { 123 _logger.error("Unable to instantiate enumerator for class: " + ContentTypeEnumerator.class.getName(), e); 124 } 125 } 126 127 private void _addContentTypeEnumeratorComponent() 128 { 129 DefaultConfiguration conf = new DefaultConfiguration("criteria"); 130 131 DefaultConfiguration enumConf = new DefaultConfiguration("enumeration"); 132 133 DefaultConfiguration customEnumerator = new DefaultConfiguration("custom-enumerator"); 134 customEnumerator.setAttribute("class", ContentTypeEnumerator.class.getName()); 135 136 if (!_restrictedContentTypes.isEmpty()) 137 { 138 DefaultConfiguration cTypeConf = new DefaultConfiguration("strictContentTypes"); 139 cTypeConf.setValue(StringUtils.join(_restrictedContentTypes, ",")); 140 customEnumerator.addChild(cTypeConf); 141 142 DefaultConfiguration excludeConf = new DefaultConfiguration("excludeMixin"); 143 excludeConf.setValue(true); 144 customEnumerator.addChild(excludeConf); 145 146 if (_containsReferenceTable) 147 { 148 DefaultConfiguration excludeRefTableConf = new DefaultConfiguration("excludeReferenceTable"); 149 excludeRefTableConf.setValue(false); 150 customEnumerator.addChild(excludeRefTableConf); 151 } 152 153 enumConf.addChild(customEnumerator); 154 conf.addChild(enumConf); 155 156 _enumeratorManager.addComponent("cms", null, ContentTypeEnumerator.class.getName(), ContentTypeEnumerator.class, conf); 157 } 158 } 159 160 private void _addContentTypeAndMixinEnumeratorComponent() 161 { 162 DefaultConfiguration conf = new DefaultConfiguration("criteria"); 163 164 DefaultConfiguration enumConf = new DefaultConfiguration("enumeration"); 165 166 DefaultConfiguration customEnumerator = new DefaultConfiguration("custom-enumerator"); 167 customEnumerator.setAttribute("class", ContentTypeEnumerator.class.getName()); 168 169 if (_restrictedContentTypes.size() > 0) 170 { 171 DefaultConfiguration cTypeConf = new DefaultConfiguration("strictContentTypes"); 172 cTypeConf.setValue(StringUtils.join(_restrictedContentTypes, ",")); 173 customEnumerator.addChild(cTypeConf); 174 175 if (_containsReferenceTable) 176 { 177 DefaultConfiguration excludeRefTableConf = new DefaultConfiguration("excludeReferenceTable"); 178 excludeRefTableConf.setValue(false); 179 customEnumerator.addChild(excludeRefTableConf); 180 } 181 182 enumConf.addChild(customEnumerator); 183 conf.addChild(enumConf); 184 185 _enumeratorManager.addComponent("cms", null, ContentTypeEnumerator.class.getName() + ".ContentTypeAndMixin", ContentTypeEnumerator.class, conf); 186 } 187 } 188 189 private void _addMixinEnumeratorComponent() 190 { 191 DefaultConfiguration conf = new DefaultConfiguration("criteria"); 192 193 DefaultConfiguration enumConf = new DefaultConfiguration("enumeration"); 194 195 DefaultConfiguration customEnumerator = new DefaultConfiguration("custom-enumerator"); 196 customEnumerator.setAttribute("class", ContentTypeEnumerator.class.getName()); 197 198 if (_restrictedMixins.size() > 0) 199 { 200 DefaultConfiguration cTypeConf = new DefaultConfiguration("strictContentTypes"); 201 cTypeConf.setValue(StringUtils.join(_restrictedMixins, ",")); 202 customEnumerator.addChild(cTypeConf); 203 204 DefaultConfiguration excludeConf = new DefaultConfiguration("excludeMixin"); 205 excludeConf.setValue(false); 206 customEnumerator.addChild(excludeConf); 207 208 enumConf.addChild(customEnumerator); 209 conf.addChild(enumConf); 210 211 _enumeratorManager.addComponent("cms", null, ContentTypeEnumerator.class.getName() + ".Mixin", ContentTypeEnumerator.class, conf); 212 } 213 } 214 215 @Override 216 public Set<String> getContentTypes(Map<String, Object> contextualParameters) 217 { 218 return _restrictedContentTypes; 219 } 220 221 @Override 222 public Set<String> getExcludedContentTypes(Map<String, Object> contextualParameters) 223 { 224 return Collections.<String>emptySet(); 225 } 226 227 @Override 228 public String getSearchUrl(Map<String, Object> contextualParameters) 229 { 230 return _wrappedModel.getSearchUrl(contextualParameters); 231 } 232 233 @Override 234 public String getSearchUrlPlugin(Map<String, Object> contextualParameters) 235 { 236 return _wrappedModel.getSearchUrlPlugin(contextualParameters); 237 } 238 239 @Override 240 public String getExportCSVUrl(Map<String, Object> contextualParameters) 241 { 242 return _wrappedModel.getExportCSVUrl(contextualParameters); 243 } 244 245 @Override 246 public String getExportCSVUrlPlugin(Map<String, Object> contextualParameters) 247 { 248 return _wrappedModel.getExportCSVUrlPlugin(contextualParameters); 249 } 250 251 @Override 252 public String getExportDOCUrl(Map<String, Object> contextualParameters) 253 { 254 return _wrappedModel.getExportDOCUrl(contextualParameters); 255 } 256 257 @Override 258 public String getExportDOCUrlPlugin(Map<String, Object> contextualParameters) 259 { 260 return _wrappedModel.getExportDOCUrlPlugin(contextualParameters); 261 } 262 263 @Override 264 public String getExportXMLUrl(Map<String, Object> contextualParameters) 265 { 266 return _wrappedModel.getExportXMLUrl(contextualParameters); 267 } 268 269 @Override 270 public String getExportXMLUrlPlugin(Map<String, Object> contextualParameters) 271 { 272 return _wrappedModel.getExportXMLUrlPlugin(contextualParameters); 273 } 274 275 @Override 276 public String getExportPDFUrl(Map<String, Object> contextualParameters) 277 { 278 return _wrappedModel.getExportPDFUrl(contextualParameters); 279 } 280 281 @Override 282 public String getExportPDFUrlPlugin(Map<String, Object> contextualParameters) 283 { 284 return _wrappedModel.getExportPDFUrlPlugin(contextualParameters); 285 } 286 287 @Override 288 public String getPrintUrl(Map<String, Object> contextualParameters) 289 { 290 return _wrappedModel.getPrintUrl(contextualParameters); 291 } 292 293 @Override 294 public String getPrintUrlPlugin(Map<String, Object> contextualParameters) 295 { 296 return _wrappedModel.getPrintUrlPlugin(contextualParameters); 297 } 298 299 @Override 300 public String getSummaryView() 301 { 302 return _wrappedModel.getSummaryView(); 303 } 304 305 @Override 306 public Map<String, SearchUICriterion> getCriteria(Map<String, Object> contextualParameters) 307 { 308 Map<String, SearchUICriterion> criteria = _wrappedModel.getCriteria(contextualParameters); 309 310 Map<String, SearchUICriterion> wrappedCriteria = new LinkedHashMap<>(criteria.size()); 311 312 for (Entry<String, SearchUICriterion> entry : criteria.entrySet()) 313 { 314 SearchUICriterion sc = entry.getValue(); 315 wrappedCriteria.put(entry.getKey(), wrap(sc)); 316 } 317 318 return wrappedCriteria; 319 } 320 321 @Override 322 public SearchUICriterion getCriterion(String id, Map<String, Object> contextualParameters) 323 { 324 SearchUICriterion criteria = _wrappedModel.getCriterion(id, contextualParameters); 325 326 return wrap(criteria); 327 } 328 329 @Override 330 public Map<String, SearchUICriterion> getFacetedCriteria(Map<String, Object> contextualParameters) 331 { 332 return _wrappedModel.getFacetedCriteria(contextualParameters); 333 } 334 335 @Override 336 public Map<String, SearchUICriterion> getAdvancedCriteria(Map<String, Object> contextualParameters) 337 { 338 return _wrappedModel.getAdvancedCriteria(contextualParameters); 339 } 340 341 public ViewItemContainer getResultItems(Map<String, Object> contextualParameters) 342 { 343 return _wrappedModel.getResultItems(contextualParameters); 344 } 345 346 public ModelViewItem getResultItem(String itemPath, Map<String, Object> contextualParameters) 347 { 348 return _wrappedModel.getResultItem(itemPath, contextualParameters); 349 } 350 351 @Override 352 public int getPageSize(Map<String, Object> contextualParameters) 353 { 354 return _wrappedModel.getPageSize(contextualParameters); 355 } 356 357 @Override 358 public String getWorkspace(Map<String, Object> contextualParameters) 359 { 360 return _wrappedModel.getWorkspace(contextualParameters); 361 } 362 363 /** 364 * Wrap an existing search criterion. 365 * @param criterion the search criterion to wrap. 366 * @return the wrapped search criterion. 367 */ 368 protected SearchUICriterion wrap(SearchUICriterion criterion) 369 { 370 try 371 { 372 if (criterion instanceof SystemSearchUICriterion systemSearchUICriterion 373 && !_restrictedContentTypes.isEmpty() 374 && !systemSearchUICriterion.isJoined()) 375 { 376 // Wrap "contentTypes" and "contentTypeOrMixin" system criteria, replacing their standard enumerator with the local one. 377 Enumerator enumerator; 378 switch (systemSearchUICriterion.getSystemPropertyId()) 379 { 380 case "contentTypes": 381 enumerator = _enumeratorManager.lookup(ContentTypeEnumerator.class.getName()); 382 return new WrappedSearchUICriterion(criterion, enumerator); 383 case "contentTypeOrMixin": 384 enumerator = _enumeratorManager.lookup(ContentTypeEnumerator.class.getName() + ".ContentTypeAndMixin"); 385 return new WrappedSearchUICriterion(criterion, enumerator); 386 case "mixins": 387 enumerator = _enumeratorManager.lookup(ContentTypeEnumerator.class.getName() + ".Mixin"); 388 return new WrappedSearchUICriterion(criterion, enumerator); 389 default: 390 break; 391 } 392 } 393 394 return criterion; 395 } 396 catch (Exception e) 397 { 398 _logger.error("Unable to lookup enumerator role.", e); 399 throw new RuntimeException("Unable to lookup enumerator role.", e); 400 } 401 } 402 403 /** 404 * Search criteria wrapper. 405 */ 406 protected class WrappedSearchUICriterion extends AbstractSearchUICriterion 407 { 408 409 /** The wrapped search criteria. */ 410 protected SearchUICriterion _wrappedCriterion; 411 412 /** 413 * Build a search criteria wrapper. 414 * @param wrappedCriteria wrapped search criteria. 415 * @param enumerator the new enumerator. 416 */ 417 public WrappedSearchUICriterion(SearchUICriterion wrappedCriteria, Enumerator enumerator) 418 { 419 _wrappedCriterion = wrappedCriteria; 420 setEnumerator(enumerator); 421 } 422 423 @Override 424 public Enumerator getEnumerator() 425 { 426 return super.getEnumerator(); 427 } 428 429 @Override 430 public String getId() 431 { 432 return _wrappedCriterion.getId(); 433 } 434 435 @Override 436 public I18nizableText getLabel() 437 { 438 return _wrappedCriterion.getLabel(); 439 } 440 441 @Override 442 public I18nizableText getDescription() 443 { 444 return _wrappedCriterion.getDescription(); 445 } 446 447 @Override 448 public String getFieldId() 449 { 450 return _wrappedCriterion.getFieldId(); 451 } 452 453 @Override 454 public String getInitClassName() 455 { 456 return _wrappedCriterion.getInitClassName(); 457 } 458 459 @Override 460 public String getSubmitClassName() 461 { 462 return _wrappedCriterion.getSubmitClassName(); 463 } 464 465 @Override 466 public String getChangeClassName() 467 { 468 return _wrappedCriterion.getChangeClassName(); 469 } 470 471 @Override 472 public boolean isHidden() 473 { 474 return _wrappedCriterion.isHidden(); 475 } 476 477 @Override 478 public MetadataType getType() 479 { 480 return _wrappedCriterion.getType(); 481 } 482 483 @Override 484 public boolean isMultiple() 485 { 486 return _wrappedCriterion.isMultiple(); 487 } 488 489 @Override 490 public String getWidget() 491 { 492 return _wrappedCriterion.getWidget(); 493 } 494 495// @Override 496// public Object getValue() 497// { 498// return _wrappedCriterion.getValue(); 499// } 500 501 @Override 502 public Map<String, I18nizableText> getWidgetParameters() 503 { 504 return _wrappedCriterion.getWidgetParameters(); 505 } 506 507 @Override 508 public Validator getValidator() 509 { 510 return _wrappedCriterion.getValidator(); 511 } 512 513 @Override 514 public Object getDefaultValue() 515 { 516 return _wrappedCriterion.getDefaultValue(); 517 } 518 519 @Override 520 public String getContentTypeId() 521 { 522 return _wrappedCriterion.getContentTypeId(); 523 } 524 525 @Override 526 public Operator getOperator() 527 { 528 return _wrappedCriterion.getOperator(); 529 } 530 531 @Override 532 public Query getQuery(Object value, Operator customOperator, Map<String, Object> allValues, String language, Map<String, Object> contextualParameters) 533 { 534 return _wrappedCriterion.getQuery(value, customOperator, allValues, language, contextualParameters); 535 } 536 } 537}