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