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