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