001/*
002 *  Copyright 2010 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.cms.filter;
017
018import java.util.ArrayList;
019import java.util.HashMap;
020import java.util.LinkedHashSet;
021import java.util.List;
022import java.util.Map;
023import java.util.Set;
024
025import org.apache.excalibur.source.SourceResolver;
026
027import org.ametys.cms.contenttype.ContentTypeExtensionPoint;
028import org.ametys.cms.repository.Content;
029import org.ametys.cms.repository.ContentTypeExpression;
030import org.ametys.cms.repository.LanguageExpression;
031import org.ametys.core.util.SizeUtils.ExcludeFromSizeCalculation;
032import org.ametys.plugins.repository.AmetysObjectIterable;
033import org.ametys.plugins.repository.AmetysObjectResolver;
034import org.ametys.plugins.repository.query.SortCriteria;
035import org.ametys.plugins.repository.query.SortCriteria.SortCriterion;
036import org.ametys.plugins.repository.query.expression.AndExpression;
037import org.ametys.plugins.repository.query.expression.Expression;
038import org.ametys.plugins.repository.query.expression.Expression.Operator;
039import org.ametys.plugins.repository.query.expression.MetadataExpression;
040import org.ametys.plugins.repository.query.expression.OrExpression;
041import org.ametys.plugins.repository.query.expression.StringExpression;
042
043/**
044 *  This is the default implementation of a {@link ContentFilter}. The filter's property are set by setter function and constructor
045 */
046public class DefaultContentFilter implements ContentFilter
047{
048    /** The Ametys object resolver */
049    @ExcludeFromSizeCalculation
050    protected AmetysObjectResolver _resolver;
051    /** The source resolver */
052    @ExcludeFromSizeCalculation
053    protected SourceResolver _srcResolver;
054    
055    /** The filter id */
056    protected String _id;
057    /** The list of content types to match */
058    protected List<String> _contentTypes;
059    /** The list of content languages to match */
060    protected ContextLanguage _contextLang;
061    /** The metadata to match */
062    protected Map<String, String> _metadata;
063    /** The metadata condition*/
064    protected Condition _metadataCondition;
065    /** The number max of results */
066    protected int _length;
067    /** The view */
068    protected String _viewName;
069    /** The sort criteria */
070    protected SortCriteria _sortCriteria;
071    /** The additional expression */
072    protected Expression _additionalFilterExpression;
073    /** The extension point for content types */
074    @ExcludeFromSizeCalculation
075    protected ContentTypeExtensionPoint _contentTypeEP;
076    
077    /**
078     * Constructor
079     */
080    public DefaultContentFilter ()
081    {
082        // Empty
083    }
084    
085    /**
086     * Creates a new filter
087     * @param id The filter unique identifier
088     * @param resolver The ametys object resolver
089     * @param contentTypeExtensionPoint The extension point for content types
090     */
091    public DefaultContentFilter(String id, AmetysObjectResolver resolver, ContentTypeExtensionPoint contentTypeExtensionPoint)
092    {
093        _id = id;
094        _resolver = resolver;
095        _contentTypeEP = contentTypeExtensionPoint;
096    }
097    
098    /**
099     * Creates a new filter from copy of another
100     * @param id The filter unique identifier
101     * @param originalFilter The original filter to be copied
102     * @param resolver The ametys object resolver
103     * @param contentTypeExtensionPoint The extension point for content types
104     */
105    public DefaultContentFilter(String id, DefaultContentFilter originalFilter, AmetysObjectResolver resolver, ContentTypeExtensionPoint contentTypeExtensionPoint)
106    {
107        _id = id;
108        _resolver = resolver;
109        _contentTypeEP = contentTypeExtensionPoint;
110        _contentTypes = new ArrayList<>(originalFilter._contentTypes);
111        _metadataCondition = originalFilter._metadataCondition;
112        _contextLang = originalFilter._contextLang;
113        _length = originalFilter._length;
114        _viewName = originalFilter._viewName;
115        _metadata = new HashMap<>(originalFilter._metadata);
116        
117        SortCriteria originalSC = originalFilter._sortCriteria;
118        if (originalSC != null)
119        {
120            SortCriteria sortCriteria = new SortCriteria();
121            for (SortCriterion criterion : originalFilter.getSortCriteria().getCriteria())
122            {
123                if (criterion.getMetadataPath() != null)
124                {
125                    sortCriteria.addCriterion(criterion.getMetadataPath(), criterion.isAscending(), criterion.isNormalizedSort());
126                }
127                else if (criterion.getJcrProperty() != null)
128                {
129                    sortCriteria.addCriterion(criterion.getJcrProperty(), criterion.isAscending(), criterion.isNormalizedSort());
130                }
131            }
132            _sortCriteria = sortCriteria;
133        }
134        
135        // FIXME The filter expressions should be copied here but it missing a Expression.clone() or Expression.copy() method to do it.
136        // For now the filter expression is not used when this constructor is used, so we do nothing for now since filters will be refactored soon (for 4.3 ?)
137    }
138    
139    @Override
140    public List<String> getContentTypes()
141    {
142        return _contentTypes;
143    }
144    
145    @Override
146    public Map<String, String> getMetadataValues()
147    {
148        return _metadata;
149    }
150    
151    @Override
152    public Condition getMetadataCondition()
153    {
154        return _metadataCondition;
155    }
156    
157    @Override
158    public ContextLanguage getContextLanguage()
159    {
160        return _contextLang;
161    }
162    
163    @Override
164    public String getId()
165    {
166        return _id;
167    }
168
169    @Override
170    public int getLength()
171    {
172        return _length;
173    }
174
175    @Override
176    public String getView()
177    {
178        return _viewName;
179    }
180    
181    @Override
182    public SortCriteria getSortCriteria()
183    {
184        return _sortCriteria;
185    }
186    
187    @Override
188    public void addContentType(String cTypeId)
189    {
190        if (_contentTypes == null)
191        {
192            _contentTypes = new ArrayList<>();
193        }
194        _contentTypes.add(cTypeId);
195    }
196
197    @Override
198    public void addMetadata(String metadataId, String value)
199    {
200        if (_metadata == null)
201        {
202            _metadata = new HashMap<>();
203        }
204        _metadata.put(metadataId, value);
205    }
206    
207    @Override
208    public void setMetadataCondition(Condition condition)
209    {
210        _metadataCondition = condition;
211    }
212
213    @Override
214    public Expression getAdditionalFilterExpression()
215    {
216        return _additionalFilterExpression;
217    }
218    
219    @Override
220    public void setAdditionalFilterExpression(Expression expression)
221    {
222        _additionalFilterExpression = expression;
223    }
224    
225    @Override
226    public void setContextLanguage(ContextLanguage context)
227    {
228        _contextLang = context;
229    }
230    
231    @Override
232    public void setId(String id)
233    {
234        _id = id;
235    }
236    
237    @Override
238    public void setResolver (AmetysObjectResolver resolver)
239    {
240        _resolver = resolver;
241    }
242
243
244    @Override
245    public void setLength(int length)
246    {
247        _length = length;
248    }
249
250
251    @Override
252    public void setView(String viewName)
253    {
254        _viewName = viewName;
255    }
256    
257    @Override
258    public void addSortCriteria(String metadataId, boolean ascending, boolean useLowerCase)
259    {
260        if (_sortCriteria == null)
261        {
262            _sortCriteria = new SortCriteria();
263        }
264        _sortCriteria.addCriterion(metadataId, ascending, useLowerCase);
265    }
266    
267    @Override
268    public AmetysObjectIterable<Content> getMatchingContents()
269    {
270        String xpathQuery =  getXPathQuery();
271        AmetysObjectIterable<Content> contents = _resolver.query(xpathQuery);
272                
273        return contents;
274    }
275    
276    @Override
277    public AmetysObjectIterable<Content> getMatchingContents(String lang)
278    {
279        String xpathQuery =  getXPathQuery(lang);
280        AmetysObjectIterable<Content> contents = _resolver.query(xpathQuery);
281        
282        return contents;
283    }
284    
285    /**
286     * Creates the XPath query corresponding to this filter.
287     * @return the created XPath query
288     */
289    public String getXPathQuery ()
290    {
291        Expression expr = getFilterExpression ();
292        return org.ametys.plugins.repository.query.QueryHelper.getXPathQuery(null, "ametys:content", expr, _sortCriteria);
293    }
294    
295    /**
296     * Creates the XPath query corresponding to this filter.
297     * @param lang The current language
298     * @return the created XPath query
299     */
300    public String getXPathQuery (String lang)
301    {
302        Expression expr = getFilterExpression ();
303        Expression langExpr = getContextLanguagesExpression(lang);
304        expr = langExpr != null ? new AndExpression(expr, langExpr) : expr;
305        
306        return org.ametys.plugins.repository.query.QueryHelper.getXPathQuery(null, "ametys:content", expr, _sortCriteria);
307
308    }
309    
310    /**
311     * Get the expression corresponding to this filter
312     * @return The created expression to match contents
313     */
314    protected Expression getFilterExpression ()
315    {
316        Expression expr = getContentTypesExpression ();
317        Expression metadataExpr = getMetadataExpression();
318        Expression additionalExpr = getAdditionalFilterExpression();
319        
320        return new AndExpression(expr, metadataExpr, additionalExpr);
321    }
322    
323    /**
324     * Get the expression corresponding to the filter's content types
325     * @return The expression corresponding to the filter's content types
326     */
327    protected Expression getContentTypesExpression ()
328    {
329        Expression expr = null;
330        
331        List<Expression> cTypesExpr = new ArrayList<>();
332        if (_contentTypes != null && _contentTypes.size() > 0)
333        {
334            for (String contentType : _contentTypes)
335            {
336                // Filter on content type and all its children
337                Set<String> cTypeAndSubTypes = new LinkedHashSet<>();
338                cTypeAndSubTypes.add(contentType);
339                cTypeAndSubTypes.addAll(_contentTypeEP.getSubTypes(contentType));
340                cTypesExpr.add(new ContentTypeExpression(Operator.EQ, cTypeAndSubTypes.toArray(new String[cTypeAndSubTypes.size()])));
341            }
342            
343            expr = new OrExpression(cTypesExpr.toArray(new Expression[cTypesExpr.size()]));
344        }
345        
346        return expr;
347    }
348    
349    /**
350     * Get the {@link Expression} associated with the given language context
351     * @param lang The current language
352     * @return a {@link Expression} associated with the given language context
353     */
354    protected Expression getContextLanguagesExpression (String lang)
355    {
356        if (lang == null)
357        {
358            return null;
359        }
360        
361        Expression expr = null;
362        
363        if (ContextLanguage.CURRENT.equals(_contextLang))
364        {
365            expr = new LanguageExpression(Operator.EQ, lang);
366        }
367        else if (ContextLanguage.OTHERS.equals(_contextLang))
368        {
369            expr = new LanguageExpression(Operator.NE, lang);
370        }
371        
372        return expr;
373    }
374    
375    /**
376     * Get the expression corresponding to the filter's tags
377     * @return The expression corresponding to the filter's tags
378     */
379    protected Expression getMetadataExpression ()
380    {
381        Expression expr = null;
382        
383        List<Expression> metadataExpr = new ArrayList<>();
384        
385        if (_metadata != null && _metadata.size() > 0)
386        {
387            for (String metadataId : _metadata.keySet())
388            {
389                String value = _metadata.get(metadataId);
390                metadataExpr.add(value != null ? new StringExpression(metadataId, Operator.EQ, value) : new MetadataExpression(metadataId));
391            }
392            
393            if (_metadataCondition == Condition.OR)
394            {
395                expr = new OrExpression(metadataExpr.toArray(new Expression[metadataExpr.size()]));
396            }
397            else
398            {
399                expr = new AndExpression(metadataExpr.toArray(new Expression[metadataExpr.size()]));
400            }
401        }
402            
403        return expr;
404    }
405}
406
407