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