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