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.io.IOException;
021import java.util.Comparator;
022import java.util.List;
023import java.util.Map;
024import java.util.Map.Entry;
025import java.util.TreeMap;
026
027import javax.xml.transform.Result;
028import javax.xml.transform.TransformerConfigurationException;
029import javax.xml.transform.TransformerFactory;
030import javax.xml.transform.sax.SAXTransformerFactory;
031import javax.xml.transform.sax.TransformerHandler;
032import javax.xml.transform.stream.StreamResult;
033
034import org.apache.cocoon.xml.AttributesImpl;
035import org.apache.cocoon.xml.XMLUtils;
036import org.apache.commons.io.FileUtils;
037import org.apache.commons.lang3.StringUtils;
038import org.xml.sax.SAXException;
039
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.orgunit.OrgUnit;
046import org.ametys.odf.program.Container;
047import org.ametys.odf.program.Program;
048import org.ametys.odf.program.SubProgram;
049import org.ametys.plugins.repository.AmetysObjectIterable;
050import org.ametys.plugins.repository.AmetysObjectIterator;
051import org.ametys.plugins.repository.AmetysRepositoryException;
052import org.ametys.plugins.repository.UnknownAmetysObjectException;
053import org.ametys.plugins.repository.metadata.UnknownMetadataException;
054import org.ametys.runtime.i18n.I18nizableText;
055import org.ametys.runtime.parameter.ParameterHelper;
056
057/**
058 * Pilotage report for Apogée.
059 */
060public class ApogeeReport extends AbstractReport
061{
062    private int _order;
063    
064    @Override
065    protected void _launchByOrgUnit(String uaiCode, String catalog, String lang) throws Exception
066    {
067        _writeApogeeReport(uaiCode, catalog, lang);
068    }
069
070    @Override
071    protected String getType()
072    {
073        return "apogee";
074    }
075    
076    @Override
077    protected String getReportName()
078    {
079        return _i18nUtils.translate(new I18nizableText("plugin.odf-pilotage", "PLUGINS_ODF_PILOTAGE_APOGEE_LABEL"));
080    }
081    
082    /**
083     * Create the Apogee report for one organization unit 
084     * @param uaiCode The UAI code of the org unit
085     * @param catalog The catalog
086     * @param lang The language
087     * @throws SAXException if an error occurs while saxing
088     * @throws IOException if an I/O exception occurs
089     * @throws TransformerConfigurationException if the SAX transformer is not configured properly
090     */
091    protected void _writeApogeeReport(String uaiCode, String catalog, String lang) throws SAXException, IOException, TransformerConfigurationException
092    {
093        SAXTransformerFactory factory = (SAXTransformerFactory) TransformerFactory.newInstance();
094        String fileName = catalog + "-" + lang + "-" + _reportHelper.getAccronymOrUaiCode(uaiCode) + "-" + "[" + getType() + "-" + _currentFormattedDate + "]";
095        
096        // Delete old files
097        File xmlFile = new File(_tmpFolder, fileName + ".xml");
098        FileUtils.deleteQuietly(xmlFile);
099
100        TransformerHandler handler = factory.newTransformerHandler();
101        
102        // Write XML file
103        try (FileOutputStream fos = new FileOutputStream(xmlFile))
104        {
105            try
106            {
107                // Prepare the transformation
108                Result result = new StreamResult(fos);
109                handler.setResult(result);
110                handler.startDocument();
111            
112                AttributesImpl attrs = new AttributesImpl();
113                attrs.addCDATAAttribute("type", getType());
114                attrs.addCDATAAttribute("date", _reportHelper.getReadableCurrentDate());
115                XMLUtils.startElement(handler, "report", attrs);
116                
117                // SAX tree
118                _generateReport(handler, uaiCode, lang, catalog);
119            }
120            finally
121            {
122                XMLUtils.endElement(handler, "report");
123                handler.endDocument();
124            }
125        }
126        
127        // Convert the report to HTML-XLS
128        convertReport(_tmpFolder, fileName + ".xls", xmlFile);
129    }
130    
131    /**
132     * SAX the XML of the report
133     * @param handler the transformer handler 
134     * @param uaiCode the uai code of the organization unit to process
135     * @param lang the lang of the programs
136     * @param catalog the catalog of the programs
137     * @throws SAXException if an error occurs while generating the SAX events
138     */
139    private void _generateReport(TransformerHandler handler, String uaiCode, String lang, String catalog) throws SAXException
140    {
141        if (StringUtils.isEmpty(uaiCode))
142        {
143            throw new IllegalArgumentException("Cannot process the Apogee report without the uai code.The processing of the Apogee report is aborted.");
144        }
145        
146        try
147        {
148            _reportHelper.saxNaturesEnseignement(handler, getLogger());
149            
150            Map<Program, Object> contentsTree = _getStructure(uaiCode, lang, catalog);
151            if (contentsTree.size() > 0)
152            {
153                _order = 1;
154                _saxTree(handler, contentsTree);
155            }
156        }
157        catch (Exception e)
158        {
159            getLogger().error("An error occurred while processing the apogee report. Its generation has been aborted.", e);
160        }
161    }
162
163    /**
164     * Generate the data structure that will be used to create the report
165     * @param uaiCode the uai code of the organization unit
166     * @param lang the lang of programs
167     * @param catalog the catalog of programs
168     * @return The structure
169     */
170    private Map<Program, Object> _getStructure(String uaiCode, String lang, String catalog)
171    {
172        AmetysObjectIterable<OrgUnit> orgUnits = _reportHelper.getRootOrgUnitsByUaiCode(uaiCode);
173        AmetysObjectIterator<OrgUnit> orgUnitsIterator = orgUnits.iterator();
174        if (!orgUnitsIterator.hasNext())
175        {
176            throw new IllegalArgumentException("Unable to find any organization unit with the uai code : " + uaiCode + ".The processing of the Apogee report is aborted.");
177        }
178        
179        Map<Program, Object> programTree = new TreeMap<>(new ProgramTitleComparator());
180        
181        OrgUnit rootOrgUnit = orgUnitsIterator.next();
182        // On ne récupère que les composantes, enfant direct du root org unit, et on ignore les départements.
183        if (rootOrgUnit.getParentOrgUnit() != null && rootOrgUnit.getParentOrgUnit().getParentOrgUnit() == null)
184        {
185            List<String> orgUnitIds = _reportHelper.getSubOrgUnits(rootOrgUnit);
186            
187            // Chercher les programmes concernés par la composante sélectionnée et ses enfants
188            for (String orgUnitId : orgUnitIds)
189            {
190                AmetysObjectIterable<Program> programs = _reportHelper.getProgramsByOrgUnitId(orgUnitId, lang, catalog);
191                AmetysObjectIterator<Program> programsIterator = programs.iterator();
192                while (programsIterator.hasNext())
193                {
194                    Program program = programsIterator.next();
195                    Map<ProgramItem, Object> courses = _reportHelper.getCoursesFromContent(program);
196                    if (courses != null)
197                    {
198                        programTree.put(program, courses);
199                    }
200                }
201            }
202        }
203        
204        return programTree;
205    }
206
207    /**
208     * Sax the information related to the courses of the tree
209     * @param handler the transformer handler
210     * @param programTree the program tree to sax
211     * @throws SAXException if an error occurs when SAXing
212     */
213    @SuppressWarnings("unchecked")
214    private void _saxTree(TransformerHandler handler, Map<Program, Object> programTree) throws SAXException
215    {
216        for (Entry<Program, Object> programEntry : programTree.entrySet())
217        {
218            if (programEntry.getValue() != null && programEntry.getValue() instanceof Map<?, ?>)
219            {
220                _saxCourseFromTree(handler, (Map<ProgramItem, Object>) programEntry.getValue(), programEntry.getKey());
221            }
222        }
223    }
224
225
226    private void _saxCourseFromTree(TransformerHandler handler, Map<ProgramItem, Object> programTree, Program program) throws SAXException
227    {
228        _saxCourseFromTree(handler, programTree, program, null, null, null, null, null, null, 1, false, "");
229    }
230
231    private void _saxCourseFromTree(TransformerHandler handler, Map<ProgramItem, Object> tree, Program program, SubProgram subprogram, Container containerYear, Container containerSemester, CourseList list, Integer listPosition, Course parentCourse, int level, boolean lastLevel, String courseHierarchy) throws SAXException
232    {
233        int courseListPosition = 0;
234        for (Entry<ProgramItem, Object> entry : tree.entrySet())
235        {
236            ProgramItem child = entry.getKey();
237            @SuppressWarnings("unchecked")
238            Map<ProgramItem, Object> subTree = (Map<ProgramItem, Object>) entry.getValue();
239            
240            if (child instanceof Course)
241            {
242                Course childCourse = (Course) child;
243                String path = courseHierarchy +  " > " + childCourse.getTitle();
244                _saxCourse(handler, program, subprogram, containerYear, containerSemester, list, listPosition, (Course) child, parentCourse, level, subTree == null ? true : false, path);
245                
246                if (subTree != null)
247                {
248                    _saxCourseFromTree(handler, subTree, program, subprogram, containerYear, containerSemester, list, listPosition, childCourse, level + 1, lastLevel, path);
249                }
250            }
251            
252            if (subTree != null)
253            {
254                if (child instanceof Program)
255                {
256                    _saxCourseFromTree(handler, subTree, (Program) child, subprogram, containerYear, containerSemester, list, listPosition, parentCourse, level, lastLevel, courseHierarchy);
257                }
258                else if (child instanceof Container)
259                {
260                    Container container = (Container) child;
261                    String containerNature = _refTableHelper.getItemCode(container.getNature());
262                    
263                    if ("annee".equals(containerNature))
264                    {
265                        _saxCourseFromTree(handler, subTree, program, subprogram, container, containerSemester, list, listPosition, parentCourse, level, lastLevel, courseHierarchy);
266                    }
267                    else if ("semestre".equals(containerNature))
268                    {
269                        _saxCourseFromTree(handler, subTree, program, subprogram, containerYear, container, list, listPosition, parentCourse, level, lastLevel, courseHierarchy);
270                    }
271                    else
272                    {
273                        _saxCourseFromTree(handler, subTree, program, subprogram, containerYear, containerSemester, list, listPosition, parentCourse, level, lastLevel, courseHierarchy);
274                    }
275                }
276                else if (child instanceof SubProgram)
277                {
278                    _saxCourseFromTree(handler, subTree, program, (SubProgram) child, containerYear, containerSemester, list, listPosition, parentCourse, level, lastLevel, courseHierarchy);
279                }
280                else if (child instanceof CourseList)
281                {
282                    courseListPosition++;
283                    CourseList childCourseList = (CourseList) child;
284                    String path = courseHierarchy.equals("") ? childCourseList.getTitle() : courseHierarchy +  " > " + childCourseList.getTitle();
285                    _saxCourseFromTree(handler, subTree, program, subprogram, containerYear, containerSemester, (CourseList) child, courseListPosition, parentCourse, level, lastLevel, path);
286                }
287            }
288        }
289    }
290    
291    private String _getHierarchy(Program program, SubProgram subprogram, Container containerYear, Container containerSemester, String courseHierarchy)
292    {
293        String hierarchy = program.getTitle();
294        if (subprogram != null)
295        {
296            hierarchy += " > " + subprogram.getTitle();
297        }
298        
299        if (containerYear != null)
300        {
301            hierarchy += " > " + containerYear.getTitle();
302        }
303        
304        if (containerSemester != null)
305        {
306            hierarchy += " > " + containerSemester.getTitle();
307        }
308            
309        hierarchy += " > " + courseHierarchy;
310        
311        return hierarchy;
312    }
313
314    private void _saxCourse(TransformerHandler handler, Program program, SubProgram subprogram, Container containerYear, Container containerSemester, CourseList list, Integer listPosition, Course course, Course parentCourse, int level, boolean lastLevel, String courseHierarchy) throws SAXException
315    {
316        if (course != null)
317        {
318            String hierarchy = _getHierarchy(program, subprogram, containerYear, containerSemester, courseHierarchy);
319            
320            XMLUtils.startElement(handler, "course");
321            
322            // Ordre
323            XMLUtils.createElement(handler, "ordre", String.valueOf(_order));
324            
325            // Composante
326            _saxOrgUnits(handler, program);
327            
328            // Formation
329            XMLUtils.createElement(handler, "formation", program.getTitle());
330            XMLUtils.createElement(handler, "formationCode", program.getCode());
331            
332            _saxSubProgram(handler, subprogram);
333            
334            _saxContainer(handler, containerYear, parentCourse);
335            
336            _saxCourseList(handler, list, listPosition);
337            
338            // A des fils
339            boolean aDesFils = course.hasCourseLists();
340            XMLUtils.createElement(handler, "aDesFils", aDesFils ? "X" : "");
341
342            long courseListsSize = course.getParentCourseLists().size();
343            
344            // Partagé
345            XMLUtils.createElement(handler, "partage", courseListsSize > 1 ? "X" : "");
346
347            // Nb occurences
348            XMLUtils.createElement(handler, "occurences", _reportHelper.formatNumberToSax(courseListsSize));
349            
350            Container etape = _getEtapePorteuse(course, hierarchy);
351            
352            String porte = _getPorte(etape, containerYear);
353           
354            // Porté ("X" si l'ELP est porté par la formation (Etape porteuse=COD_ETP), vide sinon)
355            XMLUtils.createElement(handler, "porte", porte);
356            
357            // Niveau
358            XMLUtils.createElement(handler, "niveau", "niv" + level);
359
360            // Date de création
361            XMLUtils.createElement(handler, "creationDate", ParameterHelper.valueToString(course.getCreationDate()));
362
363            // Code Apogée
364            XMLUtils.createElement(handler, "codeApogee", course.getMetadataHolder().getString("elpCode", ""));
365            
366            // Nature de l'élément
367            XMLUtils.createElement(handler, "nature", _refTableHelper.getItemCode(course.getCourseType()));
368
369            // Libellé court
370            XMLUtils.createElement(handler, "libelleCourt", course.getMetadataHolder().getString("shortLabel", ""));
371            
372            // Libellé
373            XMLUtils.createElement(handler, "libelle", course.getTitle());
374            
375            // Code Ametys (ELP)
376            XMLUtils.createElement(handler, "elpCode", course.getCode());
377            
378            // Lieu
379            _reportHelper.saxMultipleEnumeratedMetadata(handler, course, "campus", "campus");
380
381            // Crédits ECTS
382            XMLUtils.createElement(handler, "ects", String.valueOf(course.getEcts()));
383            
384            String teachingActivity = _refTableHelper.getItemCode(course.getTeachingActivity());
385            String stage = teachingActivity.equals("SA") ? "X" : "";
386            
387            // Element stage
388            XMLUtils.createElement(handler, "stage", stage);
389            
390            // Code semestre
391            String periode = _refTableHelper.getItemCode(course.getTeachingTerm());
392            XMLUtils.createElement(handler, "periode", "s10".equals(periode) ? "s0" : periode);
393            
394            OrgUnit orgUnit = _getOrgUnit(course, hierarchy);
395
396            // Code composante
397            XMLUtils.createElement(handler, "codeComposante", orgUnit != null ? orgUnit.getMetadataHolder().getString("codCmp", "") : "");
398            
399            // Code CIP
400            XMLUtils.createElement(handler, "codeCIP", orgUnit != null ? orgUnit.getMetadataHolder().getString("codCipApogee", "") : "");
401            
402            // Code ANU
403            long codeAnu = course.getMetadataHolder().getLong("CodeAnu", 0);
404            XMLUtils.createElement(handler, "CodeAnu", codeAnu > 0 ? String.valueOf(codeAnu) : StringUtils.EMPTY);
405            
406            // Calcul des charges 
407            XMLUtils.createElement(handler, "calculCharges", aDesFils ? "" : "X");
408            
409            // Heures d'enseignement
410            for (CoursePart coursePart : course.getCourseParts())
411            {
412                AttributesImpl attr = new AttributesImpl();
413                attr.addCDATAAttribute("nature", coursePart.getNature());
414                XMLUtils.createElement(handler, "volumeHoraire", attr, String.valueOf(coursePart.getNumberOfHours()));
415            }
416            
417            // Etape porteuse
418            XMLUtils.createElement(handler, "etapePorteuse", lastLevel && etape != null ? etape.getMetadataHolder().getString("etpCode", "") : "");
419            
420            // VET Etape porteuse
421            XMLUtils.createElement(handler, "vetEtapePorteuse", lastLevel && etape != null ? etape.getMetadataHolder().getString("vrsEtpCode", "") : "");
422            
423            // Discipline
424            XMLUtils.createElement(handler, "discipline", course.getMetadataHolder().getString("discipline", ""));
425            
426            saxAdditionalCourseData(handler, course);
427            
428            XMLUtils.endElement(handler, "course");
429            
430            _order++;
431        }
432    }
433    
434    /**
435     * Sax a additional data of a {@link Course}.
436     * @param handler The handler
437     * @param course The course to SAX
438     * @throws UnknownMetadataException if an error occurs
439     * @throws AmetysRepositoryException if an error occurs
440     * @throws SAXException if an error occurs
441     */
442    protected void saxAdditionalCourseData(TransformerHandler handler, Course course) throws UnknownMetadataException, AmetysRepositoryException, SAXException
443    {
444        // Do nothing by default
445    }
446    
447    private void _saxContainer(TransformerHandler handler, Container container, Course parentCourse) throws UnknownMetadataException, AmetysRepositoryException, SAXException
448    {
449        if (container != null)
450        {
451            // Année
452            XMLUtils.createElement(handler, "annee", container.getTitle());
453            // COD_ETP
454            XMLUtils.createElement(handler, "COD_ETP", container.getMetadataHolder().getString("etpCode", ""));
455            // COD_VRS_ETP
456            XMLUtils.createElement(handler, "COD_VRS_ETP", container.getMetadataHolder().getString("vrsEtpCode", ""));
457            // Code Ametys ELP père 
458            XMLUtils.createElement(handler, "codeELPPere", parentCourse != null ? parentCourse.getCode() : "");
459        }
460    }
461    
462    private void _saxSubProgram(TransformerHandler handler, SubProgram subprogram) throws UnknownMetadataException, AmetysRepositoryException, SAXException
463    {
464        if (subprogram != null)
465        {
466            // Parcours 
467            XMLUtils.createElement(handler, "parcours", subprogram.getTitle());
468            XMLUtils.createElement(handler, "parcoursCode", subprogram.getCode());
469        }
470    }
471    
472    private void _saxOrgUnits(TransformerHandler handler, Program program) throws SAXException
473    {
474        StringBuilder sb = new StringBuilder();
475        List<String> orgUnits = program.getOrgUnits();
476        for (String orgUnitId : orgUnits)
477        {
478            try
479            {
480                OrgUnit orgUnit = _resolver.resolveById(orgUnitId);
481                if (sb.length() > 0)
482                {
483                    sb.append(", ");
484                }
485                sb.append(orgUnit.getTitle());
486                sb.append(" (");
487                sb.append(orgUnit.getUAICode());
488                sb.append(")");
489            }
490            catch (UnknownAmetysObjectException e)
491            {
492                getLogger().info("La composante référencée par la formation {} ({}) n'a pas été trouvée.", program.getTitle(), program.getCode());
493            }
494        }
495        XMLUtils.createElement(handler, "orgUnit", sb.toString());
496    }
497    
498    private String _getPorte(Container etape, Container containerYear)
499    {
500        String porte = "";
501        if (etape != null && containerYear != null)
502        {
503            String etpCode = containerYear.getMetadataHolder().getString("etpCode", "");
504            if (StringUtils.isNotEmpty(etpCode))
505            {
506                porte = etpCode.equals(etape.getMetadataHolder().getString("etpCode", "")) ? "X" : "";
507            }
508        }
509        return porte;
510    }
511    
512    private OrgUnit _getOrgUnit(Course course, String hierarchy)
513    {
514        OrgUnit orgUnit = null;
515        
516        List<String> courseOrgUnits = course.getOrgUnits();
517        if (!courseOrgUnits.isEmpty())
518        {
519            try
520            {
521                orgUnit = _resolver.resolveById(courseOrgUnits.get(0));
522            }
523            catch (UnknownAmetysObjectException e)
524            {
525                getLogger().info("La composante référencée par l'élément pédagogique {} ({}) n'a pas été trouvée.", hierarchy, course.getCode());
526            }
527            
528            if (courseOrgUnits.size() > 1)
529            {
530                getLogger().warn("L'élément pédagogique {} ({}) référence plus d'une composante.", hierarchy, course.getCode());
531            }
532        }
533        
534        return orgUnit;
535    }
536    
537    private Container _getEtapePorteuse(Course course, String hierarchy)
538    {
539        String etapePorteuse = course.getMetadataHolder().getString("etapePorteuse", "");
540        Container etape = null;
541        if (StringUtils.isNotEmpty(etapePorteuse))
542        {
543            try 
544            {
545                etape = _resolver.resolveById(etapePorteuse);
546            }
547            catch (UnknownAmetysObjectException e)
548            {
549                getLogger().info("L'étape porteuse référencée par l'élément pédagogique {} ({}) n'a pas été trouvée.", hierarchy, course.getCode());
550            }
551        }
552        
553        return etape;
554    }
555    
556    private void _saxCourseList(TransformerHandler handler, CourseList list, Integer position) throws SAXException
557    {
558        if (list != null)
559        {
560            XMLUtils.createElement(handler, "list", "Lst" + position);
561            _saxChoiceList(handler, list);
562        }
563    }
564    
565    private void _saxChoiceList(TransformerHandler handler, CourseList list) throws SAXException
566    {
567        // Type
568        ChoiceType typeList = list.getType();
569        if (typeList != null)
570        {
571            if (typeList.name() != null && (typeList.name().equals(ChoiceType.CHOICE.toString()) || typeList.name().equals(ChoiceType.MANDATORY.toString()) || typeList.name().equals(ChoiceType.OPTIONAL.toString())))
572            {
573                String typeListAsString = "";
574                if (typeList.name().equals(ChoiceType.CHOICE.toString()))
575                {
576                    typeListAsString = "X";
577                }
578                else if (typeList.name().equals(ChoiceType.MANDATORY.toString()))
579                {
580                    typeListAsString = "O";
581                }
582                else if (typeList.name().equals(ChoiceType.OPTIONAL.toString()))
583                {
584                    typeListAsString = "F";
585                }
586                
587                XMLUtils.createElement(handler, "typeList", typeListAsString);
588            }
589            
590            // Min-Max (Ne remplir que pour le type "CHOICE" (ne mettre que le min))
591            if (typeList.name() != null && typeList.name().equals(ChoiceType.CHOICE.toString()))
592            {
593                XMLUtils.createElement(handler, "minmax", _reportHelper.formatNumberToSax(list.getMinNumberOfCourses()));
594            }
595        }
596    }
597    
598    class ProgramTitleComparator implements Comparator<Program>
599    {
600        @Override
601        public int compare(Program p1, Program p2)
602        {
603            
604            return p1.getTitle().compareTo(p2.getTitle());
605        }
606    }
607}