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