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.impl; 017 018import java.util.ArrayList; 019import java.util.Collection; 020import java.util.Collections; 021import java.util.HashSet; 022import java.util.List; 023import java.util.Map; 024import java.util.Set; 025import java.util.stream.Collectors; 026 027import org.apache.avalon.framework.configuration.Configuration; 028import org.apache.avalon.framework.configuration.ConfigurationException; 029import org.apache.avalon.framework.service.ServiceException; 030import org.apache.avalon.framework.service.ServiceManager; 031import org.apache.commons.lang3.BooleanUtils; 032import org.apache.commons.lang3.StringUtils; 033 034import org.ametys.cms.content.properties.AbstractMultiValuesProperty; 035import org.ametys.cms.content.referencetable.HierarchicalReferenceTablesHelper; 036import org.ametys.cms.contenttype.ContentType; 037import org.ametys.cms.contenttype.ContentTypesHelper; 038import org.ametys.cms.contenttype.MetadataType; 039import org.ametys.cms.data.type.ModelItemTypeConstants; 040import org.ametys.cms.repository.Content; 041import org.ametys.cms.search.QueryBuilder; 042import org.ametys.cms.search.SearchField; 043import org.ametys.cms.search.content.ContentSearchHelper; 044import org.ametys.cms.search.model.IndexingFieldSearchCriterion; 045import org.ametys.cms.search.query.AndQuery; 046import org.ametys.cms.search.query.BooleanQuery; 047import org.ametys.cms.search.query.ContentQuery; 048import org.ametys.cms.search.query.DateQuery; 049import org.ametys.cms.search.query.DoubleQuery; 050import org.ametys.cms.search.query.GeocodeQuery; 051import org.ametys.cms.search.query.JoinQuery; 052import org.ametys.cms.search.query.LongQuery; 053import org.ametys.cms.search.query.OrQuery; 054import org.ametys.cms.search.query.Query; 055import org.ametys.cms.search.query.Query.Operator; 056import org.ametys.cms.search.query.RichTextQuery; 057import org.ametys.cms.search.query.StringQuery; 058import org.ametys.cms.search.query.UsersQuery; 059import org.ametys.core.user.UserIdentity; 060import org.ametys.core.util.date.AdaptableDateParser; 061import org.ametys.plugins.core.user.UserHelper; 062import org.ametys.plugins.repository.model.RepeaterDefinition; 063import org.ametys.runtime.model.ElementDefinition; 064import org.ametys.runtime.model.ModelHelper; 065import org.ametys.runtime.model.ModelItem; 066import org.ametys.runtime.model.ModelItemGroup; 067import org.ametys.runtime.model.exception.UndefinedItemPathException; 068import org.ametys.runtime.parameter.Validator; 069import org.ametys.runtime.plugin.component.ThreadSafeComponentManager; 070 071/** 072 * This class is a search criteria on a metadata of a content 073 */ 074public class IndexingFieldSearchUICriterion extends AbstractSearchUICriterion implements IndexingFieldSearchCriterion 075{ 076 /** Prefix for id of metadata search criteria */ 077 public static final String SEARCH_CRITERIA_METADATA_PREFIX = "metadata-"; 078 079 /** The content search helper. */ 080 protected ContentSearchHelper _searchHelper; 081 /** The hierarchical reference tables helper. */ 082 protected HierarchicalReferenceTablesHelper _hierarchicalReferenceTablesHelper; 083 /** The user helper */ 084 protected UserHelper _userHelper; 085 /** The helper for convenient methods on content types */ 086 protected ContentTypesHelper _contentTypesHelper; 087 088 /** The criteria operator */ 089 protected Operator _operator; 090 091 /** The definition used for this criterion */ 092 protected ElementDefinition _definition; 093 094 /** The field full path */ 095 protected String _fullPath; 096 097 /** The field path */ 098 protected String _fieldPath; 099 100 /** The join paths */ 101 protected List<String> _joinPaths; 102 103 /** Is it AND or OR for multiple metadata */ 104 protected boolean _isMultipleOperandAnd; 105 106 /** ComponentManager for {@link Validator}s. */ 107 protected ThreadSafeComponentManager<Validator> _validatorManager; 108 109 private boolean _isTypeContentWithMultilingualTitle; 110 111 @Override 112 public void service(ServiceManager manager) throws ServiceException 113 { 114 super.service(manager); 115 _searchHelper = (ContentSearchHelper) manager.lookup(ContentSearchHelper.ROLE); 116 _hierarchicalReferenceTablesHelper = (HierarchicalReferenceTablesHelper) manager.lookup(HierarchicalReferenceTablesHelper.ROLE); 117 _userHelper = (UserHelper) manager.lookup(UserHelper.ROLE); 118 _contentTypesHelper = (ContentTypesHelper) manager.lookup(ContentTypesHelper.ROLE); 119 } 120 121 @Override 122 public void dispose() 123 { 124 super.dispose(); 125 126 _validatorManager.dispose(); 127 _validatorManager = null; 128 } 129 130 @Override 131 public void configure(Configuration configuration) throws ConfigurationException 132 { 133 try 134 { 135 _validatorManager = new ThreadSafeComponentManager<>(); 136 _validatorManager.setLogger(_logger); 137 _validatorManager.contextualize(_context); 138 _validatorManager.service(_manager); 139 140 _enumeratorManager = new ThreadSafeComponentManager<>(); 141 _enumeratorManager.setLogger(_logger); 142 _enumeratorManager.contextualize(_context); 143 _enumeratorManager.service(_manager); 144 145 _fullPath = configuration.getChild("field").getAttribute("path"); 146 Set<String> baseContentTypeIds = new HashSet<>(); 147 for (Configuration cTypeConf : configuration.getChild("contentTypes").getChildren("baseType")) 148 { 149 baseContentTypeIds.add(cTypeConf.getAttribute("id")); 150 } 151 152 _operator = Operator.fromName(configuration.getChild("test-operator").getValue("eq")); 153 _isMultipleOperandAnd = !StringUtils.equalsIgnoreCase(configuration.getChild("multiple-operand").getValue("or"), "or"); 154 _joinPaths = new ArrayList<>(); 155 156 String id = SEARCH_CRITERIA_METADATA_PREFIX + _fullPath + "-" + _operator.getName(); 157 158 if (!baseContentTypeIds.isEmpty()) 159 { 160 try 161 { 162 Collection<ContentType> baseContentTypes = baseContentTypeIds.stream() 163 .map(_cTypeEP::getExtension) 164 .collect(Collectors.toList()); 165 ModelItem modelItem = ModelHelper.getModelItem(_fullPath, baseContentTypes); 166 if (modelItem instanceof ElementDefinition def) 167 { 168 _definition = def; 169 } 170 else 171 { 172 throw new ConfigurationException("Indexing field search criteria with path '" + _fullPath + "' refers to a group"); 173 } 174 175 _joinPaths = _searchHelper.computeJoinPaths(_fullPath, baseContentTypeIds, false); 176 } 177 catch (UndefinedItemPathException e) 178 { 179 throw new ConfigurationException("Indexing field search criteria with path '" + _fullPath + "' refers to an unknown model item", e); 180 } 181 } 182 else if (Content.ATTRIBUTE_TITLE.equals(_fullPath)) 183 { 184 // Only the title indexing field is allowed if there is no base content types. 185 _definition = _contentTypesHelper.getTitleAttributeDefinition(); 186 } 187 else 188 { 189 throw new ConfigurationException("The indexing field '" + _fullPath + "' is forbidden when no content type is specified: only title can be used."); 190 } 191 192 _fieldPath = _computeFieldPath(_definition); 193 194 setId(id); 195 setGroup(_configureI18nizableText(configuration.getChild("group", false), null)); 196 _configureLabel(configuration, _definition); 197 _configureDescription(configuration, _definition); 198 199 MetadataType type = MetadataType.fromModelItemType(_definition.getType()); 200 setType(type); 201 202// setValidator(finalDefinition.getValidator()); 203 String validatorRole = "validator"; 204 if (!_initializeValidator(_validatorManager, "cms", validatorRole, configuration)) 205 { 206 validatorRole = null; 207 } 208 209 setEnumerator(configureEnumerator(configuration, _definition)); 210 setWidget(configureWidget(configuration, _definition)); 211 setWidgetParameters(configureWidgetParameters(configuration, _definition)); 212 213 setMultiple(_definition.isMultiple()); 214 215 _configureTypeContentWithMultilingualTitle(_definition); 216 217 _configureOperator(configuration, _definition); 218 219 configureUIProperties(configuration); 220 configureValues(configuration); 221 } 222 catch (Exception e) 223 { 224 throw new ConfigurationException("Error configuring the indexing field search criterion.", configuration, e); 225 } 226 } 227 228 /** 229 * Get the operator. 230 * @return the operator. 231 */ 232 public Operator getOperator() 233 { 234 return _operator; 235 } 236 237 public String getFieldId() 238 { 239 return SEARCH_CRITERIA_METADATA_PREFIX + _fullPath; 240 } 241 242 private String _computeFieldPath(ModelItem definition) 243 { 244 StringBuilder path = new StringBuilder(); 245 246 ModelItemGroup parent = definition.getParent(); 247 if (parent != null && !(parent instanceof RepeaterDefinition)) 248 { 249 path.append(_computeFieldPath(parent)).append(ModelItem.ITEM_PATH_SEPARATOR); 250 } 251 252 path.append(definition.getName()); 253 return path.toString(); 254 } 255 256 /** 257 * Get the path of field (separated by '/') 258 * @return the path of the field. 259 */ 260 public String getFieldPath() 261 { 262 return _fieldPath; 263 } 264 265 /** 266 * Get the join paths, separated with slashes. 267 * @return the join paths. 268 */ 269 public List<String> getJoinPaths() 270 { 271 return Collections.unmodifiableList(_joinPaths); 272 } 273 274 @Override 275 public Query getQuery(Object value, Operator customOperator, Map<String, Object> allValues, String language, Map<String, Object> contextualParameters) 276 { 277 String fieldPath = getFieldPath(); 278 List<String> joinPaths = getJoinPaths(); 279 Operator operator = customOperator != null ? customOperator : getOperator(); 280 281 if (operator != Operator.EXISTS && isEmpty(value)) 282 { 283 return null; 284 } 285 boolean isValueEscaped = BooleanUtils.isTrue((Boolean) contextualParameters.get(QueryBuilder.VALUE_IS_ESCAPED)); 286 287 Query query = null; 288 switch (getType()) 289 { 290 case DATE: 291 case DATETIME: 292 query = getDateQuery(value, fieldPath, operator); 293 break; 294 case LONG: 295 query = getLongQuery(value, fieldPath, operator); 296 break; 297 case DOUBLE: 298 query = getDoubleQuery(value, fieldPath, operator); 299 break; 300 case BOOLEAN: 301 query = getBooleanQuery(value, fieldPath, operator); 302 break; 303 case STRING: 304 if (_definition instanceof AbstractMultiValuesProperty property && ModelItemTypeConstants.CONTENT_ELEMENT_TYPE_ID.equals(property.getElementDefinition().getType().getId())) 305 { 306 query = getContentQuery(value, language, fieldPath, operator); 307 } 308 else 309 { 310 if (contextualParameters.containsKey(QueryBuilder.MULTILINGUAL_SEARCH)) 311 { 312 // Force language to null if the search is concern a string field on multilingual contents 313 query = getStringQuery(value, null, fieldPath, operator, isValueEscaped); 314 } 315 else 316 { 317 query = getStringQuery(value, language, fieldPath, operator, isValueEscaped); 318 } 319 } 320 break; 321 case RICH_TEXT: 322 if (contextualParameters.containsKey(QueryBuilder.MULTILINGUAL_SEARCH)) 323 { 324 // Force language to null if the search is concern a string field on multilingual contents 325 query = getRichTextQuery(value, null, fieldPath, operator, isValueEscaped); 326 } 327 else 328 { 329 query = getRichTextQuery(value, language, fieldPath, operator, isValueEscaped); 330 } 331 break; 332 case MULTILINGUAL_STRING: 333 query = getStringQuery(value, language, fieldPath, operator, isValueEscaped); 334 break; 335 case USER: 336 query = getUserQuery(value, fieldPath, operator); 337 break; 338 case CONTENT: 339 query = getContentQuery(value, language, fieldPath, operator); 340 break; 341 case REFERENCE: 342 query = getStringQuery(value, language, fieldPath, operator, isValueEscaped); 343 break; 344 case GEOCODE: 345 query = new GeocodeQuery(fieldPath, operator, (Map<String, Integer>) value); 346 break; 347 default: 348 return null; 349 } 350 351 if (query != null && !joinPaths.isEmpty()) 352 { 353 query = new JoinQuery(query, joinPaths); 354 } 355 356 return query; 357 } 358 359 private static boolean isEmpty(Object value) 360 { 361 return value == null 362 || value instanceof String && ((String) value).length() == 0 363 || value instanceof List && ((List) value).isEmpty(); 364 } 365 366 /** 367 * Get a string query. 368 * @param value The value to use for this criterion. 369 * @param language The search language. 370 * @param fieldPath The field path. 371 * @param operator The query operator to use. 372 * @param isValueEscaped <code>true</code> if the given value is already escaped 373 * @return The query. 374 */ 375 protected Query getStringQuery(Object value, String language, String fieldPath, Operator operator, boolean isValueEscaped) 376 { 377 if (operator == Operator.EXISTS) 378 { 379 return new StringQuery(fieldPath, operator, null, language); 380 } 381 else 382 { 383 return getTextQuery(value, language, fieldPath, operator, isValueEscaped); 384 } 385 } 386 387 /** 388 * Get a boolean query. 389 * @param value The value to use for this criterion. 390 * @param fieldPath The field path. 391 * @param operator The query operator to use. 392 * @return The query. 393 */ 394 protected Query getBooleanQuery(Object value, String fieldPath, Operator operator) 395 { 396 if (operator == Operator.EXISTS) 397 { 398 return new BooleanQuery(fieldPath); 399 } 400 else if (value instanceof Collection<?>) 401 { 402 @SuppressWarnings("unchecked") 403 Collection<Object> values = (Collection<Object>) value; 404 405 List<Query> queries = new ArrayList<>(); 406 for (Object val : values) 407 { 408 queries.add(getBooleanQuery(val, fieldPath, operator)); 409 } 410 411 return _isMultipleOperandAnd ? new AndQuery(queries) : new OrQuery(queries); 412 } 413 else if (value instanceof String) 414 { 415 Boolean boolValue = Boolean.parseBoolean((String) value); 416 return new BooleanQuery(fieldPath, boolValue); 417 } 418 else 419 { 420 return new BooleanQuery(fieldPath, (Boolean) value); 421 } 422 } 423 424 /** 425 * Get a double query. 426 * @param value The value to use for this criterion. 427 * @param fieldPath The field path. 428 * @param operator The query operator to use. 429 * @return The query. 430 */ 431 protected Query getDoubleQuery(Object value, String fieldPath, Operator operator) 432 { 433 if (operator == Operator.EXISTS) 434 { 435 return new DoubleQuery(fieldPath); 436 } 437 else if (value instanceof Collection<?>) 438 { 439 @SuppressWarnings("unchecked") 440 Collection<Object> values = (Collection<Object>) value; 441 442 List<Query> queries = new ArrayList<>(); 443 for (Object val : values) 444 { 445 queries.add(getDoubleQuery(val, fieldPath, operator)); 446 } 447 448 return _isMultipleOperandAnd ? new AndQuery(queries) : new OrQuery(queries); 449 } 450 else if (value instanceof String) 451 { 452 Double doubleValue = Double.parseDouble((String) value); 453 return new DoubleQuery(fieldPath, operator, doubleValue); 454 } 455 else if (value instanceof Integer) 456 { 457 return new DoubleQuery(fieldPath, operator, ((Integer) value).doubleValue()); 458 } 459 else if (value instanceof Number) 460 { 461 Number valueAsNumber = (Number) value; 462 Double valueAsDouble = valueAsNumber.doubleValue(); 463 // We check that the cast to long was safe 464 if (valueAsNumber.toString().equals(valueAsDouble.toString())) 465 { 466 return new DoubleQuery(fieldPath, operator, valueAsDouble); 467 } 468 else 469 { 470 throw new NumberFormatException("Failed to convert the value " + value + " to Double. The value is out of bound."); 471 } 472 } 473 else 474 { 475 throw new NumberFormatException("Failed to convert the value " + value + " to Double."); 476 } 477 } 478 479 /** 480 * Get a long query. 481 * @param value The value to use for this criterion. 482 * @param fieldPath The field path. 483 * @param operator The query operator to use. 484 * @return The query. 485 */ 486 protected Query getLongQuery(Object value, String fieldPath, Operator operator) 487 { 488 if (operator == Operator.EXISTS) 489 { 490 return new LongQuery(fieldPath); 491 } 492 else if (value instanceof Collection<?>) 493 { 494 @SuppressWarnings("unchecked") 495 Collection<Object> values = (Collection<Object>) value; 496 497 List<Query> queries = new ArrayList<>(); 498 for (Object val : values) 499 { 500 queries.add(getLongQuery(val, fieldPath, operator)); 501 } 502 503 return _isMultipleOperandAnd ? new AndQuery(queries) : new OrQuery(queries); 504 } 505 else if (value instanceof String) 506 { 507 return new LongQuery(fieldPath, operator, Long.parseLong((String) value)); 508 } 509 else if (value instanceof Number) 510 { 511 Number valueAsNumber = (Number) value; 512 Long valueAsLong = valueAsNumber.longValue(); 513 // We check that the cast to long was safe 514 if (valueAsNumber.toString().equals(valueAsLong.toString())) 515 { 516 return new LongQuery(fieldPath, operator, valueAsLong); 517 } 518 else 519 { 520 throw new NumberFormatException("Failed to convert the value " + value + " to Long. The value is out of bound."); 521 } 522 } 523 else 524 { 525 throw new NumberFormatException("Failed to convert the value " + value + " to Long."); 526 } 527 } 528 529 /** 530 * Get a date query. 531 * @param value The value to use for this criterion. 532 * @param fieldPath The field path. 533 * @param operator The query operator to use. 534 * @return The query. 535 */ 536 protected Query getDateQuery(Object value, String fieldPath, Operator operator) 537 { 538 if (operator == Operator.EXISTS) 539 { 540 return new DateQuery(fieldPath); 541 } 542 else if (value instanceof Collection<?>) 543 { 544 @SuppressWarnings("unchecked") 545 Collection<Object> values = (Collection<Object>) value; 546 547 List<Query> queries = new ArrayList<>(); 548 for (Object val : values) 549 { 550 queries.add(getDateQuery(val, fieldPath, operator)); 551 } 552 553 return _isMultipleOperandAnd ? new AndQuery(queries) : new OrQuery(queries); 554 } 555 else 556 { 557 return _getSingleDateQuery((String) value, fieldPath, operator); 558 } 559 } 560 561 private Query _getSingleDateQuery(String value, String fieldPath, Operator operator) 562 { 563 return new DateQuery(fieldPath, operator, AdaptableDateParser.parse(value)); 564 } 565 566 /** 567 * Get a content query. 568 * @param value The value to use for this criterion. 569 * @param language The search language. 570 * @param fieldPath The field path. 571 * @param operator The query operator to use. 572 * @return The query. 573 */ 574 protected Query getContentQuery(Object value, String language, String fieldPath, Operator operator) 575 { 576 boolean isMultipleOperandAnd = operator == Operator.NE ? !_isMultipleOperandAnd : _isMultipleOperandAnd; // reverse it if NE 577 return new ContentQuery(fieldPath, operator, value, _resolver, _contentHelper, _hierarchicalReferenceTablesHelper, false, isMultipleOperandAnd); 578 } 579 580 /** 581 * Get a user query 582 * @param value The value to use for this criterion. 583 * @param fieldPath The field path. 584 * @param operator The query operator to use. 585 * @return The query. 586 */ 587 @SuppressWarnings("unchecked") 588 protected Query getUserQuery(Object value, String fieldPath, Operator operator) 589 { 590 List<UserIdentity> users; 591 if (value instanceof Map<?, ?>) 592 { 593 users = List.of(_userHelper.json2userIdentity((Map<String, Object>) value)); 594 } 595 else if (value instanceof Collection< ? >) 596 { 597 users = ((Collection<Map<String, Object>>) value).stream() 598 .map(_userHelper::json2userIdentity).toList(); 599 } 600 else 601 { 602 throw new IllegalArgumentException("The value for the field '" + fieldPath + "' of type USER cannot be cast to a Map or a Collection"); 603 } 604 return new UsersQuery(fieldPath + "_s", operator, users); 605 } 606 607 /** 608 * Get a geocode query. 609 * @param value The value to use for this criterion. 610 * @param fieldPath The field path. 611 * @param operator The query operator to use. 612 * @return The query. 613 */ 614 protected Query getGeocodeQuery(Object value, String fieldPath, Operator operator) 615 { 616 if (operator == Operator.EXISTS) 617 { 618 return new GeocodeQuery(fieldPath); 619 } 620 else if (value instanceof Collection<?>) 621 { 622 @SuppressWarnings("unchecked") 623 Collection<Map<String, Integer>> values = (Collection<Map<String, Integer>>) value; 624 625 List<Query> queries = new ArrayList<>(); 626 for (Object val : values) 627 { 628 queries.add(getGeocodeQuery(val, fieldPath, operator)); 629 } 630 631 return _isMultipleOperandAnd ? new AndQuery(queries) : new OrQuery(queries); 632 } 633 else if (value instanceof Map<?, ?>) 634 { 635 return new GeocodeQuery(fieldPath, operator, (Map<String, Integer>) value); 636 } 637 else 638 { 639 throw new IllegalArgumentException("The value for the field '" + fieldPath + "' of type GEOCODE cannot be cast to a Map or a Collection"); 640 } 641 } 642 643 /** 644 * Get a rich text query. 645 * @param value The value to use for this criterion. 646 * @param language The search language. 647 * @param fieldPath The field path. 648 * @param operator The query operator to use. 649 * @param isValueEscaped <code>true</code> if the given value is already escaped 650 * @return The query. 651 */ 652 protected Query getRichTextQuery(Object value, String language, String fieldPath, Operator operator, boolean isValueEscaped) 653 { 654 if (operator == Operator.EXISTS) 655 { 656 return new RichTextQuery(fieldPath, operator, null, language); 657 } 658 else 659 { 660 return getTextQuery(value, language, fieldPath, operator, isValueEscaped); 661 } 662 } 663 664 /** 665 * Get a text query. 666 * @param value The value to use for this criterion. 667 * @param language The search language. 668 * @param fieldPath The field path. 669 * @param operator The query operator to use. 670 * @param isValueEscaped <code>true</code> if the given value is already escaped 671 * @return The query. 672 */ 673 protected Query getTextQuery(Object value, String language, String fieldPath, Operator operator, boolean isValueEscaped) 674 { 675 if (value instanceof Collection<?>) 676 { 677 @SuppressWarnings("unchecked") 678 Collection<Object> values = (Collection<Object>) value; 679 680 List<Query> queries = new ArrayList<>(); 681 for (Object val : values) 682 { 683 queries.add(getStringQuery(val, language, fieldPath, operator, isValueEscaped)); 684 } 685 686 return _isMultipleOperandAnd ? new AndQuery(queries) : new OrQuery(queries); 687 } 688 else if (operator.equals(Operator.LIKE)) 689 { 690 String stringValue = (String) value; 691 692 if (StringUtils.isNotEmpty(stringValue)) 693 { 694 boolean doNotUseLanguage = getType() != MetadataType.MULTILINGUAL_STRING; 695 return new StringQuery(fieldPath, operator, stringValue, doNotUseLanguage ? null : language, isValueEscaped); 696 } 697 698 return null; 699 } 700 else 701 { 702 return new StringQuery(fieldPath, operator, (String) value, language, isValueEscaped); 703 } 704 } 705 706 @Override 707 public SearchField getSearchField() 708 { 709 return _searchHelper.getMetadataSearchField(getJoinPaths(), getFieldPath(), getType(), _isTypeContentWithMultilingualTitle); 710 } 711 712 private void _configureTypeContentWithMultilingualTitle(ModelItem modelItem) 713 { 714 _isTypeContentWithMultilingualTitle = _searchHelper.isTitleMultilingual(modelItem); 715 } 716 717 /** 718 * Configure the criterion operator. 719 * @param configuration the global criterion configuration. 720 * @param definition the metadata definition. 721 * @throws ConfigurationException if an error occurs. 722 */ 723 @SuppressWarnings("unchecked") 724 private void _configureOperator(Configuration configuration, ElementDefinition definition) throws ConfigurationException 725 { 726 try 727 { 728 String definitionTypeId = definition.getType().getId(); 729 if (definition instanceof AbstractMultiValuesProperty property) 730 { 731 definitionTypeId = property.getElementDefinition().getType().getId(); 732 } 733 734 String op = configuration.getChild("test-operator").getValue(""); 735 if (StringUtils.isNotBlank(op)) 736 { 737 _operator = Operator.fromName(op); 738 } 739 else 740 { 741 if (definition.getCriterionEnumerator(configuration, _enumeratorManager) == null 742 && (org.ametys.runtime.model.type.ModelItemTypeConstants.STRING_TYPE_ID.equals(definitionTypeId) 743 || ModelItemTypeConstants.MULTILINGUAL_STRING_ELEMENT_TYPE_ID.equals(definitionTypeId) 744 || ModelItemTypeConstants.RICH_TEXT_ELEMENT_TYPE_ID.equals(definitionTypeId) 745 || ModelItemTypeConstants.REFERENCE_ELEMENT_TYPE_ID.equals(definitionTypeId))) 746 { 747 _operator = Operator.SEARCH; 748 } 749 else 750 { 751 _operator = Operator.EQ; 752 } 753 } 754 } 755 catch (IllegalArgumentException e) 756 { 757 throw new ConfigurationException("Invalid operator", configuration, e); 758 } 759 } 760 761 private void _configureLabel(Configuration configuration, ModelItem modelItem) 762 { 763 setLabel(_configureI18nizableText(configuration.getChild("label", false), modelItem.getLabel())); 764 } 765 766 private void _configureDescription(Configuration configuration, ModelItem modelItem) 767 { 768 setDescription(_configureI18nizableText(configuration.getChild("description", false), modelItem.getDescription())); 769 } 770}