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