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