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.plugins.newsletter.category;
017
018import java.util.ArrayList;
019import java.util.Collection;
020import java.util.HashSet;
021import java.util.List;
022import java.util.Set;
023
024import javax.jcr.RepositoryException;
025
026import org.apache.avalon.framework.configuration.Configurable;
027import org.apache.avalon.framework.configuration.Configuration;
028import org.apache.avalon.framework.configuration.ConfigurationException;
029import org.apache.avalon.framework.logger.LogEnabled;
030import org.apache.avalon.framework.logger.Logger;
031import org.apache.avalon.framework.service.ServiceException;
032import org.apache.avalon.framework.service.ServiceManager;
033import org.apache.avalon.framework.service.Serviceable;
034import org.apache.commons.lang.StringUtils;
035
036import org.ametys.cms.repository.Content;
037import org.ametys.cms.repository.ContentTypeExpression;
038import org.ametys.plugins.repository.AmetysObject;
039import org.ametys.plugins.repository.AmetysObjectIterable;
040import org.ametys.plugins.repository.AmetysObjectResolver;
041import org.ametys.plugins.repository.AmetysRepositoryException;
042import org.ametys.plugins.repository.ModifiableTraversableAmetysObject;
043import org.ametys.plugins.repository.TraversableAmetysObject;
044import org.ametys.plugins.repository.UnknownAmetysObjectException;
045import org.ametys.plugins.repository.jcr.DefaultTraversableAmetysObject;
046import org.ametys.plugins.repository.jcr.JCRAmetysObject;
047import org.ametys.plugins.repository.query.expression.AndExpression;
048import org.ametys.plugins.repository.query.expression.Expression;
049import org.ametys.plugins.repository.query.expression.Expression.Operator;
050import org.ametys.plugins.repository.query.expression.ExpressionContext;
051import org.ametys.plugins.repository.query.expression.StringExpression;
052import org.ametys.runtime.i18n.I18nizableText;
053import org.ametys.runtime.plugin.component.PluginAware;
054import org.ametys.web.repository.site.SiteManager;
055
056/**
057 * Class representing a JCR categories provider
058 */
059public class JCRCategoryProvider implements LogEnabled, CategoryProvider, Serviceable, Configurable, PluginAware
060{
061    /** The id */
062    protected String _id;
063    /** The label */
064    protected I18nizableText _label;
065    /** The description */
066    protected I18nizableText _description;
067    /** The plugin name */
068    protected String _pluginName;
069    /** The feature name */
070    protected String _featureName;
071    /** The Logger */
072    protected Logger _logger;
073    
074    private AmetysObjectResolver _resolver;
075    private SiteManager _siteManager;
076    
077    @Override
078    public void service(ServiceManager serviceManager) throws ServiceException
079    {
080        _resolver = (AmetysObjectResolver) serviceManager.lookup(AmetysObjectResolver.ROLE);
081        _siteManager = (SiteManager) serviceManager.lookup(SiteManager.ROLE);
082    }
083    
084    public void configure(Configuration configuration) throws ConfigurationException
085    {
086        _label = configureLabel(configuration);
087        _description = configureDescription(configuration);
088    }
089    
090    public void enableLogging(Logger logger)
091    {
092        _logger = logger;
093    }
094    
095    @Override
096    public boolean isWritable()
097    {
098        return true;
099    }
100    
101    @Override
102    public List<Category> getCategories(String siteName, String lang)
103    {
104        if (StringUtils.isEmpty(siteName))
105        {
106            return null;
107        }
108        
109        List<Category> categories = new ArrayList<>();
110        
111        try
112        {
113            DefaultTraversableAmetysObject rootNode = (DefaultTraversableAmetysObject) _getRootNode(siteName, lang);
114            
115            AmetysObjectIterable<AmetysObject> it = rootNode.getChildren();
116            for (AmetysObject object : it)
117            {
118                if (object instanceof JCRCategory)
119                {
120                    JCRCategory jcrCategory = (JCRCategory) object;
121                    Category category = new Category(jcrCategory.getId(), jcrCategory.getName(), rootNode.getId(), new I18nizableText(jcrCategory.getTitle()), new I18nizableText(jcrCategory.getDescription()), jcrCategory.getTemplate(), siteName, lang);
122                    
123                    categories.add(category);
124                }
125            }
126        }
127        catch (RepositoryException e)
128        {
129            _logger.error("Unable to list the newsletter categories for site " + siteName + " and language " + lang, e);
130        }
131        
132        return categories;
133    }
134    
135    @Override
136    public Collection<Category> getAllCategories(String siteName, String lang)
137    {
138        if (StringUtils.isEmpty(siteName))
139        {
140            return null;
141        }
142        
143        Set<Category> categories = new HashSet<>();
144        
145        try
146        {
147            TraversableAmetysObject rootNode = _getRootNode(siteName, lang);
148            
149            // Recursively add categories from the root node.
150            addCategories(categories, rootNode, siteName, lang);
151        }
152        catch (RepositoryException e)
153        {
154            _logger.error("Unable to list the newsletter categories for site " + siteName + " and language " + lang, e);
155        }
156        
157        return categories;
158    }
159
160    /**
161     * Recursively find {@link JCRCategory} objects in the given TraversableAmetysObject.
162     * @param categories the Set to fill with categories.
163     * @param parentObject the parent {@link TraversableAmetysObject}.
164     * @param siteName the site name.
165     * @param lang the language.
166     */
167    protected void addCategories(Set<Category> categories, TraversableAmetysObject parentObject, String siteName, String lang)
168    {
169        AmetysObjectIterable<AmetysObject> it = parentObject.getChildren();
170        for (AmetysObject object : it)
171        {
172            if (object instanceof JCRCategory)
173            {
174                JCRCategory jcrCategory = (JCRCategory) object;
175                Category category = new Category(jcrCategory.getId(), jcrCategory.getName(), parentObject.getId(), new I18nizableText(jcrCategory.getTitle()), new I18nizableText(jcrCategory.getDescription()), jcrCategory.getTemplate(), siteName, lang);
176                
177                categories.add(category);
178                
179                // 
180                addCategories(categories, jcrCategory, siteName, lang);
181            }
182        }
183    }
184    
185    @Override
186    public Category getCategory(String categoryID)
187    {
188        try
189        {
190            JCRCategory category = _resolver.resolveById(categoryID);
191            
192            AmetysObject parent = category.getParent();
193            String parentId = parent.getId();
194            if (!(parent instanceof JCRCategory))
195            {
196                parentId = "provider_" + getId();
197            }
198            
199            return new Category(category.getId(), category.getName(), parentId, new I18nizableText(category.getTitle()), new I18nizableText(category.getDescription()), category.getTemplate(), category.getSiteName(), category.getLang());
200        }
201        catch (UnknownAmetysObjectException e) 
202        {
203            _logger.warn("Unable to retrieve newsletter category of id " + categoryID, e);
204            return null;
205        }
206    }
207    
208    @Override
209    public void setTemplate(Category category, String templateName)
210    {
211        JCRCategory jcrCategory = _resolver.resolveById(category.getId());
212        jcrCategory.setTemplate(templateName);
213        jcrCategory.saveChanges();
214    }
215    
216    @Override
217    public Collection<String> getAutomaticIds(String categoryId)
218    {
219        JCRCategory jcrCategory = _resolver.resolveById(categoryId);
220        return jcrCategory.getAutomaticIds();
221    }
222    
223    @Override
224    public void setAutomatic(String categoryId, Collection<String> automaticNewsletterIds)
225    {
226        JCRCategory jcrCategory = _resolver.resolveById(categoryId);
227        jcrCategory.setAutomaticIds(automaticNewsletterIds);
228        jcrCategory.saveChanges();
229    }
230    
231    @Override
232    public boolean hasCategory(String categoryID)
233    {
234        try
235        {
236            AmetysObject object = _resolver.resolveById(categoryID);
237            return object instanceof JCRCategory;
238        }
239        catch (UnknownAmetysObjectException e)
240        {
241            _logger.debug("Unable to retrieve newsletter category of id " + categoryID, e);
242            return false;
243        }
244        catch (AmetysRepositoryException e) 
245        {
246            _logger.debug("Unable to retrieve newsletter category of id " + categoryID, e);
247            return false;
248        }
249    }
250    
251    @Override
252    public List<Category> getCategories(String categoryID)
253    {
254        List<Category> categories = new ArrayList<>();
255        
256        try
257        {
258            JCRCategory category = _resolver.resolveById(categoryID);
259            AmetysObjectIterable<AmetysObject> it = category.getChildren();
260            for (AmetysObject object : it)
261            {
262                if (object instanceof JCRCategory)
263                {
264                    JCRCategory jcrCategory = (JCRCategory) object;
265                    Category child = new Category(jcrCategory.getId(), jcrCategory.getName(), category.getId(), new I18nizableText(jcrCategory.getTitle()), new I18nizableText(jcrCategory.getDescription()), jcrCategory.getTemplate(), jcrCategory.getSiteName(), jcrCategory.getLang());
266                    
267                    categories.add(child);
268                }
269            }
270        }
271        catch (AmetysRepositoryException e)
272        {
273            _logger.error("Unable to list the newsletter categories for category of id " + categoryID, e);
274        }
275        
276        
277        return categories;
278    }
279    
280    
281    @Override
282    public boolean hasChildren(String categoryID)
283    {
284        try
285        {
286            JCRCategory category = _resolver.resolveById(categoryID);
287            return category.getChildren().iterator().hasNext();
288        }
289        catch (AmetysRepositoryException e)
290        {
291            _logger.error("Unable to retrieve newsletter category of id " + categoryID, e);
292            return false;
293        }
294        
295    }
296    
297    @Override
298    public AmetysObjectIterable<Content> getNewsletters(String categoryID, String siteName, String lang)
299    {
300        Expression cTypeExpr = new ContentTypeExpression(Operator.EQ, "org.ametys.plugins.newsletter.Content.newsletter");
301        Expression siteExpr = new StringExpression("site", Operator.EQ, siteName);
302        Expression catExpr = new StringExpression("category", Operator.EQ, categoryID, ExpressionContext.newInstance().withInternal(true));
303        Expression expr = new AndExpression(cTypeExpr, siteExpr, catExpr);
304        
305        String xpathQuery = org.ametys.plugins.repository.query.QueryHelper.getXPathQuery(null, "ametys:content", expr);
306        return _resolver.query(xpathQuery);
307    }
308    
309    @Override
310    public boolean hasNewsletters(String categoryID, String siteName, String lang)
311    {
312        Expression expr = new ContentTypeExpression(Operator.EQ, "org.ametys.plugins.newsletter.Content.newsletter");
313        Expression siteExpr = new StringExpression("site", Operator.EQ, siteName);
314        expr = new AndExpression(expr, siteExpr);
315        Expression catExpr = new StringExpression("category", Operator.EQ, categoryID, ExpressionContext.newInstance().withInternal(true));
316        expr = new AndExpression(expr, catExpr);
317        
318        String xpathQuery = org.ametys.plugins.repository.query.QueryHelper.getXPathQuery(null, "ametys:content", expr);
319        
320        return _resolver.query(xpathQuery).iterator().hasNext();
321    }
322    
323    @Override
324    public I18nizableText getLabel()
325    {
326        return _label;
327    }
328    
329    @Override
330    public I18nizableText getDescription()
331    {
332        return _description;
333    }
334
335    @Override
336    public String getId()
337    {
338        return _id;
339    }
340    
341    /**
342     * Configure label from the passed configuration
343     * @param configuration The configuration
344     * @return The label
345     * @throws ConfigurationException If an error occurred
346     */
347    protected I18nizableText configureLabel (Configuration configuration) throws ConfigurationException
348    {
349        Configuration labelConfiguration = configuration.getChild("label");
350        
351        if (labelConfiguration.getAttributeAsBoolean("i18n", false))
352        {
353            return new I18nizableText("plugin." + _pluginName, labelConfiguration.getValue(""));
354        }
355        else
356        {
357            return new I18nizableText(labelConfiguration.getValue(""));
358        }
359    }
360    
361    /**
362     * Configure description from the passed configuration
363     * @param configuration The configuration
364     * @return The description
365     * @throws ConfigurationException If an error occurred
366     */
367    protected I18nizableText configureDescription (Configuration configuration) throws ConfigurationException
368    {
369        Configuration descConfiguration = configuration.getChild("description");
370        
371        if (descConfiguration.getAttributeAsBoolean("i18n", false))
372        {
373            return new I18nizableText("plugin." + _pluginName, descConfiguration.getValue(""));
374        }
375        else
376        {
377            return new I18nizableText(descConfiguration.getValue(""));
378        }
379    }
380    
381    @Override
382    public void setPluginInfo(String pluginName, String featureName, String id)
383    {
384        _pluginName = pluginName;
385        _featureName = featureName;
386        _id = id;
387    }
388    
389    /**
390     * Get the plugin name
391     * @return the plugin name
392     */
393    public String getPluginName()
394    {
395        return _pluginName;
396    }
397    
398    @Override
399    public String getRootId(String siteName, String lang)
400    {
401        try
402        {
403            return _getRootNode(siteName, lang).getId();
404        }
405        catch (RepositoryException e) 
406        {
407            _logger.error("Unable to retrieve the root node of newsletter categories for site " + siteName + " and language " + lang, e);
408            return null;
409        }
410    }
411    
412    
413    private TraversableAmetysObject _getRootNode (String sitename, String lang) throws RepositoryException
414    {
415        ModifiableTraversableAmetysObject pluginsNode = _siteManager.getSite(sitename).getRootPlugins();
416        
417        ModifiableTraversableAmetysObject categoriesNode = null;
418        if (!pluginsNode.hasChild("newsletter"))
419        {
420            categoriesNode = ((ModifiableTraversableAmetysObject) pluginsNode.createChild("newsletter", "ametys:unstructured")).createChild("ametys:categories", "ametys:unstructured");
421        }
422        else
423        {
424            categoriesNode = pluginsNode.getChild("newsletter/ametys:categories");
425        }
426        
427        if (!categoriesNode.hasChild(lang))
428        {
429            categoriesNode.createChild(lang, "ametys:unstructured");
430            ((JCRAmetysObject) pluginsNode).getNode().getSession().save();
431        }
432        
433        return pluginsNode.getChild("newsletter/ametys:categories/" + lang);
434    }
435
436}