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}