001/* 002 * Copyright 2017 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.plugins.extraction.component; 017 018import java.util.ArrayList; 019import java.util.Arrays; 020import java.util.Collection; 021import java.util.Collections; 022import java.util.HashSet; 023import java.util.LinkedHashSet; 024import java.util.List; 025import java.util.Locale; 026import java.util.Map; 027import java.util.Optional; 028import java.util.Set; 029import java.util.regex.Matcher; 030import java.util.regex.Pattern; 031import java.util.stream.Collectors; 032 033import org.apache.avalon.framework.configuration.Configuration; 034import org.apache.avalon.framework.configuration.ConfigurationException; 035import org.apache.avalon.framework.service.ServiceException; 036import org.apache.avalon.framework.service.ServiceManager; 037import org.xml.sax.ContentHandler; 038 039import org.ametys.cms.content.ContentHelper; 040import org.ametys.cms.contenttype.ContentType; 041import org.ametys.cms.data.ContentValue; 042import org.ametys.cms.data.type.ModelItemTypeConstants; 043import org.ametys.cms.repository.Content; 044import org.ametys.cms.repository.ContentAttributeTypeExtensionPoint; 045import org.ametys.cms.search.GetQueryFromJSONHelper; 046import org.ametys.cms.search.QueryBuilder; 047import org.ametys.cms.search.content.ContentSearcherFactory; 048import org.ametys.cms.search.content.ContentSearcherFactory.SimpleContentSearcher; 049import org.ametys.cms.search.model.SystemProperty; 050import org.ametys.cms.search.model.SystemPropertyExtensionPoint; 051import org.ametys.cms.search.query.AbstractTextQuery; 052import org.ametys.cms.search.query.Query.Operator; 053import org.ametys.cms.search.query.QuerySyntaxException; 054import org.ametys.cms.search.query.StringQuery; 055import org.ametys.cms.search.solr.SolrContentQueryHelper; 056import org.ametys.cms.search.ui.model.SearchUIModel; 057import org.ametys.core.util.JSONUtils; 058import org.ametys.core.util.LambdaUtils; 059import org.ametys.core.util.StringUtils; 060import org.ametys.plugins.extraction.execution.ExtractionExecutionContext; 061import org.ametys.plugins.extraction.execution.ExtractionExecutionContextHierarchyElement; 062import org.ametys.plugins.queriesdirectory.Query; 063import org.ametys.plugins.repository.AmetysObjectIterable; 064import org.ametys.plugins.repository.AmetysObjectResolver; 065import org.ametys.plugins.repository.EmptyIterable; 066import org.ametys.plugins.thesaurus.ThesaurusDAO; 067import org.ametys.runtime.model.ModelHelper; 068import org.ametys.runtime.model.ModelItem; 069import org.ametys.runtime.model.type.ElementType; 070import org.ametys.runtime.model.type.ModelItemType; 071 072/** 073 * This class represents an extraction component with a solr query 074 */ 075public abstract class AbstractSolrExtractionComponent extends AbstractExtractionComponent 076{ 077 /** 078 * Regex used to extract variables from a join expression: \$\{(\.\.(?:\/\.\.)*(?:\/[^\/}]+)?)\} 079 * a variable is inside a ${} 080 * variable starts with .. (to get the direct parent), 081 * has several /.. (to get parent of parent of (...)) 082 * and can have a /metadataName (to specify the metadata to join on) 083 */ 084 private static final String EXTRACT_JOIN_VARIABLES_REGEX = "\\$\\{(\\.\\.(?:\\/\\.\\.)*(?:\\/[^\\/}]+)?)\\}"; 085 086 /** Content types concerned by the solr search */ 087 protected Set<String> _contentTypes = new HashSet<>(); 088 089 /** Reference id of a recorded query */ 090 protected String _queryReferenceId; 091 092 /** The list of clauses */ 093 protected List<ExtractionClause> _clauses = new ArrayList<>(); 094 095 /** Helper to resolve referenced query infos */ 096 protected GetQueryFromJSONHelper _getQueryFromJSONHelper; 097 098 /** Util class to manipulate JSON String */ 099 protected JSONUtils _jsonUtils; 100 101 private AmetysObjectResolver _resolver; 102 private SystemPropertyExtensionPoint _systemPropertyExtensionPoint; 103 private ContentHelper _contentHelper; 104 private ContentSearcherFactory _contentSearcherFactory; 105 private QueryBuilder _queryBuilder; 106 private ContentAttributeTypeExtensionPoint _contentAttributeTypeExtensionPoint; 107 108 @Override 109 public void service(ServiceManager serviceManager) throws ServiceException 110 { 111 super.service(serviceManager); 112 _jsonUtils = (JSONUtils) serviceManager.lookup(JSONUtils.ROLE); 113 _getQueryFromJSONHelper = (GetQueryFromJSONHelper) serviceManager.lookup(GetQueryFromJSONHelper.ROLE); 114 _resolver = (AmetysObjectResolver) serviceManager.lookup(AmetysObjectResolver.ROLE); 115 _systemPropertyExtensionPoint = (SystemPropertyExtensionPoint) serviceManager.lookup(SystemPropertyExtensionPoint.ROLE); 116 _contentHelper = (ContentHelper) serviceManager.lookup(ContentHelper.ROLE); 117 _contentSearcherFactory = (ContentSearcherFactory) serviceManager.lookup(ContentSearcherFactory.ROLE); 118 _queryBuilder = (QueryBuilder) serviceManager.lookup(QueryBuilder.ROLE); 119 _contentAttributeTypeExtensionPoint = (ContentAttributeTypeExtensionPoint) serviceManager.lookup(ContentAttributeTypeExtensionPoint.ROLE); 120 } 121 122 @Override 123 public void configure(Configuration configuration) throws ConfigurationException 124 { 125 super.configure(configuration); 126 127 Configuration clauses = configuration.getChild("clauses"); 128 for (Configuration clause : clauses.getChildren("clause")) 129 { 130 addClauses(clause.getValue()); 131 } 132 133 _contentTypes = new HashSet<>(); 134 if (Arrays.asList(configuration.getAttributeNames()).contains("ref")) 135 { 136 if (Arrays.asList(configuration.getAttributeNames()).contains("contentTypes")) 137 { 138 throw new IllegalArgumentException(getLogsPrefix() + "a component with a query reference should not specify a content type"); 139 } 140 141 _queryReferenceId = configuration.getAttribute("ref"); 142 } 143 else 144 { 145 String contentTypesString = configuration.getAttribute("contentTypes"); 146 _contentTypes.addAll(StringUtils.stringToCollection(contentTypesString)); 147 } 148 } 149 150 @Override 151 public void prepareComponentExecution(ExtractionExecutionContext context) throws Exception 152 { 153 super.prepareComponentExecution(context); 154 155 if (_queryReferenceId != null && !_queryReferenceId.isEmpty()) 156 { 157 Query referencedQuery = _resolver.resolveById(_queryReferenceId); 158 computeReferencedQueryInfos(referencedQuery.getContent()); 159 } 160 161 _computeClausesInfos(context); 162 } 163 164 /** 165 * Manages the stored query referenced by the component 166 * @param refQueryContent referenced query content 167 * @throws QuerySyntaxException if there is a syntax error in the referenced query 168 */ 169 @SuppressWarnings("unchecked") 170 protected void computeReferencedQueryInfos(String refQueryContent) throws QuerySyntaxException 171 { 172 Map<String, Object> contentMap = _jsonUtils.convertJsonToMap(refQueryContent); 173 Map<String, Object> exportParams = (Map<String, Object>) contentMap.get("exportParams"); 174 String modelId = (String) exportParams.get("model"); 175 176 String q; 177 if (modelId.contains("solr")) 178 { 179 Map<String, Object> values = (Map<String, Object>) exportParams.get("values"); 180 String baseQuery = (String) values.get("query"); 181 182 _contentTypes = new HashSet<>((List<String>) values.get("contentTypes")); 183 184 q = SolrContentQueryHelper.buildQuery(_queryBuilder, baseQuery, _contentTypes, Collections.emptySet()); 185 } 186 else 187 { 188 SearchUIModel model = _getQueryFromJSONHelper.getSearchUIModel(exportParams); 189 List<String> contentTypesToFill = new ArrayList<>(); 190 org.ametys.cms.search.query.Query query = _getQueryFromJSONHelper.getQueryFromModel(model, exportParams, contentTypesToFill); 191 192 q = query.build(); 193 _contentTypes = new HashSet<>(contentTypesToFill); 194 } 195 196 ExtractionClause clause = new ExtractionClause(); 197 clause.setExpression(q); 198 _clauses.add(0, clause); 199 } 200 201 private void _computeClausesInfos(ExtractionExecutionContext context) 202 { 203 for (ExtractionClause clause : _clauses) 204 { 205 String clauseExpression = clause.getExpression(); 206 clause.setExpression(_resolveExpression(clauseExpression, context.getClauseVariables())); 207 208 List<ExtractionClauseGroup> groups = _extractGroupExpressionsFromClause(clauseExpression); 209 if (!groups.isEmpty()) 210 { 211 Collection<String> groupExpressions = groups.stream() 212 .map(ExtractionClauseGroup::getCompleteExpression) 213 .collect(Collectors.toList()); 214 if (_hasVariablesOutsideGroups(clauseExpression, groupExpressions)) 215 { 216 throw new IllegalArgumentException(getLogsPrefix() + "if there's at least one group, every variable should be in a group."); 217 } 218 } 219 else 220 { 221 // The only group is the entire expression 222 // The complete expression is the same as the classic one (there is no characters used to identify the group) 223 ExtractionClauseGroup group = new ExtractionClauseGroup(); 224 group.setCompleteExpression(clauseExpression); 225 group.setExpression(clauseExpression); 226 groups.add(group); 227 } 228 229 for (ExtractionClauseGroup group : groups) 230 { 231 Set<String> variables = new HashSet<>(_extractVariableFromClauseExpression(group.getExpression())); 232 if (!variables.isEmpty()) 233 { 234 if (variables.size() > 1) 235 { 236 throw new IllegalArgumentException(getLogsPrefix() + "only variables with same name are allowed within a single group"); 237 } 238 239 for (String variable : variables) 240 { 241 String[] pathSegments = variable.split(JOIN_HIERARCHY_SEPARATOR); 242 String fieldPath = pathSegments[pathSegments.length - 1]; 243 244 group.setVariable(variable); 245 group.setFieldPath(fieldPath); 246 } 247 } 248 249 clause.addGroup(group); 250 } 251 } 252 } 253 254 private String _resolveExpression(String expression, Map<String, List<String>> queryVariables) 255 { 256 String resolvedExpression = expression; 257 for (Map.Entry<String, List<String>> entry : queryVariables.entrySet()) 258 { 259 String variableName = entry.getKey(); 260 261 List<String> escapedContentIds = entry.getValue() 262 .stream() 263 .map(contentId -> AbstractTextQuery.escapeStringValue(contentId, Operator.EQ)) 264 .collect(Collectors.toList()); 265 String joinedContentIds = org.apache.commons.lang3.StringUtils.join(escapedContentIds, " OR "); 266 if (escapedContentIds.size() > 1) 267 { 268 joinedContentIds = "(" + joinedContentIds + ")"; 269 } 270 271 resolvedExpression = resolvedExpression.replace("${" + variableName + "}", joinedContentIds); 272 } 273 274 return resolvedExpression; 275 } 276 277 private boolean _hasVariablesOutsideGroups(String clauseExpression, Collection<String> groupExpressions) 278 { 279 List<String> variablesInClause = _extractVariableFromClauseExpression(clauseExpression); 280 List<String> variablesInGroups = new ArrayList<>(); 281 for (String groupExpression : groupExpressions) 282 { 283 variablesInGroups.addAll(_extractVariableFromClauseExpression(groupExpression)); 284 } 285 return variablesInClause.size() > variablesInGroups.size(); 286 } 287 288 List<ExtractionClauseGroup> _extractGroupExpressionsFromClause(String expression) 289 { 290 List<ExtractionClauseGroup> groupExpressions = new ArrayList<>(); 291 int indexOfGroup = expression.indexOf("#{"); 292 while (indexOfGroup != -1) 293 { 294 StringBuilder currentGroupSb = new StringBuilder(); 295 int endIndex = indexOfGroup; 296 int braceLevel = 0; 297 for (int i = indexOfGroup + 2; i < expression.length(); i++) 298 { 299 endIndex = i; 300 char currentChar = expression.charAt(i); 301 if ('{' == currentChar) 302 { 303 braceLevel++; 304 } 305 else if ('}' == currentChar) 306 { 307 if (0 == braceLevel) 308 { 309 ExtractionClauseGroup group = new ExtractionClauseGroup(); 310 String currentGroup = currentGroupSb.toString(); 311 group.setCompleteExpression("#{" + currentGroup + "}"); 312 group.setExpression(currentGroup); 313 groupExpressions.add(group); 314 break; 315 } 316 braceLevel--; 317 } 318 currentGroupSb.append(currentChar); 319 } 320 321 indexOfGroup = expression.indexOf("#{", endIndex); 322 } 323 return groupExpressions; 324 } 325 326 List<String> _extractVariableFromClauseExpression(String expression) 327 { 328 List<String> variables = new ArrayList<>(); 329 330 Pattern pattern = Pattern.compile(EXTRACT_JOIN_VARIABLES_REGEX); 331 Matcher matcher = pattern.matcher(expression); 332 333 while (matcher.find()) 334 { 335 variables.add(matcher.group(1)); 336 } 337 338 return variables; 339 } 340 341 @Override 342 public void executeComponent(ContentHandler contentHandler, ExtractionExecutionContext context) throws Exception 343 { 344 Iterable<Content> contents = getContents(context); 345 if (contents.iterator().hasNext()) 346 { 347 processContents(contents, contentHandler, context); 348 } 349 } 350 351 List<String> _getClauseQueries(ExtractionExecutionContext context) 352 { 353 List<String> clauseQueries = new ArrayList<>(); 354 355 for (ExtractionClause clause : _clauses) 356 { 357 String expression = clause.getExpression(); 358 359 for (ExtractionClauseGroup group : clause.getGroups()) 360 { 361 String variable = group.getVariable(); 362 363 if (variable != null && !variable.isEmpty()) 364 { 365 ExtractionExecutionContextHierarchyElement currentContextHierarchyElement = _getCurrentContextElementFromVariable(variable, context.getHierarchyElements()); 366 367 String fieldPath = group.getFieldPath(); 368 369 ExtractionComponent contextComponent = currentContextHierarchyElement.getComponent(); 370 String attributeTypeId = _getAttributeTypeId(fieldPath, contextComponent.getContentTypes()); 371 Collection<Object> values = _getValuesFromVariable(fieldPath, attributeTypeId, currentContextHierarchyElement, context.getDefaultLocale()); 372 373 if (values.isEmpty()) 374 { 375 getLogger().warn(getLogsPrefix() + "no value found for field '" + fieldPath + "'. The query of this component can't be achieved"); 376 return null; 377 } 378 379 Collection<String> groupExpressions = new ArrayList<>(); 380 for (Object value : values) 381 { 382 String valueAsString = _getValueAsString(value, attributeTypeId, fieldPath); 383 groupExpressions.add(group.getExpression().replace("${" + variable + "}", valueAsString)); 384 } 385 386 String groupReplacement = org.apache.commons.lang3.StringUtils.join(groupExpressions, " OR "); 387 expression = expression.replace(group.getCompleteExpression(), "(" + groupReplacement + ")"); 388 } 389 } 390 391 clauseQueries.add(expression); 392 } 393 394 return clauseQueries; 395 } 396 397 private ExtractionExecutionContextHierarchyElement _getCurrentContextElementFromVariable(String variable, List<ExtractionExecutionContextHierarchyElement> context) 398 { 399 int lastIndexOfSlash = variable.lastIndexOf(JOIN_HIERARCHY_SEPARATOR); 400 int indexOfCurrentContext = -1; 401 if (lastIndexOfSlash == -1) 402 { 403 indexOfCurrentContext = context.size() - 1; 404 } 405 else 406 { 407 int hierarchicalLevel = (lastIndexOfSlash + 1) / 3; 408 indexOfCurrentContext = context.size() - hierarchicalLevel; 409 if (variable.endsWith(JOIN_HIERARCHY_ELEMENT)) 410 { 411 indexOfCurrentContext--; 412 } 413 } 414 if (indexOfCurrentContext < 0 || indexOfCurrentContext >= context.size()) 415 { 416 throw new IllegalArgumentException(getLogsPrefix() + "join on '" + variable + "' does not refer to an existing parent"); 417 } 418 return context.get(indexOfCurrentContext); 419 } 420 421 /** 422 * Retrieves the field path's attribute type identifier from content types 423 * @param fieldPath the field path 424 * @param contentTypeIds the content types identifiers 425 * @return the attribute type identifier 426 */ 427 protected String _getAttributeTypeId(String fieldPath, Collection<String> contentTypeIds) 428 { 429 // Manage direct content references 430 if (JOIN_HIERARCHY_ELEMENT.equals(fieldPath)) 431 { 432 return ModelItemTypeConstants.CONTENT_ELEMENT_TYPE_ID; 433 } 434 435 // Manage System Properties 436 String[] pathSegments = fieldPath.split(EXTRACTION_ITEM_PATH_SEPARATOR); 437 String propertyName = pathSegments[pathSegments.length - 1]; 438 if (_systemPropertyExtensionPoint.hasExtension(propertyName)) 439 { 440 SystemProperty systemProperty = _systemPropertyExtensionPoint.getExtension(propertyName); 441 return systemProperty.getType().name().toLowerCase().replaceAll("_", "-"); 442 } 443 444 String fieldPathWthClassicSeparator = fieldPath.replaceAll(EXTRACTION_ITEM_PATH_SEPARATOR, ModelItem.ITEM_PATH_SEPARATOR); 445 Collection<ContentType> contentTypes = contentTypeIds.stream() 446 .map(_contentTypeExtensionPoint::getExtension) 447 .collect(Collectors.toList()); 448 449 if (ModelHelper.hasModelItem(fieldPathWthClassicSeparator, contentTypes)) 450 { 451 ModelItem modelItem = ModelHelper.getModelItem(fieldPathWthClassicSeparator, contentTypes); 452 return modelItem.getType().getId(); 453 } 454 455 throw new IllegalArgumentException(getLogsPrefix() + "join on '" + fieldPath + "'. This attribute is not available"); 456 } 457 458 private Collection<Object> _getValuesFromVariable(String fieldPath, String attributeTypeId, ExtractionExecutionContextHierarchyElement contextHierarchyElement, Locale defaultLocale) 459 { 460 Collection<Object> values = new LinkedHashSet<>(); 461 462 Iterable<Content> contents = contextHierarchyElement.getContents(); 463 for (Content content: contents) 464 { 465 boolean isAutoposting = contextHierarchyElement.isAutoposting(); 466 Collection<Object> contentValues = _getContentValuesFromVariable(content, fieldPath, attributeTypeId, isAutoposting, defaultLocale); 467 values.addAll(contentValues); 468 } 469 470 return values; 471 } 472 473 private Collection<Object> _getContentValuesFromVariable(Content content, String fieldPath, String attributeTypeId, boolean isAutoposting, Locale defaultLocale) 474 { 475 Collection<Object> values = new LinkedHashSet<>(); 476 477 Object value = _getContentValue(content, fieldPath); 478 if (value == null) 479 { 480 return Collections.emptyList(); 481 } 482 483 if (value instanceof Collection<?>) 484 { 485 values.addAll((Collection<?>) value); 486 } 487 else 488 { 489 values.add(value); 490 } 491 492 Collection<Object> result = new LinkedHashSet<>(values); 493 494 if (isAutoposting && ModelItemTypeConstants.CONTENT_ELEMENT_TYPE_ID.equals(attributeTypeId)) 495 { 496 for (Object object : values) 497 { 498 Optional<? extends Content> parent = object instanceof ContentValue ? ((ContentValue) object).getContentIfExists() : Optional.of((Content) object); 499 ContentType contentType = parent.map(_contentTypesHelper::getFirstContentType) 500 .orElse(null); 501 502 // Manage autoposting only if the current value is a thesaurus term 503 if (contentType != null && Arrays.asList(_contentTypesHelper.getSupertypeIds(contentType.getId()).getLeft()).contains(ThesaurusDAO.MICROTHESAURUS_ABSTRACT_CONTENT_TYPE)) 504 { 505 AmetysObjectIterable<Content> chidren = _thesaurusDAO.getChildTerms(contentType.getId(), parent.get().getId()); 506 for (Content child : chidren) 507 { 508 Collection<Object> childValues = _getContentValuesFromVariable(child, JOIN_HIERARCHY_ELEMENT, attributeTypeId, isAutoposting, defaultLocale); 509 result.addAll(childValues); 510 } 511 } 512 } 513 } 514 515 return result; 516 } 517 518 private Object _getContentValue(Content content, String fieldPath) 519 { 520 if (JOIN_HIERARCHY_ELEMENT.equals(fieldPath)) 521 { 522 return content; 523 } 524 else 525 { 526 String fieldPathWthClassicSeparator = fieldPath.replaceAll(EXTRACTION_ITEM_PATH_SEPARATOR, ModelItem.ITEM_PATH_SEPARATOR); 527 return _contentHelper.getValue(content, fieldPathWthClassicSeparator); 528 } 529 } 530 531 @SuppressWarnings("static-access") 532 private <T> String _getValueAsString(Object value, String attributeTypeId, String fieldPath) 533 { 534 ModelItemType modelItemType = _contentAttributeTypeExtensionPoint.getExtension(attributeTypeId); 535 if (modelItemType instanceof ElementType) 536 { 537 @SuppressWarnings("unchecked") 538 ElementType<T> elementType = (ElementType<T>) modelItemType; 539 T typedValue = elementType.castValue(value); 540 String valueAsString = elementType.toString(typedValue); 541 return StringQuery.escapeStringValue(valueAsString, Operator.EQ); 542 } 543 else 544 { 545 throw new IllegalArgumentException(getLogsPrefix() + "join on '" + fieldPath + "'. Attribute type '" + attributeTypeId + "' is not supported by extraction module"); 546 } 547 } 548 549 /** 550 * Retrieves the content searcher to use for solr search 551 * @return the content searcher 552 */ 553 protected SimpleContentSearcher getContentSearcher() 554 { 555 return _contentSearcherFactory.create(_contentTypes); 556 } 557 558 /** 559 * Gets the content results from Solr 560 * @param context component execution context 561 * @return the content results from Solr 562 * @throws Exception if an error occurs 563 */ 564 protected Iterable<Content> getContents(ExtractionExecutionContext context) throws Exception 565 { 566 List<String> clauseQueries = _getClauseQueries(context); 567 if (clauseQueries == null) 568 { 569 return new EmptyIterable<>(); 570 } 571 else 572 { 573 List<String> filterQueryStrings = clauseQueries 574 .stream() 575 .map(LambdaUtils.wrap(clauseQuery -> SolrContentQueryHelper.buildQuery(_queryBuilder, clauseQuery, Collections.emptySet(), Collections.emptySet()))) 576 .collect(Collectors.toList()); 577 return getContentSearcher() 578 .withFilterQueryStrings(filterQueryStrings) 579 .setCheckRights(false) 580 .search("*:*"); 581 } 582 } 583 584 /** 585 * Process result contents to format the result document 586 * @param contents search results 587 * @param contentHandler result document 588 * @param context component execution context 589 * @throws Exception if an error occurs 590 */ 591 protected abstract void processContents(Iterable<Content> contents, ContentHandler contentHandler, ExtractionExecutionContext context) throws Exception; 592 593 @Override 594 public Map<String, Object> getComponentDetailsForTree() 595 { 596 Map<String, Object> details = super.getComponentDetailsForTree(); 597 598 @SuppressWarnings("unchecked") 599 Map<String, Object> data = (Map<String, Object>) details.get("data"); 600 601 List<String> clauses = new ArrayList<>(); 602 for (ExtractionClause clause : this.getClauses()) 603 { 604 clauses.add(clause.getExpression()); 605 } 606 data.put("clauses", clauses); 607 608 data.put("useQueryRef", org.apache.commons.lang.StringUtils.isNotEmpty(_queryReferenceId)); 609 data.put("contentTypes", this.getContentTypes()); 610 data.put("queryReferenceId", this.getQueryReferenceId()); 611 612 return details; 613 } 614 615 public Set<String> getContentTypes() 616 { 617 return _contentTypes; 618 } 619 620 /** 621 * Add content types to component 622 * @param contentTypes Array of content types to add 623 */ 624 public void addContentTypes(String... contentTypes) 625 { 626 _contentTypes.addAll(Arrays.asList(contentTypes)); 627 } 628 629 /** 630 * Retrieves the id of the referenced query 631 * @return the id of the referenced query 632 */ 633 public String getQueryReferenceId() 634 { 635 return _queryReferenceId; 636 } 637 638 /** 639 * Sets the id of the referenced query 640 * @param queryReferenceId The id of the referenced query to set 641 */ 642 public void setQueryReferenceId(String queryReferenceId) 643 { 644 _queryReferenceId = queryReferenceId; 645 } 646 647 /** 648 * Retrieves the component clauses 649 * @return the component clauses 650 */ 651 public List<ExtractionClause> getClauses() 652 { 653 return _clauses; 654 } 655 656 /** 657 * Add clauses to the component. Do not manage clauses' groups 658 * @param expressions Array clauses expressions to add 659 */ 660 public void addClauses(String... expressions) 661 { 662 for (String expression : expressions) 663 { 664 ExtractionClause clause = new ExtractionClause(); 665 clause.setExpression(expression); 666 _clauses.add(clause); 667 } 668 } 669}