001/*
002 *  Copyright 2018 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.plugins.odfpilotage.report.impl;
017
018import java.io.File;
019import java.io.FileOutputStream;
020import java.util.LinkedHashMap;
021import java.util.Map;
022import java.util.Map.Entry;
023import java.util.Optional;
024import java.util.Set;
025
026import javax.xml.transform.Result;
027import javax.xml.transform.TransformerFactory;
028import javax.xml.transform.sax.SAXTransformerFactory;
029import javax.xml.transform.sax.TransformerHandler;
030import javax.xml.transform.stream.StreamResult;
031
032import org.apache.cocoon.xml.AttributesImpl;
033import org.apache.cocoon.xml.XMLUtils;
034import org.apache.commons.io.FileUtils;
035import org.apache.commons.lang3.StringUtils;
036import org.xml.sax.SAXException;
037
038import org.ametys.cms.data.ContentValue;
039import org.ametys.cms.repository.ModifiableContent;
040import org.ametys.odf.ProgramItem;
041import org.ametys.odf.course.Course;
042import org.ametys.odf.courselist.CourseList;
043import org.ametys.odf.courselist.CourseList.ChoiceType;
044import org.ametys.odf.coursepart.CoursePart;
045import org.ametys.odf.program.Container;
046import org.ametys.odf.program.Program;
047import org.ametys.odf.program.SubProgram;
048import org.ametys.plugins.repository.UnknownAmetysObjectException;
049import org.ametys.plugins.repository.jcr.NameHelper;
050
051/**
052 * Class to generate course extract as DOC.
053 */
054public class MaquetteExtract extends AbstractExtract
055{
056    @Override
057    protected String getType()
058    {
059        return "maquette";
060    }
061    
062    @Override
063    protected Set<String> getSupportedOutputFormats()
064    {
065        return Set.of(OUTPUT_FORMAT_DOC, OUTPUT_FORMAT_XLS);
066    }
067    
068    @Override
069    protected void _saxProgram(Program program)
070    {
071        Map<ProgramItem, Object> programTree = _reportHelper.getCoursesFromContent(program);
072        if (programTree != null)
073        {
074            _saxProgramsTree(programTree, program);
075        }
076        else
077        {
078            getLogger().info("La formation '{}' ne contient pas d'éléments pédagogiques", program.getTitle());
079        }
080    }
081
082    /**
083     * Get the report filename for a given program
084     * @param program The program
085     * @return the file name
086     */
087    private String _getReportFileName(Program program)
088    {
089        StringBuilder sb = new StringBuilder();
090
091        sb.append("maquette-");
092        
093        // Catalog
094        sb.append(program.getCatalog());
095        sb.append("-");
096        
097        // Lang
098        sb.append(program.getLanguage());
099        sb.append("-");
100
101        // Mention or title
102        String mentionId = program.getMention();
103        if (StringUtils.isBlank(mentionId))
104        {
105            sb.append(program.getTitle());
106        }
107        else
108        {
109            sb.append(_refTableHelper.getItemLabel(mentionId, program.getLanguage()));
110        }
111
112        // Code Ametys
113        String code = program.getCode();
114        if (StringUtils.isNotBlank(code))
115        {
116            sb.append("-");
117            sb.append(code);
118        }
119        
120        // Date
121        sb.append("-");
122        sb.append(_currentFormattedDate);
123        
124        return NameHelper.filterName(sb.toString());
125    }
126    
127    private void _saxProgramsTree(Map<ProgramItem, Object>  programTree, Program program)
128    {
129        SAXTransformerFactory factory = (SAXTransformerFactory) TransformerFactory.newInstance();
130        
131        String fileName = _getReportFileName(program);
132        
133        File xmlFile = new File(_tmpFolder, fileName + ".xml");
134        FileUtils.deleteQuietly(xmlFile);
135        
136        try (FileOutputStream fos = new FileOutputStream(xmlFile))
137        {
138            TransformerHandler handler = factory.newTransformerHandler();
139            
140            Result result = new StreamResult(fos);
141            handler.setResult(result);
142            
143            handler.startDocument();
144            
145            AttributesImpl attrs = new AttributesImpl();
146            attrs.addCDATAAttribute("xmlns:i18n", "http://apache.org/cocoon/i18n/2.1");
147            attrs.addCDATAAttribute("type", getType());
148            attrs.addCDATAAttribute("date", _reportHelper.getReadableCurrentDate());
149            XMLUtils.startElement(handler, "report", attrs);
150            
151            _saxReport(handler, program, programTree);
152            
153            XMLUtils.endElement(handler, "report");
154            handler.endDocument();
155
156            // Convert the report to configured output format
157            convertReport(_tmpFolder, fileName, xmlFile);
158        }
159        catch (Exception e)
160        {
161            getLogger().error("An error occured while generating 'Maquette' extract for program '{}' ({})", program.getTitle(), program.getCode(), e);
162        }
163        finally
164        {
165            FileUtils.deleteQuietly(xmlFile);
166        }
167    }
168    
169    private void _saxReport(TransformerHandler handler, Program program, Map<ProgramItem, Object>  programTree) throws SAXException
170    {
171        _reportHelper.saxNaturesEnseignement(handler, getLogger());
172        
173        AttributesImpl attr = new AttributesImpl();
174
175        attr.addCDATAAttribute("formation", program.getTitle());
176        attr.addCDATAAttribute("COD_DIP", _reportHelper.getCodeDIP(program));
177        attr.addCDATAAttribute("COD_VDI", _reportHelper.getCodeVRSVDI(program));
178
179        XMLUtils.startElement(handler, "program", attr);
180
181        _saxProgramTree(handler, programTree);
182
183        XMLUtils.endElement(handler, "program");
184    }
185
186    @SuppressWarnings("unchecked")
187    private void _saxProgramTree(TransformerHandler handler, Map<ProgramItem, Object> programTree) throws SAXException
188    {
189        if (programTree != null)
190        {
191            for (Entry<ProgramItem, Object> node : programTree.entrySet())
192            {
193                if (node.getKey() instanceof SubProgram)
194                {
195                    _saxSubProgram(handler, (SubProgram) node.getKey(), (Map<ProgramItem, Object>) node.getValue());
196                }
197                else if (node.getKey() instanceof Container)
198                {
199                    _saxContainer(handler, (Container) node.getKey(), (Map<ProgramItem, Object>) node.getValue());
200                }
201                else if (node.getKey() instanceof Course)
202                {
203                    _saxCourse(handler, (Course) node.getKey(), (Map<ProgramItem, Object>) node.getValue());
204                }
205                else if (node.getKey() instanceof CourseList)
206                {
207                    if (_saxProgramTreeOnCourseList(handler, programTree, node))
208                    {
209                        break;
210                    }
211                }
212            }
213        }
214    }
215
216    @SuppressWarnings("unchecked")
217    private boolean _saxProgramTreeOnCourseList(TransformerHandler handler, Map<ProgramItem, Object> programTree, Entry<ProgramItem, Object> node) throws SAXException
218    {
219        if (programTree.size() > 1)
220        {
221            Map<ProgramItem, Object> orderedMandatoryLists = new LinkedHashMap<>();
222            Map<ProgramItem, Object> orderedChoiceLists = new LinkedHashMap<>();
223            Map<ProgramItem, Object> orderedOptionalLists = new LinkedHashMap<>();
224            for (Entry<ProgramItem, Object> nodeList : programTree.entrySet())
225            {
226                if (nodeList.getKey() instanceof CourseList)
227                {
228                    CourseList list = (CourseList) nodeList.getKey();
229                    if (list.getType().equals(CourseList.ChoiceType.MANDATORY))
230                    {
231                        orderedMandatoryLists.put(list, nodeList.getValue());
232                    }
233                    else if (list.getType().equals(CourseList.ChoiceType.CHOICE))
234                    {
235                        orderedChoiceLists.put(list, nodeList.getValue());
236                    }
237                    else
238                    {
239                        orderedOptionalLists.put(list, nodeList.getValue());
240                    }
241                }
242                else if (nodeList.getKey() instanceof SubProgram)
243                {
244                    _saxSubProgram(handler, (SubProgram) nodeList.getKey(), (Map<ProgramItem, Object>) nodeList.getValue());
245                }
246                else if (nodeList.getKey() instanceof Container)
247                {
248                    _saxContainer(handler, (Container) nodeList.getKey(), (Map<ProgramItem, Object>) nodeList.getValue());
249                }
250                
251            }
252            
253            for (Entry<ProgramItem, Object> nodeList : orderedMandatoryLists.entrySet())
254            {
255                _saxCourseList(handler, (CourseList) nodeList.getKey(), (Map<ProgramItem, Object>) nodeList.getValue());
256            }
257            for (Entry<ProgramItem, Object> nodeList : orderedChoiceLists.entrySet())
258            {
259                _saxCourseList(handler, (CourseList) nodeList.getKey(), (Map<ProgramItem, Object>) nodeList.getValue());
260            }
261            for (Entry<ProgramItem, Object> nodeList : orderedOptionalLists.entrySet())
262            {
263                _saxCourseList(handler, (CourseList) nodeList.getKey(), (Map<ProgramItem, Object>) nodeList.getValue());
264            }
265            return true;
266        }
267        else
268        {
269            _saxCourseList(handler, (CourseList) node.getKey(), (Map<ProgramItem, Object>) node.getValue());
270        }
271        return false;
272    }
273
274    private void _saxSubProgram(TransformerHandler handler, SubProgram subprogram, Map<ProgramItem, Object> programTree) throws SAXException
275    {
276        AttributesImpl attr = new AttributesImpl();
277
278        attr.addCDATAAttribute("parcours", subprogram.getTitle());
279
280        XMLUtils.startElement(handler, "subprogram", attr);
281
282        _saxProgramTree(handler, programTree);
283
284        XMLUtils.endElement(handler, "subprogram");
285    }
286
287    private void _saxContainer(TransformerHandler handler, Container container, Map<ProgramItem, Object> programTree) throws SAXException
288    {
289        AttributesImpl attr = new AttributesImpl();
290
291        attr.addCDATAAttribute("id", container.getId());
292        attr.addCDATAAttribute("libelle", container.getTitle());
293
294        String containerNature = _refTableHelper.getItemCode(container.getNature());
295        attr.addCDATAAttribute("type", containerNature);
296        if ("annee".equals(containerNature))
297        {
298            attr.addCDATAAttribute("COD_ETP", container.getValue("etpCode", false, StringUtils.EMPTY));
299        }
300
301        long codeAnu = container.getValue("CodeAnu", false, 0L);
302        if (codeAnu > 0)
303        {
304            attr.addCDATAAttribute("HeaderCodeAnu", "Maquette " + String.valueOf(codeAnu) + "-" + String.valueOf(codeAnu + 1));
305        }
306
307        XMLUtils.startElement(handler, "container", attr);
308
309        _saxProgramTree(handler, programTree);
310
311        XMLUtils.endElement(handler, "container");
312    }
313
314    private void _saxCourse(TransformerHandler handler, Course course, Map<ProgramItem, Object> programTree) throws SAXException
315    {
316        AttributesImpl attr = new AttributesImpl();
317        
318        attr.addCDATAAttribute("CodeAmetysELP", course.getCode());
319        attr.addCDATAAttribute("CodeApogee", course.getValue("elpCode", false, StringUtils.EMPTY));
320        attr.addCDATAAttribute("natureElement", _refTableHelper.getItemCode(course.getCourseType()));
321        attr.addCDATAAttribute("ects", String.valueOf(course.getEcts()));
322        
323        Container containerEtape = Optional.ofNullable((ContentValue) course.getValue("etapePorteuse"))
324                                           .flatMap(contentValue -> _getEtapePorteuseIfExists(contentValue, course))
325                                           .map(Container.class::cast)
326                                           .orElse(null);
327        
328        if (containerEtape != null)
329        {
330            attr.addCDATAAttribute("etapePorteuse", containerEtape.getTitle());
331            attr.addCDATAAttribute("idEtapePorteuse", containerEtape.getId());
332            if (containerEtape.hasValue("etpCode"))
333            {
334                attr.addCDATAAttribute("codeEtapePorteuse", containerEtape.getValue("etpCode"));
335            }
336        }
337        
338        if (course.hasValue("CodeAnu"))
339        {
340            long codeAnu = course.getValue("CodeAnu");
341            attr.addCDATAAttribute("HeaderCodeAnu", "Maquette " + String.valueOf(codeAnu) + "-" + String.valueOf(codeAnu + 1));
342        }
343
344        XMLUtils.startElement(handler, "course", attr);
345
346        String shortLabel = course.getValue("shortLabel");
347        if (StringUtils.isNotBlank(shortLabel))
348        {
349            XMLUtils.createElement(handler, "shortLabel", shortLabel);
350        }
351        XMLUtils.createElement(handler, "libelle", course.getTitle());
352
353        long courseListsSize = course.getParentCourseLists().size();
354        
355        // Partagé
356        XMLUtils.createElement(handler, "partage", courseListsSize > 1 ? "X" : "");
357
358        // Nb occurrences
359        XMLUtils.createElement(handler, "occurrences", _reportHelper.formatNumberToSax(courseListsSize));
360        
361        // Heures d'enseignement
362        for (CoursePart coursePart : course.getCourseParts())
363        {
364            attr = new AttributesImpl();
365            attr.addCDATAAttribute("nature", coursePart.getNature());
366            XMLUtils.createElement(handler, "volumeHoraire", attr, String.valueOf(coursePart.getNumberOfHours()));
367        }
368        
369        _saxProgramTree(handler, programTree);
370        
371        XMLUtils.endElement(handler, "course");
372
373    }
374    
375    private Optional<ModifiableContent> _getEtapePorteuseIfExists(ContentValue etapePorteuse, Course course)
376    {
377        try
378        {
379            return Optional.ofNullable(etapePorteuse.getContent());
380        }
381        catch (UnknownAmetysObjectException e)
382        {
383            getLogger().error("Course {} {}", course.getId(), e.getMessage(), e);
384            return Optional.empty();
385        }
386    }
387
388    private void _saxCourseList(TransformerHandler handler, CourseList courseList, Map<ProgramItem, Object> programTree) throws SAXException
389    {
390        AttributesImpl attr = new AttributesImpl();
391        ChoiceType typeList = courseList.getType();
392        if (typeList.name().equals(ChoiceType.CHOICE.toString()) && courseList.getMinNumberOfCourses() > 0)
393        {
394            attr.addCDATAAttribute("choice", String.valueOf(courseList.getMinNumberOfCourses()));
395        }
396        else if (typeList.name().equals(ChoiceType.OPTIONAL.toString()))
397        {
398            attr.addCDATAAttribute("optional", "true"); 
399        }
400        XMLUtils.startElement(handler, "courselist", attr);
401
402        _saxProgramTree(handler, programTree);
403
404        XMLUtils.endElement(handler, "courselist");
405    }
406}