001/*
002 *  Copyright 2014 Anyware Services
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package org.ametys.odf.cdmfr;
017
018import java.time.LocalDate;
019import java.util.Optional;
020import java.util.Set;
021
022import org.apache.avalon.framework.service.ServiceException;
023import org.apache.avalon.framework.service.ServiceManager;
024import org.apache.avalon.framework.service.Serviceable;
025import org.apache.cocoon.xml.AttributesImpl;
026import org.apache.cocoon.xml.XMLUtils;
027import org.apache.commons.lang3.ArrayUtils;
028import org.apache.commons.lang3.StringUtils;
029import org.apache.excalibur.source.SourceResolver;
030import org.xml.sax.ContentHandler;
031import org.xml.sax.SAXException;
032
033import org.ametys.cms.data.ContentValue;
034import org.ametys.cms.data.File;
035import org.ametys.cms.data.Geocode;
036import org.ametys.cms.data.RichText;
037import org.ametys.cms.data.type.ModelItemTypeConstants;
038import org.ametys.cms.repository.Content;
039import org.ametys.cms.repository.ModifiableContent;
040import org.ametys.odf.catalog.Catalog;
041import org.ametys.odf.catalog.CatalogsManager;
042import org.ametys.odf.course.Course;
043import org.ametys.odf.coursepart.CoursePart;
044import org.ametys.odf.enumeration.MonthEnumerator;
045import org.ametys.odf.enumeration.OdfReferenceTableHelper;
046import org.ametys.odf.orgunit.OrgUnit;
047import org.ametys.odf.person.Person;
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.SubProgram;
053import org.ametys.odf.skill.ODFSkillsHelper;
054import org.ametys.plugins.repository.AmetysObjectResolver;
055import org.ametys.plugins.repository.data.holder.group.ModelAwareRepeater;
056import org.ametys.plugins.repository.data.holder.group.ModelAwareRepeaterEntry;
057import org.ametys.plugins.repository.model.RepositoryDataContext;
058import org.ametys.runtime.model.ModelItem;
059import org.ametys.runtime.model.type.DataContext;
060
061/**
062 * base CDMfr extension for Ametys
063 */
064public class AmetysCDMfrExtension extends AbstractCDMfrExtension implements Serviceable
065{
066    private OdfReferenceTableHelper _refTableHelper;
067    private CatalogsManager _catalogsManager;
068    private SourceResolver _sourceResolver;
069    private AmetysObjectResolver _resolver;
070    private MonthEnumerator _monthEnumerator;
071    
072    @Override
073    public void service(ServiceManager manager) throws ServiceException
074    {
075        _refTableHelper = (OdfReferenceTableHelper) manager.lookup(OdfReferenceTableHelper.ROLE);
076        _catalogsManager = (CatalogsManager) manager.lookup(CatalogsManager.ROLE);
077        _sourceResolver = (SourceResolver) manager.lookup(SourceResolver.ROLE);
078        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
079        _monthEnumerator = (MonthEnumerator) manager.lookup(MonthEnumerator.ROLE);
080    }
081    
082    @Override
083    public void abstractProgram2CDM(ContentHandler contentHandler, AbstractProgram<? extends ProgramFactory> program, Set<String> persons, Set<String> orgUnits) throws SAXException
084    {
085        AttributesImpl attrs = new AttributesImpl();
086        
087        // Certified
088        // <ametys-cdm:certified/>
089        if (program.isCertified())
090        {
091            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + AbstractProgram.CERTIFIED);
092        }
093        
094        // Passerelle et réorientation
095        // <ametys-cdm:reorientation>...</ametys-cdm:reorientation>
096        DataContext reorientationContext = RepositoryDataContext.newInstance()
097                                                                .withObject(program)
098                                                                .withDataPath(AbstractProgram.REORIENTATION);
099        CDMHelper.richText2CDM(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + AbstractProgram.REORIENTATION, program.getReorientation(), reorientationContext, _sourceResolver, attrs);
100        
101        
102        // Date de fin de la formation
103        // <ametys-cdm:teachingEnd>...</ametys-cdm:teachingEnd>
104        LocalDate teachingEnd = program.getTeachingEnd();
105        if (teachingEnd != null)
106        {
107            CDMHelper.date2CDM(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + CDMFRTagsConstants.TAG_TEACHING_END, teachingEnd);
108        }
109
110        // Ouvert en stage
111        // <ametys-cdm:internshipOpen/>
112        if (program.isInternshipOpen())
113        {
114            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + AbstractProgram.INTERNSHIP_OPEN);
115        }
116        
117        // Stages
118        // <ametys-cdm:training>
119        //      <ametys-cdm:trainingInfo value="internship_mandatory">Obligatoire</ametys-cdm:trainingInfo>
120        //      <ametys-cdm:trainingDuration>6 mois</ametys-cdm:trainingInfo>
121        //      <ametys-cdm:trainingAbroadInfo value="internship_mandatory">Obligatoire</ametys-cdm:trainingAbroadInfo>
122        //      <ametys-cdm:trainingAbroadDuration>6 mois</ametys-cdm:trainingAbroadDuration>
123        //      <ametys-cdm:trainingDetails>
124        //          <ametys-cdm:trainingDetail>
125        //              <ametys-cdm:title>...</ametys-cmd:title>
126        //              <ametys-cdm:duration>...</ametys-cmd:duration>
127        //              <ametys-cdm:period>...</ametys-cmd:period>
128        //              <ametys-cdm:kind>...</ametys-cmd:kind>
129        //          </ametys-cdm:trainingDetail>
130        //      </ametys-cdm:trainingDetails>
131        // </ametys-cdm:training>
132        saxTraining(contentHandler, program);
133        
134        // Etablissement partenaires
135        // <ametys-cdm:partnerSchools/>
136        saxPartnerSchools(contentHandler, program);
137        
138        // Laboratoires partenaires
139        // <ametys-cdm:partnerLaboratories/>
140        saxPartnerLaboratories(contentHandler, program);
141        
142        // Fichier attachés
143        // <ametys-cdm:attachments/>
144        saxAttachments(contentHandler, program);
145        
146        // Niveau RNCP
147        // <ametys-cdm:rncpLevel/>
148        if (program instanceof Program)
149        {
150            String[] rncpLevels = program.getRncpLevel();
151            
152            if (rncpLevels.length > 0)
153            {
154                for (String rncpLevel : rncpLevels)
155                {
156                    String rncpValue = _refTableHelper.getItemCDMfrValue(rncpLevel, true);
157                    String rncpLabel = _refTableHelper.getItemLabel(rncpLevel, program.getLanguage());
158                    
159                    attrs = new AttributesImpl();
160                    attrs.addCDATAAttribute("value", rncpValue);
161                    XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + AbstractProgram.RNCP_LEVEL, attrs, rncpLabel);
162                }
163            }
164            else
165            {
166                XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + AbstractProgram.RNCP_LEVEL);
167            }
168        }
169        
170        // RNCP code
171        // <ametys-cdm:rncpCode>Code RNCP</ametys-cdm:rncpCode>
172        for (String rncpCode : program.getRncpCode())
173        {
174            if (StringUtils.isNotEmpty(rncpCode))
175            {
176                XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + "rncpCode", rncpCode);
177            }
178        }
179        
180        // Niveau(x) d'entrée
181        // <ametys-cdm:educationEntryLevel code="code">Label</ametys-cdm:educationEntryLevel>
182        _saxMultipleEnumeration(contentHandler, program.getEducationLevelEntry(), AbstractProgram.EDUCATION_ENTRY_LEVEL, program.getLanguage());
183        
184        // Campus
185        // <ametys-cdm:campus code="code">Label</ametys-cdm:campus>
186        _saxMultipleEnumeration(contentHandler, program.getCampus(), AbstractProgram.CAMPUS, program.getLanguage());
187        
188        // Lieu(x) à l'étranger
189        // <ametys-cdm:foreignPlace code="code">Label</ametys-cdm:foreignPlace>
190        _saxMultipleEnumeration(contentHandler, program.getForeignPlace(), AbstractProgram.FOREIGN_PLACE, program.getLanguage());
191        
192        // Champ(s) de formation
193        // <ametys-cdm:programField code="code">Label</ametys-cdm:programField>
194        _saxMultipleEnumeration(contentHandler, program.getProgramField(), AbstractProgram.PROGRAM_FIELD, program.getLanguage());
195        
196        // Disciplines
197        // <ametys-cdm:disciplines code="code">Label</ametys-cdm:disciplines>
198        _saxMultipleEnumeration(contentHandler, program.getDisciplines(), AbstractProgram.DISCIPLINES, program.getLanguage());
199        
200        // Poursuite d'études dans l'établissement
201        // <ametys-cdm:furtherStudyPrograms code="cdm-code">Label</ametys-cdm:furtherStudyPrograms>
202        for (String furtherStudyProgramId : program.getFurtherStudyPrograms())
203        {
204            AbstractProgram furtherStudyProgram  = _resolver.resolveById(furtherStudyProgramId);
205            attrs = new AttributesImpl();
206            attrs.addCDATAAttribute("code", furtherStudyProgram.getCdmCode());
207            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + AbstractProgram.FURTHER_STUDY_PROGRAMS, attrs, furtherStudyProgram.getTitle());
208        }
209        
210        // Secteurs d'activité
211        // <ametys-cdm:sectors code="code">Label</ametys-cdm:sectors>
212        _saxMultipleEnumeration(contentHandler, program.getSectors(), AbstractProgram.SECTORS, program.getLanguage());
213        
214        // Ouvert en alternance
215        // <ametys-cdm:apprenticeshipOpen />
216        if (program.isApprenticeshipOpen())
217        {
218            XMLUtils.createElement(contentHandler, AbstractProgram.APPRENTICESHIP_OPEN);
219        }
220        
221        // Rythme d'alternance
222        // <ametys-cdm:apprenticeshipPeriod>...</ametys-cdm:apprenticeshipPeriod>
223        DataContext apprenticeshipPeriodContext = RepositoryDataContext.newInstance()
224                                                                       .withObject(program)
225                                                                       .withDataPath(AbstractProgram.APPRENTICESHIP_PERIOD);
226        CDMHelper.richText2CDM(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + AbstractProgram.APPRENTICESHIP_PERIOD, program.getApprenticeshipPeriod(), apprenticeshipPeriodContext, _sourceResolver);
227        
228        // Modalitités d'alternance
229        // <ametys-cdm:apprenticeshipModalities>...</ametys-cdm:apprenticeshipModalities>
230        DataContext apprenticeshipModalitiesContext = RepositoryDataContext.newInstance()
231                                                                       .withObject(program)
232                                                                       .withDataPath(AbstractProgram.APPRENTICESHIP_MODALITIES);
233        CDMHelper.richText2CDM(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + AbstractProgram.APPRENTICESHIP_MODALITIES, program.getApprenticeshipModalities(), apprenticeshipModalitiesContext, _sourceResolver);
234        
235        // Type(s) de contrat
236        // <ametys-cdm:apprenticeshipContract code="code">Label</ametys-cdm:apprenticeshipContract>
237        _saxMultipleEnumeration(contentHandler, program.getApprenticeshipContract(), AbstractProgram.APPRENTICESHIP_CONTRACT, program.getLanguage());
238        
239        // Formation internationale
240        // <ametys-cdm:internationalEducation code="code">Label</ametys-cdm:internationalEducation>
241        _saxMultipleEnumeration(contentHandler, program.getInternationalEducation(), AbstractProgram.INTERNATIONAL_EDUCATION, program.getLanguage());
242        
243        // Dimension internationale
244        // <ametys-cdm:internationalDimension>...</ametys-cdm:internationalDimension>
245        DataContext internationalDimensionContext = RepositoryDataContext.newInstance()
246                                                                         .withObject(program)
247                                                                         .withDataPath(AbstractProgram.INTERNATIONAL_DIMENSION);
248        CDMHelper.richText2CDM(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + AbstractProgram.INTERNATIONAL_DIMENSION, program.getInternationalDimension(), internationalDimensionContext, _sourceResolver);
249        
250        // Géolocalisation
251        // <ametys-cdm:disciplines code="code">Label</ametys-cdm:disciplines>
252        Geocode geocode = program.getGeocode();
253        if (geocode != null)
254        {
255            Double latitude = geocode.getLatitude();
256            Double longitude = geocode.getLongitude();
257            if (latitude != null && longitude != null)
258            {
259                attrs = new AttributesImpl();
260                attrs.addCDATAAttribute("latitude", String.valueOf(latitude));
261                attrs.addCDATAAttribute("longitude", String.valueOf(longitude));
262                XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + AbstractProgram.GEOCODE, attrs);
263            }
264        }
265        
266        // Autres structures partenaires
267        DataContext otherPartnersContext = RepositoryDataContext.newInstance()
268                                                                .withObject(program)
269                                                                .withDataPath(AbstractProgram.OTHER_PARTNERS);
270        CDMHelper.richText2CDM(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + AbstractProgram.OTHER_PARTNERS, program.getOtherPartners(), otherPartnersContext, _sourceResolver);
271        
272        // Certifications possibles
273        // <ametys-cdm:disciplines code="code">Label</ametys-cdm:disciplines>
274        _saxMultipleEnumeration(contentHandler, program.getAvailableCertification(), AbstractProgram.AVAILABLE_CERTIFICATION, program.getLanguage());
275        
276        // Autre contact
277        // <ametys-cdm:otherContact>...</ametys-cdm:otherContact>
278        RichText otherContact = program.getValue(AbstractProgram.OTHER_CONTACT, false, null);
279        if (otherContact != null)
280        {
281            DataContext otherContactContext = RepositoryDataContext.newInstance()
282                                                                   .withObject(program)
283                                                                   .withDataPath(AbstractProgram.OTHER_CONTACT);
284            CDMHelper.richText2CDM(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + AbstractProgram.OTHER_CONTACT, otherContact, otherContactContext, _sourceResolver);
285        }
286    }
287    
288    /**
289     * SAX a multiple metadata based on an reference table.
290     * &lt;ametys-cdm:tagName code="code"&gt;Label&lt;/ametys-cdm:tagName&gt;
291     * @param contentHandler The handler
292     * @param values Values to SAX
293     * @param tagName The tag name
294     * @param lang The language to translate the label
295     * @throws SAXException if an error occurs
296     */
297    private void _saxMultipleEnumeration(ContentHandler contentHandler, String[] values, String tagName, String lang) throws SAXException
298    {
299        for (String value : values)
300        {
301            _saxSingleEnumeration(contentHandler, value, tagName, lang);
302        }
303    }
304    
305    private void _saxSingleEnumeration(ContentHandler contentHandler, String value, String tagName, String lang) throws SAXException
306    {
307        String code = _refTableHelper.getItemCDMfrValue(value, true);
308        String label = _refTableHelper.getItemLabel(value, lang);
309        
310        AttributesImpl attrs = new AttributesImpl();
311        attrs.addCDATAAttribute("code", code);
312        XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + tagName, attrs, label);
313    }
314    
315    /**
316     * Sax training and internship informations.
317     * @param contentHandler the receiving contentHandler.
318     * @param program the abstract program (common for program and subprogram)
319     * @throws SAXException if an error occurs during CDM processing.
320     */
321    protected void saxTraining(ContentHandler contentHandler, AbstractProgram<? extends ProgramFactory> program) throws SAXException
322    {
323        XMLUtils.startElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + "training");
324        
325        String internship = program.getInternship();
326        if (!internship.isEmpty())
327        {
328            AttributesImpl attrs = new AttributesImpl();
329            attrs.addCDATAAttribute("value", _refTableHelper.getItemCDMfrValue(internship, true));
330            String internshipLabel = _refTableHelper.getItemLabel(internship, program.getLanguage());
331            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + "trainingInfo", attrs, internshipLabel);
332        }
333        else
334        {
335            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + "trainingInfo");
336        }
337        
338        XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + "trainingDuration", program.getInternshipDuration());
339        
340        String internshipAbroad = program.getInternshipAbroad();
341        if (!internshipAbroad.isEmpty())
342        {
343            AttributesImpl attrs = new AttributesImpl();
344            attrs.addCDATAAttribute("value", _refTableHelper.getItemCDMfrValue(internshipAbroad, true));
345            String internshipLabel = _refTableHelper.getItemLabel(internshipAbroad, program.getLanguage());
346            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + "trainingAbroadInfo", attrs, internshipLabel);
347        }
348        else
349        {
350            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + "trainingAbroadInfo");
351        }
352        XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + "trainingAbroadDuration", program.getAbroadInternshipDuration());
353
354        if (program.hasValue(AbstractProgram.INTERNSHIP_DESCRIPTION))
355        {
356            ModelAwareRepeater internshipDescriptions = program.getRepeater(AbstractProgram.INTERNSHIP_DESCRIPTION);
357            if (internshipDescriptions.getSize() > 0)
358            {
359                XMLUtils.startElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + "trainingDetails");
360                for (ModelAwareRepeaterEntry internshipDescription : internshipDescriptions.getEntries())
361                {
362                    XMLUtils.startElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + "trainingDetail");
363                    XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + "title", internshipDescription.getValue(AbstractProgram.INTERNSHIP_DESCRIPTION_TITLE, false, StringUtils.EMPTY));
364                    XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + "duration", internshipDescription.getValue(AbstractProgram.INTERNSHIP_DESCRIPTION_DURATION, false, StringUtils.EMPTY));
365
366                    for (String value : internshipDescription.getValue(AbstractProgram.INTERNSHIP_DESCRIPTION_PERIOD, false, ArrayUtils.EMPTY_STRING_ARRAY))
367                    {
368                        AttributesImpl attrs = new AttributesImpl();
369                        attrs.addCDATAAttribute("code", value);
370                        XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + "period", attrs, _monthEnumerator.getLabel(value, program.getLanguage()));
371                    }
372                    
373                    StringBuilder dataPath = new StringBuilder(AbstractProgram.INTERNSHIP_DESCRIPTION)
374                            .append("[")
375                            .append(internshipDescription.getPosition())
376                            .append("]")
377                            .append(ModelItem.ITEM_PATH_SEPARATOR)
378                            .append(AbstractProgram.INTERNSHIP_DESCRIPTION_KIND);
379                    DataContext internshipDescriptionKindContext = RepositoryDataContext.newInstance()
380                                                                                        .withObject(program)
381                                                                                        .withDataPath(dataPath.toString());
382                    CDMHelper.richText2CDM(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + AbstractProgram.INTERNSHIP_DESCRIPTION_KIND, internshipDescription.getValue(AbstractProgram.INTERNSHIP_DESCRIPTION_KIND), internshipDescriptionKindContext, _sourceResolver);
383                    
384                    XMLUtils.endElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + "trainingDetail");
385                }
386                XMLUtils.endElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + "trainingDetails");
387            }
388        }
389
390        XMLUtils.endElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + "training");
391    }
392    
393    /**
394     * Sax partner schools (&lt;ametys-cdm:partnerSchools/&gt;).
395     * @param contentHandler the receiving contentHandler.
396     * @param program the abstract program (common for program and subprogram)
397     * @throws SAXException if an error occurs during CDM processing.
398     */
399    protected void saxPartnerSchools(ContentHandler contentHandler, AbstractProgram<? extends ProgramFactory> program) throws SAXException
400    {
401        if (program.hasValue(AbstractProgram.PARTNER_SCHOOLS))
402        {
403            XMLUtils.startElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + AbstractProgram.PARTNER_SCHOOLS);
404            
405            ModelAwareRepeater partnerSchools = program.getRepeater(AbstractProgram.PARTNER_SCHOOLS);
406            for (ModelAwareRepeaterEntry partnerSchool : partnerSchools.getEntries())
407            {
408                String linkUrl = partnerSchool.getValue(AbstractProgram.PARTNER_SCHOOLS_LINK_URL, false, StringUtils.EMPTY);
409                String linkLabel = partnerSchool.getValue(AbstractProgram.PARTNER_SCHOOLS_LINK_LABEL, false, StringUtils.EMPTY);
410                
411                if (!linkUrl.isEmpty() || !linkLabel.isEmpty())
412                {
413                    XMLUtils.startElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + "partnerSchool");
414                    XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + "linkUrl", linkUrl);
415                    XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + "linkLabel", linkLabel);
416                    XMLUtils.endElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + "partnerSchool");
417                }
418            }
419            
420            XMLUtils.endElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + AbstractProgram.PARTNER_SCHOOLS);
421        }
422    }
423
424    /**
425     * Sax partner laboratories (&lt;ametys-cdm:partnerLaboratories/&gt;).
426     * @param contentHandler the receiving contentHandler.
427     * @param program the abstract program (common for program and subprogram)
428     * @throws SAXException if an error occurs during CDM processing.
429     */
430    protected void saxPartnerLaboratories(ContentHandler contentHandler, AbstractProgram<? extends ProgramFactory> program) throws SAXException
431    {
432        if (program.hasValue(AbstractProgram.PARTNER_LABORATORIES))
433        {
434            XMLUtils.startElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + AbstractProgram.PARTNER_LABORATORIES);
435            
436            ModelAwareRepeater partnerLaboratories = program.getRepeater(AbstractProgram.PARTNER_LABORATORIES);
437            for (ModelAwareRepeaterEntry partnerLaboratory : partnerLaboratories.getEntries())
438            {
439                String linkUrl = partnerLaboratory.getValue(AbstractProgram.PARTNER_LABORATORIES_LINK_URL, false, StringUtils.EMPTY);
440                String linkLabel = partnerLaboratory.getValue(AbstractProgram.PARTNER_LABORATORIES_LINK_LABEL, false, StringUtils.EMPTY);
441                
442                if (!linkUrl.isEmpty() || !linkLabel.isEmpty())
443                {
444                    XMLUtils.startElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + "partnerLaboratory");
445                    XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + "linkUrl", linkUrl);
446                    XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + "linkLabel", linkLabel);
447                    XMLUtils.endElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + "partnerLaboratory");
448                }
449            }
450            
451            XMLUtils.endElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM +  AbstractProgram.PARTNER_LABORATORIES);
452        }
453    }
454
455    /**
456     * Sax attachments (&lt;ametys-cdm:attachments/&gt;).
457     * @param contentHandler the receiving contentHandler.
458     * @param program the abstract program (common for program and subprogram)
459     * @throws SAXException if an error occurs during CDM processing.
460     */
461    protected void saxAttachments(ContentHandler contentHandler, AbstractProgram<? extends ProgramFactory> program) throws SAXException
462    {
463        if (program.hasValue(AbstractProgram.ATTACHMENTS))
464        {
465            XMLUtils.startElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + AbstractProgram.ATTACHMENTS);
466            
467            ModelAwareRepeater attachments = program.getRepeater(AbstractProgram.ATTACHMENTS);
468            for (ModelAwareRepeaterEntry entry: attachments.getEntries())
469            {
470                if (entry.hasValue(AbstractProgram.ATTACHMENTS_ATTACHMENT))
471                {
472                    String url = null;
473                    if (ModelItemTypeConstants.FILE_ELEMENT_TYPE_ID.equals(entry.getType(AbstractProgram.ATTACHMENTS_ATTACHMENT).getId()))
474                    {
475                        File file = entry.getValue(AbstractProgram.ATTACHMENTS_ATTACHMENT);
476                        url = _getFileAbsoluteUrl(program, file, "attachments[" + entry.getPosition() + "]/attachment");
477                    }
478                    
479                    String text = entry.getValue(AbstractProgram.ATTACHMENTS_ATTACHMENT_TEXT, false, StringUtils.EMPTY);
480                    
481                    XMLUtils.startElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + "attachment");
482                    XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + "text", text);
483                    if (url != null)
484                    {
485                        XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + "href", url);
486                    }
487                    XMLUtils.endElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + "attachment");
488                }
489            }
490            
491            XMLUtils.endElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + AbstractProgram.ATTACHMENTS);
492        }
493    }
494    
495    /**
496     * Sax the acquired skills
497     * @param contentHandler the receiving contentHandler.
498     * @param course the course
499     * @throws SAXException if an error occurs during CDM processing.
500     */
501    protected void saxAcquiredSkills(ContentHandler contentHandler, Course course) throws SAXException
502    {
503        if (course.hasValue(Course.ACQUIRED_MICRO_SKILLS))
504        {
505            XMLUtils.startElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + Course.ACQUIRED_MICRO_SKILLS);
506
507            ModelAwareRepeater repeater = course.getRepeater(Course.ACQUIRED_MICRO_SKILLS);
508            for (ModelAwareRepeaterEntry entry : repeater.getEntries())
509            {
510                ContentValue programValue = entry.getValue(Course.ACQUIRED_MICRO_SKILLS_PROGRAM, false, null);
511                if (programValue != null)
512                {
513                    XMLUtils.startElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + "acquiredMicroSkillByProgram");
514                    
515                    Optional<ModifiableContent> programIfExists = programValue.getContentIfExists();
516                    if (programIfExists.isPresent())
517                    {
518                        Program program = (Program) programIfExists.get();
519                        AttributesImpl attrs = new AttributesImpl();
520                        attrs.addCDATAAttribute("id", program.getId());
521                        attrs.addCDATAAttribute("code", program.getCode());
522                        String label = program.getTitle();
523                        attrs.addCDATAAttribute("title", label);
524                        
525                        attrs.addCDATAAttribute(CDMFRTagsConstants.ATTRIBUTE_ID_REF, program.getCDMId());
526                        
527                        XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + "program", attrs, label);
528                    }
529                    
530                    ContentValue[] skills = entry.getValue(Course.ACQUIRED_MICRO_SKILLS_SKILLS, false, null);
531                    if (skills != null)
532                    {
533                        for (ContentValue skill : skills)
534                        {
535                            Optional<ModifiableContent> skillContent = skill.getContentIfExists();
536                            if (skillContent.isPresent())
537                            {
538                                saxAcquiredSkill(contentHandler, skillContent.get());
539                            }
540                        }
541                    }
542
543                    XMLUtils.endElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + "acquiredMicroSkillByProgram");
544
545                }
546            }
547
548            XMLUtils.endElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + Course.ACQUIRED_MICRO_SKILLS);
549        }
550    }
551    
552    /**
553     * Sax the acquired skill
554     * @param contentHandler the receiving contentHandler.
555     * @param skill The skill
556     * @throws SAXException if an error occurs during CDM processing.
557     */
558    protected void saxAcquiredSkill(ContentHandler contentHandler, Content skill) throws SAXException
559    {
560        AttributesImpl attrs = new AttributesImpl();
561        attrs.addCDATAAttribute("id", skill.getId());
562        attrs.addCDATAAttribute("code", skill.getValue("code"));
563        attrs.addCDATAAttribute("skillCode", skill.getValue("skillCode"));
564        String label = skill.getTitle();
565        attrs.addCDATAAttribute("title", label);
566        XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + "skill", attrs, label);
567    }
568    
569    @Override
570    public void program2CDM(ContentHandler contentHandler, Program program, Set<String> persons, Set<String> orgUnits) throws SAXException
571    {
572        // Catalogue de formation
573        String catalogCode = program.getCatalog();
574        if (StringUtils.isNotEmpty(catalogCode))
575        {
576            AttributesImpl attrs = new AttributesImpl();
577            attrs.addCDATAAttribute("value", catalogCode);
578            
579            Catalog catalog = _catalogsManager.getCatalog(catalogCode);
580            String catalogLabel = catalog != null ? catalog.getTitle() : catalogCode;
581            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + "catalog", attrs, catalogLabel);
582        }
583    }
584    
585    public void subProgram2CDM(ContentHandler contentHandler, SubProgram subProgram, Set<String> persons, Set<String> orgUnits) throws SAXException
586    {
587        // nothing specific to subprograms in the default Ametys model
588    }
589
590    @Override
591    public void course2CDM(ContentHandler contentHandler, Course course, Set<String> persons, Set<String> orgUnits) throws SAXException
592    {
593        // Catalogue de formation
594        String catalogCode = course.getCatalog();
595        if (StringUtils.isNotEmpty(catalogCode))
596        {
597            AttributesImpl attrs = new AttributesImpl();
598            attrs.addCDATAAttribute("value", catalogCode);
599            
600            Catalog catalog = _catalogsManager.getCatalog(catalogCode);
601            String catalogLabel = catalog != null ? catalog.getTitle() : catalogCode;
602            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + "catalog", attrs, catalogLabel);
603        }
604        
605        // Ouvert aux étudiants en échange
606        // <ametys-cdm:openToExchangeStudents />
607        if (course.isOpenToExchangeStudents())
608        {
609            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + Course.OPEN_TO_EXCHANGE_STUDENTS);
610        }
611        
612        if (ODFSkillsHelper.isSkillsEnabled())
613        {
614            // Compétence acquises
615            saxAcquiredSkills(contentHandler, course);
616        }
617        
618        // Course parts
619        for (CoursePart coursePart : course.getCourseParts())
620        {
621            _coursePart2CDM(contentHandler, coursePart);
622        }
623    }
624    
625    /**
626     * Sax a {@link CoursePart} with the following structure :
627     * &lt;ametys-cdm:coursePart @ametys-cdm:code="code"&gt;
628     *     &lt;ametys-cdm:title&gt;Title&lt;/ametys-cdm:title&gt;
629     *     &lt;ametys-cdm:nature&gt;Nature CDM-fr value or code&lt;/ametys-cdm:nature&gt;
630     *     &lt;ametys-cdm:nbHours&gt;Number of hours&lt;/ametys-cdm:nbHours&gt;
631     *     &lt;ametys-cdm:courseHolder&gt;CDM ID of the course holder&lt;/ametys-cdm:courseHolder&gt;
632     * &lt;/ametys-cdm:coursePart&gt;
633     * @param contentHandler The content handler
634     * @param coursePart The {@link CoursePart} to sax
635     * @throws SAXException if an error occurs
636     */
637    protected void _coursePart2CDM(ContentHandler contentHandler, CoursePart coursePart) throws SAXException
638    {
639        AttributesImpl attrs = new AttributesImpl();
640        attrs.addCDATAAttribute("code", coursePart.getCode());
641        
642        XMLUtils.startElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + "coursePart", attrs);
643        XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + Content.ATTRIBUTE_TITLE, coursePart.getTitle());
644        String nature = _refTableHelper.getItemCDMfrValue(coursePart.getNature(), true);
645        XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + CoursePart.NATURE, nature);
646        XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + CoursePart.NB_HOURS, String.valueOf(coursePart.getNumberOfHours()));
647        
648        Course courseHolder = coursePart.getCourseHolder();
649        if (courseHolder != null)
650        {
651            // it is not a normal case, but sometimes, the courseHolder references a not existing course, and we don't want to fail here
652            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + CoursePart.COURSE_HOLDER, courseHolder.getCDMId());
653        }
654        
655        XMLUtils.endElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + "coursePart");
656    }
657
658    @Override
659    public void orgunit2CDM(ContentHandler contentHandler, OrgUnit orgunit) throws SAXException
660    {
661        // nothing
662    }
663
664    @Override
665    public void person2CDM(ContentHandler contentHandler, Person person) throws SAXException
666    {
667        // Titre du contenu (à ne pas confondre avec personTitle)
668        String title = person.getTitle();
669        if (StringUtils.isNotEmpty(title))
670        {
671            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + "title", title);
672        }
673    }
674
675    @Override
676    public void container2CDM(ContentHandler contentHandler, Container container, Set<String> persons, Set<String> orgUnits) throws SAXException
677    {
678        // Période
679        // <ametys-cdm:period code="code">Label</ametys-cdm:period>
680        String period = container.getPeriod();
681        if (StringUtils.isNotEmpty(period))
682        {
683            String code = _refTableHelper.getItemCDMfrValue(period, true);
684            String label = _refTableHelper.getItemLabel(period, container.getLanguage());
685    
686            AttributesImpl attrs = new AttributesImpl();
687            attrs.addCDATAAttribute("code", code);
688            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + Container.PERIOD, attrs, label);
689        }
690    }
691}