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