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