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