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