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