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        
085        contentHandler.startDocument();
086
087        AttributesImpl attrs = new AttributesImpl();
088        
089        String archiveDateAsString = (String) request.getAttribute(ArchiveEducationalBookletSchedulable.PARAM_ARCHIVE_DATE);
090        if (StringUtils.isNotBlank(archiveDateAsString))
091        {
092            attrs.addCDATAAttribute("isValidated", "true");
093            attrs.addCDATAAttribute("archiveDate", archiveDateAsString);
094        }
095        else
096        {
097            attrs.addCDATAAttribute("isValidated", "false");
098        }
099
100        XMLUtils.startElement(contentHandler, "booklet", attrs);
101        
102        String programItemId = (String) request.getAttribute(EducationalBookletSchedulable.PARAM_PROGRAM_ITEM_ID);
103        ProgramItem programItem = _resolver.resolveById(programItemId);
104        
105        _saxCatalog(programItem);
106        
107        try
108        {
109            Content content = (Content) programItem;
110            _odfHelper.switchToLiveVersion((DefaultContent) content);
111            _saxProgramItem(programItem, "programItem");
112            
113            boolean includeSubPrograms = (boolean) request.getAttribute(EducationalBookletSchedulable.PARAM_INCLUDE_SUBPROGRAMS);
114            
115            if (includeSubPrograms)
116            {
117                for (SubProgram subProgram : _getChildSubPrograms(programItem))
118                {
119                    _saxProgramItem(subProgram, "subprogram");
120                }
121            }
122            
123            for (Course course : _getChildCourses(programItem))
124            {
125                _saxProgramItem(course, "course");
126            }
127        }
128        catch (NoLiveVersionException e) 
129        {
130            throw new IllegalArgumentException("The program item with id " + programItemId + " has no live version.");
131        }
132        
133        XMLUtils.endElement(contentHandler, "booklet");
134        contentHandler.endDocument();
135    }
136    
137    /**
138     * Sax the catalog of program item
139     * @param programItem the program item
140     * @throws SAXException if an error occurred
141     */
142    protected void _saxCatalog(ProgramItem programItem) throws SAXException
143    {
144        String catalogName = programItem.getCatalog();
145        Catalog catalog = _catalogManager.getCatalog(catalogName);
146        
147        if (catalog != null)
148        {
149            AttributesImpl attrs = new AttributesImpl();
150            attrs.addCDATAAttribute("name", catalogName);
151            XMLUtils.createElement(contentHandler, "catalog", attrs, catalog.getTitle());
152        }
153    }
154    
155    /**
156     * Get all child courses from the program item
157     * @param programItem the program item
158     * @return the set of child courses
159     * @throws MalformedURLException if an error occurred
160     * @throws IOException if an error occurred
161     * @throws SAXException if an error occurred
162     */
163    protected Set<Course> _getChildCourses(ProgramItem programItem) throws MalformedURLException, IOException, SAXException
164    {
165        Set<Course> courses = new LinkedHashSet<>();
166        for (ProgramItem childProgramItem : _odfHelper.getChildProgramItems(programItem))
167        {
168            try
169            {
170                _odfHelper.switchToLiveVersion((DefaultAmetysObject) childProgramItem);
171                if (childProgramItem instanceof Course)
172                {
173                    courses.add((Course) childProgramItem);
174                }
175                courses.addAll(_getChildCourses(childProgramItem));
176            }
177            catch (NoLiveVersionException e) 
178            {
179                getLogger().warn("Cannot add the course to the educational booklet : Live label is required but there is no Live version for content " + childProgramItem.getId());
180            }
181        }
182        
183        return courses;
184    }
185    
186    /**
187     * Get the direct child {@link SubProgram}s of a {@link ProgramItem}
188     * @param programItem the program item
189     * @return the subprograms
190     */
191    protected Set<SubProgram> _getChildSubPrograms(ProgramItem programItem)
192    {
193        return _odfHelper.getChildProgramItems(programItem)
194            .stream()
195            .filter(SubProgram.class::isInstance)
196            .map(SubProgram.class::cast)
197            .collect(Collectors.toSet());
198    }
199    
200    /**
201     * Sax a {@link ProgramItem} as fo
202     * @param programItem the program item
203     * @param tagName the xml tag name
204     * @throws MalformedURLException if an error occurred
205     * @throws IOException if an error occurred
206     * @throws SAXException if an error occurred
207     */
208    protected void _saxProgramItem(ProgramItem programItem, String tagName) throws MalformedURLException, IOException, SAXException
209    {
210        AttributesImpl attrs = new AttributesImpl();
211        attrs.addCDATAAttribute("id", programItem.getId());
212        attrs.addCDATAAttribute("name", programItem.getName());
213        attrs.addCDATAAttribute("title", ((Content) programItem).getTitle());
214        attrs.addCDATAAttribute("code", programItem.getCode());
215        
216        XMLUtils.startElement(contentHandler, tagName, attrs);
217        _saxContentAsFo((Content) programItem);
218        XMLUtils.endElement(contentHandler, tagName);
219    }
220    
221    /**
222     * Sax content as fo
223     * @param content the content
224     * @throws MalformedURLException if an error occurred
225     * @throws IOException if an error occurred
226     * @throws SAXException if an error occurred
227     */
228    protected void _saxContentAsFo(Content content) throws MalformedURLException, IOException, SAXException
229    {
230        XMLUtils.startElement(contentHandler, "fo");
231        SitemapSource src = null;      
232        
233        try
234        {
235            Map<String, Object> pdfParameters = new HashMap<>();
236            pdfParameters.put("versionLabel", CmsConstants.LIVE_LABEL);
237            pdfParameters.put("exportMode", __EXPORT_MODE);
238            
239            String uri = "cocoon://_plugins/odf/_content/" + content.getName() + ".fo";
240            src = (SitemapSource) resolver.resolveURI(uri, null, pdfParameters);
241            src.toSAX(new IgnoreRootHandler(contentHandler));
242        }
243        catch (UnknownAmetysObjectException e)
244        {
245            // The content may be archived
246        }
247        finally
248        {
249            resolver.release(src);
250        }
251        
252        XMLUtils.endElement(contentHandler, "fo");
253    }
254}