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