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