001/*
002 *  Copyright 2010 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.program;
017
018import java.time.LocalDate;
019import java.util.ArrayList;
020import java.util.Arrays;
021import java.util.HashSet;
022import java.util.LinkedHashMap;
023import java.util.List;
024import java.util.Map;
025import java.util.Optional;
026import java.util.Set;
027import java.util.stream.Collectors;
028
029import javax.jcr.Node;
030
031import org.apache.commons.lang3.ArrayUtils;
032import org.apache.commons.lang3.StringUtils;
033
034import org.ametys.cms.data.ContentDataHelper;
035import org.ametys.cms.data.ContentValue;
036import org.ametys.cms.data.Geocode;
037import org.ametys.cms.data.RichText;
038import org.ametys.odf.courselist.CourseList;
039import org.ametys.odf.courselist.CourseListContainer;
040import org.ametys.odf.data.EducationalPath;
041import org.ametys.plugins.repository.data.holder.group.ModelAwareRepeater;
042import org.ametys.plugins.repository.data.holder.group.ModelAwareRepeaterEntry;
043import org.ametys.runtime.model.ElementDefinition;
044import org.ametys.runtime.model.exception.UndefinedItemPathException;
045
046/**
047 * Abstract common superclass for {@link Program} and {@link SubProgram}.
048 * @param <F> The actual type of the factory
049 */
050public abstract class AbstractProgram<F extends ProgramFactory> extends AbstractTraversableProgramPart<F> implements CourseListContainer
051{
052    // ------------------------------------------------//
053    //
054    // PROGRAM ATTRIBUTES
055    //
056    // -----------------------------------------------//
057    /** Constants for attribute 'degree' */
058    public static final String DEGREE = "degree";
059    
060    /** Constants for attribute 'certified' */
061    public static final String CERTIFIED = "certified";
062
063    /** Constants for attribute 'domain' */
064    public static final String DOMAIN = "domain";
065    
066    /** Constants for attribute 'mention' */
067    public static final String MENTION = "mention";
068
069    /** Constants for attribute 'speciality' */
070    public static final String SPECIALITY = "speciality";
071
072    /** Constants for attribute 'educationLevel' */
073    public static final String LEVEL = "educationLevel";
074
075    /** Constants for attribute 'duration' */
076    public static final String DURATION = "duration";
077    
078    /** Constants for attribute 'educationLanguage' */
079    public static final String EDUC_LANGUAGE = "educationLanguage";
080
081    /** Constants for attribute 'presentation' */
082    public static final String PRESENTATION = "presentation";
083
084    /** Constants for attribute 'objectives' */
085    public static final String OBJECTIVES = "objectives";
086
087    /** Constants for attribute 'qualification' */
088    public static final String QUALIFICATION = "qualification";
089
090    /** Constants for attribute 'teachingOrganization' */
091    public static final String TEACHING_ORGANIZATION = "teachingOrganization";
092    
093    /** Constants for attribute 'apprenticeshipModalities' */
094    public static final String APPRENTICESHIP_MODALITIES = "apprenticeshipModalities";
095
096    /** Constants for attribute 'accessCondition' */
097    public static final String ACCESS_CONDITION = "accessCondition";
098
099    /** Constants for attribute 'neededPrerequisite' */
100    public static final String NEEDED_PREREQUISITE = "neededPrerequisite";
101
102    /** Constants for attribute 'recommendedPrerequisite' */
103    public static final String RECOMMENDED_PREREQUISITE = "recommendedPrerequisite";
104
105    /** Constants for attribute 'expectedResults' */
106    public static final String EXPECTED_RESULTS = "expectedResults";
107    
108    /** Constants for attribute 'furtherStudy' */
109    public static final String FURTHER_STUDY = "furtherStudy";
110
111    /** Constants for attribute 'studyAbroad' */
112    public static final String STUDY_ABROAD = "studyAbroad";
113
114    /** Constants for attribute 'targetGroup' */
115    public static final String TARGET_GROUP = "targetGroup";
116
117    /** Constants for attribute 'jobOpportunities' */
118    public static final String JOB_OPPORTUNITIES = "jobOpportunities";
119
120    /** Constants for attribute 'trainingStrategy' */
121    public static final String TRAINING_STRATEGY = "trainingStrategy";
122    
123    /** Constants for attribute 'knowledgeCheck' */
124    public static final String KNOWLEDGE_CHECK = "knowledgeCheck";
125
126    /** Constants for attribute 'universalAdjustment' */
127    public static final String UNIVERSAL_ADJUSTMENT = "universalAdjustment";
128
129    /** Constants for attribute 'certifying' */
130    public static final String CERTIFYING = "certifying";
131
132    /** Constants for attribute 'expenses' */
133    public static final String EXPENSES = "expenses";
134
135    /** Constants for attribute 'additionalInformations' */
136    public static final String ADDITIONNAL_INFORMATIONS = "additionalInformations";
137
138    /** Constants for attribute 'jointOrgUnit' */
139    public static final String JOINT_ORGUNIT = "jointOrgUnit";
140
141    /** Constants for attribute 'place' */
142    public static final String PLACE = "place";
143
144    /** Constants for attribute 'distanceLearning' */
145    public static final String DISTANCE_LEARNING = "distanceLearning";
146
147    /** Constants for attribute 'educationKind' */
148    public static final String EDUCATION_KIND = "educationKind";
149
150    /** Constant for attribute 'formofteachingOrg' */
151    public static final String FORM_OF_TEACHING_ORG = "formofteachingOrg";
152
153    /** Constants for attribute 'ects' */
154    public static final String ECTS = "ects";
155
156    /** Constants for attribute 'internship' */
157    public static final String INTERNSHIP = "internship";
158
159    /** Constants for attribute 'internshipDuration' */
160    public static final String INTERNSHIP_DURATION = "internshipDuration";
161
162    /** Constants for attribute 'internshipAbroad' */
163    public static final String INTERNSHIP_ABROAD = "internshipAbroad";
164
165    /** Constants for attribute 'internshipAbroadDuration' */
166    public static final String INTERNSHIP_ABROAD_DURATION = "internshipAbroadDuration";
167    
168    /** Constants for attribute 'registrationStart' */
169    public static final String REGISTRATION_START = "registrationStart";
170    
171    /** Constants for attribute 'registrationDeadline' */
172    public static final String REGISTRATION_DEADLINE = "registrationDeadline";
173    
174    /** Constants for attribute 'teachingStart' */
175    public static final String TEACHING_START = "teachingStart";
176    
177    /** Constants for attribute 'teachingEnd' */
178    public static final String TEACHING_END = "teachingEnd";
179    
180    /** Constants for attribute 'partnerSchools' */
181    public static final String PARTNER_SCHOOLS = "partnerSchools";
182
183    /** Constants for attribute 'partnerSchools/linkUrl' */
184    public static final String PARTNER_SCHOOLS_LINK_URL = "linkUrl";
185
186    /** Constants for attribute 'partnerSchools/linkLabel' */
187    public static final String PARTNER_SCHOOLS_LINK_LABEL = "linkLabel";
188
189    /** Constants for attribute 'partnerLaboratories' */
190    public static final String PARTNER_LABORATORIES = "partnerLaboratories";
191
192    /** Constants for attribute 'partnerLaboratories/linkUrl' */
193    public static final String PARTNER_LABORATORIES_LINK_URL = "linkUrl";
194
195    /** Constants for attribute 'partnerLaboratories/linkLabel' */
196    public static final String PARTNER_LABORATORIES_LINK_LABEL = "linkLabel";
197
198    /** Constants for attribute 'rncpCode' */
199    public static final String RNCP_CODE = "rncpCode";
200    
201    /** Constants for attribute 'rncpLevel' */
202    public static final String RNCP_LEVEL = "rncpLevel";
203
204    /** Constants for attribute 'siseCode' */
205    public static final String SISE_CODE = "siseCode";
206
207    /** Constants for attribute 'Cite 97' */
208    public static final String CITE97_CODE = "cite97Code";
209
210    /** Constants for attribute 'erasmusCode' */
211    public static final String ERASMUS_CODE = "erasmusCode";
212    
213    /** Constants for attribute 'erasmusCode' */
214    public static final String DGESIP_CODE = "dgesipCode";
215    
216    /** Constants for attribute 'formacode' */
217    public static final String FORMACODE = "formacode";
218    
219    /** Constants for attribute 'romeCode' */
220    public static final String ROME_CODE = "romeCode";
221    
222    /** Constants for attribute 'fapCode' */
223    public static final String FAP_CODE = "fapCode";
224
225    /** Constants for attribute 'nsfCode' */
226    public static final String NSF_CODE = "nsfCode";
227
228    /** Constants for attribute 'programWebSite' */
229    public static final String PROGRAM_WEBSITE = "programWebSite";
230    
231    /** Constants for attribute 'programWebSiteLabel' */
232    public static final String PROGRAM_WEBSITE_LABEL = "programWebSiteLabel";
233
234    /** Constants for attribute 'programWebSiteUrl' */
235    public static final String PROGRAM_WEBSITE_URL = "programWebSiteUrl";
236
237    /** Constants for attribute 'attachments' */
238    public static final String ATTACHMENTS = "attachments";
239
240    /** Constants for attribute 'attachments/attachment' */
241    public static final String ATTACHMENTS_ATTACHMENT = "attachment";
242
243    /** Constants for attribute 'attachments/attachment-text' */
244    public static final String ATTACHMENTS_ATTACHMENT_TEXT = "attachment-text";
245
246    /** Constants for attribute 'numberOfStudents' */
247    public static final String NUMBER_OF_STUDENTS = "numberOfStudents";
248    
249    /** Constants for attribute 'successRate' */
250    public static final String SUCCESSRATE = "successRate";
251
252    /** Constants for attribute 'reorientation' */
253    public static final String REORIENTATION = "reorientation";
254    
255    /** Constants for attribute 'keywords' */
256    public static final String KEYWORDS = "keywords";
257    
258    /** Constants for attribute 'educationEntryLevel' */
259    public static final String EDUCATION_ENTRY_LEVEL = "educationEntryLevel";
260    
261    /** Constants for attribute 'mandatoryEntryLevel' */
262    public static final String MANDATORY_ENTRY_LEVEL = "mandatoryEntryLevel";
263    
264    /** Constants for attribute 'programField' */
265    public static final String PROGRAM_FIELD = "programField";
266    
267    /** Constants for attribute 'availableCertification' */
268    public static final String AVAILABLE_CERTIFICATION = "availableCertification";
269    
270    /** Constants for attribute 'disciplines' */
271    public static final String DISCIPLINES = "disciplines";
272    
273    /** Constants for attribute 'sectors' */
274    public static final String SECTORS = "sectors";
275    
276    /** Constants for attribute 'internshipOpen' */
277    public static final String INTERNSHIP_OPEN = "internshipOpen";
278
279    /** Constants for attribute 'internshipDescription' */
280    public static final String INTERNSHIP_DESCRIPTION = "internshipDescription";
281    
282    /** Constants for attribute 'internshipDescription/title' */
283    public static final String INTERNSHIP_DESCRIPTION_TITLE = "title";
284    
285    /** Constants for attribute 'internshipDescription/duration' */
286    public static final String INTERNSHIP_DESCRIPTION_DURATION = "duration";
287    
288    /** Constants for attribute 'internshipDescription/period' */
289    public static final String INTERNSHIP_DESCRIPTION_PERIOD = "period";
290    
291    /** Constants for attribute 'internshipDescription/kind' */
292    public static final String INTERNSHIP_DESCRIPTION_KIND = "kind";
293    
294    /** Constants for attribute 'apprenticeshipOpen' */
295    public static final String APPRENTICESHIP_OPEN = "apprenticeshipOpen";
296    
297    /** Constants for attribute 'apprenticeshipPeriod' */
298    public static final String APPRENTICESHIP_PERIOD = "apprenticeshipPeriod";
299    
300    /** Constants for attribute 'apprenticeshipContract' */
301    public static final String APPRENTICESHIP_CONTRACT = "apprenticeshipContract";
302    
303    /** Constants for attribute 'internationalEducation' */
304    public static final String INTERNATIONAL_EDUCATION = "internationalEducation";
305    
306    /** Constants for attribute 'internationalDimension' */
307    public static final String INTERNATIONAL_DIMENSION = "internationalDimension";
308    
309    /** Constants for attribute 'geoCode' */
310    public static final String GEOCODE = "geoCode";
311    
312    /** Constants for attribute 'otherPartners' */
313    public static final String OTHER_PARTNERS = "otherPartners";
314    
315    /** Constants for attribute 'otherPartners' */
316    public static final String OTHER_CONTACT = "otherContact";
317    
318    /** Constants for attribute 'campus' */
319    public static final String CAMPUS = "campus";
320    
321    /** Constants for attribute 'foreignPlace' */
322    public static final String FOREIGN_PLACE = "foreignPlace";
323    
324    /** Constants for attribute 'inscription' */
325    public static final String INSCRIPTION = "inscription";
326    
327    /** Constants for attribute 'furtherStudyPrograms' */
328    public static final String FURTHER_STUDY_PROGRAMS = "furtherStudyPrograms";
329    
330    /**
331     * References
332     */
333       
334    /** Constants for attribute 'contacts' */
335    public static final String CONTACTS = "contacts";
336    
337    /** Constants for attribute 'contacts/role' */
338    public static final String CONTACTS_ROLE = "role";
339    
340    /** Constants for attribute 'contacts/persons' */
341    public static final String CONTACTS_PERSONS = "persons";
342
343    private String _contextPath;
344
345    private List<EducationalPath> _currentEducationalPaths;
346
347    // ------------------------------------------------//
348    //
349    // PROGRAM METHODS
350    //
351    // -----------------------------------------------//
352    /**
353     * Constructor
354     * @param node The JCR node
355     * @param parentPath The parent path
356     * @param factory The factory
357     */
358    public AbstractProgram(Node node, String parentPath, F factory)
359    {
360        super(node, parentPath, factory);
361    }
362    
363    // --------------------------------------------------------------------------------------//
364    // CONTEXT
365    // --------------------------------------------------------------------------------------//
366    
367    // Méthodes utilisées lors du parcours d'une maquette uniquement, afin de contextualiser l'élément
368    // A ne pas utiliser n'importe ou ni n'importe comment
369    
370    /**
371     * Set the parent path for links and breadcrumb
372     * @param path the parent path
373     */
374    public void setContextPath (String path)
375    {
376        _contextPath = path;
377    }
378    
379    /**
380     * Get the parent path. Can be null.
381     * @return the parent path
382     */
383    public String getContextPath ()
384    {
385        return _contextPath;
386    }
387    
388    /**
389     * Set the current educational path of this abstract program
390     * @param paths the current educational path
391     */
392    public void setCurrentEducationalPaths(List<EducationalPath> paths)
393    {
394        _currentEducationalPaths = paths;
395    }
396    
397    /**
398     * Get the current educational path of this abstract program
399     * @return the current educational path
400     */
401    public List<EducationalPath> getCurrentEducationalPaths()
402    {
403        return _currentEducationalPaths;
404    }
405    
406    // --------------------------------------------------------------------------------------//
407    // CONTACTS
408    // --------------------------------------------------------------------------------------//
409
410    /**
411     * Return the list of Persons in charge binded to this program
412     * @return a list of roles and UUID
413     */
414    public Set<String> getContacts()
415    {
416        Set<String> contactIds = new HashSet<>();
417        
418        ModelAwareRepeater contacts = getRepeater(CONTACTS);
419        if (contacts != null)
420        {
421            for (ModelAwareRepeaterEntry contactEntry : contacts.getEntries())
422            {
423                // Remove empty values
424                contactIds.addAll(ContentDataHelper.getContentIdsStreamFromMultipleContentData(contactEntry, CONTACTS_PERSONS)
425                        .filter(contentId -> !contentId.isEmpty())
426                        .collect(Collectors.toSet()));
427            }
428        }
429
430        return contactIds;
431    }
432    
433    /**
434     * Return the list of Persons in charge binded to this program
435     * @return a list of roles and UUID
436     */
437    public Map<String, List<String>> getContactsByRole()
438    {
439        Map<String, List<String>> contactsByRole = new LinkedHashMap<>();
440        
441        ModelAwareRepeater contacts = getRepeater(CONTACTS);
442        if (contacts != null)
443        {
444            for (ModelAwareRepeaterEntry contactEntry : contacts.getEntries())
445            {
446                // Remove empty values
447                List<String> persons = ContentDataHelper.getContentIdsStreamFromMultipleContentData(contactEntry, CONTACTS_PERSONS)
448                                                    .filter(contentId -> !contentId.isEmpty())
449                                                    .collect(Collectors.toList());
450                if (!persons.isEmpty())
451                {
452                    String role = ContentDataHelper.getContentIdFromContentData(contactEntry, CONTACTS_ROLE);
453                    List<String> personList = contactsByRole.getOrDefault(role, new ArrayList<>());
454                    personList.addAll(persons);
455                    contactsByRole.put(role, personList);
456                }
457            }
458        }
459
460        return contactsByRole;
461    }
462
463    // --------------------------------------------------------------------------------------//
464    // GETTERS AND SETTERS
465    // --------------------------------------------------------------------------------------//
466    /**
467     * Returns <code>true</code> if the {@link AbstractProgram} is certified
468     * @return <code>true</code> if certified or default value (true in the kernel) if not defined
469     */
470    public boolean isCertified()
471    {
472        return getValue(CERTIFIED, true, true);
473    }
474    
475    /**
476     * Get the domain
477     * @return the domain or an empty array
478     */
479    public String[] getDomain()
480    {
481        try
482        {
483            ElementDefinition definition = (ElementDefinition) getDefinition(DOMAIN);
484            if (definition.isMultiple())
485            {
486                return ContentDataHelper.getContentIdsArrayFromMultipleContentData(this, DOMAIN);
487            }
488            else
489            {
490                return new String[] {ContentDataHelper.getContentIdFromContentData(this, DOMAIN)};
491            }
492        }
493        catch (UndefinedItemPathException e)
494        {
495            return null; // this attribute is not part of model
496        }
497        
498    }
499
500    /**
501     * Get the education presentation
502     * @return the education presentation or null if not set
503     */
504    public RichText getPresentation()
505    {
506        try
507        {
508            return getValue(PRESENTATION);
509        }
510        catch (UndefinedItemPathException e)
511        {
512            return null; // this attribute is not part of model
513        }
514    }
515
516    /**
517     * Get the education objectives
518     * @return the education objectives or null if not set
519     */
520    public RichText getObjectives()
521    {
522        try
523        {
524            return getValue(OBJECTIVES);
525        }
526        catch (UndefinedItemPathException e)
527        {
528            return null; // this attribute is not part of model
529        }
530    }
531
532    /**
533     * Get the education qualification
534     * @return the education qualification or null if not set
535     */
536    public RichText getQualification()
537    {
538        try
539        {
540            return getValue(QUALIFICATION);
541        }
542        catch (UndefinedItemPathException e)
543        {
544            return null; // this attribute is not part of model
545        }
546    }
547
548    /**
549     * Get the education organization
550     * @return the education organization or null if not set
551     */
552    public RichText getTeachingOrganization()
553    {
554        try
555        {
556            return getValue(TEACHING_ORGANIZATION);
557        }
558        catch (UndefinedItemPathException e)
559        {
560            return null; // this attribute is not part of model
561        }
562    }
563    
564    /**
565     * Get the apprenticeship modalities
566     * @return the apprenticeship modalities or null if not set
567     */
568    public RichText getApprenticeshipModalities()
569    {
570        try
571        {
572            return getValue(APPRENTICESHIP_MODALITIES);
573        }
574        catch (UndefinedItemPathException e)
575        {
576            return null; // this attribute is not part of model
577        }
578    }
579
580    /**
581     * Get the access conditions
582     * @return  the access conditions  or null if not set
583     */
584    public RichText getAccessCondition()
585    {
586        try
587        {
588            return getValue(ACCESS_CONDITION);
589        }
590        catch (UndefinedItemPathException e)
591        {
592            return null; // this attribute is not part of model
593        }
594    }
595
596    /**
597     * Get the education requirements
598     * @return the education requirements or null if not set
599     */
600    public RichText getNeededPrerequisite()
601    {
602        try
603        {
604            return getValue(NEEDED_PREREQUISITE);
605        }
606        catch (UndefinedItemPathException e)
607        {
608            return null; // this attribute is not part of model
609        }
610    }
611
612    /**
613     * Get the education recommended prerequisite
614     * @return the education recommended prerequisite or null if not set
615     */
616    public RichText getRecommendedPrerequisite()
617    {
618        try
619        {
620            return getValue(RECOMMENDED_PREREQUISITE);
621        }
622        catch (UndefinedItemPathException e)
623        {
624            return null; // this attribute is not part of model
625        }
626    }
627    
628    /**
629     * Get the education expected results
630     * @return the education expected results or null if not set
631     */
632    public RichText getExpectedResults()
633    {
634        try
635        {
636            return getValue(EXPECTED_RESULTS);
637        }
638        catch (UndefinedItemPathException e)
639        {
640            return null; // this attribute is not part of model
641        }
642    }
643
644    /**
645     * Get the education further study
646     * @return the education further study or null if not set
647     */
648    public RichText getFurtherStudy()
649    {
650        try
651        {
652            return getValue(FURTHER_STUDY);
653        }
654        catch (UndefinedItemPathException e)
655        {
656            return null; // this attribute is not part of model
657        }
658    }
659
660    /**
661     * Get the education study abroard
662     * @return the education study abroard or null if not set
663     */
664    public RichText getStudyAbroad()
665    {
666        try
667        {
668            return getValue(STUDY_ABROAD);
669        }
670        catch (UndefinedItemPathException e)
671        {
672            return null; // this attribute is not part of model
673        }
674    }
675
676    /**
677     * Get the education target
678     * @return the education target or null if not set
679     */
680    public RichText getTargetGroup()
681    {
682        try
683        {
684            return getValue(TARGET_GROUP);
685        }
686        catch (UndefinedItemPathException e)
687        {
688            return null; // this attribute is not part of model
689        }
690    }
691
692    /**
693     * Get the job opportunities
694     * @return the job opportunities or null if not set
695     */
696    public RichText getJobOpportunities()
697    {
698        try
699        {
700            return getValue(JOB_OPPORTUNITIES);
701        }
702        catch (UndefinedItemPathException e)
703        {
704            return null; // this attribute is not part of model
705        }
706    }
707    
708    /**
709     * Get the training strategy
710     * @return the training strategy or null if not set
711     */
712    public RichText getTrainingStrategy()
713    {
714        try
715        {
716            return getValue(TRAINING_STRATEGY);
717        }
718        catch (UndefinedItemPathException e)
719        {
720            return null; // this attribute is not part of model
721        }
722    }
723    
724    /**
725     * Get acknowledgments
726     * @return acknowledgments or null if not set
727     */
728    public RichText getKnowledgeCheck()
729    {
730        try
731        {
732            return getValue(KNOWLEDGE_CHECK);
733        }
734        catch (UndefinedItemPathException e)
735        {
736            return null; // this attribute is not part of model
737        }
738    }
739
740    /**
741     * Get universal adjustment
742     * @return universal adjustment or null if not set
743     */
744    public RichText getUniversalAdjustment()
745    {
746        try
747        {
748            return getValue(UNIVERSAL_ADJUSTMENT);
749        }
750        catch (UndefinedItemPathException e)
751        {
752            return null; // this attribute is not part of model
753        }
754    }
755    
756    /**
757     * Is certifying
758     * @return <code>true</code> if the progam is certifying
759     */
760    public boolean isCertifying()
761    {
762        try
763        {
764            return getValue(CERTIFYING, false, false);
765        }
766        catch (UndefinedItemPathException e)
767        {
768            return false; // this attribute is not part of model
769        }
770    }
771
772    /**
773     * Get the additional informations
774     * @return the additional informations or null if not set
775     */
776    public RichText getAdditionalInformations()
777    {
778        try
779        {
780            return getValue(ADDITIONNAL_INFORMATIONS);
781        }
782        catch (UndefinedItemPathException e)
783        {
784            return null; // this attribute is not part of model
785        }
786    }
787
788    /**
789     * Get the education level
790     * @return the education level or null
791     */
792    public String getEducationLevel()
793    {
794        try
795        {
796            return ContentDataHelper.getContentIdFromContentData(this, LEVEL);
797        }
798        catch (UndefinedItemPathException e)
799        {
800            return null; // this attribute is not part of model
801        }
802    }
803
804    /**
805     * Get the RNCP code
806     * @return the RNCP code
807     */
808    public String[] getRncpCode()
809    {
810        try
811        {
812            return getValue(RNCP_CODE, false, ArrayUtils.EMPTY_STRING_ARRAY);
813        }
814        catch (UndefinedItemPathException e)
815        {
816            return ArrayUtils.EMPTY_STRING_ARRAY; // this attribute is not part of model
817        }
818    }
819    
820    /**
821     * Get the RNCP level
822     * @return the RNCP level
823     */
824    public String[] getRncpLevel()
825    {
826        try
827        {
828            return ContentDataHelper.getContentIdsArrayFromMultipleContentData(this, RNCP_LEVEL);
829        }
830        catch (UndefinedItemPathException e)
831        {
832            return ArrayUtils.EMPTY_STRING_ARRAY; // this attribute is not part of model
833        }
834    }
835    
836    /**
837     * Get the SISE code
838     * @return the SISE code
839     */
840    public String[] getSiseCode()
841    {
842        try
843        {
844            return ContentDataHelper.getContentIdsArrayFromMultipleContentData(this, SISE_CODE);
845        }
846        catch (UndefinedItemPathException e)
847        {
848            return ArrayUtils.EMPTY_STRING_ARRAY; // this attribute is not part of model
849        }
850    }
851
852    /**
853     * Get the CITE97 code
854     * @return the CITE97 code
855     */
856    public String[] getCite97Code()
857    {
858        try
859        {
860            return ContentDataHelper.getContentIdsArrayFromMultipleContentData(this, CITE97_CODE);
861        }
862        catch (UndefinedItemPathException e)
863        {
864            return ArrayUtils.EMPTY_STRING_ARRAY; // this attribute is not part of model
865        }
866    }
867
868    /**
869     * Get the DGESIP code
870     * @return the DGESIP code
871     */
872    public String[] getDGESIPCode()
873    {
874        try
875        {
876            return ContentDataHelper.getContentIdsArrayFromMultipleContentData(this, DGESIP_CODE);
877        }
878        catch (UndefinedItemPathException e)
879        {
880            return ArrayUtils.EMPTY_STRING_ARRAY; // this attribute is not part of model
881        }
882    }
883
884    /**
885     * Get the Erasmus code
886     * @return the Erasmus code
887     */
888    public String[] getErasmusCode()
889    {
890        try
891        {
892            return ContentDataHelper.getContentIdsArrayFromMultipleContentData(this, ERASMUS_CODE);
893        }
894        catch (UndefinedItemPathException e)
895        {
896            return ArrayUtils.EMPTY_STRING_ARRAY; // this attribute is not part of model
897        }
898    }
899
900    /**
901     * Get the FORMACODE
902     * @return the FORMACODE
903     */
904    public String[] getFORMACODE()
905    {
906        try
907        {
908            return getValue(FORMACODE, false, ArrayUtils.EMPTY_STRING_ARRAY);
909        }
910        catch (UndefinedItemPathException e)
911        {
912            return ArrayUtils.EMPTY_STRING_ARRAY; // this attribute is not part of model
913        }
914    }
915    
916    /**
917     * Get the ROME code
918     * @return the ROME code
919     */
920    public String[] getRomeCode()
921    {
922        try
923        {
924            return ContentDataHelper.getContentIdsArrayFromMultipleContentData(this, ROME_CODE);
925        }
926        catch (UndefinedItemPathException e)
927        {
928            return ArrayUtils.EMPTY_STRING_ARRAY; // this attribute is not part of model
929        }
930    }
931    
932    /**
933     * Get the FAP code
934     * @return the FAP code
935     */
936    public String[] getFapCode()
937    {
938        try
939        {
940            return ContentDataHelper.getContentIdsArrayFromMultipleContentData(this, FAP_CODE);
941        }
942        catch (UndefinedItemPathException e)
943        {
944            return ArrayUtils.EMPTY_STRING_ARRAY; // this attribute is not part of model
945        }
946    }
947    
948    /**
949     * Get the NSF code
950     * @return the NSF code
951     */
952    public String getNSFCode()
953    {
954        try
955        {
956            return ContentDataHelper.getContentIdFromContentData(this, NSF_CODE);
957        }
958        catch (UndefinedItemPathException e)
959        {
960            return null; // this attribute is not part of model
961        }
962    }
963    
964    /**
965     * Get the mention
966     * @return the mention or null
967     */
968    public String getMention()
969    {
970        try
971        {
972            return ContentDataHelper.getContentIdFromContentData(this, MENTION);
973        }
974        catch (UndefinedItemPathException e)
975        {
976            return null; // this attribute is not part of model
977        }
978    }
979
980    /**
981     * Get the speciality
982     * @return the speciality or null
983     */
984    public String getSpeciality()
985    {
986        try
987        {
988            return getValue(SPECIALITY, false, StringUtils.EMPTY);
989        }
990        catch (UndefinedItemPathException e)
991        {
992            return null; // this attribute is not part of model
993        }
994    }
995
996    /**
997     * Get the org units
998     * @return the org units
999     */
1000    public String[] getJointOrgUnit()
1001    {
1002        try
1003        {
1004            return ContentDataHelper.getContentIdsArrayFromMultipleContentData(this, JOINT_ORGUNIT);
1005        }
1006        catch (UndefinedItemPathException e)
1007        {
1008            return ArrayUtils.EMPTY_STRING_ARRAY; // this attribute is not part of model
1009        }
1010    }
1011
1012    /**
1013     * Get the places
1014     * @return the places as an Array of String
1015     */
1016    public String[] getPlace()
1017    {
1018        try
1019        {
1020            return ContentDataHelper.getContentIdsArrayFromMultipleContentData(this, PLACE);
1021        }
1022        catch (UndefinedItemPathException e)
1023        {
1024            return ArrayUtils.EMPTY_STRING_ARRAY; // this attribute is not part of model
1025        }
1026    }
1027
1028    /**
1029     * Get the list of websites
1030     * @return the list of website or an empty list
1031     */
1032    public Set<WebsiteLink> getWebsiteLinks()
1033    {
1034        Set<WebsiteLink> websites = new HashSet<>();
1035        
1036        try
1037        {
1038            ModelAwareRepeater webSites = getRepeater(PROGRAM_WEBSITE);
1039            if (webSites != null)
1040            {
1041                for (ModelAwareRepeaterEntry entry : webSites.getEntries())
1042                {
1043                    WebsiteLink website = new WebsiteLink(entry.getValue(PROGRAM_WEBSITE_URL, false, StringUtils.EMPTY), entry.getValue(PROGRAM_WEBSITE_LABEL, false, StringUtils.EMPTY));
1044                    websites.add(website);
1045                }
1046            }
1047        }
1048        catch (UndefinedItemPathException e)
1049        {
1050            // this attribute is not part of model
1051        }
1052        
1053        return websites;
1054    }
1055
1056    /**
1057     * Get the ECTS credits
1058     * @return the ECTS credits
1059     */
1060    public String getEcts()
1061    {
1062        try
1063        {
1064            return ContentDataHelper.getContentIdFromContentData(this, ECTS);
1065        }
1066        catch (UndefinedItemPathException e)
1067        {
1068            return null; // this attribute is not part of model
1069        }
1070    }
1071
1072    /**
1073     * Get the kind of education
1074     * @return the kind of education or null
1075     */
1076    public String getEducationKind()
1077    {
1078        try
1079        {
1080            return ContentDataHelper.getContentIdFromContentData(this, EDUCATION_KIND);
1081        }
1082        catch (UndefinedItemPathException e)
1083        {
1084            return null; // this attribute is not part of model
1085        }
1086    }
1087    
1088    /**
1089     * Get the duration
1090     * @return the duration
1091     */
1092    public String getDuration()
1093    {
1094        try
1095        {
1096            return ContentDataHelper.getContentIdFromContentData(this, DURATION);
1097        }
1098        catch (UndefinedItemPathException e)
1099        {
1100            return null; // this attribute is not part of model
1101        }
1102    }
1103
1104    /**
1105     * Get the education languages
1106     * @return the education languages
1107     */
1108    public String[] getEducationLanguage()
1109    {
1110        try
1111        {
1112            return ContentDataHelper.getContentIdsArrayFromMultipleContentData(this, EDUC_LANGUAGE);
1113        }
1114        catch (UndefinedItemPathException e)
1115        {
1116            return ArrayUtils.EMPTY_STRING_ARRAY; // this attribute is not part of model
1117        }
1118    }
1119
1120    /**
1121     * Get the effectives
1122     * @return the effectives
1123     */
1124    public RichText getEffectives()
1125    {
1126        try
1127        {
1128            return getValue(NUMBER_OF_STUDENTS);
1129        }
1130        catch (UndefinedItemPathException e)
1131        {
1132            return null; // this attribute is not part of model
1133        }
1134    }
1135    
1136    /**
1137     * Get the SuccessRate
1138     * @return the SuccessRate
1139     */
1140    public String getSuccessRate()
1141    {
1142        try
1143        {
1144            return getValue(SUCCESSRATE, false, StringUtils.EMPTY);
1145        }
1146        catch (UndefinedItemPathException e)
1147        {
1148            return null; // this attribute is not part of model
1149        }
1150    }
1151    
1152    /**
1153     * Get the expenses
1154     * @return the expenses
1155     */
1156    public RichText getExpenses()
1157    {
1158        try
1159        {
1160            return getValue(EXPENSES);
1161        }
1162        catch (UndefinedItemPathException e)
1163        {
1164            return null; // this attribute is not part of model
1165        }
1166    }
1167    
1168    /**
1169     * Get the form of teaching organization
1170     * @return the form of teaching organization
1171     */
1172    public String[] getFormOfTeachingOrgs()
1173    {
1174        try
1175        {
1176            return ContentDataHelper.getContentIdsArrayFromMultipleContentData(this, FORM_OF_TEACHING_ORG);
1177        }
1178        catch (UndefinedItemPathException e)
1179        {
1180            return ArrayUtils.EMPTY_STRING_ARRAY; // this attribute is not part of model
1181        }
1182    }
1183    
1184    /**
1185     * Get the REORIENTATION
1186     * @return RichText
1187     */
1188    public RichText getReorientation()
1189    {
1190        try
1191        {
1192            return getValue(REORIENTATION);
1193        }
1194        catch (UndefinedItemPathException e)
1195        {
1196            return null; // this attribute is not part of model
1197        }
1198    }
1199    
1200    /**
1201     * Get the distance learning
1202     * @return the distance learning or null
1203     */
1204    public String getDistanceLearning()
1205    {
1206        try
1207        {
1208            return ContentDataHelper.getContentIdFromContentData(this, DISTANCE_LEARNING);
1209        }
1210        catch (UndefinedItemPathException e)
1211        {
1212            return null; // this attribute is not part of model
1213        }
1214    }
1215
1216    /**
1217     * Get the internship
1218     * @return the internship or null
1219     */
1220    public String getInternship()
1221    {
1222        try
1223        {
1224            return ContentDataHelper.getContentIdFromContentData(this, INTERNSHIP);
1225        }
1226        catch (UndefinedItemPathException e)
1227        {
1228            return null; // this attribute is not part of model
1229        }
1230    }
1231
1232    /**
1233     * Get the internship duration
1234     * @return the internship duration or null
1235     */
1236    public String getInternshipDuration()
1237    {
1238        try
1239        {
1240            return getValue(INTERNSHIP_DURATION, false, StringUtils.EMPTY);
1241        }
1242        catch (UndefinedItemPathException e)
1243        {
1244            return null; // this attribute is not part of model
1245        }
1246    }
1247
1248    /**
1249     * Get the internship abroad
1250     * @return the internship abroad or null
1251     */
1252    public String getInternshipAbroad()
1253    {
1254        try
1255        {
1256            return ContentDataHelper.getContentIdFromContentData(this, INTERNSHIP_ABROAD);
1257        }
1258        catch (UndefinedItemPathException e)
1259        {
1260            return null; // this attribute is not part of model
1261        }
1262    }
1263
1264    /**
1265     * Get the internship abroad duration
1266     * @return  the internship abroad duration or null
1267     */
1268    public String getAbroadInternshipDuration()
1269    {
1270        try
1271        {
1272            return getValue(INTERNSHIP_ABROAD_DURATION, false, StringUtils.EMPTY);
1273        }
1274        catch (UndefinedItemPathException e)
1275        {
1276            return null; // this attribute is not part of model
1277        }
1278    }
1279
1280    /**
1281     * Get the registration start date.
1282     * @return The registration start date, can be null.
1283     */
1284    public LocalDate getRegistrationStart()
1285    {
1286        try
1287        {
1288            return getValue(REGISTRATION_START);
1289        }
1290        catch (UndefinedItemPathException e)
1291        {
1292            return null; // this attribute is not part of model
1293        }
1294    }
1295    
1296    /**
1297     * Get the registration deadline date.
1298     * @return The registration deadline date, can be null.
1299     */
1300    public LocalDate getRegistrationDeadline()
1301    {
1302        try
1303        {
1304            return getValue(REGISTRATION_DEADLINE);
1305        }
1306        catch (UndefinedItemPathException e)
1307        {
1308            return null; // this attribute is not part of model
1309        }
1310    }
1311    
1312    /**
1313     * Get the teaching start date.
1314     * @return The teaching start date, can be null.
1315     */
1316    public LocalDate getTeachingStart()
1317    {
1318        try
1319        {
1320            return getValue(TEACHING_START);
1321        }
1322        catch (UndefinedItemPathException e)
1323        {
1324            return null; // this attribute is not part of model
1325        }
1326    }
1327    
1328    /**
1329     * Get the teaching end date.
1330     * @return The teaching end date, can be null.
1331     */
1332    public LocalDate getTeachingEnd()
1333    {
1334        try
1335        {
1336            return getValue(TEACHING_END);
1337        }
1338        catch (UndefinedItemPathException e)
1339        {
1340            return null; // this attribute is not part of model
1341        }
1342    }
1343    
1344    /**
1345     * Get the keywords
1346     * @return the keywords
1347     */
1348    public String[] getKeywords()
1349    {
1350        try
1351        {
1352            return getValue(KEYWORDS, false, ArrayUtils.EMPTY_STRING_ARRAY);
1353        }
1354        catch (UndefinedItemPathException e)
1355        {
1356            return ArrayUtils.EMPTY_STRING_ARRAY; // this attribute is not part of model
1357        }
1358    }
1359
1360    /**
1361     * Get the education level entry
1362     * @return the education level entry
1363     */
1364    public String[] getEducationLevelEntry()
1365    {
1366        try
1367        {
1368            return ContentDataHelper.getContentIdsArrayFromMultipleContentData(this, EDUCATION_ENTRY_LEVEL);
1369        }
1370        catch (UndefinedItemPathException e)
1371        {
1372            return ArrayUtils.EMPTY_STRING_ARRAY; // this attribute is not part of model
1373        }
1374    }
1375    
1376    /**
1377     * Is the entry level mandatory
1378     * @return <code>true</code> if the entry level is mandatory
1379     */
1380    public boolean isMandatoryEntryLevel()
1381    {
1382        try
1383        {
1384            return getValue(MANDATORY_ENTRY_LEVEL, false, false);
1385        }
1386        catch (UndefinedItemPathException e)
1387        {
1388            return false; // this attribute is not part of model
1389        }
1390    }
1391    
1392    /**
1393     * Get the program fields
1394     * @return the program fields
1395     */
1396    public String[] getProgramField()
1397    {
1398        try
1399        {
1400            return ContentDataHelper.getContentIdsArrayFromMultipleContentData(this, PROGRAM_FIELD);
1401        }
1402        catch (UndefinedItemPathException e)
1403        {
1404            return ArrayUtils.EMPTY_STRING_ARRAY; // this attribute is not part of model
1405        }
1406    }
1407    
1408    /**
1409     * Get the available certifications
1410     * @return the available certifications
1411     */
1412    public String[] getAvailableCertification()
1413    {
1414        try
1415        {
1416            return ContentDataHelper.getContentIdsArrayFromMultipleContentData(this, AVAILABLE_CERTIFICATION);
1417        }
1418        catch (UndefinedItemPathException e)
1419        {
1420            return ArrayUtils.EMPTY_STRING_ARRAY; // this attribute is not part of model
1421        }
1422    }
1423    
1424    /**
1425     * Get the disciplines
1426     * @return the disciplines
1427     */
1428    public String[] getDisciplines()
1429    {
1430        try
1431        {
1432            return ContentDataHelper.getContentIdsArrayFromMultipleContentData(this, DISCIPLINES);
1433        }
1434        catch (UndefinedItemPathException e)
1435        {
1436            return ArrayUtils.EMPTY_STRING_ARRAY; // this attribute is not part of model
1437        }
1438    }
1439    
1440    /**
1441     * Get the sectors
1442     * @return the sectors
1443     */
1444    public String[] getSectors()
1445    {
1446        try
1447        {
1448            return ContentDataHelper.getContentIdsArrayFromMultipleContentData(this, SECTORS);
1449        }
1450        catch (UndefinedItemPathException e)
1451        {
1452            return ArrayUtils.EMPTY_STRING_ARRAY; // this attribute is not part of model
1453        }
1454    }
1455
1456    /**
1457     * Is intership open
1458     * @return <code>true</code> if internship is open
1459     */
1460    public boolean isInternshipOpen()
1461    {
1462        try
1463        {
1464            return getValue(INTERNSHIP_OPEN, false, false);
1465        }
1466        catch (UndefinedItemPathException e)
1467        {
1468            return false; // this attribute is not part of model
1469        }
1470    }
1471
1472    /**
1473     * Is apprenticeship open
1474     * @return <code>true</code> if apprenticeship is open
1475     */
1476    public boolean isApprenticeshipOpen()
1477    {
1478        try
1479        {
1480            return getValue(APPRENTICESHIP_OPEN, false, false);
1481        }
1482        catch (UndefinedItemPathException e)
1483        {
1484            return false; // this attribute is not part of model
1485        }
1486    }
1487
1488    /**
1489     * Get the apprenticeship period description
1490     * @return the apprenticeship period
1491     */
1492    public RichText getApprenticeshipPeriod()
1493    {
1494        try
1495        {
1496            return getValue(APPRENTICESHIP_PERIOD);
1497        }
1498        catch (UndefinedItemPathException e)
1499        {
1500            return null; // this attribute is not part of model
1501        }
1502    }
1503    
1504    /**
1505     * Get the available apprenticeship contracts
1506     * @return the apprenticeship contracts
1507     */
1508    public String[] getApprenticeshipContract()
1509    {
1510        try
1511        {
1512            return ContentDataHelper.getContentIdsArrayFromMultipleContentData(this, APPRENTICESHIP_CONTRACT);
1513        }
1514        catch (UndefinedItemPathException e)
1515        {
1516            return ArrayUtils.EMPTY_STRING_ARRAY; // this attribute is not part of model
1517        }
1518    }
1519
1520    /**
1521     * Get the international education
1522     * @return the international education
1523     */
1524    public String[] getInternationalEducation()
1525    {
1526        try
1527        {
1528            return ContentDataHelper.getContentIdsArrayFromMultipleContentData(this, INTERNATIONAL_EDUCATION);
1529        }
1530        catch (UndefinedItemPathException e)
1531        {
1532            return ArrayUtils.EMPTY_STRING_ARRAY; // this attribute is not part of model
1533        }
1534    }
1535
1536    /**
1537     * Get the international dimension
1538     * @return the international dimension
1539     */
1540    public RichText getInternationalDimension()
1541    {
1542        try
1543        {
1544            return getValue(INTERNATIONAL_DIMENSION);
1545        }
1546        catch (UndefinedItemPathException e)
1547        {
1548            return null; // this attribute is not part of model
1549        }
1550    }
1551    
1552    /**
1553     * Get the geocode latitude and longitude
1554     * @return the geocode
1555     */
1556    public Geocode getGeocode()
1557    {
1558        try
1559        {
1560            return getValue(GEOCODE);
1561        }
1562        catch (UndefinedItemPathException e)
1563        {
1564            return null; // this attribute is not part of model
1565        }
1566    }
1567    
1568    /**
1569     * Get the other partners
1570     * @return the other partners
1571     */
1572    public RichText getOtherPartners()
1573    {
1574        try
1575        {
1576            return getValue(OTHER_PARTNERS);
1577        }
1578        catch (UndefinedItemPathException e)
1579        {
1580            return null; // this attribute is not part of model
1581        }
1582    }
1583    
1584    /**
1585     * Get the campus
1586     * @return the campus
1587     */
1588    public String[] getCampus()
1589    {
1590        try
1591        {
1592            return ContentDataHelper.getContentIdsArrayFromMultipleContentData(this, CAMPUS);
1593        }
1594        catch (UndefinedItemPathException e)
1595        {
1596            return ArrayUtils.EMPTY_STRING_ARRAY; // this attribute is not part of model
1597        }
1598    }
1599
1600    /**
1601     * Get the foreign places
1602     * @return the foreign places
1603     */
1604    public String[] getForeignPlace()
1605    {
1606        try
1607        {
1608            return ContentDataHelper.getContentIdsArrayFromMultipleContentData(this, FOREIGN_PLACE);
1609        }
1610        catch (UndefinedItemPathException e)
1611        {
1612            return ArrayUtils.EMPTY_STRING_ARRAY; // this attribute is not part of model
1613        }
1614    }
1615    
1616    /**
1617     * Get the inscription
1618     * @return the inscription
1619     */
1620    public RichText getInscription()
1621    {
1622        try
1623        {
1624            return getValue(INSCRIPTION);
1625        }
1626        catch (UndefinedItemPathException e)
1627        {
1628            return null; // this attribute is not part of model
1629        }
1630    }
1631    
1632    /**
1633     * Get the further study programs
1634     * @return the further study programs
1635     */
1636    public String[] getFurtherStudyPrograms()
1637    {
1638        try
1639        {
1640            return ContentDataHelper.getContentIdsArrayFromMultipleContentData(this, FURTHER_STUDY_PROGRAMS);
1641        }
1642        catch (UndefinedItemPathException e)
1643        {
1644            return ArrayUtils.EMPTY_STRING_ARRAY; // this attribute is not part of model
1645        }
1646    }
1647    
1648    // --------------------------------------------------------------------------------------//
1649    // Methods of CourseListContainer
1650    // --------------------------------------------------------------------------------------//
1651    @Override
1652    public List<CourseList> getCourseLists()
1653    {
1654        return Arrays.stream(getValue(CHILD_PROGRAM_PARTS, false, new ContentValue[0]))
1655                .map(ContentValue::getContentIfExists)
1656                .flatMap(Optional::stream)
1657                // This cast is not checked because an exception must be thrown if the retrieved content is not a program part
1658                // TODO: change this behavior to throw our own exception and not a CassCastException
1659                .map(ProgramPart.class::cast)
1660                // Program parts that are not course lists are simply ignored
1661                .filter(CourseList.class::isInstance)
1662                .map(CourseList.class::cast)
1663                .collect(Collectors.toList());
1664    }
1665    
1666    @Override
1667    public boolean containsCourseList(String clId)
1668    {
1669        return ArrayUtils.contains(ContentDataHelper.getContentIdsArrayFromMultipleContentData(this, CHILD_PROGRAM_PARTS), clId);
1670    }
1671
1672    @Override
1673    public boolean hasCourseLists()
1674    {
1675        return !getCourseLists().isEmpty();
1676    }
1677    
1678    // --------------------------------------------------------------------------------------//
1679    // CDMfr
1680    // --------------------------------------------------------------------------------------//
1681    @Override
1682    protected String getCDMType()
1683    {
1684        return "PR";
1685    }
1686    
1687    /**
1688     * Returns the surrounding tag name in the CDM-fr representation.
1689     * @return the surrounding tag name
1690     */
1691    public abstract String getCDMTagName();
1692}