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