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