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.course;
017
018import java.time.LocalDate;
019import java.util.Arrays;
020import java.util.Collections;
021import java.util.HashMap;
022import java.util.HashSet;
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.RichText;
037import org.ametys.cms.repository.ModifiableDefaultContent;
038import org.ametys.odf.ProgramItem;
039import org.ametys.odf.courselist.CourseList;
040import org.ametys.odf.courselist.CourseListContainer;
041import org.ametys.odf.coursepart.CoursePart;
042import org.ametys.plugins.repository.AmetysRepositoryException;
043import org.ametys.plugins.repository.data.external.ExternalizableDataProvider.ExternalizableDataStatus;
044import org.ametys.plugins.repository.data.holder.group.ModelAwareRepeater;
045import org.ametys.plugins.repository.data.holder.group.ModelAwareRepeaterEntry;
046import org.ametys.plugins.repository.data.holder.group.ModifiableModelAwareRepeater;
047import org.ametys.plugins.repository.data.holder.group.ModifiableModelAwareRepeaterEntry;
048import org.ametys.plugins.repository.data.holder.values.ValueContext;
049
050/**
051 * Class representing a {@link Course}
052 */
053public class Course extends ModifiableDefaultContent<CourseFactory> implements CourseListContainer, ProgramItem
054{
055    /** Name of attribute for parent course lists */
056    public static final String PARENT_COURSE_LISTS = "parentCourseLists";
057    
058    /** Name of attribute for parent course lists */
059    public static final String CHILD_COURSE_LISTS = "courseLists";
060    
061    /** Mandatory Identifier to generate the CDM-fr id */
062    public static final String CDM_CODE = "cdmCode";
063    
064    /** Constants for ects attribute */
065    public static final String ECTS = "ects";
066
067    /** Constants for level attribute */
068    public static final String LEVEL = "level";
069
070    /** Constants for description attribute */
071    public static final String DESCRIPTION = "description";
072
073    /** Constants for objectives attribute */
074    public static final String OBJECTIVES = "objectives";
075
076    /** Constants for nbHours attribute */
077    public static final String NUMBER_OF_HOURS = "nbHours";
078
079    /** Constants for neededPrerequisite attribute */
080    public static final String NEEDED_PREREQUISITE = "neededPrerequisite";
081
082    /** Constants for formOfAssessment attribute */
083    public static final String FORM_OF_ASSESSMENT = "formOfAssessment";
084    
085    /** Constants for attribute 'requiredSkills' */
086    public static final String REQUIRED_SKILLS = "requiredSkills";
087    
088    /** Constants for repeater 'acquiredSkills' */
089    public static final String ACQUIRED_SKILLS = "acquiredSkills";
090
091    /** The attribute name of the skills */
092    public static final String ACQUIRED_SKILLS_SKILLSET = "skillSet";
093    
094    /** The attribute name of the skills in acquiredSkills */
095    public static final String ACQUIRED_SKILLS_SKILLS = "skills";
096
097    /** The skill attribute name in acquiredSkills/skills */
098    public static final String ACQUIRED_SKILLS_SKILLS_SKILL = "skill";
099    
100    /** The skill acquisition level attribute name in acquiredSkills/skills */
101    public static final String ACQUIRED_SKILLS_SKILLS_ACQUISITION_LEVEL = "acquisitionLevel";
102
103    /** Constants for syllabus attribute */
104    public static final String SYLLABUS = "syllabus";
105
106    /** Constants for additionalInformations attribute */
107    public static final String ADDITIONAL_INFORMATIONS = "additionalInformations";
108
109    /** Constants for erasmusCode attribute */
110    public static final String ERASMUS_CODE = "erasmusCode";
111
112    /** Constants for teachingLocation attribute */
113    public static final String TEACHING_LOCATION = "teachingLocation";
114
115    /** Constants for maxNumberOfStudents attribute */
116    public static final String MAX_NUMBER_OF_STUDENTS = "maxNumberOfStudents";
117
118    /** Constants for teachingTerm attribute */
119    public static final String TEACHING_TERM = "teachingTerm";
120
121    /** Constants for timeSlot attribute */
122    public static final String TIME_SLOT = "timeSlot";
123
124    /** Constants for trainingCourseDuration attribute */
125    public static final String TRAINING_COURSE_DURATION = "trainingCourseDuration";
126
127    /** Constants for teachingMethod attribute */
128    public static final String FORMODFTEACHING_METHOD = "formofteachingMethod";
129    
130    /** Constants for formofteachingOrg attribute */
131    public static final String FORMOFTEACHING_ORG = "formofteachingOrg";
132
133    /** Constants for formOfTeaching attribute */
134    public static final String TEACHING_ACTIVITY = "teachingActivity";
135
136    /** Constants for teachingLanguage attribute */
137    public static final String TEACHING_LANGUAGE = "teachingLanguage";
138
139    /** Constants for startDate attribute */
140    public static final String START_DATE = "startDate";
141
142    /** Constants for keywords attribute */
143    public static final String KEYWORDS = "keywords";
144
145    /** Constants for webLinkLabel attribute */
146    public static final String WEB_LINK_LABEL = "webLinkLabel";
147
148    /** Constants for webLinkUrl attribute */
149    public static final String WEB_LINK_URL = "webLinkUrl";
150
151    /** Constants for lomSheets attribute */
152    public static final String LOM_SHEETS = "lomSheets";
153
154    /** Constants for LOM Sheet URL attribute */
155    public static final String LOM_SHEET_URL = "linkUrl";
156    
157    /** Constants for LOM Sheet label attribute */
158    public static final String LOM_SHEET_LABEL = "linkLabel";
159    
160    /** Constants for orgunits attribute */
161    public static final String ORG_UNITS = "orgUnit";
162    
163    /** Constants for attribute 'contacts' */
164    public static final String CONTACTS = "contacts";
165    
166    /** Constants for attribute 'contacts/role' */
167    public static final String CONTACTS_ROLE = "role";
168    
169    /** Constants for attribute 'contacts/persons' */
170    public static final String CONTACTS_PERSONS = "persons";
171    
172    /** Constants for attribute 'courseType' */
173    public static final String COURSE_TYPE = "courseType";
174    
175    /** Constants for attribute 'bibliography' */
176    public static final String BIBLIOGRAPHY = "bibliography";
177
178    /** Constants for attribute 'skills' */
179    public static final String SKILLS = "skills";
180
181    /** Constants for attribute 'openToExchangeStudents' */
182    public static final String OPEN_TO_EXCHANGE_STUDENTS = "openToExchangeStudents";
183    
184    /** Constants for attribute 'courseParts' */
185    public static final String CHILD_COURSE_PARTS = "courseParts";
186
187    private String _contextPath;
188
189    /**
190     * Constructor.
191     * @param node the JCR Node.
192     * @param parentPath the parent path
193     * @param factory the corresponding factory.
194     */
195    public Course(Node node, String parentPath, CourseFactory factory)
196    {
197        super(node, parentPath, factory);
198    }
199
200    @Override
201    public List<CourseList> getCourseLists()
202    {
203        return Arrays.stream(getValue(CHILD_COURSE_LISTS, false, new ContentValue[0]))
204                .map(ContentValue::getContentIfExists)
205                .filter(Optional::isPresent)
206                .map(Optional::get)
207                .map(CourseList.class::cast)
208                .collect(Collectors.toList());
209    }
210    
211    /**
212     * Get the parent course lists
213     * @return The parent course lists
214     */
215    public List<CourseList> getParentCourseLists()
216    {
217        return Arrays.stream(getValue(PARENT_COURSE_LISTS, false, new ContentValue[0]))
218                .map(ContentValue::getContentIfExists)
219                .filter(Optional::isPresent)
220                .map(Optional::get)
221                .map(CourseList.class::cast)
222                .collect(Collectors.toList());
223    }
224
225    @Override
226    public boolean hasCourseLists()
227    {
228        return !ContentDataHelper.isMultipleContentDataEmpty(this, CHILD_COURSE_LISTS);
229    }
230    
231    @Override
232    public boolean containsCourseList(String clId)
233    {
234        return ArrayUtils.contains(ContentDataHelper.getContentIdsArrayFromMultipleContentData(this, CHILD_COURSE_LISTS), clId);
235    }
236    
237    // --------------------------------------------------------------------------------------//
238    // CONTACTS
239    // --------------------------------------------------------------------------------------//
240
241    /**
242     * Return the list of Persons in charge binded to this program 
243     * @return the id of contacts
244     */
245    public Set<String> getContacts()
246    {
247        Set<String> contactIds = new HashSet<>();
248        
249        ModelAwareRepeater contacts = getRepeater(CONTACTS);
250        if (contacts != null)
251        {
252            for (ModelAwareRepeaterEntry contactEntry : contacts.getEntries())
253            {
254                // Remove empty values
255                contactIds.addAll(ContentDataHelper.getContentIdsStreamFromMultipleContentData(contactEntry, CONTACTS_PERSONS)
256                        .filter(contentId -> !contentId.isEmpty())
257                        .collect(Collectors.toSet()));
258            }
259        }
260
261        return Collections.EMPTY_SET;
262    }
263    
264    /**
265     * Return the list of Persons in charge binded to this program 
266     * @return a list of roles and UUID
267     */
268    public Map<String, List<String>> getContactsByRole()
269    {
270        Map<String, List<String>> contactsByRole = new HashMap<>();
271        
272        ModelAwareRepeater contacts = getRepeater(CONTACTS);
273        if (contacts != null)
274        {
275            for (ModelAwareRepeaterEntry contactEntry : contacts.getEntries())
276            {
277                // Remove empty values
278                List<String> persons = ContentDataHelper.getContentIdsStreamFromMultipleContentData(contactEntry, CONTACTS_PERSONS)
279                                                    .filter(contentId -> !contentId.isEmpty())
280                                                    .collect(Collectors.toList());
281                if (!persons.isEmpty())
282                {
283                    String role = ContentDataHelper.getContentIdFromContentData(contactEntry, CONTACTS_ROLE);
284                    contactsByRole.put(role, persons);
285                }
286            }
287        }
288
289        return contactsByRole;
290    }
291   
292    
293    // --------------------------------------------------------------------------------------//
294    // CONTEXT
295    // --------------------------------------------------------------------------------------//
296    /**
297     * Set the parent path for links and breadcrumb
298     * @param path the parent path
299     */
300    public void setContextPath (String path)
301    {
302        _contextPath = path;
303    }
304    
305    /**
306     * Get the parent path. Can be null.
307     * @return the parent path
308     */
309    public String getContextPath ()
310    {
311        return _contextPath;
312    }
313    
314    // --------------------------------------------------------------------------------------//
315    // ORGUNITS
316    // --------------------------------------------------------------------------------------//
317    /**
318     * Return the id of orgunit content binded to this course
319     * @return The id of contents
320     */
321    public List<String> getOrgUnits()
322    {
323        return ContentDataHelper.getContentIdsListFromMultipleContentData(this, ORG_UNITS);
324    }
325
326    /**
327     * Return the id of local orgunit contents binded to this course
328     * @return The id of contents
329     */
330    public List<String> getLocalOrgUnits()
331    {
332        ValueContext localValueContext = ValueContext.newInstance().withStatus(ExternalizableDataStatus.LOCAL);
333        return ContentDataHelper.getContentIdsListFromMultipleContentData(this, ORG_UNITS, localValueContext);
334    }
335
336    /**
337     * Return the id of remote orgunit contents binded to this course
338     * @return The id of contents
339     */
340    public List<String> getRemoteOrgUnits()
341    {
342        ValueContext externalValueContext = ValueContext.newInstance().withStatus(ExternalizableDataStatus.EXTERNAL);
343        return ContentDataHelper.getContentIdsListFromMultipleContentData(this, ORG_UNITS, externalValueContext);
344    }
345    
346    // --------------------------------------------------------------------------------------//
347    // GETTERS AND SETTERS
348    // --------------------------------------------------------------------------------------//
349    @Override
350    public String getCatalog()
351    {
352        return getValue(CATALOG);
353    }
354    
355    @Override
356    public void setCatalog(String catalog) throws AmetysRepositoryException
357    {
358        setValue(CATALOG, catalog);
359    }
360    
361    @Override
362    public String getCode()
363    {
364        return getValue(CODE, false, StringUtils.EMPTY);
365    }
366    
367    @Override
368    public void setCode(String code) throws AmetysRepositoryException
369    {
370        setValue(CODE, code);
371    }
372    
373    public boolean isPublishable()
374    {
375        return getInternalDataHolder().getValue(PUBLISHABLE, true);
376    }
377    
378    public void setPublishable(boolean isPublishable)
379    {
380        getInternalDataHolder().setValue(PUBLISHABLE, isPublishable);
381    }
382    
383    /**
384     * Get the description
385     * @return the description or null
386     */
387    public RichText getDescription()
388    {
389        return getValue(DESCRIPTION);
390    }
391
392    /**
393     * Get the objectives
394     * @return objectives
395     */
396    public RichText getObjectives()
397    {
398        return getValue(OBJECTIVES);
399    }
400    
401    /**
402     * Get the course parts.
403     * @return The {@link List} of attached {@link CoursePart}s
404     */
405    public List<CoursePart> getCourseParts()
406    {
407        return Arrays.stream(getValue(CHILD_COURSE_PARTS, false, new ContentValue[0]))
408                .map(ContentValue::getContentIfExists)
409                .filter(Optional::isPresent)
410                .map(Optional::get)
411                .map(CoursePart.class::cast)
412                .collect(Collectors.toList());
413    }
414    
415    /**
416     * Get the needed prerequisites
417     * @return the needed prerequisites or null if not set
418     */
419    public RichText getNeededPrerequisite()
420    {
421        return getValue(NEEDED_PREREQUISITE);
422    }
423
424    /**
425     * Get the formOfAssessment attribute
426     * @return the formOfAssessment or null if not set
427     */
428    public RichText getFormOfAssessment()
429    {
430        return getValue(FORM_OF_ASSESSMENT);
431    }
432    
433    /**
434     * Get the syllabus
435     * @return the syllabus or null if not set
436     */
437    public RichText getSyllabus()
438    {
439        return getValue(SYLLABUS);
440    }
441    
442    /**
443     * Get the list of LOM sheets
444     * @return the list of LOMsheets or an empty list
445     */
446    public Set<LOMSheet> getLOMSheets()
447    {
448        Set<LOMSheet> lomSheets = new HashSet<>();
449        
450        ModelAwareRepeater attributeLOMSheets = getRepeater(LOM_SHEETS);
451        if (attributeLOMSheets != null)
452        {
453            for (ModelAwareRepeaterEntry attributeLOMSheet : attributeLOMSheets.getEntries())
454            {
455                LOMSheet lomSheet = new LOMSheet(attributeLOMSheet.getValue(LOM_SHEET_URL, false, StringUtils.EMPTY), attributeLOMSheet.getValue(LOM_SHEET_LABEL, false, StringUtils.EMPTY));
456                lomSheets.add(lomSheet);
457            }
458        }
459
460        return lomSheets;
461    }
462    
463    /**
464     * Set the LOM sheets
465     * @param sheets The LOM sheets
466     */
467    public void setLOMSheets (Set<LOMSheet> sheets)
468    {
469        // Remove old LOM sheets if exist
470        removeValue(LOM_SHEETS);
471        
472        ModifiableModelAwareRepeater attributeLOMSheets = getRepeater(LOM_SHEETS, true);
473        for (LOMSheet lomSheet : sheets)
474        {
475            ModifiableModelAwareRepeaterEntry attributeLOMSheet = attributeLOMSheets.addEntry();
476            attributeLOMSheet.setValue(LOM_SHEET_URL, lomSheet.getUrl());
477            attributeLOMSheet.setValue(LOM_SHEET_LABEL, lomSheet.getLabel());
478        }
479    }
480    
481    /**
482     * Determines if the course has LOM sheet
483     * @param lomSheet The LOM sheet to test
484     * @return <code>true</code> if the course has LOM sheet
485     */
486    public boolean hasLOMSheet (LOMSheet lomSheet)
487    {
488        for (LOMSheet lom : getLOMSheets())
489        {
490            if (lom.getUrl().equals(lomSheet.getUrl()) && lom.getLabel().equals(lomSheet.getLabel()))
491            {
492                return true;
493            }
494        }
495        
496        return false;
497    }
498    
499    /**
500     * Get the additional information
501     * @return the additional information
502     */
503    public RichText getAdditionalInformations()
504    {
505        return getValue(ADDITIONAL_INFORMATIONS);
506    }
507
508    /**
509     * Get the Erasmus code
510     * @return the Erasmus code
511     */
512    public String getErasmusCode()
513    {
514        return ContentDataHelper.getContentIdFromContentData(this, ERASMUS_CODE);
515    }
516
517    /**
518     * Get the teaching location
519     * @return the teaching location
520     */
521    public String[] getTeachingLocation()
522    {
523        return ContentDataHelper.getContentIdsArrayFromMultipleContentData(this, TEACHING_LOCATION);
524    }
525
526    /**
527     * Set the teaching location
528     * @param teachingLocation the teaching location to set
529     * @throws AmetysRepositoryException if failed to set attribute
530     */
531    public void setTeachingLocation(String[] teachingLocation) throws AmetysRepositoryException
532    {
533        setValue(Course.TEACHING_LOCATION, teachingLocation);
534    }
535
536    /**
537     * Get the ECTS
538     * @return the ECTS
539     */
540    public double getEcts()
541    {
542        return getValue(ECTS, false, 0D);
543    }
544
545    /**
546     * Get the number of hours
547     * @return the number of hours
548     */
549    public double getNumberOfHours()
550    {
551        return getValue(NUMBER_OF_HOURS, false, 0D);
552    }
553
554    /**
555     * Get the effectives
556     * @return the effectives
557     * @throws AmetysRepositoryException if failed to get attribute
558     */
559    public String getMaxNumberOfStudents()
560    {
561        return getValue(MAX_NUMBER_OF_STUDENTS, false, StringUtils.EMPTY);
562    }
563
564    /**
565     * Get the teaching term
566     * @return the teaching term
567     */
568    public String getTeachingTerm()
569    {
570        return ContentDataHelper.getContentIdFromContentData(this, TEACHING_TERM);
571    }
572
573    /**
574     * Get the level
575     * @return the level
576     */
577    public String getLevel()
578    {
579        return ContentDataHelper.getContentIdFromContentData(this, LEVEL);
580    }
581
582    /**
583     * Get the teaching method
584     * @return the teaching method
585     */
586    public String getFormOfTeachingMethod()
587    {
588        return ContentDataHelper.getContentIdFromContentData(this, FORMODFTEACHING_METHOD);
589    }
590
591    /**
592     * Get the teaching organizations
593     * @return the teaching organizations
594     */
595    public String[] getFormOfTeachingOrgs()
596    {
597        return ContentDataHelper.getContentIdsArrayFromMultipleContentData(this, FORMOFTEACHING_ORG);
598    }
599
600    /**
601     * Get the teaching activity
602     * @return the teaching activity
603     */
604    public String getTeachingActivity()
605    {
606        return ContentDataHelper.getContentIdFromContentData(this, TEACHING_ACTIVITY);
607    }
608
609    /**
610     * Get the teaching language
611     * @return the teaching language
612     */
613    public String[] getTeachingLanguage()
614    {
615        return ContentDataHelper.getContentIdsArrayFromMultipleContentData(this, TEACHING_LANGUAGE);
616    }
617    
618    /**
619     * Get the required skills
620     * @return the required skills
621     */
622    public String[] getRequiredSkills()
623    {
624        return ContentDataHelper.getContentIdsArrayFromMultipleContentData(this, REQUIRED_SKILLS);
625    }
626
627    /**
628     * Get the start date
629     * @return the start date
630     */
631    public LocalDate getStartDate()
632    {
633        return getValue(START_DATE);
634    }
635
636    /**
637     * Get the time slot
638     * @return the time slot
639     */
640    public String getTimeSlot()
641    {
642        return ContentDataHelper.getContentIdFromContentData(this, TIME_SLOT);
643    }
644
645    /**
646     * Get the keywords
647     * @return the keywords
648     */
649    public String[] getKeywords()
650    {
651        return getValue(KEYWORDS, false, ArrayUtils.EMPTY_STRING_ARRAY);
652    }
653
654    /**
655     * Get the web link label
656     * @return the web link label
657     */
658    public String getWebLinkLabel()
659    {
660        return getValue(WEB_LINK_LABEL, false, StringUtils.EMPTY);
661    }
662
663    /**
664     * Get the web link URL
665     * @return the web link URL
666     */
667    public String getWebLinkUrl()
668    {
669        return getValue(WEB_LINK_URL, false, StringUtils.EMPTY);
670    }
671
672    /**
673     * Get the course type (nature)
674     * @return the course type
675     */
676    public String getCourseType()
677    {
678        return ContentDataHelper.getContentIdFromContentData(this, COURSE_TYPE);
679    }
680
681    /**
682     * Get the bibliography
683     * @return the bibliography
684     */
685    public RichText getBibliography()
686    {
687        return getValue(BIBLIOGRAPHY);
688    }
689    
690    /**
691     * Get the skills
692     * @return the skills
693     */
694    public RichText getSkills()
695    {
696        return getValue(SKILLS);
697    }
698    
699    /**
700     * Is open to exchange students
701     * @return <code>true</code> if the course is open to exchange students
702     */
703    public boolean isOpenToExchangeStudents()
704    {
705        return getValue(OPEN_TO_EXCHANGE_STUDENTS, false, false);
706    }
707    
708    // --------------------------------------------------------------------------------------//
709    // CDM-fr
710    // --------------------------------------------------------------------------------------//
711    @Override
712    public String getCDMId()
713    {
714        return "FRUAI" + _getFactory()._getRootOrgUnitRNE() + "CO" + getCode();
715    }
716    
717    @Override
718    public String getCdmCode()
719    {
720        return getValue(CDM_CODE, false, StringUtils.EMPTY);
721    }
722    
723    @Override
724    public void setCdmCode(String cdmCode) 
725    {
726        setValue(CDM_CODE, cdmCode);
727    }
728}