001/*
002 *  Copyright 2019 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.content;
017
018import java.io.IOException;
019import java.util.List;
020import java.util.Locale;
021import java.util.Optional;
022import java.util.Set;
023
024import org.apache.avalon.framework.service.ServiceException;
025import org.apache.avalon.framework.service.ServiceManager;
026import org.apache.cocoon.ProcessingException;
027import org.apache.cocoon.environment.ObjectModelHelper;
028import org.apache.cocoon.environment.Request;
029import org.apache.cocoon.generation.ServiceableGenerator;
030import org.apache.cocoon.xml.AttributesImpl;
031import org.apache.cocoon.xml.XMLUtils;
032import org.apache.commons.lang.StringUtils;
033import org.xml.sax.SAXException;
034
035import org.ametys.cms.contenttype.ContentTypesHelper;
036import org.ametys.cms.repository.Content;
037import org.ametys.odf.NoLiveVersionException;
038import org.ametys.odf.ODFHelper;
039import org.ametys.odf.ProgramItem;
040import org.ametys.odf.course.Course;
041import org.ametys.odf.courselist.CourseList;
042import org.ametys.odf.coursepart.CoursePart;
043import org.ametys.odf.enumeration.OdfReferenceTableEntry;
044import org.ametys.odf.enumeration.OdfReferenceTableHelper;
045import org.ametys.odf.program.AbstractProgram;
046import org.ametys.odf.program.Container;
047import org.ametys.odf.program.Program;
048import org.ametys.odf.program.SubProgram;
049import org.ametys.odf.skill.ODFSkillsHelper;
050import org.ametys.plugins.repository.AmetysObjectResolver;
051import org.ametys.plugins.repository.AmetysRepositoryException;
052import org.ametys.plugins.repository.jcr.DefaultAmetysObject;
053import org.ametys.runtime.model.View;
054import org.ametys.runtime.model.exception.BadItemTypeException;
055import org.ametys.runtime.model.type.DataContext;
056
057/**
058 * SAX the structure (ie. the child program items) of a {@link ProgramItem}
059 *
060 */
061public class ProgramItemStructureGenerator extends ServiceableGenerator
062{
063    private static final Set<String> __ALLOWED_VIEW_NAMES = Set.of("main", "pdf");
064    
065    /** The ODF helper */
066    protected ODFHelper _odfHelper;
067    /** Helper for ODF reference table */
068    protected OdfReferenceTableHelper _odfReferenceTableHelper;
069    /** The content types helper */
070    protected ContentTypesHelper _cTypesHelper;
071    /** The ODF skills helper */
072    protected ODFSkillsHelper _odfSkillsHelper;
073    /** The Ametys object resolver */
074    protected AmetysObjectResolver _resolver;
075    
076    @Override
077    public void service(ServiceManager smanager) throws ServiceException
078    {
079        _odfHelper = (ODFHelper) smanager.lookup(ODFHelper.ROLE);
080        _odfReferenceTableHelper = (OdfReferenceTableHelper) smanager.lookup(OdfReferenceTableHelper.ROLE);
081        _cTypesHelper = (ContentTypesHelper) smanager.lookup(ContentTypesHelper.ROLE);
082        _odfSkillsHelper = (ODFSkillsHelper) smanager.lookup(ODFSkillsHelper.ROLE);
083        _resolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE);
084    }
085    
086    public void generate() throws IOException, SAXException, ProcessingException
087    {
088        Request request = ObjectModelHelper.getRequest(objectModel);
089        Content content = (Content) request.getAttribute(Content.class.getName());
090        
091        if (content == null)
092        {
093            String contentId = parameters.getParameter("contentId", null);
094            if (StringUtils.isBlank(contentId))
095            {
096                throw new IllegalArgumentException("Content is missing in request attribute or parameters");
097            }
098            content = _resolver.resolveById(contentId);
099        }
100
101        String viewName = parameters.getParameter("viewName", StringUtils.EMPTY);
102        String fallbackViewName = parameters.getParameter("fallbackViewName", StringUtils.EMPTY);
103        
104        View view = _cTypesHelper.getViewWithFallback(viewName, fallbackViewName, content);
105        
106        contentHandler.startDocument();
107        
108        if (view != null && __ALLOWED_VIEW_NAMES.contains(view.getName()))
109        {
110            if (content instanceof ProgramItem)
111            {
112                XMLUtils.startElement(contentHandler, "structure");
113                saxProgramItem((ProgramItem) content); 
114                XMLUtils.endElement(contentHandler, "structure");
115            }
116            else
117            {
118                getLogger().warn("Cannot get the structure of a non program item '" + content.getId() + "'");
119            }
120        }
121        
122        contentHandler.endDocument();
123    }
124        
125    /**
126     * SAX a program item with its child program items
127     * @param programItem the program item
128     * @throws SAXException if an error occurs while saxing
129     */
130    protected void saxProgramItem(ProgramItem programItem) throws SAXException
131    {
132        if (programItem instanceof Program)
133        {
134            saxProgram((Program) programItem);
135        }
136        else if (programItem instanceof SubProgram)
137        {
138            saxSubProgram((SubProgram) programItem);
139        }
140        else if (programItem instanceof Container)
141        {
142            saxContainer((Container) programItem);
143        }
144        else if (programItem instanceof CourseList)
145        {
146            saxCourseList((CourseList) programItem);
147        }
148        else if (programItem instanceof Course)
149        {
150            saxCourse((Course) programItem);
151        }
152    }
153    
154    /**
155     * SAX the child program items
156     * @param programItem the program item
157     * @throws SAXException if an error occurs while saxing
158     */
159    protected void saxChildProgramItems(ProgramItem programItem) throws SAXException
160    {
161        List<ProgramItem> childProgramItems = _odfHelper.getChildProgramItems(programItem);
162        for (ProgramItem childProgramItem : childProgramItems)
163        {
164            try
165            {
166                _odfHelper.switchToLiveVersionIfNeeded((DefaultAmetysObject) childProgramItem);
167                saxProgramItem(childProgramItem);
168            }
169            catch (NoLiveVersionException e) 
170            {
171                // Just ignore the program item
172            }
173        }
174    }
175        
176    /**
177     * SAX a program
178     * @param program the subprogram to SAX
179     * @throws SAXException if an error occurs
180     */
181    protected void saxProgram(Program program) throws SAXException
182    {
183        AttributesImpl attrs = new AttributesImpl();
184        _saxCommonAttributes(program, attrs);
185        
186        XMLUtils.startElement(contentHandler, "program", attrs);
187        
188        XMLUtils.startElement(contentHandler, "attributes");
189        _saxStructureViewIfExists(program);
190        XMLUtils.endElement(contentHandler, "attributes");
191        
192        saxChildProgramItems(program);
193        XMLUtils.endElement(contentHandler, "program");
194    }
195    
196    /**
197     * SAX a subprogram
198     * @param subProgram the subprogram to SAX
199     * @throws SAXException if an error occurs
200     */
201    protected void saxSubProgram(SubProgram subProgram) throws SAXException
202    {
203        AttributesImpl attrs = new AttributesImpl();
204        _saxCommonAttributes(subProgram, attrs);
205        
206        XMLUtils.startElement(contentHandler, "subprogram", attrs);
207        
208        XMLUtils.startElement(contentHandler, "attributes");
209        _saxReferenceTableItem(subProgram.getEcts(), AbstractProgram.ECTS, subProgram.getLanguage());
210        _saxStructureViewIfExists(subProgram);
211        XMLUtils.endElement(contentHandler, "attributes");
212        
213        saxChildProgramItems(subProgram);
214        
215        XMLUtils.endElement(contentHandler, "subprogram");
216    }
217    
218    /**
219     * SAX a container
220     * @param container the container to SAX
221     * @throws SAXException if an error occurs while saxing
222     */
223    protected void saxContainer(Container container) throws SAXException
224    {
225        AttributesImpl attrs = new AttributesImpl();
226        _saxCommonAttributes(container, attrs);
227        
228        XMLUtils.startElement(contentHandler, "container", attrs);
229        
230        XMLUtils.startElement(contentHandler, "attributes");
231        _saxReferenceTableItem(container.getNature(), Container.NATURE, container.getLanguage());
232        _saxReferenceTableItem(container.getPeriod(), Container.PERIOD, container.getLanguage());
233        
234        _saxStructureViewIfExists(container);
235        
236        XMLUtils.endElement(contentHandler, "attributes");
237        
238        saxChildProgramItems(container);
239        
240        XMLUtils.endElement(contentHandler, "container");
241    }
242    
243    /**
244     * SAX a course list
245     * @param cl the course list to SAX
246     * @throws SAXException if an error occurs while saxing
247     */
248    protected void saxCourseList(CourseList cl) throws SAXException
249    {
250        AttributesImpl attrs = new AttributesImpl();
251        _saxCommonAttributes(cl, attrs);
252        
253        XMLUtils.startElement(contentHandler, "courselist", attrs);
254        
255        XMLUtils.startElement(contentHandler, "attributes");
256        _saxStructureViewIfExists(cl);
257        XMLUtils.endElement(contentHandler, "attributes");
258        
259        saxChildProgramItems(cl);
260        
261        XMLUtils.endElement(contentHandler, "courselist");
262    }
263    
264    /**
265     * SAX a course
266     * @param course the container to SAX
267     * @throws SAXException if an error occurs while saxing
268     */
269    protected void saxCourse(Course course) throws SAXException
270    {
271        AttributesImpl attrs = new AttributesImpl();
272        _saxCommonAttributes(course, attrs);
273        
274        XMLUtils.startElement(contentHandler, "course", attrs);
275        
276        XMLUtils.startElement(contentHandler, "attributes");
277        _saxReferenceTableItem(course.getCourseType(), Course.COURSE_TYPE, course.getLanguage());
278        _saxStructureViewIfExists(course);
279        XMLUtils.endElement(contentHandler, "attributes");
280        
281        saxChildProgramItems(course);
282        
283        saxCourseParts(course);
284        
285        XMLUtils.endElement(contentHandler, "course");
286    }
287    
288    /**
289     * SAX a course part
290     * @param course The course
291     * @throws SAXException if an error occurs
292     */
293    protected void saxCourseParts(Course course) throws SAXException
294    {
295        List<CoursePart> courseParts = course.getCourseParts();
296        
297        double totalHours = 0;
298        for (CoursePart coursePart : courseParts)
299        {
300            _odfHelper.switchToLiveVersionIfNeeded(coursePart);
301            totalHours += coursePart.getNumberOfHours();
302        }
303        
304        AttributesImpl attrs = new AttributesImpl();
305        attrs.addCDATAAttribute("totalHours", String.valueOf(totalHours));
306        XMLUtils.startElement(contentHandler, "courseparts", attrs);
307
308        for (CoursePart coursePart : courseParts)
309        {
310            saxCoursePart(coursePart);
311        }
312        
313        XMLUtils.endElement(contentHandler, "courseparts");
314    }
315    
316    /**
317     * SAX a course part
318     * @param coursePart The course part to SAX
319     * @throws SAXException if an error occurs
320     */
321    protected void saxCoursePart(CoursePart coursePart) throws SAXException
322    {
323        AttributesImpl attrs = new AttributesImpl();
324        attrs.addCDATAAttribute("id", coursePart.getId());
325        attrs.addCDATAAttribute("title", coursePart.getTitle());
326        _addAttrIfNotEmpty(attrs, "code", coursePart.getCode());
327
328        XMLUtils.startElement(contentHandler, "coursepart", attrs);
329        
330        XMLUtils.startElement(contentHandler, "attributes");
331        _saxReferenceTableItem(coursePart.getNature(), CoursePart.NATURE, coursePart.getLanguage());
332        
333        _saxStructureViewIfExists(coursePart);
334        
335        XMLUtils.endElement(contentHandler, "attributes");
336        
337        XMLUtils.endElement(contentHandler, "coursepart");
338    }
339    
340    /**
341     * SAX the 'structure' view if exists
342     * @param content the content
343     * @throws SAXException if an error occurs
344     */
345    protected void _saxStructureViewIfExists(Content content) throws SAXException
346    {
347        View view = _cTypesHelper.getView("structure", content.getTypes(), content.getMixinTypes());
348        if (view != null)
349        {
350            try
351            {
352                content.dataToSAX(contentHandler, view, DataContext.newInstance().withLocale(new Locale(content.getLanguage())).withEmptyValues(false));
353            }
354            catch (BadItemTypeException | AmetysRepositoryException e)
355            {
356                throw new SAXException("Fail to sax the 'structure' view for content " + content.getId(), e);
357            }
358        }
359    }
360    
361    /**
362     * SAX the common attributes for program item
363     * @param programItem the program item
364     * @param attrs the attributes
365     */
366    protected void _saxCommonAttributes(ProgramItem programItem, AttributesImpl attrs)
367    {
368        attrs.addCDATAAttribute("title", ((Content) programItem).getTitle());
369        attrs.addCDATAAttribute("id", programItem.getId());
370        attrs.addCDATAAttribute("code", programItem.getCode());
371        attrs.addCDATAAttribute("name", programItem.getName());
372    
373        boolean excludedFromSkills = _odfSkillsHelper.isExcluded(programItem);
374        if (excludedFromSkills)
375        {
376            attrs.addCDATAAttribute("excludedFromSkills", String.valueOf(excludedFromSkills));
377        }
378    }
379    
380    /**
381     * SAX the item of a reference table
382     * @param itemId the item id
383     * @param tagName the tag name
384     * @param lang the language to use
385     * @throws SAXException if an error occurs while saxing
386     */
387    protected void _saxReferenceTableItem(String itemId, String tagName, String lang) throws SAXException
388    {
389        OdfReferenceTableEntry item = Optional.ofNullable(itemId)
390                                              .filter(StringUtils::isNotEmpty)
391                                              .map(_odfReferenceTableHelper::getItem)
392                                              .orElse(null);
393        
394        if (item != null)
395        {
396            AttributesImpl attrs = new AttributesImpl();
397            attrs.addCDATAAttribute("id", item.getId());
398            _addAttrIfNotEmpty(attrs, "code", item.getCode());
399            
400            XMLUtils.createElement(contentHandler, tagName, attrs, item.getLabel(lang));
401        }
402    }
403    
404    /**
405     * Add an attribute if its not null or empty.
406     * @param attrs The attributes
407     * @param attrName The attribute name
408     * @param attrValue The attribute value
409     */
410    protected void _addAttrIfNotEmpty(AttributesImpl attrs, String attrName, String attrValue)
411    {
412        if (StringUtils.isNotEmpty(attrValue))
413        {
414            attrs.addCDATAAttribute(attrName, attrValue);
415        }
416    }
417
418}