001/*
002 *  Copyright 2022 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 */
016
017package org.ametys.plugins.odfsync.pegase.ws.structure;
018
019import java.io.IOException;
020import java.math.BigDecimal;
021import java.util.HashMap;
022import java.util.HashSet;
023import java.util.List;
024import java.util.Map;
025import java.util.Optional;
026import java.util.Set;
027import java.util.regex.Pattern;
028import java.util.stream.Collectors;
029import java.util.stream.Stream;
030
031import org.apache.avalon.framework.activity.Initializable;
032import org.apache.avalon.framework.component.Component;
033import org.apache.avalon.framework.service.ServiceException;
034import org.apache.avalon.framework.service.ServiceManager;
035import org.apache.commons.lang3.StringUtils;
036
037import org.ametys.cms.data.ContentValue;
038import org.ametys.cms.data.RichText;
039import org.ametys.cms.data.RichTextHelper;
040import org.ametys.cms.repository.Content;
041import org.ametys.odf.ODFHelper;
042import org.ametys.odf.ProgramItem;
043import org.ametys.odf.course.Course;
044import org.ametys.odf.course.CourseFactory;
045import org.ametys.odf.courselist.CourseList;
046import org.ametys.odf.enumeration.OdfReferenceTableEntry;
047import org.ametys.odf.enumeration.OdfReferenceTableHelper;
048import org.ametys.odf.program.AbstractProgram;
049import org.ametys.odf.program.Container;
050import org.ametys.odf.program.Program;
051import org.ametys.odf.program.ProgramFactory;
052import org.ametys.odf.program.ProgramPart;
053import org.ametys.odf.program.SubProgram;
054import org.ametys.plugins.odfsync.export.AbstractExportStructure;
055import org.ametys.plugins.odfsync.export.ExportReport;
056import org.ametys.plugins.odfsync.export.ExportReport.ExportStatus;
057import org.ametys.plugins.odfsync.export.ExportReport.ProblemTypes;
058import org.ametys.plugins.odfsync.pegase.ws.PegaseApiManager;
059import org.ametys.plugins.odfsync.pegase.ws.PegaseExportException;
060import org.ametys.runtime.config.Config;
061import org.ametys.runtime.i18n.I18nizableText;
062import org.ametys.runtime.model.ElementDefinition;
063
064import fr.pcscol.pegase.cof.ApiException;
065import fr.pcscol.pegase.cof.model.Enfant;
066import fr.pcscol.pegase.cof.model.EnfantDetails;
067import fr.pcscol.pegase.cof.model.Formation;
068import fr.pcscol.pegase.cof.model.FormationRef;
069import fr.pcscol.pegase.cof.model.FormationTypeFormation;
070import fr.pcscol.pegase.cof.model.Groupement;
071import fr.pcscol.pegase.cof.model.ObjetFormation;
072import fr.pcscol.pegase.cof.model.ObjetFormationType;
073import fr.pcscol.pegase.cof.model.ObjetMaquette;
074import fr.pcscol.pegase.cof.model.Pageable;
075import fr.pcscol.pegase.cof.model.PagedObjetMaquette;
076import fr.pcscol.pegase.cof.model.Ref;
077import fr.pcscol.pegase.cof.model.ValeurNum;
078
079/**
080 * The structure to export the program in Pegase
081 */
082public class PegaseProgramStructure extends AbstractExportStructure implements Component, Initializable
083{
084    /** Role */
085    public static final String ROLE = PegaseProgramStructure.class.getName();
086    
087    /** The attribute name for the code Pégase */
088    public static final String CODE_PEGASE_ATTRIBUTE_NAME = "codePegase";
089    
090    /* Constants */
091    private static final String __PEGASE_SYNC_CODE = "pegaseSyncCode";
092    private static final Pattern __PROGRAM_CODE_PATTERN = Pattern.compile("^[A-Z0-9\\-]{3,25}(/[1-9][0-9]*)?$");
093    private static final Pattern __DEFAULT_CODE_PATTERN = Pattern.compile("^[A-Z0-9\\-]{3,25}$");
094    private static final String __CODE_FORMATION_DIPLOMANTE = "0";
095    private static final Map<String, Set<String>> __MANDATORY_ATTRIBUTES_BY_CONTENT_TYPE = new HashMap<>();
096    static 
097    {
098        __MANDATORY_ATTRIBUTES_BY_CONTENT_TYPE.put(
099                ProgramFactory.PROGRAM_CONTENT_TYPE,
100                Set.of(
101                        AbstractProgram.DOMAIN,
102                        AbstractProgram.EDUCATION_KIND,
103                        AbstractProgram.DEGREE,
104                        AbstractProgram.LEVEL,
105                        AbstractProgram.RNCP_LEVEL
106                )
107        );
108        __MANDATORY_ATTRIBUTES_BY_CONTENT_TYPE.put(
109                CourseFactory.COURSE_CONTENT_TYPE,
110                Set.of(Course.COURSE_TYPE)
111        );
112        __MANDATORY_ATTRIBUTES_BY_CONTENT_TYPE.put(
113                OdfReferenceTableHelper.DEGREE,
114                Set.of(
115                        "degreeNature",
116                        "cursus"
117                )
118        );
119    }
120    
121    /* Components */
122    private PegaseApiManager _pegaseApiManager;
123    private ODFHelper _odfHelper;
124    private RichTextHelper _richTextHelper;
125
126    /* Pégase configuration */
127    private boolean _isActive;
128    private String _structureCode;
129    private boolean _trustAmetys;
130    
131    @Override
132    public void service(ServiceManager manager) throws ServiceException
133    {
134        super.service(manager);
135        
136        _odfHelper = (ODFHelper) manager.lookup(ODFHelper.ROLE);
137        _richTextHelper = (RichTextHelper) manager.lookup(RichTextHelper.ROLE);
138        _pegaseApiManager = (PegaseApiManager) manager.lookup(PegaseApiManager.ROLE);
139    }
140    
141    public void initialize() throws Exception
142    {
143        _isActive = Config.getInstance().getValue("pegase.activate", true, false);
144        if (_isActive)
145        {
146            _structureCode = Config.getInstance().getValue("pegase.structure.code");
147            _trustAmetys = Config.getInstance().getValue("pegase.trust", true, false);
148        }
149    }
150    
151    private String _getPegaseCodeOrCode(Content content)
152    {
153        String code = content.getValue(CODE_PEGASE_ATTRIBUTE_NAME);
154        if (StringUtils.isEmpty(code))
155        {
156            code = content.getValue(OdfReferenceTableEntry.CODE);
157        }
158        return code;
159    }
160
161    private String _getPegaseCodeOrCodeOfAttribute(Content content, String attributeName)
162    {
163        return _getContentValue(content, attributeName)
164            .map(this::_getPegaseCodeOrCode)
165            .orElse(null);
166    }
167
168    private void _addMandatoryDataPathAndReport(Content content, String dataPath, ExportReport report)
169    {
170        I18nizableText invalidMessage = new I18nizableText(
171            "plugin.odf-sync",
172            "PLUGINS_ODF_SYNC_EXPORT_PEGASE_MANDATORY_FIELD",
173            Map.of("fieldName", content.getDefinition(dataPath).getLabel())
174        );
175        report.addInvalidDataPath(content, invalidMessage);
176    }
177    
178    private Optional<Content> _getContentValue(Content content, String attributeName)
179    {
180        return Optional.of(attributeName)
181            .map(content::<ContentValue>getValue)
182            .flatMap(ContentValue::getContentIfExists);
183    }
184    
185    private Stream<Content> _getContentValues(Content content, String attributeName)
186    {
187        return Optional.of(attributeName)
188            .map(content::<ContentValue[]>getValue)
189            .map(Stream::of)
190            .orElseGet(Stream::of)
191            .map(ContentValue::getContentIfExists)
192            .filter(Optional::isPresent)
193            .map(Optional::get);
194    }
195    
196    /**
197     * Checks if the program has all the required fields, their Pegase correspondence and that the program has a valid structure
198     * @param program the program
199     * @param report the Pegase export report
200     */
201    @Override
202    public void checkProgram(Program program, ExportReport report)
203    {
204        if (!_isActive)
205        {
206            throw new UnsupportedOperationException("Pégase is not active in the configuration, you cannot check the program for the export.");
207        }
208        
209        try
210        {
211            _checkAttributes(program, report);
212            
213            if (program.hasValue(__PEGASE_SYNC_CODE))
214            {
215                String codeAndVersion = program.getValue(__PEGASE_SYNC_CODE);
216                Map<String, Object> dataCodeAndVersion = _getCodeAndVersion(codeAndVersion);
217                String code = (String) dataCodeAndVersion.get("code");
218                String versionString = (String) dataCodeAndVersion.get("version");
219                
220                // If the code is not missing, check if it is valid
221                if (StringUtils.isNotBlank(versionString))
222                {
223                    BigDecimal version = new BigDecimal(versionString);
224                    _checkProgramVersionCoherence(report, code, version);
225                }
226            }
227            
228            // If the education kind has a Pegase correspondence and is of code "0" (diplomante)
229            if (__CODE_FORMATION_DIPLOMANTE.equals(_getPegaseCodeOrCodeOfAttribute(program, AbstractProgram.EDUCATION_KIND)))
230            {
231                _checkProgramDiplomanteField(program, report);
232            }
233            
234            _checkChildren(program, report);
235            
236        }
237        catch (IOException e)
238        {
239            report.updateStatus(ExportStatus.ERROR);
240            getLogger().error("Le jeton d'authentification à Pégase n'a pas pu être récupéré", e);
241        }
242    }
243    
244    private void _checkAttributes(Content content, ExportReport report)
245    {
246        if (content instanceof ProgramItem)
247        {
248            if (!content.hasValue(__PEGASE_SYNC_CODE))
249            {
250                _addMandatoryDataPathAndReport(content, __PEGASE_SYNC_CODE, report);
251            }
252            else if (content instanceof Program)
253            {
254                if (!_matches(__PROGRAM_CODE_PATTERN, content, __PEGASE_SYNC_CODE))
255                {
256                    report.addInvalidDataPath(content, new I18nizableText("plugin.odf-sync", "PLUGINS_ODF_SYNC_EXPORT_PEGASE_INVALID_CODE_PROGRAM"));
257                }
258            }
259            else if (!_matches(__DEFAULT_CODE_PATTERN, content, __PEGASE_SYNC_CODE))
260            {
261                report.addInvalidDataPath(content, new I18nizableText("plugin.odf-sync", "PLUGINS_ODF_SYNC_EXPORT_PEGASE_INVALID_CODE_DEFAULT"));
262            }
263        }
264        
265        String contentType = content.getTypes()[0];
266        Set<String> mandatoryAttributes = __MANDATORY_ATTRIBUTES_BY_CONTENT_TYPE.getOrDefault(contentType, Set.of());
267        for (String attribute : mandatoryAttributes)
268        {
269            if (!content.hasValue(attribute))
270            {
271                _addMandatoryDataPathAndReport(content, attribute, report);
272            }
273        }
274    }
275    
276    private boolean _matches(Pattern regex, Content content, String attributeName)
277    {
278        return regex.matcher(content.getValue(attributeName)).matches();
279    }
280    
281    private void _checkProgramVersionCoherence(ExportReport report, String code, BigDecimal version) throws IOException
282    {
283        // If the Program already exist in this version
284        ObjetMaquette objetMaquette = _getObjetMaquetteIfAlreadyExists(code, version);
285        
286        Long versionLong = version.longValue();
287        if (objetMaquette != null)
288        {
289            // If it is not of type "FORMATION" or if it is not editable
290            if (!objetMaquette.getType().getCode().equals("FORMATION") || !objetMaquette.getModifiable())
291            {
292                report.setStatus(ExportStatus.NON_EDITABLE_PROGRAM_ALREADY_EXISTS);
293            }
294        }
295        
296        // If nothing was found for this code and version, and the version is not null
297        else
298        {
299            // Get the element for this code without requesting a specific version
300            objetMaquette = _getObjetMaquetteIfAlreadyExists(code);
301            
302            // If something was found and it's a Program
303            if (objetMaquette != null && objetMaquette.getType().getCode().equals("FORMATION"))
304            {
305                // Check if the version is compatible (versionFound == versionRequested or versionFound == versionRequested - 1)
306                Long versionFound = Long.valueOf(objetMaquette.getDetail().getFormation().getVersion());
307                if (versionLong != versionFound && versionLong != (versionFound + 1))
308                {
309                    report.setStatus(ExportStatus.PROGRAM_IMPOSSIBLE_VERSION);
310                }
311            }
312            
313            // If something was found but it is not a Program
314            else if (objetMaquette != null && !objetMaquette.getType().getCode().equals("FORMATION"))
315            {
316                report.setStatus(ExportStatus.NON_EDITABLE_PROGRAM_ALREADY_EXISTS);
317            }
318            
319            // If nothing was found, check if the version requested is one
320            else if (versionLong != 1)
321            {
322                report.setStatus(ExportStatus.PROGRAM_IMPOSSIBLE_VERSION);
323            }
324        }
325    }
326    
327    private void _checkProgramDiplomanteField(Program program, ExportReport report)
328    {
329        // dataDegree which contains degree, cursus and degreeNature
330        _getContentValue(program, AbstractProgram.DEGREE)
331                .ifPresent(degree -> _checkAttributes(degree, report));
332    }
333    
334    private void _checkChildren(ProgramItem programItem, ExportReport report)
335    {
336        for (ProgramItem child : _odfHelper.getChildProgramItems(programItem))
337        {
338            _checkProgramItem(child, report);
339        }
340    }
341    
342    private void _checkChildrenOfYear(Container container, ExportReport report)
343    {
344        container.getProgramPartChildren()
345            .stream()
346            .filter(Container.class::isInstance)
347            .map(Container.class::cast)
348            .filter(c -> "annee".equals(getContainerNatureCode(c)))
349            .forEach(
350                child ->
351                {
352                    report.setStatus(ExportStatus.CONTENT_STRUCTURE_INVALID);
353                    getLogger().error("L'élément {} (Année) ne peut avoir comme enfant une année : {}", container.getTitle(), child.getTitle());
354                }
355            );
356    }
357    
358    private void _checkChildrenOfSemester(Container container, ExportReport report)
359    {
360        container.getProgramPartChildren()
361            .stream()
362            .filter(Container.class::isInstance)
363            .map(Container.class::cast)
364            .filter(c ->
365            {
366                String nature = getContainerNatureCode(c);
367                return "annee".equals(nature) || "semestre".equals(nature);
368            })
369            .forEach(
370                child ->
371                {
372                    report.setStatus(ExportStatus.CONTENT_STRUCTURE_INVALID);
373                    getLogger().error("L'élément {} (Semestre) ne peut avoir comme enfant une année ou un semestre : {}", container.getTitle(), child.getTitle());
374                }
375            );
376    }
377    
378    private void _checkChildrenOfSubProgram(SubProgram subProgram, ExportReport report)
379    {
380        for (ProgramItem child : _odfHelper.getChildProgramItems(subProgram))
381        {
382            // If the child is a SubProgram, then it is not compatible with the parent : SubProgram
383            if (child instanceof SubProgram childSubProgram)
384            {
385                report.setStatus(ExportStatus.CONTENT_STRUCTURE_INVALID);
386                getLogger().error("L'élément {} (Parcours) ne peut avoir comme enfant un parcours : {}", subProgram.getTitle(), childSubProgram.getTitle());
387            }
388            
389            _checkProgramItem(child, report);
390        }
391    }
392    
393    private void _checkProgramItem(ProgramItem programItem, ExportReport report)
394    {
395        _checkAttributes((Content) programItem, report);
396        
397        if (programItem instanceof CourseList couresList)
398        {
399            // Check if the courseList has children, because a Pegase group has to have at least one child
400            if (!couresList.hasCourses())
401            {
402                getLogger().error("L'élément {} n'a pas d'enfant alors qu'il aurait dû être exporté en tant que groupement", couresList.getTitle());
403                report.setStatus(ExportStatus.CONTENT_STRUCTURE_INVALID);
404            }
405        }
406        else if (programItem instanceof Container container)
407        {
408            String childNatureCode = getContainerNatureCode(container);
409            
410            if ("annee".equals(childNatureCode))
411            {
412                _checkChildrenOfYear(container, report);
413            }
414            else if ("semestre".equals(childNatureCode))
415            {
416                _checkChildrenOfSemester(container, report);
417            }
418            else
419            {
420                // Check if the container (without a nature) has children, because a Pegase group has to have at least one child
421                if (!container.hasProgramPartChildren())
422                {
423                    getLogger().error("L'élément {} n'a pas d'enfant alors qu'il aurait dû être exporté en tant que groupement", container.getTitle());
424                    report.setStatus(ExportStatus.CONTENT_STRUCTURE_INVALID);
425                }
426            }
427        }
428        
429        else if (programItem instanceof SubProgram subProgram)
430        {
431            _checkChildrenOfSubProgram(subProgram, report);
432        }
433        
434        for (ProgramItem child : _odfHelper.getChildProgramItems(programItem))
435        {
436            _checkProgramItem(child, report);
437        }
438    }
439    
440    private ObjetMaquette _getObjetMaquetteIfAlreadyExists(String code) throws IOException
441    {
442        return _getObjetMaquetteIfAlreadyExists(code, null);
443    }
444    
445    /**
446     * Get the latest version of an objetMaquette if it exists
447     * @param code The code wanted
448     * @param version The version wanted
449     * @return The ObjetMaquette wanted or null if it was not found
450     * @throws IOException If an error occurs while retrieving the token
451     */
452    private ObjetMaquette _getObjetMaquetteIfAlreadyExists(String code, BigDecimal version) throws IOException
453    {
454        Pageable pageable = new Pageable();
455        pageable.setPage(0);
456        pageable.setTaille(10);
457        pageable.setTri(List.of("version,desc"));
458        
459        PagedObjetMaquette pagedObjetMaquette;
460        try
461        {
462            // Get the objetMaquette for the code and the version
463            pagedObjetMaquette = _pegaseApiManager.getObjetsMaquetteApi().lireListeObjetsMaquette(_structureCode, null, null, null, code, null, null, null, null, null, null, version, null, null, null, null, null, null, pageable);
464            List<ObjetMaquette> objetsMaquette = pagedObjetMaquette.getItems();
465            
466            // If it was found, return it
467            if (objetsMaquette.size() >= 1)
468            {
469                return objetsMaquette.get(0);
470            }
471        }
472        catch (ApiException e)
473        {
474            getLogger().warn("Une erreur est survenue lors la recherche de l'élément de code de synchronization Pégase {} pour vérifier son existance préalable dans Pégase", code, e);
475        }
476        
477        return null;
478    }
479    
480    private Map<String, Object> _getCodeAndVersion(String codeAndVersion)
481    {
482        Map<String, Object> result = new HashMap<>();
483        
484        String code = codeAndVersion;
485        String version = null;
486        
487        if (codeAndVersion.contains("/"))
488        {
489            String[] codeAndVersionTab = codeAndVersion.split("/");
490            if (codeAndVersionTab.length == 2)
491            {
492                code = codeAndVersionTab[0];
493                version = codeAndVersionTab[1];
494            } 
495        }
496        
497        result.put("code", code);
498        result.put("version", version);
499        
500        return result;
501    }
502    
503    private boolean _isModifiable(ObjetMaquette objetMaquette, String code, ExportReport report) throws IOException
504    {
505        // If the object is not editable, return false
506        if (!objetMaquette.getModifiable())
507        {
508            return false;
509        }
510        
511        // The object is editable, now we check if it is linked to this Program
512        List<FormationRef> parentPrograms = objetMaquette.getFormationsParentes();
513        if (parentPrograms != null && parentPrograms.size() >= 1)
514        {
515            FormationRef parentProgram = parentPrograms.get(0);
516            
517            // If it is already linked to the Program, return true
518            if (report.getCodeParentProgram().equals(parentProgram.getCode()) && report.getVersionParentProgram() == parentProgram.getVersion())
519            {
520                return true;
521            }
522        }
523        
524        // If the object is "mutualise" or orphan in Pégase then it can be linked to the Program
525        if (objetMaquette.getMutualise() || _isOrphan(code))
526        {
527            return true;
528        }
529        
530        return false;
531    }
532    
533    private boolean _isOrphan(String code) throws IOException
534    {
535        PagedObjetMaquette pagedObjetMaquette;
536        try
537        {
538            // Try to get the objetMaquette for the code wanted and that are isolated
539            pagedObjetMaquette = _pegaseApiManager.getObjetsMaquetteApi().lireListeObjetsMaquette(_structureCode, null, null, null, code, null, null, null, true, null, null, null, null, null, null, null, null, null, null);
540            
541            List<ObjetMaquette> objetsMaquette = pagedObjetMaquette.getItems();
542
543            return objetsMaquette.size() >= 1;
544        }
545        catch (ApiException e)
546        {
547            getLogger().info("L'élément de code de synchronisation Pégase {} n'a pas pu être trouvé dans Pégase, nous ne pouvons donc pas vérifier si l'élément est isolé.", e);
548        }
549        
550        return false;
551    }
552    
553    private Enfant _createPegaseChild(Content child, String pegaseId) 
554    {
555        Enfant pegaseChild = new Enfant();
556        
557        pegaseChild.setDetails(
558            new EnfantDetails()
559                .libelle(child.getTitle())
560        );
561        
562        pegaseChild.setRef(
563            new Ref()
564                .id(pegaseId)
565                .code(child.getValue(__PEGASE_SYNC_CODE))
566        );
567        
568        return pegaseChild;
569    }
570    
571    private Enfant _createChild(Content content, ExportReport report)
572    {
573        boolean success = true;
574        try
575        {
576            String childPegaseId = _createPegaseInstance(content, report);
577            return _createPegaseChild(content, childPegaseId);
578        }
579        catch (Exception ex)
580        {
581            success = false;
582            getLogger().error("Erreur lors de l'export de l'élément {}", content.getTitle(), ex);
583            return null;
584        }
585        finally
586        {
587            if (success)
588            {
589                report.addElementExported(content);
590            }
591        }
592    }
593    
594    private String _createPegaseInstance(Content content, ExportReport report) throws PegaseExportException, IOException
595    {
596        // Conteneur : semestre, année -> OTT ou Groupement
597        if (content instanceof Container)
598        {
599            return _createOTTOrGroupFromContainer((Container) content, report);
600        }
601        
602        // Parcours -> OO
603        if (content instanceof SubProgram)
604        {
605            return _createOOFromSubProgram((SubProgram) content, report);
606        }
607        
608        // ELP -> OP
609        if (content instanceof Course)
610        {
611            return _createOPFromCourse((Course) content, report);
612        }
613        
614        // liste d'ELP -> Groupement
615        if (content instanceof CourseList)
616        {
617            return _createGroupFromCourseList((CourseList) content, report);
618        }
619        
620        return null;
621    }
622    
623    private Map<String, Enfant> _createAllChildren(Map<String, Enfant> children, List<ProgramItem> programItem, ExportReport report)
624    {
625        for (ProgramItem childContent : programItem) 
626        {
627            Enfant containerChild = _createChild((Content) childContent, report);
628            
629            // If the child was created, add it to the list of children to attach
630            if (containerChild != null)
631            {
632                children.put(containerChild.getRef().getId(), containerChild);
633            }
634        }
635        
636        return children;
637    }
638
639    private void _attachAllChildren(Content content, String pegaseId, Map<String, Enfant> children, ExportReport report) throws IOException
640    {
641        List<Enfant> childrenAlreadyAttached;
642        try
643        {
644            // Get the children already attached to the programItem
645            childrenAlreadyAttached = _pegaseApiManager.getObjetsMaquetteApi().lireEnfants(_structureCode, pegaseId);
646            
647            // If there are children already attached and the option trustAmetys is checked
648            if (childrenAlreadyAttached != null && _trustAmetys)
649            {
650                // For every child already attached
651                for (Enfant child : childrenAlreadyAttached)
652                {
653                    // Try to detach it
654                    try
655                    {
656                        _pegaseApiManager.getObjetsMaquetteApi().detacherEnfant(_structureCode, pegaseId, child.getRef().getId());
657                    }
658                    catch (ApiException e)
659                    {
660                        report.updateExportReport(ExportStatus.ERROR, ProblemTypes.API_ERROR, content);
661                        getLogger().warn("L'enfant de code Pégase {} de l'élément {} n'a pas pu être détaché dans Pégase", child.getRef().getCode(), content.getTitle(), e);
662                    }
663                }
664            }
665            
666            // If the option trustAmetys is not checked
667            else if (childrenAlreadyAttached != null)
668            {
669                // For every child already attached
670                for (Enfant childAlreadyAttached : childrenAlreadyAttached)
671                {
672                    // Remove it from the list of children to attach
673                    children.remove(childAlreadyAttached.getRef().getId());
674                }
675            }
676            
677            List<Enfant> childrenToAttach = List.copyOf(children.values());
678            
679            // If there are children that need to be attached
680            if (!childrenToAttach.isEmpty())
681            {
682                // Try to attach them
683                try 
684                {
685                    _pegaseApiManager.getObjetsMaquetteApi().attacherEnfant(_structureCode, pegaseId, childrenToAttach);
686                }
687                catch (ApiException ex)
688                {
689                    report.updateExportReport(ExportStatus.WARN, ProblemTypes.LINKS_MISSING, content);
690                    
691                    String childrenFailedToAttach = "";
692                    
693                    for (Enfant child : childrenToAttach)
694                    {
695                        childrenFailedToAttach += child.getRef().getCode() + ",";
696                    }
697                    
698                    getLogger().warn("Une erreur est survenue lors de l'attachement des enfants ({}) à l'élément ({}) dans Pégase", childrenFailedToAttach, content.getTitle(), ex);
699                }
700            }
701        }
702        catch (ApiException e)
703        {
704            report.updateExportReport(ExportStatus.WARN, ProblemTypes.API_ERROR, content);
705            
706            getLogger().warn("Les liens avec les enfants de l'élément {} n'ont pas pu être traités car les enfants depuis Pégase n'ont pas pu être récupérés.", content.getTitle(), e);
707        }
708    }
709    
710    private void _attachAllChildrenGroupementCase(Content content, String pegaseId, Map<String, Enfant> children, ExportReport report) throws IOException
711    {
712        // If the list of children that need to be attached is empty, throw an Exception
713        if (children.isEmpty())
714        {
715            report.updateExportReport(ExportStatus.ERROR, ProblemTypes.GROUPEMENT_WITHOUT_CHILDREN, content);
716            
717            getLogger().warn("Aucuns des enfants de la liste d'elp ou le conteneur {}, devant être exporté en groupement, n'ont pu être exportés, les enfants de ce groupement resterons donc inchangés ", content.getTitle());
718        }
719        else
720        {
721            List<Enfant> childenAlreadyAttached;
722            try
723            {
724                // Get the children already attached
725                childenAlreadyAttached = _pegaseApiManager.getObjetsMaquetteApi().lireEnfants(_structureCode, pegaseId);
726                List<String> childrenAttachedInTheEnd = List.copyOf(children.keySet());
727                
728                // If there are children already attached
729                if (childenAlreadyAttached != null)
730                {
731                    // For every child already attached
732                    for (Enfant childAlreadyAttached : childenAlreadyAttached)
733                    {
734                        // Remove it from the list of children to attach
735                        children.remove(childAlreadyAttached.getRef().getId());
736                    }
737                }
738                
739                List<Enfant> childrenToAttach = List.copyOf(children.values());
740                
741                // If there are children to attach, try to attach them
742                if (!childrenToAttach.isEmpty())
743                {
744                    try 
745                    {
746                        _pegaseApiManager.getObjetsMaquetteApi().attacherEnfant(_structureCode, pegaseId, childrenToAttach);
747                    }
748                    catch (ApiException ex)
749                    {
750                        report.updateExportReport(ExportStatus.WARN, ProblemTypes.LINKS_MISSING, content);
751                        
752                        if (getLogger().isWarnEnabled())
753                        {
754                            getLogger().warn("Une erreur est survenue lors de l'attachement des enfants ({}) à l'élément ({}) dans Pégase",
755                                childrenToAttach.stream()
756                                    .map(Enfant::getRef)
757                                    .map(Ref::getCode)
758                                    .distinct()
759                                    .collect(Collectors.joining(", ")),
760                                    content.getTitle(),
761                                ex
762                            );
763                        }
764                    }
765                }
766                
767                // If the option trustAmetys is checked, try to detach the other children (the children that were already attached to the content
768                if (_trustAmetys && childenAlreadyAttached != null)
769                {
770                    // For every child that was already there
771                    for (Enfant child : childenAlreadyAttached)
772                    {
773                        String childId = child.getRef().getId();
774                        
775                        // If it was not part of the children that needed to be attached, try to detach it
776                        if (!childrenAttachedInTheEnd.contains(childId))
777                        {
778                            try
779                            {
780                                _pegaseApiManager.getObjetsMaquetteApi().detacherEnfant(_structureCode, pegaseId, child.getRef().getId());
781                            }
782                            catch (ApiException e)
783                            {
784                                report.updateExportReport(ExportStatus.ERROR, ProblemTypes.API_ERROR, content);
785
786                                getLogger().warn("L'enfant de code Pégase {} de l'élément {} n'a pas pu être détaché dans Pégase", child.getRef().getCode(), content.getTitle(), e);
787                            }
788                        }
789                    }
790                }
791            }
792            catch (ApiException e)
793            {
794                report.updateExportReport(ExportStatus.WARN, ProblemTypes.LINKS_MISSING, content);
795
796                getLogger().warn("Les liens avec les enfants de l'élément ({}) n'ont pas pu être traités car les enfants depuis Pégase n'ont pas pu être récupérés.", content.getTitle(), e);
797            }
798        }
799    }
800    
801    private ObjetFormation _createObjetFormation(Content content, String type, ExportReport report) throws PegaseExportException, IOException
802    {
803        // Create the ObjetFormation
804        ObjetFormation objetFormation = new ObjetFormation();
805        
806        String code = content.getValue(__PEGASE_SYNC_CODE);
807        String label = content.getTitle();
808        
809        objetFormation.setCode(code);
810        
811        objetFormation.setLibelle(StringUtils.truncate(label, 50)); // le libellé court doit faire moins de 50 caractères
812        objetFormation.setLibelleLong(StringUtils.truncate(label, 150)); // le libellé long doit faire moins de 150 caractères
813        
814        ObjetFormationType ob = new ObjetFormationType();
815        ob.setCode(type);
816        objetFormation.setType(ob);
817
818        objetFormation.setMutualise(true);
819        
820        if (content.hasValue("ects"))
821        {
822            Double ects = null;
823            if (content instanceof AbstractProgram)
824            {
825                ects = _getContentValue(content, AbstractProgram.ECTS)
826                    .map(c -> c.<String>getValue(OdfReferenceTableEntry.CODE))
827                    .map(Double::parseDouble)
828                    .orElse(null);
829            }
830            else
831            {
832                ects = content.getValue("ects");
833            }
834            
835            if (ects != null)
836            {
837                objetFormation.putChampsAdditionnelsItem("ECTS", new ValeurNum().valeur(ects));
838            }
839        }
840        
841        Object desc = null;
842        if (content.hasValue("description"))
843        {
844            desc = content.getValue("description");
845        }
846        else if (content.hasValue("presentation"))
847        {
848            desc = content.getValue("presentation");
849        }
850
851        if (desc != null)
852        {
853            RichText descRichText = (RichText) desc;
854            String description = _richTextHelper.richTextToString(descRichText);
855            objetFormation.setDescription(StringUtils.truncate(description, 2000)); // la description doit faire moins de 2000 caractères
856        }
857
858        // Check if the object already exists
859        ObjetMaquette objetMaquette = _getObjetMaquetteIfAlreadyExists(code);
860        if (objetMaquette != null)
861        {
862            // If it already exist, check if it can be edited
863            if (_isModifiable(objetMaquette, code, report))
864            {
865                String id = objetMaquette.getId();
866                objetFormation.setId(id);
867                
868                // Edit the already existing object
869                try
870                {
871                    objetFormation = _pegaseApiManager.getObjetsFormationApi().modifierObjetFormation(_structureCode, id, objetFormation);
872                    return objetFormation;
873                }
874                catch (ApiException e)
875                {
876                    report.updateExportReport(ExportStatus.ERROR, ProblemTypes.ELEMENT_NOT_EXPORTED);
877
878                    throw new PegaseExportException("L'élément " + content.getTitle() + " n'a pas pu être exportée car sa modification dans Pégase a posé un problème", e);
879                }
880            }
881            
882            report.updateExportReport(ExportStatus.ERROR, ProblemTypes.ELEMENT_ALREADY_EXIST);
883
884            throw new PegaseExportException("Erreur lors de l'export de " + label + "Un élément avec le code " + code + " existe déjà et n'est pas mutualisable ou modifiable");
885        }
886        
887        // If it did not already exist, create the object
888        try
889        {
890            objetFormation = _pegaseApiManager.getObjetsFormationApi().creerObjetFormation(_structureCode, objetFormation);
891            
892            return objetFormation;
893        }
894        catch (ApiException e)
895        {
896            report.updateExportReport(ExportStatus.ERROR, ProblemTypes.ELEMENT_NOT_EXPORTED);
897
898            throw new PegaseExportException("L'élément " + content.getTitle() + " n'a pas pu être exporté dû à un problème rencontré avec Pégase", e);
899        }
900    }
901    
902    private Formation _createProgram(Program program, ExportReport report) throws PegaseExportException, IOException
903    {
904        Formation pegaseProgram = new Formation();
905        
906        String codeAndVersion = program.getValue(__PEGASE_SYNC_CODE);
907        
908        Map<String, Object> dataCodeAndVersion = _getCodeAndVersion(codeAndVersion);
909        String code = (String) dataCodeAndVersion.get("code");
910        BigDecimal version = new BigDecimal((String) dataCodeAndVersion.get("version"));
911        
912        pegaseProgram.setCode(code);
913
914        pegaseProgram.setLibelle(StringUtils.truncate(program.getTitle(), 50)); // le libellé court doit faire moins de 50 caractères
915        pegaseProgram.setLibelleLong(StringUtils.truncate(program.getTitle(), 150)); // le libellé court doit faire moins de 150 caractères
916
917        _getContentValue(program, AbstractProgram.ECTS)
918            .map(c -> c.<String>getValue(OdfReferenceTableEntry.CODE))
919            .map(Double::parseDouble)
920            .ifPresent(pegaseProgram::setEcts);
921
922        pegaseProgram.setTypeFormation(new FormationTypeFormation().code(_getPegaseCodeOrCodeOfAttribute(program, AbstractProgram.EDUCATION_KIND)));
923        
924        // If the Program is of education kind "diplomante", add the additional fields
925        if (__CODE_FORMATION_DIPLOMANTE.equals(pegaseProgram.getTypeFormation().getCode()))
926        {
927            _fieldsIfDiplomante(program, pegaseProgram);
928        }
929        
930        return _createOrEditProgram(code, version, pegaseProgram, report);
931    }
932    
933    private void _fieldsIfDiplomante(Program program, Formation pegaseProgram)
934    {
935        Content degree = _getContentValue(program, AbstractProgram.DEGREE).orElse(null);
936        
937        // Degree -> typeDiplome
938        pegaseProgram.setTypeDiplome(_getPegaseCodeOrCode(degree));
939        
940        // degreeNature -> natureDiplome
941        Optional.ofNullable(degree)
942            .map(d -> d.<String>getValue("degreeNature"))
943            .ifPresent(pegaseProgram::setNatureDiplome);
944        
945        // Cursus
946        Optional.ofNullable(degree)
947            .map(d -> d.<String>getValue("cursus"))
948            .ifPresent(pegaseProgram::setCursus);
949        
950        // educationLevel -> niveauFormation
951        pegaseProgram.setNiveauFormation(_getPegaseCodeOrCodeOfAttribute(program, AbstractProgram.LEVEL));
952        
953        // RncpLevel -> niveauDiplome
954        String rncpLevelValue = _getContentValues(program, AbstractProgram.RNCP_LEVEL)
955            .map(this::_getPegaseCodeOrCode)
956            .collect(Collectors.joining(","));
957        pegaseProgram.setNiveauDiplome(rncpLevelValue);
958        
959        // Domain
960        if (((ElementDefinition) program.getDefinition(AbstractProgram.DOMAIN)).isMultiple())
961        {
962            _getContentValues(program, AbstractProgram.DOMAIN)
963                .findFirst()
964                .map(this::_getPegaseCodeOrCode)
965                .ifPresent(pegaseProgram::setDomaineFormation);
966        }
967        else
968        {
969            pegaseProgram.setDomaineFormation(_getPegaseCodeOrCodeOfAttribute(program, AbstractProgram.DOMAIN));
970        }
971        
972        // ProgramFields -> champFormation
973        String programFieldsValue = _getContentValues(program, AbstractProgram.PROGRAM_FIELD)
974            .map(this::_getPegaseCodeOrCode)
975            .collect(Collectors.joining(","));
976        if (StringUtils.isNotBlank(programFieldsValue))
977        {
978            pegaseProgram.setChampFormation(programFieldsValue);
979        }
980
981        pegaseProgram.setMention(_getPegaseCodeOrCodeOfAttribute(program, AbstractProgram.MENTION));
982    }
983    
984    private Formation _createOrEditProgram(String code, BigDecimal version, Formation pegaseProgram, ExportReport report) throws PegaseExportException, IOException
985    {
986        // Check if the Program already exist in this version
987        ObjetMaquette objetMaquette = _getObjetMaquetteIfAlreadyExists(code, version);
988        
989        // If no version is requested
990        if (version == null)
991        {
992            // If no program with this code was found in Pegase, create a new Program
993            if (objetMaquette == null)
994            {
995                try
996                {
997                    return _pegaseApiManager.getFormationsApi().creerFormation(_structureCode, pegaseProgram);
998                }
999                catch (ApiException e)
1000                {
1001                    report.updateExportReport(ExportStatus.ERROR, ProblemTypes.API_ERROR);
1002
1003                    throw new PegaseExportException("La formation n'a pas pu être exportée car sa modification dans Pégase a posé un problème", e);
1004                }
1005            }
1006            
1007            // If a program was found, try to edit it
1008            String id = objetMaquette.getId();
1009            pegaseProgram.setId(id);
1010          
1011            try
1012            {
1013                return _pegaseApiManager.getFormationsApi().modifierFormation(_structureCode, id, pegaseProgram);
1014            }
1015            catch (ApiException e)
1016            {
1017                report.updateExportReport(ExportStatus.ERROR, ProblemTypes.API_ERROR);
1018                
1019                throw new PegaseExportException("La formation n'a pas pu être exportée car sa modification dans Pégase a posé un problème", e);
1020            }
1021        }
1022        
1023        // If a version was requested 
1024        
1025        // If a program already exist in this version, try to edit it
1026        if (objetMaquette != null)
1027        {
1028            try
1029            {
1030                String id = objetMaquette.getId();
1031                
1032                pegaseProgram.setId(id);
1033                
1034                return _pegaseApiManager.getFormationsApi().modifierFormation(_structureCode, id, pegaseProgram);
1035            }
1036            catch (ApiException e)
1037            {
1038                report.updateExportReport(ExportStatus.ERROR, ProblemTypes.API_ERROR);
1039
1040                throw new PegaseExportException("La formation n'a pas pu être exportée car sa modification dans Pégase a posé un problème", e);
1041            }
1042        }
1043        
1044        // If no program was found in Pegase with this code and version
1045        // Check if the program exist in another version
1046        objetMaquette = _getObjetMaquetteIfAlreadyExists(code);
1047      
1048        // If the program exist in another version
1049        if (objetMaquette != null)
1050        {
1051            try
1052            {
1053                // Generate a new version of the Pegase Program
1054                Formation newVersionProgram = _pegaseApiManager.getFormationsApi().generer(_structureCode, objetMaquette.getId());
1055              
1056                String id = newVersionProgram.getId();
1057                pegaseProgram.setId(id);
1058                
1059                // Edit the new Program created
1060                return _pegaseApiManager.getFormationsApi().modifierFormation(_structureCode, id, pegaseProgram);
1061            }
1062            catch (ApiException e)
1063            {
1064                report.updateExportReport(ExportStatus.ERROR, ProblemTypes.API_ERROR);
1065                
1066                throw new PegaseExportException("La formation n'a pas pu être exportée car sa modification dans Pégase a posé un problème", e);
1067            }
1068        }
1069        
1070        // If no program was found for this code at all
1071        else
1072        {
1073            try
1074            {
1075                // Create the program in Pegase
1076                return _pegaseApiManager.getFormationsApi().creerFormation(_structureCode, pegaseProgram);
1077            }
1078            catch (ApiException e)
1079            {
1080                report.updateExportReport(ExportStatus.ERROR, ProblemTypes.API_ERROR);
1081
1082                throw new PegaseExportException("La formation n'a pas pu être exportée car sa modification dans Pégase a posé un problème", e);
1083            }
1084        }
1085    }
1086    
1087    private String _createOTTOrGroupFromContainer(Container container, ExportReport report) throws PegaseExportException, IOException
1088    {
1089        String type = null;
1090        
1091        String childNatureCode = getContainerNatureCode(container);
1092        boolean isYear = "annee".equals(childNatureCode);
1093        boolean isSemester = "semestre".equals(childNatureCode);
1094        
1095        // If it is a container of nature : semester of year, export it as an OTT of type semester or year
1096        if (isYear || isSemester)
1097        {
1098            type = isYear ? "ANNEE" : "SEMESTRE";
1099            
1100            ObjetFormation objetFormationCreated = _createObjetFormation(container, type, report);
1101            String pegaseId = objetFormationCreated.getId();
1102
1103            Map<String, Enfant> containerChildren = new HashMap<>();
1104
1105            // Create the children
1106            containerChildren = _createAllChildren(containerChildren, _odfHelper.getChildProgramItems(container), report);
1107            
1108            // Attach the children that were created
1109            _attachAllChildren(container, pegaseId, containerChildren, report);
1110            
1111            return pegaseId;
1112        }
1113        
1114        // If it is a container without a nature, export it as a group
1115        return _createGroupFromContainer(container, report);
1116    }
1117
1118    private String _createGroupFromContainer(Container container, ExportReport report) throws PegaseExportException, IOException
1119    {
1120        Map<String, Enfant> coursesChild = new HashMap<>();
1121        Groupement pegaseGroup = _createGroupFromContent(container);
1122
1123        // Create the children
1124        coursesChild = _createAllChildren(coursesChild, _odfHelper.getChildProgramItems(container), report);
1125        
1126        pegaseGroup.setEnfants(List.copyOf(coursesChild.keySet()));
1127        
1128        // Create the group
1129        Groupement groupementCreated = _createGroup(pegaseGroup, report);
1130        String pegaseId = groupementCreated.getId();
1131        
1132        // Attach the children to the group
1133        _attachAllChildrenGroupementCase(container, pegaseId, coursesChild, report);
1134        
1135        return pegaseId;
1136    }
1137    
1138    private Groupement _createGroup(Groupement pegaseGroup, ExportReport report) throws PegaseExportException, IOException
1139    {
1140        String code = pegaseGroup.getCode();
1141        ObjetMaquette objetMaquette = _getObjetMaquetteIfAlreadyExists(code);
1142        Groupement pegaseGroupCreated;
1143        
1144        // Check if the object already exists
1145        if (objetMaquette != null)
1146        {
1147            // If it already exist, check if it can be edited
1148            if (_isModifiable(objetMaquette, code, report))
1149            {
1150                String id = objetMaquette.getId();
1151                pegaseGroup.setId(id);
1152        
1153                // Edit the already existing object
1154                try
1155                {
1156                    return _pegaseApiManager.getGroupementsApi().modifierGroupement(_structureCode, id, pegaseGroup);
1157                }
1158                catch (ApiException e)
1159                {
1160                    report.updateExportReport(ExportStatus.ERROR, ProblemTypes.ELEMENT_NOT_EXPORTED);
1161                    
1162                    throw new PegaseExportException("Le groupement " + pegaseGroup.getLibelle() + " n'a pas pu être exportée car sa modification dans Pégase a posé un problème", e);
1163                }
1164            }
1165            
1166            report.updateExportReport(ExportStatus.ERROR, ProblemTypes.ELEMENT_ALREADY_EXIST);
1167
1168            throw new PegaseExportException("Un élément avec ce code existe déjà et n'est pas mutualisable ou modifiable");
1169        }
1170        
1171        // If it did not already exist, create the object
1172        try
1173        {
1174            pegaseGroupCreated = _pegaseApiManager.getGroupementsApi().creerGroupement(_structureCode, pegaseGroup);
1175            
1176            if (pegaseGroupCreated != null)
1177            {
1178                return pegaseGroupCreated;
1179            }
1180        }
1181        catch (ApiException e)
1182        {
1183            report.updateExportReport(ExportStatus.ERROR, ProblemTypes.ELEMENT_NOT_EXPORTED);
1184            
1185            throw new PegaseExportException("Le groupement " + pegaseGroup.getLibelle() + " n'a pas pu être exporté dû à un problème rencontré avec Pégase", e);
1186        }
1187        
1188        return null;
1189    }
1190
1191    private String _createOOFromSubProgram(SubProgram subProgram, ExportReport report) throws PegaseExportException, IOException
1192    {
1193        ObjetFormation objetFormationCreated = _createObjetFormation(subProgram, "PARCOURS-TYPE", report);
1194        String pegaseId = objetFormationCreated.getId();
1195
1196        Map<String, Enfant> containerChildren = new HashMap<>();
1197
1198        // Create the children
1199        containerChildren = _createAllChildren(containerChildren, _odfHelper.getChildProgramItems(subProgram), report);
1200
1201        // Attach the children
1202        _attachAllChildren(subProgram, pegaseId, containerChildren, report);
1203        
1204        return pegaseId;
1205    }
1206    
1207    private String _createOPFromCourse(Course course, ExportReport report) throws PegaseExportException, IOException
1208    {
1209        // Get the nature of the course
1210        String type = _getPegaseCodeOrCodeOfAttribute(course, Course.COURSE_TYPE);
1211        
1212        // Create the course in Pegase
1213        ObjetFormation objetFormationCreated = _createObjetFormation(course, type, report);
1214        String pegaseId = objetFormationCreated.getId();
1215        Map<String, Enfant> containerChildren = new HashMap<>();
1216
1217        // Create the children
1218        containerChildren = _createAllChildren(containerChildren, _odfHelper.getChildProgramItems(course), report);
1219
1220        // Attach the children
1221        _attachAllChildren(course, pegaseId, containerChildren, report);
1222
1223        return pegaseId;
1224    }
1225    
1226    private Groupement _createGroupFromContent(Content content)
1227    {
1228        Groupement pegaseGroup = new Groupement();
1229        String code = content.getValue(__PEGASE_SYNC_CODE);
1230        
1231        pegaseGroup.setStructure(_structureCode);
1232        pegaseGroup.setCode(code);
1233        pegaseGroup.setLibelle(StringUtils.truncate(content.getTitle(), 50)); // le libellé est limité à 50 caractères
1234        pegaseGroup.setPlageDeChoix(false);
1235        pegaseGroup.setMutualise(true);
1236        pegaseGroup.setEnfants(List.of());
1237        
1238        return pegaseGroup;
1239    }
1240    
1241    private String _createGroupFromCourseList(CourseList courseList, ExportReport report) throws PegaseExportException, IOException
1242    {
1243        Map<String, Enfant> coursesChild = new HashMap<>();
1244        Groupement pegaseGroup = _createGroupFromContent(courseList);
1245        
1246        coursesChild = _createAllChildren(coursesChild, _odfHelper.getChildProgramItems(courseList), report);
1247        
1248        pegaseGroup.setEnfants(List.copyOf(coursesChild.keySet()));
1249        
1250        Groupement pegaseGroupCreated = _createGroup(pegaseGroup, report);
1251        
1252        String pegaseId = pegaseGroupCreated.getId();
1253
1254        _attachAllChildrenGroupementCase(courseList, pegaseId, coursesChild, report);
1255        
1256        return pegaseId;
1257    }
1258
1259    /**
1260     * Create a program in Pegase
1261     * @param program the program to export
1262     * @param report the Pegase export report
1263     */
1264    @Override
1265    public void createProgram(Program program, ExportReport report)
1266    {
1267        if (!_isActive)
1268        {
1269            throw new UnsupportedOperationException("Pégase is not active in the configuration, you cannot import or synchronize a program in Pégase.");
1270        }
1271        
1272        int nbTotal = _getEveryElements(program).size();
1273        
1274        report.setNbTotal(nbTotal);
1275        
1276        Formation pegaseProgramCreated;
1277        try
1278        {
1279            // Create the Program in Pegase
1280            pegaseProgramCreated = _createProgram(program, report);
1281            report.addElementExported(program);
1282            
1283            // Get the Id of the created formation from Pegase that is going to be used to attach the children to the program
1284            String pegaseProgramId = pegaseProgramCreated.getId();
1285            
1286            String codeParentProgram = pegaseProgramCreated.getCode();
1287            int versionParentProgram = 1;
1288            
1289            if (codeParentProgram.contains("/"))
1290            {
1291                String[] codeAndVersionTab = codeParentProgram.split("/");
1292                if (codeAndVersionTab.length == 2)
1293                {
1294                    codeParentProgram = codeAndVersionTab[0];
1295                    versionParentProgram = Integer.parseInt(codeAndVersionTab[1]);
1296                } 
1297            }
1298            
1299            report.setCodeAndVersion(codeParentProgram, versionParentProgram);
1300            
1301            // Get the children of the Ametys program
1302            List<ProgramPart> children = program.getProgramPartChildren();
1303            Map<String, Enfant> childrenToLink = new HashMap<>();
1304            
1305            for (ProgramPart child : children)
1306            {
1307                // Create the pegase instance of the child
1308                Enfant enfant = _createChild((Content) child, report);
1309                
1310                // Add the Enfant (created from the Pegase Id of the child) to the list of "Enfant" to link, if it is not already attached
1311                if (enfant != null)
1312                {
1313                    childrenToLink.put(enfant.getRef().getId(), enfant);
1314                }
1315            }
1316            
1317            _attachAllChildren(program, pegaseProgramId, childrenToLink, report);
1318            
1319        }
1320        catch (IOException e)
1321        {
1322            report.updateExportReport(ExportStatus.ERROR, ProblemTypes.API_ERROR);
1323            getLogger().error("Le jeton d'authentification à Pégase n'a pas pu être récupéré", e);
1324        }
1325        catch (PegaseExportException e)
1326        {
1327            report.updateStatus(ExportStatus.ERROR);
1328            getLogger().error("Une erreur est survenue lors de l'export de la formation '{}' ({}) dans Pégase", program.getTitle(), program.getId(), e);
1329        }
1330    }
1331    
1332    private Set<ProgramItem> _getEveryElements(ProgramItem program)
1333    {
1334        Set<ProgramItem> elementNotExported = new HashSet<>();
1335        elementNotExported.add(program);
1336        
1337        for (ProgramItem child : _odfHelper.getChildProgramItems(program))
1338        {
1339            elementNotExported.addAll(_getEveryElements(child));
1340        }
1341        
1342        return elementNotExported;
1343    }
1344}