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.time.ZonedDateTime;
020import java.util.Collection;
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.excalibur.source.SourceResolver;
035import org.xml.sax.SAXException;
036
037import org.ametys.cms.repository.Content;
038import org.ametys.core.util.DateUtils;
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.web.repository.page.Page;
045import org.ametys.web.repository.site.Site;
046import org.ametys.web.repository.site.SiteManager;
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 Manager. */
060    protected SiteManager _siteManager;
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        _sourceResolver = (SourceResolver) serviceManager.lookup(SourceResolver.ROLE);
072        _siteManager = (SiteManager) serviceManager.lookup(SiteManager.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        Site site = _siteManager.getSite(siteName);
092        Long postsPerPage = site.getValue("posts-service-max-count", false, Long.valueOf(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", Long.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", DateUtils.dateToString(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 = true;
181        if (siteName != null)
182        {
183            Site site = _siteManager.getSite(siteName);
184            displayFuturePosts = site.getValue("display-future-posts", false, false);
185        }
186        
187        Iterator<Post> postIt = null;
188        if ("all".equals(type))
189        {
190            postIt = _blogCache.getSortedPosts(siteName, language).iterator();
191        }
192        else if ("year".equals(type))
193        {
194            postIt = _blogCache.getSortedPostsByYear(siteName, language, year).iterator();
195        }
196        else if ("month".equals(type))
197        {
198            postIt = _blogCache.getSortedPostsByMonth(siteName, language, year, month).iterator();
199        }
200        else if ("tag".equals(type))
201        {
202            postIt = _blogCache.getSortedPostsByTag(siteName, language, tagName, true).iterator();
203        }
204        
205        if (postIt != null)
206        {
207            int count = 0;
208            while (postIt.hasNext() && (maxCount == 0 || count < maxCount))
209            {
210                Post post = postIt.next();
211                if (displayFuturePosts || !_isFuturePost(post))
212                {
213                    posts.add(post.getId());
214                    count++;
215                }
216            }
217        }
218        
219        return posts;
220    }
221    
222    private boolean _isFuturePost (Post post)
223    {
224        ZonedDateTime postDate = post.getDate();
225        return postDate != null && postDate.isAfter(ZonedDateTime.now());
226    }
227}