001/* 002 * Copyright 2019 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.web.frontoffice.search.metamodel.impl; 017 018import java.util.ArrayList; 019import java.util.Arrays; 020import java.util.Collection; 021import java.util.Comparator; 022import java.util.HashMap; 023import java.util.HashSet; 024import java.util.List; 025import java.util.Map; 026import java.util.Objects; 027import java.util.Optional; 028import java.util.Set; 029import java.util.stream.Collectors; 030 031import org.apache.avalon.framework.activity.Disposable; 032import org.apache.avalon.framework.activity.Initializable; 033import org.apache.avalon.framework.configuration.Configuration; 034import org.apache.avalon.framework.configuration.ConfigurationException; 035import org.apache.avalon.framework.configuration.DefaultConfiguration; 036import org.apache.avalon.framework.context.Context; 037import org.apache.avalon.framework.context.ContextException; 038import org.apache.avalon.framework.context.Contextualizable; 039import org.apache.avalon.framework.service.ServiceException; 040import org.apache.avalon.framework.service.ServiceManager; 041import org.apache.commons.lang3.tuple.Pair; 042import org.apache.commons.math3.util.IntegerSequence.Incrementor; 043 044import org.ametys.cms.contenttype.ContentType; 045import org.ametys.cms.contenttype.ContentTypeExtensionPoint; 046import org.ametys.cms.contenttype.MetadataDefinition; 047import org.ametys.cms.contenttype.MetadataType; 048import org.ametys.cms.contenttype.indexing.IndexingField; 049import org.ametys.cms.contenttype.indexing.MetadataIndexingField; 050import org.ametys.cms.repository.Content; 051import org.ametys.cms.search.model.SystemProperty; 052import org.ametys.cms.search.model.SystemPropertyExtensionPoint; 053import org.ametys.cms.search.ui.model.SearchUICriterion; 054import org.ametys.cms.search.ui.model.impl.IndexingFieldSearchUICriterion; 055import org.ametys.cms.search.ui.model.impl.SystemSearchUICriterion; 056import org.ametys.cms.tag.TagProviderExtensionPoint; 057import org.ametys.core.util.JSONUtils; 058import org.ametys.plugins.repository.AmetysObjectResolver; 059import org.ametys.runtime.i18n.I18nizableText; 060import org.ametys.runtime.plugin.component.ThreadSafeComponentManager; 061import org.ametys.web.frontoffice.search.metamodel.AdditionalParameterValueMap; 062import org.ametys.web.frontoffice.search.metamodel.Returnable; 063import org.ametys.web.frontoffice.search.metamodel.ReturnableExtensionPoint; 064import org.ametys.web.frontoffice.search.metamodel.SearchCriterionDefinition; 065import org.ametys.web.frontoffice.search.metamodel.Searchable; 066 067/** 068 * Abstract class for all {@link Searchable} based on {@link Content}s 069 */ 070public abstract class AbstractContentBasedSearchable extends AbstractParameterAdderSearchable implements Initializable, Contextualizable, Disposable 071{ 072 // Push ids of System Properties you do not want to appear in the list 073 private static final List<String> __EXCLUDED_SYSTEM_PROPERTIES = Arrays.asList("site", 074 "parents", 075 "workflowStep"); 076 // Push ids of Indexing Fields you do not want to appear in the list (for instance title which is handled separately) 077 private static final List<String> __EXCLUDED_INDEXING_FIELD = Arrays.asList("title"); 078 079 /** The label */ 080 protected I18nizableText _label; 081 /** The criteria position */ 082 protected int _criteriaPosition; 083 /** The page returnable */ 084 protected Returnable _pageReturnable; 085 /** The associated content returnable */ 086 protected Returnable _associatedContentReturnable; 087 088 /** The extension point for content types */ 089 protected ContentTypeExtensionPoint _cTypeEP; 090 091 private Context _context; 092 private ReturnableExtensionPoint _returnableEP; 093 private SystemPropertyExtensionPoint _systemPropertyEP; 094 private AmetysObjectResolver _ametysObjectResolver; 095 private TagProviderExtensionPoint _tagProviderEP; 096 private JSONUtils _jsonUtils; 097 098 private List<ContentSearchCriterionDefinition> _systemPropertySearchCriterionDefs; 099 private Map<String, Collection<CriterionDefinitionAndSourceContentType>> _indexingFieldSearchCriterionDefsCache; 100 private ContentSearchCriterionDefinition _titleIndexingFieldSearchCriterionDefCache; 101 private Set<ThreadSafeComponentManager<?>> _managers; 102 103 @Override 104 public void configure(Configuration configuration) throws ConfigurationException 105 { 106 super.configure(configuration); 107 _label = I18nizableText.parseI18nizableText(configuration.getChild("label"), "plugin." + _pluginName); 108 _criteriaPosition = configuration.getChild("criteriaPosition").getValueAsInteger(); 109 } 110 111 @Override 112 public void service(ServiceManager manager) throws ServiceException 113 { 114 super.service(manager); 115 _returnableEP = (ReturnableExtensionPoint) manager.lookup(ReturnableExtensionPoint.ROLE); 116 _pageReturnable = _returnableEP.getExtension(PageReturnable.ROLE); 117 _systemPropertyEP = (SystemPropertyExtensionPoint) manager.lookup(SystemPropertyExtensionPoint.ROLE); 118 _cTypeEP = (ContentTypeExtensionPoint) manager.lookup(ContentTypeExtensionPoint.ROLE); 119 _ametysObjectResolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 120 _tagProviderEP = (TagProviderExtensionPoint) manager.lookup(TagProviderExtensionPoint.ROLE); 121 _jsonUtils = (JSONUtils) manager.lookup(JSONUtils.ROLE); 122 } 123 124 /** 125 * Sets {@link #_associatedContentReturnable}. Called during {@link #initialize()} 126 */ 127 protected void _setAssociatedContentReturnable() 128 { 129 _associatedContentReturnable = _returnableEP.getExtension(associatedContentReturnableRole()); 130 } 131 132 /** 133 * The Avalon Role for the associated Content Returnable 134 * @return The Avalon Role for the associated Content Returnable 135 */ 136 protected abstract String associatedContentReturnableRole(); 137 138 @Override 139 public void contextualize(Context context) throws ContextException 140 { 141 _context = context; 142 } 143 144 @Override 145 public void initialize() throws Exception 146 { 147 _setAssociatedContentReturnable(); 148 _managers = new HashSet<>(); 149 150 _systemPropertySearchCriterionDefs = new ArrayList<>(); 151 for (String propId : _systemPropertyEP.getSearchProperties()) 152 { 153 if (__EXCLUDED_SYSTEM_PROPERTIES.contains(propId)) 154 { 155 continue; 156 } 157 158 ContentSearchCriterionDefinition def = _getSystemPropSearchCriterionDef(propId); 159 _systemPropertySearchCriterionDefs.add(def); 160 } 161 162 _indexingFieldSearchCriterionDefsCache = new HashMap<>(); 163 } 164 165 private ContentSearchCriterionDefinition _getSystemPropSearchCriterionDef(String propId) 166 { 167 try 168 { 169 ThreadSafeComponentManager<SearchUICriterion> searchCriterionManager = new ThreadSafeComponentManager<>(); 170 _managers.add(searchCriterionManager); 171 searchCriterionManager.setLogger(getLogger()); 172 searchCriterionManager.contextualize(_context); 173 searchCriterionManager.service(_manager); 174 175 String role = propId; 176 Configuration criteriaConf = _getSystemCriteriaConfiguration(propId); 177 searchCriterionManager.addComponent(_pluginName, null, role, SystemSearchUICriterion.class, criteriaConf); 178 179 searchCriterionManager.initialize(); 180 181 SearchUICriterion criterion = searchCriterionManager.lookup(role); 182 String id = getSystemPropertyCriterionDefinitionPrefix() + role; 183 ContentSearchCriterionDefinition criterionDef = _criterionDefinition(id, criterion); 184 return criterionDef; 185 } 186 catch (Exception e) 187 { 188 throw new RuntimeException("An error occured when retrieving SystemPropertySearchCriterionDefinitions", e); 189 } 190 } 191 192 private ContentSearchCriterionDefinition _criterionDefinition(String id, SearchUICriterion criterion) 193 { 194 if (criterion instanceof SystemSearchUICriterion) 195 { 196 SystemSearchUICriterion systemCriterion = (SystemSearchUICriterion) criterion; 197 if ("tags".equals(systemCriterion.getSystemPropertyId())) 198 { 199 return new TagSearchCriterionDefinition(id, _pluginName, Optional.of(this), criterion, Optional.empty(), _tagProviderEP, _jsonUtils); 200 } 201 } 202 return new ContentSearchCriterionDefinition(id, _pluginName, Optional.of(this), criterion, Optional.empty()); 203 } 204 205 /** 206 * Gets the prefix for the ids of system property criterion definitions 207 * @return the prefix for the ids of system property criterion definitions 208 */ 209 protected String getSystemPropertyCriterionDefinitionPrefix() 210 { 211 return getCriterionDefinitionPrefix() + "systemProperty$"; 212 } 213 214 /** 215 * Gets the prefix for criterion definitions 216 * @return the prefix for criterion definitions 217 */ 218 protected abstract String getCriterionDefinitionPrefix(); 219 220 private Configuration _getSystemCriteriaConfiguration(String propertyId) 221 { 222 DefaultConfiguration conf = new DefaultConfiguration("criteria"); 223 224 DefaultConfiguration propConf = new DefaultConfiguration("systemProperty"); 225 propConf.setAttribute("name", propertyId); 226 conf.addChild(propConf); 227 228 // By default, SystemSearchUICriterion sets the multiple status to 'false', so here explicitly copy the multiple status from the property itself 229 SystemProperty property = _systemPropertyEP.getExtension(propertyId); 230 conf.setAttribute("multiple", property.isMultiple()); 231 232 return conf; 233 } 234 235 @Override 236 public void dispose() 237 { 238 _systemPropertySearchCriterionDefs 239 .forEach(ContentSearchCriterionDefinition::dispose); 240 _systemPropertySearchCriterionDefs.clear(); 241 242 _indexingFieldSearchCriterionDefsCache.values() 243 .stream() 244 .flatMap(Collection::stream) 245 .map(CriterionDefinitionAndSourceContentType::criterionDefinition) 246 .forEach(ContentSearchCriterionDefinition::dispose); 247 _indexingFieldSearchCriterionDefsCache.clear(); 248 249 _managers.forEach(ThreadSafeComponentManager::dispose); 250 _managers.clear(); 251 } 252 253 @Override 254 public I18nizableText getLabel() 255 { 256 return _label; 257 } 258 259 @Override 260 public int criteriaPosition() 261 { 262 return _criteriaPosition; 263 } 264 265 @Override 266 public Collection<SearchCriterionDefinition> getCriteria(AdditionalParameterValueMap additionalParameterValues) 267 { 268 Collection<SearchCriterionDefinition> criteria = new ArrayList<>(); 269 270 // Content types 271 Collection<String> contentTypes = getContentTypes(additionalParameterValues); 272 273 // Special case for title 274 criteria.add(_getTitleIndexingFieldSearchCriterionDef()); 275 276 // Indexing fields 277 Collection<CriterionDefinitionAndSourceContentType> indexingFieldCriterionDefs = _getIndexingFieldSearchCriterionDefs(contentTypes); 278 criteria.addAll(_finalIndexingFieldCriterionDefs(indexingFieldCriterionDefs)); 279 280 // System properties 281 criteria.addAll(_systemPropertySearchCriterionDefs); 282 283 if (getLogger().isInfoEnabled()) 284 { 285 getLogger().info("#getCriteria for contentTypes '{}' returned '{}'", 286 contentTypes, 287 criteria.stream() 288 .map(SearchCriterionDefinition::getId) 289 .collect(Collectors.toList())); 290 } 291 292 return criteria; 293 } 294 295 /** 296 * Gets the content types which will be used to retrieve the criteria relative to the Indexing Model 297 * @param additionalParameterValues The additional parameter values 298 * @return the content types which will be used to retrieve the criteria relative to the Indexing Model 299 */ 300 protected abstract Collection<String> getContentTypes(AdditionalParameterValueMap additionalParameterValues); 301 302 private synchronized Collection<CriterionDefinitionAndSourceContentType> _getIndexingFieldSearchCriterionDefs(Collection<String> contentTypeIds) 303 { 304 return contentTypeIds 305 .stream() 306 .map(this::_getIndexingFieldSearchCriterionDefs) 307 .flatMap(Collection::stream) 308 .collect(Collectors.toList()); 309 } 310 311 private Collection<CriterionDefinitionAndSourceContentType> _getIndexingFieldSearchCriterionDefs(String contentTypeId) 312 { 313 if (_indexingFieldSearchCriterionDefsCache.containsKey(contentTypeId)) 314 { 315 // found in cache 316 Collection<CriterionDefinitionAndSourceContentType> defs = _indexingFieldSearchCriterionDefsCache.get(contentTypeId); 317 getLogger().info("IndexingFieldSearchUICriteria for '{}' cache hit ({}).", contentTypeId, defs); 318 return defs; 319 } 320 else 321 { 322 ContentType contentType = _cTypeEP.getExtension(contentTypeId); 323 Collection<IndexingField> indexingModelFields = _getIndexingFields(contentType); 324 Collection<CriterionDefinitionAndSourceContentType> indexingFieldSearchCriterionDefs = _createIndexingFieldSearchCriterionDefs(indexingModelFields, contentType); 325 // add in cache 326 _indexingFieldSearchCriterionDefsCache.put(contentTypeId, indexingFieldSearchCriterionDefs); 327 getLogger().info("IndexingFieldSearchUICriteria for '{}' cache miss. They will be created and added in cache ({}).", contentTypeId, indexingFieldSearchCriterionDefs); 328 return indexingFieldSearchCriterionDefs; 329 } 330 } 331 332 private Collection<IndexingField> _getIndexingFields(ContentType contentType) 333 { 334 return contentType 335 .getIndexingModel() 336 .getFields(); 337 } 338 339 private Collection<CriterionDefinitionAndSourceContentType> _createIndexingFieldSearchCriterionDefs(Collection<IndexingField> indexingModelFields, ContentType requestedContentType) 340 { 341 List<CriterionDefinitionAndSourceContentType> criteria = new ArrayList<>(); 342 String requestedContentTypeId = requestedContentType.getId(); 343 344 try 345 { 346 ThreadSafeComponentManager<SearchUICriterion> searchCriterionManager = new ThreadSafeComponentManager<>(); 347 _managers.add(searchCriterionManager); 348 searchCriterionManager.setLogger(getLogger()); 349 searchCriterionManager.contextualize(_context); 350 searchCriterionManager.service(_manager); 351 List<Pair<String, ContentType>> searchCriteriaRoles = new ArrayList<>(); 352 353 for (IndexingField indexingField : indexingModelFields) 354 { 355 if (__EXCLUDED_INDEXING_FIELD.contains(indexingField.getName())) 356 { 357 continue; 358 } 359 // Get only first-level field (ignore composites and repeaters) 360 if (indexingField.getType() != MetadataType.COMPOSITE) 361 { 362 ContentType fromContentType = Optional.of(indexingField) 363 .filter(MetadataIndexingField.class::isInstance) 364 .map(MetadataIndexingField.class::cast) 365 .map(MetadataIndexingField::getMetadataDefinition) 366 .map(MetadataDefinition::getReferenceContentType) 367 .map(_cTypeEP::getExtension) 368 .orElse(requestedContentType); 369 370 final String path = indexingField.getName(); 371 final String role = path; 372 Configuration criteriaConf = _getIndexingFieldCriteriaConfiguration(path, Optional.of(requestedContentTypeId)); 373 searchCriterionManager.addComponent(_pluginName, null, role, IndexingFieldSearchUICriterion.class, criteriaConf); 374 375 searchCriteriaRoles.add(Pair.of(role, fromContentType)); 376 } 377 } 378 379 searchCriterionManager.initialize(); 380 381 final String prefix = getIndexingFieldCriterionDefinitionPrefix(); 382 for (Pair<String, ContentType> roleAndCType : searchCriteriaRoles) 383 { 384 String searchCriteriaRole = roleAndCType.getLeft(); 385 ContentType fromContentType = roleAndCType.getRight(); 386 SearchUICriterion criterion = searchCriterionManager.lookup(searchCriteriaRole); 387 String id = prefix + fromContentType.getId() + "$" + searchCriteriaRole; 388 ContentSearchCriterionDefinition criterionDef = _criterionDefinition(id, criterion, fromContentType); 389 criteria.add(new CriterionDefinitionAndSourceContentType(criterionDef, fromContentType)); 390 } 391 } 392 catch (Exception e) 393 { 394 throw new RuntimeException("An error occured when retrieving IndexingFieldSearchCriterionDefinitions", e); 395 } 396 397 criteria.sort(Comparator.comparing( 398 critDefAndSourceCtype -> critDefAndSourceCtype._contentTypeId, 399 new ContentTypeComparator(requestedContentType, _cTypeEP) 400 .reversed())); 401 return criteria; 402 } 403 404 private ContentSearchCriterionDefinition _criterionDefinition(String id, SearchUICriterion criterion, ContentType fromContentType) 405 { 406 boolean isAttributeContent = criterion.getType() == MetadataType.CONTENT; 407 return isAttributeContent 408 ? new ContentAttributeContentSearchCriterionDefinition(id, _pluginName, Optional.of(this), criterion, Optional.of(fromContentType), _ametysObjectResolver, _cTypeEP) 409 : new ContentSearchCriterionDefinition(id, _pluginName, Optional.of(this), criterion, Optional.of(fromContentType)); 410 } 411 412 private static class ContentTypeComparator implements Comparator<String> 413 { 414 /* The purpose here is to fill a Map with an Integer 415 * for each content type id in the hierarchy, and to base 416 * the comparator on those values. 417 * For instance, if we have the following hierarchy: 418 * 419 * _______A______ 420 * _____/___\____ 421 * _____B____C___ 422 * ____/_\___|___ 423 * ___B1_B2__C1__ 424 * 425 * which means that <A extends B,C> & <B extends B1,B2> & <C extends C1> 426 * then we want to generate the Map: 427 * {A=1, B=2, B1=3, B2=4, C=5, C1=6} 428 * (which means we do a depth-first search with pre-order i.e. the children are processed after their parent, from left to right) 429 * (See also https://en.wikipedia.org/wiki/Tree_traversal#Pre-order_(NLR)) 430 * 431 * Then with this map, we generate the following order: 432 * [A, B, B1, B2, C, C1] 433 * (which will then be reversed by #_createIndexingFieldSearchCriterionDefs) 434 */ 435 String _baseCTypeId; 436 private Map<String, Integer> _orderByContentType; 437 438 ContentTypeComparator(ContentType baseContentType, ContentTypeExtensionPoint cTypeEP) 439 { 440 _baseCTypeId = baseContentType.getId(); 441 _orderByContentType = new HashMap<>(); 442 Incrementor incrementor = Incrementor.create() 443 .withStart(0) 444 .withMaximalCount(Integer.MAX_VALUE); 445 _fillOrderByContentType(baseContentType, incrementor, cTypeEP); 446 } 447 448 private void _fillOrderByContentType(ContentType contentType, Incrementor incrementor, ContentTypeExtensionPoint cTypeEP) 449 { 450 String contentTypeId = contentType.getId(); 451 incrementor.increment(); 452 _orderByContentType.put(contentTypeId, incrementor.getCount()); 453 Arrays.asList(contentType.getSupertypeIds()) 454 .stream() 455 .sequential() 456 .filter(id -> !_orderByContentType.containsKey(id)) // do not re-process already encountered content types 457 .map(cTypeEP::getExtension) 458 .filter(Objects::nonNull) 459 .forEachOrdered(childContentType -> _fillOrderByContentType(childContentType, incrementor, cTypeEP)); 460 } 461 462 @Override 463 public int compare(String c1ContentTypeId, String c2ContentTypeId) 464 { 465 if (c1ContentTypeId.equals(c2ContentTypeId)) 466 { 467 return 0; 468 } 469 470 if (!_orderByContentType.containsKey(c1ContentTypeId) || !_orderByContentType.containsKey(c2ContentTypeId)) 471 { 472 String message = String.format("An unexpected error occured with the ContentType comparator for base '%s', cannot compare '%s' and '%s'.\nThe orderByContentType map is: %s", _baseCTypeId, c1ContentTypeId, c2ContentTypeId, _orderByContentType.toString()); 473 throw new IllegalStateException(message); 474 } 475 476 return Integer.compare(_orderByContentType.get(c1ContentTypeId), _orderByContentType.get(c2ContentTypeId)); 477 } 478 } 479 480 481 private Collection<ContentSearchCriterionDefinition> _finalIndexingFieldCriterionDefs(Collection<CriterionDefinitionAndSourceContentType> indexingFieldCriterionDefs) 482 { 483 return indexingFieldCriterionDefs 484 .stream() 485 // we want to not have duplicates, i.e. having same indexing field brought by two ContentTypes because it is declared in their common super ContentType 486 // this is done by calling #distinct(), and thus this is based on the #equals impl of CriterionDefinitionAndSourceContentType 487 .distinct() 488 .map(CriterionDefinitionAndSourceContentType::criterionDefinition) 489 .collect(Collectors.toList()); 490 } 491 492 private synchronized ContentSearchCriterionDefinition _getTitleIndexingFieldSearchCriterionDef() 493 { 494 if (_titleIndexingFieldSearchCriterionDefCache != null) 495 { 496 // found in cache 497 getLogger().info("'title' IndexingFieldSearchUICriterion cache hit."); 498 } 499 else 500 { 501 // add in cache 502 _titleIndexingFieldSearchCriterionDefCache = _createTitleIndexingFieldSearchCriterionDef(); 503 getLogger().info("'title' IndexingFieldSearchUICriterion cache miss. It will be created and added in cache."); 504 } 505 return _titleIndexingFieldSearchCriterionDefCache; 506 } 507 508 private ContentSearchCriterionDefinition _createTitleIndexingFieldSearchCriterionDef() 509 { 510 try 511 { 512 ThreadSafeComponentManager<SearchUICriterion> searchCriterionManager = new ThreadSafeComponentManager<>(); 513 _managers.add(searchCriterionManager); 514 searchCriterionManager.setLogger(getLogger()); 515 searchCriterionManager.contextualize(_context); 516 searchCriterionManager.service(_manager); 517 518 String searchCriteriaRole = "title"; 519 Configuration criteriaConf = _getIndexingFieldCriteriaConfiguration(searchCriteriaRole, Optional.empty()); 520 searchCriterionManager.addComponent(_pluginName, null, searchCriteriaRole, IndexingFieldSearchUICriterion.class, criteriaConf); 521 522 searchCriterionManager.initialize(); 523 524 SearchUICriterion criterion = searchCriterionManager.lookup(searchCriteriaRole); 525 String id = getIndexingFieldCriterionDefinitionPrefix() + "_common$" + searchCriteriaRole; 526 return new ContentSearchCriterionDefinition(id, _pluginName, Optional.of(this), criterion, Optional.empty()); 527 } 528 catch (Exception e) 529 { 530 throw new RuntimeException("An error occured when retrieving IndexingFieldSearchCriterionDefinitions", e); 531 } 532 } 533 534 /** 535 * Gets the prefix for the ids of indexing field criterion definitions 536 * @return the prefix for the ids of indexing field criterion definitions 537 */ 538 protected String getIndexingFieldCriterionDefinitionPrefix() 539 { 540 return getCriterionDefinitionPrefix() + "indexingField$"; 541 } 542 543 private Configuration _getIndexingFieldCriteriaConfiguration(String path, Optional<String> contentTypeId) 544 { 545 DefaultConfiguration criteriaConf = new DefaultConfiguration("criteria"); 546 DefaultConfiguration metaConf = new DefaultConfiguration("field"); 547 criteriaConf.addChild(metaConf); 548 metaConf.setAttribute("path", path); 549 550 DefaultConfiguration cTypesConf = new DefaultConfiguration("contentTypes"); 551 criteriaConf.addChild(cTypesConf); 552 if (contentTypeId.isPresent()) 553 { 554 DefaultConfiguration baseTypeConf = new DefaultConfiguration("baseType"); 555 baseTypeConf.setAttribute("id", contentTypeId.get()); 556 cTypesConf.addChild(baseTypeConf); 557 } 558 559 return criteriaConf; 560 } 561 562 @Override 563 public Collection<Returnable> relationsWith() 564 { 565 return Arrays.asList(_pageReturnable, _associatedContentReturnable); 566 } 567 568 // wraps a CriterionDefinition and where it comes from 569 private static class CriterionDefinitionAndSourceContentType 570 { 571 String _contentTypeId; 572 private ContentSearchCriterionDefinition _critDef; 573 private String _critDefId; 574 575 CriterionDefinitionAndSourceContentType(ContentSearchCriterionDefinition critDef, ContentType contentType) 576 { 577 _critDef = critDef; 578 _critDefId = critDef.getId(); 579 _contentTypeId = contentType.getId(); 580 } 581 582 ContentSearchCriterionDefinition criterionDefinition() 583 { 584 return _critDef; 585 } 586 587 @Override 588 public String toString() 589 { 590 return _critDefId; 591 } 592 593 @Override 594 public int hashCode() 595 { 596 final int prime = 31; 597 int result = 1; 598 result = prime * result + ((_contentTypeId == null) ? 0 : _contentTypeId.hashCode()); 599 result = prime * result + ((_critDefId == null) ? 0 : _critDefId.hashCode()); 600 return result; 601 } 602 603 @Override 604 public boolean equals(Object obj) 605 { 606 if (this == obj) 607 { 608 return true; 609 } 610 if (obj == null) 611 { 612 return false; 613 } 614 if (!(obj instanceof CriterionDefinitionAndSourceContentType)) 615 { 616 return false; 617 } 618 CriterionDefinitionAndSourceContentType other = (CriterionDefinitionAndSourceContentType) obj; 619 if (_contentTypeId == null) 620 { 621 if (other._contentTypeId != null) 622 { 623 return false; 624 } 625 } 626 else if (!_contentTypeId.equals(other._contentTypeId)) 627 { 628 return false; 629 } 630 if (_critDefId == null) 631 { 632 if (other._critDefId != null) 633 { 634 return false; 635 } 636 } 637 else if (!_critDefId.equals(other._critDefId)) 638 { 639 return false; 640 } 641 return true; 642 } 643 } 644}