001/*
002 *  Copyright 2020 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.odf.export.pdf;
017
018import java.io.IOException;
019import java.net.MalformedURLException;
020import java.util.HashMap;
021import java.util.LinkedHashSet;
022import java.util.Map;
023import java.util.Set;
024import java.util.stream.Collectors;
025
026import org.apache.avalon.framework.service.ServiceException;
027import org.apache.avalon.framework.service.ServiceManager;
028import org.apache.cocoon.ProcessingException;
029import org.apache.cocoon.components.source.impl.SitemapSource;
030import org.apache.cocoon.environment.ObjectModelHelper;
031import org.apache.cocoon.environment.Request;
032import org.apache.cocoon.generation.ServiceableGenerator;
033import org.apache.cocoon.xml.AttributesImpl;
034import org.apache.cocoon.xml.XMLUtils;
035import org.apache.commons.lang.StringUtils;
036import org.xml.sax.SAXException;
037
038import org.ametys.cms.CmsConstants;
039import org.ametys.cms.repository.Content;
040import org.ametys.cms.repository.DefaultContent;
041import org.ametys.core.util.IgnoreRootHandler;
042import org.ametys.odf.NoLiveVersionException;
043import org.ametys.odf.ODFHelper;
044import org.ametys.odf.ProgramItem;
045import org.ametys.odf.catalog.Catalog;
046import org.ametys.odf.catalog.CatalogsManager;
047import org.ametys.odf.course.Course;
048import org.ametys.odf.program.SubProgram;
049import org.ametys.odf.schedulable.ArchiveEducationalBookletSchedulable;
050import org.ametys.odf.schedulable.EducationalBookletSchedulable;
051import org.ametys.plugins.repository.AmetysObjectResolver;
052import org.ametys.plugins.repository.UnknownAmetysObjectException;
053import org.ametys.plugins.repository.jcr.DefaultAmetysObject;
054
055
056/**
057 * Generator producing the SAX of subprogram and its courses for the educational booklet 
058 */ 
059public class EducationalBookletGenerator extends ServiceableGenerator
060{
061    private static final String __EXPORT_MODE = "educational-booklet";
062    
063    /** The Ametys object resolver */
064    protected AmetysObjectResolver _resolver;
065    
066    /** The ODF helper */
067    protected ODFHelper _odfHelper;
068
069    /** The catalog manager */
070    protected CatalogsManager _catalogManager;
071    
072    @Override
073    public void service(ServiceManager sManager) throws ServiceException
074    {
075        super.service(sManager);
076        _resolver = (AmetysObjectResolver) sManager.lookup(AmetysObjectResolver.ROLE);
077        _odfHelper = (ODFHelper) sManager.lookup(ODFHelper.ROLE);
078        _catalogManager = (CatalogsManager) sManager.lookup(CatalogsManager.ROLE);
079    }
080    
081    public void generate() throws IOException, SAXException, ProcessingException
082    {
083        Request request = ObjectModelHelper.getRequest(objectModel);
084        request.setAttribute(ODFHelper.REQUEST_ATTRIBUTE_VALID_LABEL, true);
085        
086        contentHandler.startDocument();
087
088        AttributesImpl attrs = new AttributesImpl();
089        
090        String archiveDateAsString = (String) request.getAttribute(ArchiveEducationalBookletSchedulable.PARAM_ARCHIVE_DATE);
091        if (StringUtils.isNotBlank(archiveDateAsString))
092        {
093            attrs.addCDATAAttribute("isValidated", "true");
094            attrs.addCDATAAttribute("archiveDate", archiveDateAsString);
095        }
096        else
097        {
098            attrs.addCDATAAttribute("isValidated", "false");
099        }
100
101        XMLUtils.startElement(contentHandler, "booklet", attrs);
102        
103        String programItemId = (String) request.getAttribute(EducationalBookletSchedulable.PARAM_PROGRAM_ITEM_ID);
104        ProgramItem programItem = _resolver.resolveById(programItemId);
105        
106        _saxCatalog(programItem);
107        
108        try
109        {
110            Content content = (Content) programItem;
111            _odfHelper.switchToLiveVersion((DefaultContent) content);
112            _saxProgramItem(programItem, "programItem");
113            
114            boolean includeSubPrograms = (boolean) request.getAttribute(EducationalBookletSchedulable.PARAM_INCLUDE_SUBPROGRAMS);
115            
116            if (includeSubPrograms)
117            {
118                for (SubProgram subProgram : _getChildSubPrograms(programItem))
119                {
120                    _saxProgramItem(subProgram, "subprogram");
121                }
122            }
123            
124            for (Course course : _getChildCourses(programItem))
125            {
126                _saxProgramItem(course, "course");
127            }
128        }
129        catch (NoLiveVersionException e) 
130        {
131            throw new IllegalArgumentException("The program item with id " + programItemId + " has no live version.");
132        }
133        
134        XMLUtils.endElement(contentHandler, "booklet");
135        contentHandler.endDocument();
136    }
137    
138    /**
139     * Sax the catalog of program item
140     * @param programItem the program item
141     * @throws SAXException if an error occurred
142     */
143    protected void _saxCatalog(ProgramItem programItem) throws SAXException
144    {
145        String catalogName = programItem.getCatalog();
146        Catalog catalog = _catalogManager.getCatalog(catalogName);
147        
148        if (catalog != null)
149        {
150            AttributesImpl attrs = new AttributesImpl();
151            attrs.addCDATAAttribute("name", catalogName);
152            XMLUtils.createElement(contentHandler, "catalog", attrs, catalog.getTitle());
153        }
154    }
155    
156    /**
157     * Get all child courses from the program item
158     * @param programItem the program item
159     * @return the set of child courses
160     * @throws MalformedURLException if an error occurred
161     * @throws IOException if an error occurred
162     * @throws SAXException if an error occurred
163     */
164    protected Set<Course> _getChildCourses(ProgramItem programItem) throws MalformedURLException, IOException, SAXException
165    {
166        Set<Course> courses = new LinkedHashSet<>();
167        for (ProgramItem childProgramItem : _odfHelper.getChildProgramItems(programItem))
168        {
169            try
170            {
171                _odfHelper.switchToLiveVersion((DefaultAmetysObject) childProgramItem);
172                if (childProgramItem instanceof Course)
173                {
174                    courses.add((Course) childProgramItem);
175                }
176                courses.addAll(_getChildCourses(childProgramItem));
177            }
178            catch (NoLiveVersionException e) 
179            {
180                getLogger().warn("Cannot add the course to the educational booklet : Live label is required but there is no Live version for content " + childProgramItem.getId());
181            }
182        }
183        
184        return courses;
185    }
186    
187    /**
188     * Get the direct child {@link SubProgram}s of a {@link ProgramItem}
189     * @param programItem the program item
190     * @return the subprograms
191     */
192    protected Set<SubProgram> _getChildSubPrograms(ProgramItem programItem)
193    {
194        return _odfHelper.getChildProgramItems(programItem)
195            .stream()
196            .filter(SubProgram.class::isInstance)
197            .map(SubProgram.class::cast)
198            .collect(Collectors.toSet());
199    }
200    
201    /**
202     * Sax a {@link ProgramItem} as fo
203     * @param programItem the program item
204     * @param tagName the xml tag name
205     * @throws MalformedURLException if an error occurred
206     * @throws IOException if an error occurred
207     * @throws SAXException if an error occurred
208     */
209    protected void _saxProgramItem(ProgramItem programItem, String tagName) throws MalformedURLException, IOException, SAXException
210    {
211        AttributesImpl attrs = new AttributesImpl();
212        attrs.addCDATAAttribute("id", programItem.getId());
213        attrs.addCDATAAttribute("name", programItem.getName());
214        attrs.addCDATAAttribute("title", ((Content) programItem).getTitle());
215        attrs.addCDATAAttribute("code", programItem.getCode());
216        
217        XMLUtils.startElement(contentHandler, tagName, attrs);
218        _saxContentAsFo((Content) programItem);
219        XMLUtils.endElement(contentHandler, tagName);
220    }
221    
222    /**
223     * Sax content as fo
224     * @param content the content
225     * @throws MalformedURLException if an error occurred
226     * @throws IOException if an error occurred
227     * @throws SAXException if an error occurred
228     */
229    protected void _saxContentAsFo(Content content) throws MalformedURLException, IOException, SAXException
230    {
231        XMLUtils.startElement(contentHandler, "fo");
232        SitemapSource src = null;      
233        
234        try
235        {
236            Map<String, Object> pdfParameters = new HashMap<>();
237            pdfParameters.put("versionLabel", CmsConstants.LIVE_LABEL);
238            pdfParameters.put("exportMode", __EXPORT_MODE);
239            
240            String uri = "cocoon://_plugins/odf/_content/" + content.getName() + ".fo";
241            src = (SitemapSource) resolver.resolveURI(uri, null, pdfParameters);
242            src.toSAX(new IgnoreRootHandler(contentHandler));
243        }
244        catch (UnknownAmetysObjectException e)
245        {
246            // The content may be archived
247        }
248        finally
249        {
250            resolver.release(src);
251        }
252        
253        XMLUtils.endElement(contentHandler, "fo");
254    }
255}