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.generators;
017
018import java.io.IOException;
019import java.time.LocalDate;
020import java.time.ZoneId;
021import java.util.Date;
022import java.util.Iterator;
023
024import org.apache.avalon.framework.service.ServiceException;
025import org.apache.avalon.framework.service.ServiceManager;
026import org.apache.cocoon.ProcessingException;
027import org.apache.cocoon.components.source.impl.SitemapSource;
028import org.apache.cocoon.environment.ObjectModelHelper;
029import org.apache.cocoon.environment.Request;
030import org.apache.cocoon.generation.ServiceableGenerator;
031import org.apache.cocoon.xml.AttributesImpl;
032import org.apache.cocoon.xml.XMLUtils;
033import org.apache.commons.lang.StringUtils;
034import org.apache.excalibur.source.SourceResolver;
035import org.xml.sax.SAXException;
036
037import org.ametys.cms.repository.Content;
038import org.ametys.cms.repository.ContentTypeExpression;
039import org.ametys.cms.repository.DefaultContent;
040import org.ametys.core.util.DateUtils;
041import org.ametys.core.util.FilenameUtils;
042import org.ametys.core.util.IgnoreRootHandler;
043import org.ametys.core.util.URIUtils;
044import org.ametys.plugins.newsletter.category.Category;
045import org.ametys.plugins.newsletter.category.CategoryProviderExtensionPoint;
046import org.ametys.plugins.repository.AmetysObjectIterable;
047import org.ametys.plugins.repository.AmetysObjectResolver;
048import org.ametys.plugins.repository.provider.RequestAttributeWorkspaceSelector;
049import org.ametys.plugins.repository.query.SortCriteria;
050import org.ametys.plugins.repository.query.expression.AndExpression;
051import org.ametys.plugins.repository.query.expression.Expression;
052import org.ametys.plugins.repository.query.expression.Expression.Operator;
053import org.ametys.plugins.repository.query.expression.MetadataExpression;
054import org.ametys.plugins.repository.query.expression.StringExpression;
055import org.ametys.runtime.model.type.ElementType;
056import org.ametys.web.WebConstants;
057
058/**
059 * SAX the newsletters of a given category
060 *
061 */
062public class NewsletterListGenerator extends ServiceableGenerator
063{
064    
065    /** The category provider extension point. */
066    protected CategoryProviderExtensionPoint _categoryProviderEP;
067
068    /** The excalibur source resolver. */
069    protected SourceResolver _sourceResolver;
070    
071    private AmetysObjectResolver _resolver;
072
073    @Override
074    public void service(ServiceManager smanager) throws ServiceException
075    {
076        super.service(smanager);
077        _resolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE);
078        _sourceResolver = (SourceResolver) smanager.lookup(SourceResolver.ROLE);
079        _categoryProviderEP = (CategoryProviderExtensionPoint) smanager.lookup(CategoryProviderExtensionPoint.ROLE);
080    }
081    
082    @Override
083    public void generate() throws IOException, SAXException, ProcessingException
084    {
085        Request request = ObjectModelHelper.getRequest(objectModel);
086        String workspaceName = RequestAttributeWorkspaceSelector.getForcedWorkspace(request);
087        
088        // Force workspace live for search
089        RequestAttributeWorkspaceSelector.setForcedWorkspace(request, WebConstants.LIVE_WORKSPACE);
090        
091        String siteName = parameters.getParameter("siteName", (String) request.getAttribute(WebConstants.REQUEST_ATTR_SITE_NAME));
092        String categoryId = parameters.getParameter("category", null);
093        String encodedCategoryId = parameters.getParameter("encodedCategoryId", null);
094        boolean saxNewsletterContent = parameters.getParameterAsBoolean("newsletterContent", false);
095        String header = parameters.getParameter("header", "");
096        int length = parameters.getParameterAsInteger("length", 0);
097        boolean displayRss = parameters.getParameterAsBoolean("rss", false);
098        
099        if (categoryId == null && StringUtils.isNotEmpty(encodedCategoryId))
100        {
101            categoryId = URIUtils.decode(encodedCategoryId);
102        }
103        
104        Expression cTypeExpr = new ContentTypeExpression(Operator.EQ, "org.ametys.plugins.newsletter.Content.newsletter");
105        Expression validExpr = new MetadataExpression(DefaultContent.METADATA_LAST_VALIDATION);
106        Expression siteExpr = new StringExpression("site", Operator.EQ, siteName);
107        Expression categoryExpr = new StringExpression("category", Operator.EQ, categoryId);
108        
109        Expression expression = new AndExpression(cTypeExpr, validExpr, siteExpr, categoryExpr);
110        
111        SortCriteria sortCriteria = new SortCriteria();
112        sortCriteria.addCriterion(DefaultContent.METADATA_LAST_VALIDATION, false, false);
113        String xpathQuery = org.ametys.plugins.repository.query.QueryHelper.getXPathQuery(null, "ametys:content", expression, sortCriteria);
114        
115        try (AmetysObjectIterable<Content> contents = _resolver.query(xpathQuery);)
116        {
117            AttributesImpl attrs = new AttributesImpl();
118            if (StringUtils.isNotEmpty(header))
119            {
120                attrs.addCDATAAttribute("service-title", header);
121            }
122            
123            Category category = categoryId != null ? _categoryProviderEP.getCategory(categoryId) : null;
124            
125            contentHandler.startDocument();
126            XMLUtils.startElement(contentHandler, "Newsletters", attrs);
127            
128            if (category != null)
129            {
130                _saxCategory(category);
131            }
132            
133            int index = 0;
134            Iterator<Content> it = contents.iterator();
135            while (it.hasNext() && index < length)
136            {
137                Content content = it.next();
138                _saxNewsletter(content, saxNewsletterContent);
139                index++;
140            }
141            
142            if (displayRss && category != null)
143            {
144                _saxRssFeed(category);
145            }
146            
147            XMLUtils.endElement(contentHandler, "Newsletters");
148            contentHandler.endDocument();
149            
150            RequestAttributeWorkspaceSelector.setForcedWorkspace(request, workspaceName);
151        }
152    }
153
154    /**
155     * Sax a category.
156     * @param category the category to sax
157     * @throws SAXException if an error occurred while saxing
158     */
159    protected void _saxCategory(Category category) throws SAXException
160    {
161        AttributesImpl attrs = new AttributesImpl();
162        attrs.addCDATAAttribute("id", category.getId());
163        
164        XMLUtils.startElement(contentHandler, "category", attrs);
165        
166        category.getTitle().toSAX(contentHandler, "title");
167        category.getDescription().toSAX(contentHandler, "description");
168        
169        XMLUtils.endElement(contentHandler, "category");
170    }
171    
172    private void _saxNewsletter (Content content, boolean saxContent) throws SAXException, IOException
173    {
174        AttributesImpl attr = new AttributesImpl();
175        attr.addAttribute("", "id", "id", "CDATA", content.getId());
176        attr.addAttribute("", "name", "name", "CDATA", content.getName());
177        XMLUtils.startElement(contentHandler, "newsletter", attr);
178        
179        XMLUtils.createElement(contentHandler, "title", content.getTitle());
180        
181        long number = content.getValue("newsletter-number", false, 0L);
182        if (number != 0)
183        {
184            XMLUtils.createElement(contentHandler, "number", String.valueOf(number));
185        }
186        
187        LocalDate defaultValue = DateUtils.asLocalDate(content.getLastMajorValidationDate());
188        LocalDate date = content.getValue("newsletter-date", false, defaultValue);
189        @SuppressWarnings("unchecked")
190        ElementType<LocalDate> dateType = (ElementType<LocalDate>) content.getType("newsletter-date");
191        
192        AttributesImpl dateAttrs = new AttributesImpl();
193        Long millis = date.atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli();
194        dateAttrs.addCDATAAttribute("millis", Long.toString(millis));
195        XMLUtils.createElement(contentHandler, "date", dateAttrs, dateType.toString(date));
196        
197        Date pubDate = content.getLastMajorValidationDate();
198        dateAttrs.clear();
199        dateAttrs.addCDATAAttribute("millis", Long.toString(pubDate.getTime()));
200        XMLUtils.createElement(contentHandler, "pubDate", dateAttrs, DateUtils.dateToString(pubDate));
201        
202        if (saxContent)
203        {
204            _saxNewsletterContent(content);
205        }
206        
207        XMLUtils.endElement(contentHandler, "newsletter");
208    }
209
210    /**
211     * Sax the newsletter content.
212     * @param content the newsletter content.
213     * @throws SAXException if an error occurred while saxing
214     * @throws IOException if an I/O error occurred
215     */
216    protected void _saxNewsletterContent(Content content) throws SAXException, IOException
217    {
218        XMLUtils.startElement(contentHandler, "newsletter-content");
219        
220        String uri = "cocoon://_content.xml?contentId=" + content.getId() + "&viewName=main";
221        SitemapSource src = null;
222        
223        try
224        {
225            src = (SitemapSource) _sourceResolver.resolveURI(uri);
226            src.toSAX(new IgnoreRootHandler(contentHandler));
227        }
228        finally
229        {
230            _sourceResolver.release(src);
231        }
232        
233        XMLUtils.endElement(contentHandler, "newsletter-content");
234    }
235    
236    /**
237     * Sax the link to the newsletter archives RSS feed.
238     * @param category the category.
239     * @throws SAXException if an error occurred while saxing 
240     * @throws ProcessingException if an error occurred 
241     */
242    protected void _saxRssFeed(Category category) throws SAXException, ProcessingException
243    {
244        String categoryId = category.getId();
245        String siteName = category.getSiteName();
246        String lang = category.getLang();
247        
248        String encodedCategoryId = FilenameUtils.encodePath(categoryId);
249        String url = "plugins/newsletter/" + siteName + "/" + lang + "/archives/" + encodedCategoryId + "/rss.xml";
250        
251        AttributesImpl attrs = new AttributesImpl();
252        attrs.addCDATAAttribute("url", URIUtils.encodePath(url));
253        
254        XMLUtils.createElement(contentHandler, "rss", attrs);
255    }
256}