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