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