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