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