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}