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.apache.commons.lang3.StringUtils;
038import org.apache.solr.client.solrj.util.ClientUtils;
039import org.xml.sax.ContentHandler;
040
041import org.ametys.cms.content.ContentHelper;
042import org.ametys.cms.contenttype.ContentType;
043import org.ametys.cms.data.ContentValue;
044import org.ametys.cms.data.type.ModelItemTypeConstants;
045import org.ametys.cms.data.type.ModelItemTypeExtensionPoint;
046import org.ametys.cms.repository.Content;
047import org.ametys.cms.search.GetQueryFromJSONHelper;
048import org.ametys.cms.search.QueryBuilder;
049import org.ametys.cms.search.content.ContentSearcherFactory;
050import org.ametys.cms.search.content.ContentSearcherFactory.SimpleContentSearcher;
051import org.ametys.cms.search.model.SystemProperty;
052import org.ametys.cms.search.model.SystemPropertyExtensionPoint;
053import org.ametys.cms.search.query.QuerySyntaxException;
054import org.ametys.cms.search.solr.SolrContentQueryHelper;
055import org.ametys.cms.search.ui.model.SearchUIModel;
056import org.ametys.core.util.JSONUtils;
057import org.ametys.core.util.LambdaUtils;
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;
065import org.ametys.runtime.model.ModelHelper;
066import org.ametys.runtime.model.ModelItem;
067import org.ametys.runtime.model.type.ElementType;
068import org.ametys.runtime.model.type.ModelItemType;
069
070/**
071 * This class represents an extraction component with a solr query
072 */
073public abstract class AbstractSolrExtractionComponent extends AbstractExtractionComponent
074{
075    /** Regex used to extract variables from an expression. A variable is inside a ${} */
076    private static final String __EXTRACT_VARIABLES_REGEX = "\\$\\{([^{}]+)\\}";
077    
078    /**
079     * Regex used to check variables from a join expression: \.\.(?:\/\.\.)*(?:\/[^\/}]+)?
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 __CHECK_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 ModelItemTypeExtensionPoint _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 = (ModelItemTypeExtensionPoint) serviceManager.lookup(ModelItemTypeExtensionPoint.ROLE_CONTENT_ATTRIBUTE);
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(org.ametys.core.util.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(clauseExpression);
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, context.getClauseVariables().keySet()))
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(), context.getClauseVariables().keySet()));
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 boolean _hasVariablesOutsideGroups(String clauseExpression, Collection<String> groupExpressions, Collection<String> clauseVariableNames)
255    {
256        List<String> variablesInClause = _extractVariableFromClauseExpression(clauseExpression, clauseVariableNames);
257        List<String> variablesInGroups = new ArrayList<>();
258        for (String groupExpression : groupExpressions)
259        {
260            variablesInGroups.addAll(_extractVariableFromClauseExpression(groupExpression, clauseVariableNames));
261        }
262        return variablesInClause.size() > variablesInGroups.size();
263    }
264
265    List<ExtractionClauseGroup> _extractGroupExpressionsFromClause(String expression)
266    {
267        List<ExtractionClauseGroup> groupExpressions = new ArrayList<>();
268        int indexOfGroup = expression.indexOf("#{");
269        while (indexOfGroup != -1)
270        {
271            StringBuilder currentGroupSb = new StringBuilder();
272            int endIndex = indexOfGroup;
273            int braceLevel = 0;
274            for (int i = indexOfGroup + 2; i < expression.length(); i++)
275            {
276                endIndex = i;
277                char currentChar = expression.charAt(i);
278                if ('{' == currentChar)
279                {
280                    braceLevel++;
281                }
282                else if ('}' == currentChar)
283                {
284                    if (0  == braceLevel)
285                    {
286                        ExtractionClauseGroup group = new ExtractionClauseGroup();
287                        String currentGroup = currentGroupSb.toString();
288                        group.setCompleteExpression("#{" + currentGroup + "}");
289                        group.setExpression(currentGroup);
290                        groupExpressions.add(group);
291                        break;
292                    }
293                    braceLevel--;
294                }
295                currentGroupSb.append(currentChar);
296            }
297            
298            indexOfGroup = expression.indexOf("#{", endIndex);
299        }
300        return groupExpressions;
301    }
302
303    List<String> _extractVariableFromClauseExpression(String expression, Collection<String> clauseVariableNames)
304    {
305        List<String> variables = new ArrayList<>();
306        
307        Pattern variablePattern = Pattern.compile(__EXTRACT_VARIABLES_REGEX);
308        Matcher variableMatcher = variablePattern.matcher(expression);
309        
310        while (variableMatcher.find())
311        {
312            String variable = variableMatcher.group(1);
313            Pattern joinPattern = Pattern.compile(__CHECK_JOIN_VARIABLES_REGEX);
314            Matcher joinMatcher = joinPattern.matcher(variable);
315            if (clauseVariableNames.contains(variable) || joinMatcher.matches())
316            {
317                variables.add(variable);
318            }
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            // Resolve all groups
343            for (ExtractionClauseGroup group : clause.getGroups())
344            {
345                String variable = group.getVariable();
346                
347                if (StringUtils.isNotEmpty(variable))
348                {
349                    String fieldPath = group.getFieldPath();
350                    String attributeTypeId;
351                    Collection<Object> values;
352                    if (context.getClauseVariables().containsKey(variable))
353                    {
354                        attributeTypeId = ModelItemTypeConstants.CONTENT_ELEMENT_TYPE_ID;
355                        values = context.getClauseVariables()
356                                        .get(variable)
357                                        .stream()
358                                        .map(Object.class::cast)
359                                        .collect(Collectors.toList());
360                    }
361                    else
362                    {
363                        ExtractionExecutionContextHierarchyElement currentContextHierarchyElement = _getCurrentContextElementFromVariable(variable, context.getHierarchyElements());
364                        
365                        
366                        ExtractionComponent contextComponent = currentContextHierarchyElement.getComponent();
367                        attributeTypeId = _getAttributeTypeId(fieldPath, contextComponent.getContentTypes());
368                        values = _getValuesFromVariable(fieldPath, attributeTypeId, currentContextHierarchyElement, context.getDefaultLocale());
369                    }
370                    
371                    if (values.isEmpty())
372                    {
373                        getLogger().warn(getLogsPrefix() + "no value found for field '" + fieldPath + "'. The query of this component can't be achieved");
374                        return null;
375                    }
376                    
377                    Collection<String> groupExpressions = new ArrayList<>();
378                    for (Object value : values)
379                    {
380                        String valueAsString = _getValueAsString(value, attributeTypeId, fieldPath);
381                        groupExpressions.add("(" + group.getExpression().replace("${" + variable + "}", valueAsString) + ")");
382                    }
383                    
384                    String groupReplacement =  StringUtils.join(groupExpressions, " OR ");
385                    expression = expression.replace(group.getCompleteExpression(), "(" + groupReplacement + ")");
386                }
387            }
388            
389            clauseQueries.add(expression);
390        }
391        
392        return clauseQueries;
393    }
394
395    private ExtractionExecutionContextHierarchyElement _getCurrentContextElementFromVariable(String variable, List<ExtractionExecutionContextHierarchyElement> context)
396    {
397        int lastIndexOfSlash = variable.lastIndexOf(JOIN_HIERARCHY_SEPARATOR);
398        int indexOfCurrentContext = -1;
399        if (lastIndexOfSlash == -1)
400        {
401            indexOfCurrentContext = context.size() - 1;
402        }
403        else
404        {
405            int hierarchicalLevel = (lastIndexOfSlash + 1) / 3;
406            indexOfCurrentContext = context.size() - hierarchicalLevel;
407            if (variable.endsWith(JOIN_HIERARCHY_ELEMENT))
408            {
409                indexOfCurrentContext--;
410            }
411        }
412        if (indexOfCurrentContext < 0 || indexOfCurrentContext >= context.size())
413        {
414            throw new IllegalArgumentException(getLogsPrefix() + "join on '" + variable + "' does not refer to an existing parent");
415        }
416        return context.get(indexOfCurrentContext);
417    }
418    
419    /**
420     * Retrieves the field path's attribute type identifier from content types
421     * @param fieldPath the field path
422     * @param contentTypeIds the content types identifiers
423     * @return the attribute type identifier
424     */
425    protected String _getAttributeTypeId(String fieldPath, Collection<String> contentTypeIds)
426    {
427        // Manage direct content references
428        if (JOIN_HIERARCHY_ELEMENT.equals(fieldPath))
429        {
430            return ModelItemTypeConstants.CONTENT_ELEMENT_TYPE_ID;
431        }
432        
433        // Manage System Properties
434        String[] pathSegments = fieldPath.split(EXTRACTION_ITEM_PATH_SEPARATOR);
435        String propertyName = pathSegments[pathSegments.length - 1];
436        if (_systemPropertyExtensionPoint.hasExtension(propertyName))
437        {
438            SystemProperty systemProperty = _systemPropertyExtensionPoint.getExtension(propertyName);
439            return systemProperty.getType().getId();
440        }
441        
442        String fieldPathWthClassicSeparator = fieldPath.replaceAll(EXTRACTION_ITEM_PATH_SEPARATOR, ModelItem.ITEM_PATH_SEPARATOR);
443        Collection<ContentType> contentTypes = contentTypeIds.stream()
444                .map(_contentTypeExtensionPoint::getExtension)
445                .collect(Collectors.toList());
446        
447        if (ModelHelper.hasModelItem(fieldPathWthClassicSeparator, contentTypes))
448        {
449            ModelItem modelItem = ModelHelper.getModelItem(fieldPathWthClassicSeparator, contentTypes);
450            return modelItem.getType().getId();
451        }
452        
453        throw new IllegalArgumentException(getLogsPrefix() + "join on '" + fieldPath + "'. This attribute is not available");
454    }
455
456    private Collection<Object> _getValuesFromVariable(String fieldPath, String attributeTypeId, ExtractionExecutionContextHierarchyElement contextHierarchyElement, Locale defaultLocale)
457    {
458        Collection<Object> values = new LinkedHashSet<>();
459        
460        Iterable<Content> contents = contextHierarchyElement.getContents();
461        for (Content content: contents)
462        {
463            boolean isAutoposting = contextHierarchyElement.isAutoposting();
464            Collection<Object> contentValues = _getContentValuesFromVariable(content, fieldPath, attributeTypeId, isAutoposting, defaultLocale);
465            values.addAll(contentValues);
466        }
467        
468        return values;
469    }
470    
471    private Collection<Object> _getContentValuesFromVariable(Content content, String fieldPath, String attributeTypeId, boolean isAutoposting, Locale defaultLocale)
472    {
473        Collection<Object> values = new LinkedHashSet<>();
474        
475        Object value = _getContentValue(content, fieldPath);
476        if (value == null)
477        {
478            return Collections.emptyList();
479        }
480        
481        if (value instanceof Collection<?>)
482        {
483            values.addAll((Collection<?>) value);
484        }
485        else
486        {
487            values.add(value);
488        }
489        
490        Collection<Object> result = new LinkedHashSet<>(values);
491        
492        if (isAutoposting && ModelItemTypeConstants.CONTENT_ELEMENT_TYPE_ID.equals(attributeTypeId))
493        {
494            for (Object object : values)
495            {
496                Optional<? extends Content> parent = object instanceof ContentValue ? ((ContentValue) object).getContentIfExists() : Optional.of((Content) object);
497                ContentType contentType = parent.map(_contentTypesHelper::getFirstContentType)
498                                                .orElse(null);
499                
500                // Manage autoposting only if the current value is a thesaurus term
501                if (contentType != null && Arrays.asList(_contentTypesHelper.getSupertypeIds(contentType.getId()).getLeft()).contains(ThesaurusDAO.MICROTHESAURUS_ABSTRACT_CONTENT_TYPE))
502                {
503                    AmetysObjectIterable<Content> chidren = _thesaurusDAO.getChildTerms(contentType.getId(), parent.get().getId());
504                    for (Content child : chidren)
505                    {
506                        Collection<Object> childValues = _getContentValuesFromVariable(child, JOIN_HIERARCHY_ELEMENT, attributeTypeId, isAutoposting, defaultLocale);
507                        result.addAll(childValues);
508                    }
509                }
510            }
511        }
512
513        return result;
514    }
515    
516    private Object _getContentValue(Content content, String fieldPath)
517    {
518        if (JOIN_HIERARCHY_ELEMENT.equals(fieldPath))
519        {
520            return content;
521        }
522        else 
523        {
524            String fieldPathWthClassicSeparator = fieldPath.replaceAll(EXTRACTION_ITEM_PATH_SEPARATOR, ModelItem.ITEM_PATH_SEPARATOR);
525            return _contentHelper.getValue(content, fieldPathWthClassicSeparator);
526        }
527    }
528    
529    private <T> String _getValueAsString(Object value, String attributeTypeId, String fieldPath)
530    {
531        ModelItemType modelItemType = _contentAttributeTypeExtensionPoint.getExtension(attributeTypeId);
532        if (modelItemType instanceof ElementType)
533        {
534            @SuppressWarnings("unchecked")
535            ElementType<T> elementType = (ElementType<T>) modelItemType;
536            T typedValue = elementType.castValue(value);
537            String valueAsString = elementType.toString(typedValue);
538            return ClientUtils.escapeQueryChars(valueAsString);
539        }
540        else
541        {
542            throw new IllegalArgumentException(getLogsPrefix() + "join on '" + fieldPath + "'. Attribute type '" + attributeTypeId + "' is not supported by extraction module");
543        }
544    }
545    
546    /**
547     * Retrieves the content searcher to use for solr search
548     * @return the content searcher
549     */
550    protected SimpleContentSearcher getContentSearcher()
551    {
552        return _contentSearcherFactory.create(_contentTypes);
553    }
554    
555    /**
556     * Gets the content results from Solr
557     * @param context component execution context
558     * @return the content results from Solr
559     * @throws Exception if an error occurs
560     */
561    protected Iterable<Content> getContents(ExtractionExecutionContext context) throws Exception
562    {
563        List<String> clauseQueries = _getClauseQueries(context);
564        if (clauseQueries == null)
565        {
566            return new EmptyIterable<>();
567        }
568        else
569        {
570            List<String> filterQueryStrings = clauseQueries
571                    .stream()
572                    .map(LambdaUtils.wrap(clauseQuery -> SolrContentQueryHelper.buildQuery(_queryBuilder, clauseQuery, Collections.emptySet(), Collections.emptySet())))
573                    .collect(Collectors.toList());
574            return getContentSearcher()
575                    .withFilterQueryStrings(filterQueryStrings)
576                    .setCheckRights(false)
577                    .search("*:*");
578        }
579    }
580
581    /**
582     * Process result contents to format the result document
583     * @param contents search results
584     * @param contentHandler result document
585     * @param context component execution context
586     * @throws Exception if an error occurs
587     */
588    protected abstract void processContents(Iterable<Content> contents, ContentHandler contentHandler, ExtractionExecutionContext context) throws Exception;
589
590    @Override
591    public Map<String, Object> getComponentDetailsForTree()
592    {
593        Map<String, Object> details = super.getComponentDetailsForTree();
594        
595        @SuppressWarnings("unchecked")
596        Map<String, Object> data = (Map<String, Object>) details.get("data");
597        
598        List<String> clauses = new ArrayList<>();
599        for (ExtractionClause clause : this.getClauses())
600        {
601            clauses.add(clause.getExpression());
602        }
603        data.put("clauses", clauses);
604        
605        data.put("useQueryRef", StringUtils.isNotEmpty(_queryReferenceId));
606        data.put("contentTypes", this.getContentTypes());
607        data.put("queryReferenceId", this.getQueryReferenceId());
608        
609        return details;
610    }
611    
612    public Set<String> getContentTypes()
613    {
614        return _contentTypes;
615    }
616
617    /**
618     * Add content types to component
619     * @param contentTypes Array of content types to add
620     */
621    public void addContentTypes(String... contentTypes)
622    {
623        _contentTypes.addAll(Arrays.asList(contentTypes));
624    }
625
626    /**
627     * Retrieves the id of the referenced query
628     * @return the id of the referenced query
629     */
630    public String getQueryReferenceId()
631    {
632        return _queryReferenceId;
633    }
634    
635    /**
636     * Sets the id of the referenced query
637     * @param queryReferenceId The id of the referenced query to set
638     */
639    public void setQueryReferenceId(String queryReferenceId)
640    {
641        _queryReferenceId = queryReferenceId;
642    }
643
644    /**
645     * Retrieves the component clauses
646     * @return the component clauses
647     */
648    public List<ExtractionClause> getClauses()
649    {
650        return _clauses;
651    }
652
653    /**
654     * Add clauses to the component. Do not manage clauses' groups
655     * @param expressions Array clauses expressions to add
656     */
657    public void addClauses(String... expressions)
658    {
659        for (String expression : expressions)
660        {
661            ExtractionClause clause = new ExtractionClause();
662            clause.setExpression(expression);
663            _clauses.add(clause);
664        }
665    }
666}