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