001/*
002 *  Copyright 2015 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.blog.posts;
017
018import java.io.IOException;
019import java.util.Collection;
020import java.util.Date;
021import java.util.Iterator;
022import java.util.LinkedHashSet;
023import java.util.Set;
024
025import org.apache.avalon.framework.service.ServiceException;
026import org.apache.avalon.framework.service.ServiceManager;
027import org.apache.cocoon.ProcessingException;
028import org.apache.cocoon.components.source.impl.SitemapSource;
029import org.apache.cocoon.environment.ObjectModelHelper;
030import org.apache.cocoon.environment.Request;
031import org.apache.cocoon.generation.ServiceableGenerator;
032import org.apache.cocoon.xml.AttributesImpl;
033import org.apache.cocoon.xml.XMLUtils;
034import org.apache.commons.lang.BooleanUtils;
035import org.apache.excalibur.source.SourceResolver;
036import org.xml.sax.SAXException;
037
038import org.ametys.cms.repository.Content;
039import org.ametys.core.util.IgnoreRootHandler;
040import org.ametys.plugins.blog.BlogCacheManager;
041import org.ametys.plugins.blog.BlogCacheManager.Post;
042import org.ametys.plugins.blog.BlogConstants;
043import org.ametys.plugins.repository.AmetysObjectResolver;
044import org.ametys.runtime.parameter.ParameterHelper;
045import org.ametys.web.repository.page.Page;
046import org.ametys.web.site.SiteConfigurationExtensionPoint;
047
048/**
049 * Generates posts from the cache.
050 */
051public class PostsGenerator extends ServiceableGenerator
052{
053    /** The blog cache manager. */
054    protected BlogCacheManager _blogCache;
055    
056    /** The ametys object resolver. */
057    protected AmetysObjectResolver _ametysResolver;
058    
059    /** The site configuration. */
060    protected SiteConfigurationExtensionPoint _siteConf;
061    
062    /** The source resolver. */
063    protected SourceResolver _sourceResolver;
064    
065    @Override
066    public void service(ServiceManager serviceManager) throws ServiceException
067    {
068        super.service(serviceManager);
069        _blogCache = (BlogCacheManager) serviceManager.lookup(BlogCacheManager.ROLE);
070        _ametysResolver = (AmetysObjectResolver) serviceManager.lookup(AmetysObjectResolver.ROLE);
071        _siteConf = (SiteConfigurationExtensionPoint) serviceManager.lookup(SiteConfigurationExtensionPoint.ROLE);
072        _sourceResolver = (SourceResolver) serviceManager.lookup(SourceResolver.ROLE);
073    }
074    
075    @Override
076    public void generate() throws IOException, SAXException, ProcessingException
077    {
078        Request request = ObjectModelHelper.getRequest(objectModel);
079        
080        // Get site, language and page in request attributes. Can be null if the filter does not need them.
081        Page page = (Page) request.getAttribute(Page.class.getName());
082        String siteName = parameters.getParameter("site", page.getSiteName());
083        String language = parameters.getParameter("lang", page.getSitemapName());
084        
085        String type = parameters.getParameter("type", "all");
086        int year = parameters.getParameterAsInteger("year", -1);
087        int month = parameters.getParameterAsInteger("month", -1);
088        
089        String tagName = parameters.getParameter("tagName", "");
090        
091        Long maxConf = _siteConf.getValueAsLong(siteName, "posts-service-max-count");
092        int postsPerPage = maxConf != null ? maxConf.intValue() : BlogConstants.DEFAULT_POST_COUNT_PER_PAGE;
093        
094        String metadataSetName = parameters.getParameter("metadataSetName", "");
095        
096        AttributesImpl attrs = new AttributesImpl();
097        attrs.addCDATAAttribute("posts-per-page", Integer.toString(postsPerPage));
098        
099        contentHandler.startDocument();
100        XMLUtils.startElement(contentHandler, "contents", attrs);
101        
102        // Get all the posts.
103        Collection<String> postIds = getPosts(siteName, language, type, year, month, tagName, BlogConstants.DEFAULT_POST_TOTAL_COUNT);
104        
105        for (String postId : postIds)
106        {
107            // Resolve the content.
108            Content postContent = _ametysResolver.resolveById(postId);
109            
110            saxContent(postContent, metadataSetName);
111        }
112        
113        XMLUtils.endElement(contentHandler, "contents");
114        contentHandler.endDocument();
115    }
116    
117    /**
118     * SAX a content in its specific view
119     * @param content The content to SAX
120     * @param metadataSetName The metadata set to use
121     * @throws SAXException If an error occurs while SAXing
122     * @throws IOException If an error occurs while retrieving content.
123     */
124    public void saxContent(Content content, String metadataSetName) throws SAXException, IOException
125    {
126        AttributesImpl attrs = new AttributesImpl();
127        attrs.addCDATAAttribute("id", content.getId());
128        attrs.addCDATAAttribute("name", content.getName());
129        attrs.addCDATAAttribute("title", content.getTitle());
130        attrs.addCDATAAttribute("lastModifiedAt", ParameterHelper.valueToString(content.getLastModified()));
131        
132        XMLUtils.startElement(contentHandler, "content", attrs);
133        
134        String uri = "cocoon://_content.html?contentId=" + content.getId() + "&metadataSetName=" + metadataSetName;
135        SitemapSource src = null;
136        
137        try
138        {
139            src = (SitemapSource) _sourceResolver.resolveURI(uri);
140            src.toSAX(new IgnoreRootHandler(contentHandler));
141        }
142        finally
143        {
144            _sourceResolver.release(src);
145        }
146        
147        XMLUtils.endElement(contentHandler, "content");
148    }
149    
150    /**
151     * Get all the posts for the given criteria.
152     * @param siteName the site name.
153     * @param language the language.
154     * @param type the search type ('all', 'year', 'month' or 'tag').
155     * @param year the year in case of a 'year' or 'month' search.
156     * @param month the month, in case of a 'month' search.
157     * @param tagName the tag name in case of a 'tag' search.
158     * @return the post IDs.
159     */
160    protected Collection<String> getPosts(String siteName, String language, String type, int year, int month, String tagName)
161    {
162        return getPosts(siteName, language, type, year, month, tagName, 0);
163    }
164    
165    /**
166     * Get a limited number of posts for the given criteria.
167     * @param siteName the site name.
168     * @param language the language.
169     * @param type the search type ('all', 'year', 'month' or 'tag').
170     * @param year the year in case of a 'year' or 'month' search.
171     * @param month the month, in case of a 'month' search.
172     * @param tagName the tag name in case of a 'tag' search.
173     * @param maxCount the maximum count of posts to retrieve.
174     * @return the post IDs.
175     */
176    protected Collection<String> getPosts(String siteName, String language, String type, int year, int month, String tagName, int maxCount)
177    {
178        Set<String> posts = new LinkedHashSet<>();
179
180        boolean displayFuturePosts = siteName != null ? BooleanUtils.isTrue(_siteConf.getValueAsBoolean(siteName, "display-future-posts")) : true;
181        
182        Iterator<Post> postIt = null;
183        if ("all".equals(type))
184        {
185            postIt = _blogCache.getSortedPosts(siteName, language).iterator();
186        }
187        else if ("year".equals(type))
188        {
189            postIt = _blogCache.getSortedPostsByYear(siteName, language, year).iterator();
190        }
191        else if ("month".equals(type))
192        {
193            postIt = _blogCache.getSortedPostsByMonth(siteName, language, year, month).iterator();
194        }
195        else if ("tag".equals(type))
196        {
197            postIt = _blogCache.getSortedPostsByTag(siteName, language, tagName, true).iterator();
198        }
199        
200        if (postIt != null)
201        {
202            int count = 0;
203            while (postIt.hasNext() && (maxCount == 0 || count < maxCount))
204            {
205                Post post = postIt.next();
206                if (displayFuturePosts || !_isFuturePost(post))
207                {
208                    posts.add(post.getId());
209                    count++;
210                }
211            }
212        }
213        
214        return posts;
215    }
216    
217    private boolean _isFuturePost (Post post)
218    {
219        Date postDate = post.getDate();
220        return postDate != null && postDate.after(new Date());
221    }
222}