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