001/* 002 * Copyright 2019 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.plugins.odfsync.apogee.ws.structure; 017 018import java.rmi.RemoteException; 019import java.util.ArrayList; 020import java.util.List; 021import java.util.Map; 022import java.util.stream.Collectors; 023 024import org.apache.avalon.framework.service.ServiceException; 025import org.apache.avalon.framework.service.ServiceManager; 026import org.apache.commons.lang3.StringUtils; 027 028import org.ametys.cms.data.ContentValue; 029import org.ametys.cms.data.type.ModelItemTypeConstants; 030import org.ametys.cms.repository.Content; 031import org.ametys.odf.course.Course; 032import org.ametys.odf.courselist.CourseList; 033import org.ametys.odf.orgunit.OrgUnit; 034import org.ametys.odf.program.AbstractProgram; 035import org.ametys.odf.program.Container; 036import org.ametys.odf.program.ProgramPart; 037import org.ametys.odf.program.SubProgram; 038import org.ametys.plugins.odfsync.apogee.ws.ApogeeStructureComponent; 039import org.ametys.plugins.odfsync.apogee.ws.ApogeeWS; 040import org.ametys.plugins.odfsync.export.AbstractExportStructure; 041import org.ametys.plugins.odfsync.export.ExportReport; 042import org.ametys.plugins.odfsync.export.ExportReport.ExportStatus; 043import org.ametys.runtime.i18n.I18nizableText; 044 045import gouv.education.apogee.commun.client.ws.creationse.CreationSEMetierServiceInterface; 046 047/** 048 * The abstract class to handle an export in Apogee 049 */ 050public abstract class AbstractApogeeStructure extends AbstractExportStructure 051{ 052 /** Avalon Role */ 053 public static final String ROLE = ApogeeStructureComponent.class.getName(); 054 055 /** The attribute name for the code Apogee */ 056 public static final String CODE_APOGEE_ATTRIBUTE_NAME = "codeApogee"; 057 058 /** The false attribute name for the version Apogee */ 059 public static final String VERSION_APOGEE_ATTRIBUTE_NAME = "versionApogee"; 060 061 /** The separator between the code and the version */ 062 public static final String CODE_APOGEE_SEPARATOR = "-"; 063 064 /** The apogee WS */ 065 protected ApogeeWS _apogeeWS; 066 067 @Override 068 public void service(ServiceManager manager) throws ServiceException 069 { 070 super.service(manager); 071 _apogeeWS = (ApogeeWS) manager.lookup(ApogeeWS.ROLE); 072 } 073 074 /** 075 * Mandatory data to export a content in a DIP in Apogee 076 * @param content the content to export 077 * @return the list of mandatory data 078 */ 079 public List<String> getDIPMandatoryData(Content content) 080 { 081 List<String> mandatoryData = new ArrayList<>(); 082 mandatoryData.add(CODE_APOGEE_ATTRIBUTE_NAME); 083 mandatoryData.add(AbstractProgram.EDUCATION_KIND); 084 mandatoryData.add("cycleApogee"); 085 mandatoryData.add(AbstractProgram.DEGREE); 086 087 return mandatoryData; 088 } 089 090 /** 091 * Mandatory data to export a content in a VDI in Apogee 092 * @param content the content to export 093 * @return the list of mandatory data 094 */ 095 public List<String> getVDIMandatoryData(Content content) 096 { 097 // The orgunit Apogee Code is mandatory too 098 099 List<String> mandatoryData = new ArrayList<>(); 100 mandatoryData.add(VERSION_APOGEE_ATTRIBUTE_NAME); 101 mandatoryData.add("start-date-recruitment"); 102 mandatoryData.add("end-date-recruitment"); 103 mandatoryData.add("start-date-validation"); 104 mandatoryData.add("end-date-validation"); 105 106 return mandatoryData; 107 } 108 109 /** 110 * Mandatory data to export a content in a ETP in Apogee 111 * @param content the content to export 112 * @return the list of mandatory data 113 */ 114 public List<String> getETPMandatoryData(Content content) 115 { 116 // The orgunit Apogee Code is mandatory too 117 118 List<String> mandatoryData = new ArrayList<>(); 119 mandatoryData.add(CODE_APOGEE_ATTRIBUTE_NAME); 120 mandatoryData.add("cycleApogee"); 121 122 return mandatoryData; 123 } 124 125 /** 126 * Mandatory data to export a content in a VET in Apogee 127 * @param content the content to export 128 * @return the list of mandatory data 129 */ 130 public List<String> getVETMandatoryData(Content content) 131 { 132 // The orgunit Apogee Code is mandatory too 133 134 List<String> mandatoryData = new ArrayList<>(); 135 mandatoryData.add(VERSION_APOGEE_ATTRIBUTE_NAME); 136 mandatoryData.add("duration-apogee"); 137 mandatoryData.add("inscription-types"); 138 mandatoryData.add("cips"); 139 140 return mandatoryData; 141 } 142 143 /** 144 * Mandatory data to export a content in a LSE in Apogee 145 * @param content the content to export 146 * @return the list of mandatory data 147 */ 148 public List<String> getLSEMandatoryData(Content content) 149 { 150 List<String> mandatoryData = new ArrayList<>(); 151 mandatoryData.add(CODE_APOGEE_ATTRIBUTE_NAME); 152 mandatoryData.add("choiceType"); 153 154 String choiceTypeAmetys = content.getValue("choiceType"); 155 if (choiceTypeAmetys.equals("CHOICE")) 156 { 157 mandatoryData.add("min"); 158 } 159 160 return mandatoryData; 161 } 162 163 /** 164 * Mandatory data to export a content in a ELP in Apogee 165 * @param content the content to export 166 * @return the list of mandatory data 167 */ 168 public List<String> getELPMandatoryData(Content content) 169 { 170 // The orgunit Apogee Code is mandatory too 171 172 List<String> mandatoryData = new ArrayList<>(); 173 mandatoryData.add(CODE_APOGEE_ATTRIBUTE_NAME); 174 mandatoryData.add("cips"); 175 mandatoryData.add("orgUnit"); 176 if (content instanceof Container) 177 { 178 mandatoryData.add("nature"); 179 } 180 else if (content instanceof Course) 181 { 182 mandatoryData.add("courseType"); 183 } 184 185 return mandatoryData; 186 } 187 188 /** 189 * Mandatory data to export an orgunit for a DIP in Apogee 190 * @return the list of mandatory data 191 */ 192 public List<String> getOrgUnitMandatoryDataForDIP() 193 { 194 List<String> mandatoryData = new ArrayList<>(); 195 mandatoryData.add(CODE_APOGEE_ATTRIBUTE_NAME); 196 197 return mandatoryData; 198 } 199 200 /** 201 * Mandatory data to export an orgunit for a ETP in Apogee 202 * @return the list of mandatory data 203 */ 204 public List<String> getOrgUnitMandatoryDataForETP() 205 { 206 List<String> mandatoryData = new ArrayList<>(); 207 mandatoryData.add(CODE_APOGEE_ATTRIBUTE_NAME); 208 mandatoryData.add("codeCGE"); 209 210 return mandatoryData; 211 } 212 213 /** 214 * Mandatory data to export an orgunit for a ELP in Apogee 215 * @return the list of mandatory data 216 */ 217 public List<String> getOrgUnitMandatoryDataForELP() 218 { 219 List<String> mandatoryData = new ArrayList<>(); 220 mandatoryData.add(CODE_APOGEE_ATTRIBUTE_NAME); 221 222 return mandatoryData; 223 } 224 225 /** 226 * Get the code Apogee of the content 227 * @param content the content 228 * @return the code Apogee 229 */ 230 public String getCodeApogee(Content content) 231 { 232 String codeApogee = content.getValue(CODE_APOGEE_ATTRIBUTE_NAME); 233 if (StringUtils.isNotBlank(codeApogee) && codeApogee.contains(CODE_APOGEE_SEPARATOR)) 234 { 235 return StringUtils.substringBefore(codeApogee, CODE_APOGEE_SEPARATOR); 236 } 237 else 238 { 239 return codeApogee; 240 } 241 } 242 243 /** 244 * Get the version, Apogee of the content 245 * @param content the content 246 * @return the version Apogee 247 */ 248 public Long getVersionApogee(Content content) 249 { 250 String codeApogee = content.getValue(CODE_APOGEE_ATTRIBUTE_NAME); 251 if (StringUtils.isNotBlank(codeApogee) && codeApogee.contains(CODE_APOGEE_SEPARATOR)) 252 { 253 return Long.parseLong(StringUtils.substringAfterLast(codeApogee, CODE_APOGEE_SEPARATOR)); 254 } 255 else 256 { 257 return null; 258 } 259 } 260 261 /** 262 * Check if the subProgram has the good data and structure to be export in Apogee 263 * @param subProgram the subProgram to check 264 * @param report the Apogee export report 265 */ 266 public abstract void checkSubProgram(SubProgram subProgram, ExportReport report); 267 268 /** 269 * Check if the container as year has the good data and structure to be export in Apogee 270 * @param container the container to check 271 * @param report the Apogee export report 272 * @param containerNatureCode The container nature code 273 */ 274 public void checkContainerAsYear(Container container, ExportReport report, String containerNatureCode) 275 { 276 if (checkContainerYear(containerNatureCode, report)) 277 { 278 // The container is a semester so check the data as a semester (ELP in Apogee) 279 checkMandatoryDataForContent(container, getETPMandatoryData(container), report); 280 checkMandatoryDataForContent(container, getVETMandatoryData(container), report); 281 282 List<String> orgUnits = container.getOrgUnits(); 283 checkMandatoryDataForOrgunits(container, orgUnits, getOrgUnitMandatoryDataForETP(), report); 284 285 // Check the container structure 286 List<ProgramPart> programPartChildren = container.getProgramPartChildren(); 287 for (ProgramPart childProgramPart : programPartChildren) 288 { 289 if (childProgramPart instanceof Container) 290 { 291 Container containerChildProgramPart = (Container) childProgramPart; 292 String childContainerNatureCode = getContainerNatureCode(containerChildProgramPart); 293 294 checkContainerAsSemester(containerChildProgramPart, report, childContainerNatureCode); 295 } 296 else 297 { 298 report.setStatus(ExportStatus.CONTENT_STRUCTURE_INVALID); 299 break; 300 } 301 } 302 303 if (programPartChildren.isEmpty()) 304 { 305 // The structure is not handled by this export 306 report.setStatus(ExportStatus.CONTENT_STRUCTURE_INVALID); 307 } 308 } 309 else 310 { 311 // The structure is not handled by this export 312 report.setStatus(ExportStatus.CONTENT_STRUCTURE_INVALID); 313 } 314 } 315 316 /** 317 * Check if the container as semester has the good data and structure to be export in Apogee 318 * @param container the container to check 319 * @param report the Apogee export report 320 * @param containerNatureCode The container nature code 321 */ 322 public void checkContainerAsSemester(Container container, ExportReport report, String containerNatureCode) 323 { 324 if (checkContainerSemester(containerNatureCode, report)) 325 { 326 // The container is a semester so check the data as a semester (ELP in Apogee) 327 checkMandatoryDataForContent(container, getELPMandatoryData(container), report); 328 329 // Check the container structure 330 for (ProgramPart childProgramPart : container.getProgramPartChildren()) 331 { 332 if (childProgramPart instanceof CourseList) 333 { 334 checkCourseList((CourseList) childProgramPart, report); 335 } 336 else 337 { 338 report.setStatus(ExportStatus.CONTENT_STRUCTURE_INVALID); 339 break; 340 } 341 } 342 } 343 else 344 { 345 // The structure is not handled by this export 346 report.setStatus(ExportStatus.CONTENT_STRUCTURE_INVALID); 347 } 348 } 349 350 /** 351 * Check if the course list has the good data and structure to be export in Apogee 352 * @param courseList the course list to check 353 * @param report the Apogee export report 354 */ 355 public void checkCourseList(CourseList courseList, ExportReport report) 356 { 357 // Check mandatory data for course list 358 checkMandatoryDataForContent(courseList, getLSEMandatoryData(courseList), report); 359 360 // Check the course list structure 361 for (Course course : courseList.getCourses()) 362 { 363 checkCourse(course, report); 364 } 365 } 366 367 /** 368 * Check if the course has the good data and structure to be export in Apogee 369 * @param course the course to check 370 * @param report the Apogee export report 371 */ 372 public void checkCourse(Course course, ExportReport report) 373 { 374 // Check mandatory data for course 375 checkMandatoryDataForContent(course, getELPMandatoryData(course), report); 376 377 // Check mandatory data for course orgUnits 378 checkMandatoryDataForOrgunits(course, course.getOrgUnits(), getOrgUnitMandatoryDataForELP(), report); 379 380 // Check the course structure 381 for (CourseList courseList : course.getCourseLists()) 382 { 383 checkCourseList(courseList, report); 384 } 385 } 386 387 388 /** 389 * Check if the content has a value for all mandatory data 390 * @param content the content to check 391 * @param mandatoryData the list of mandatory data path 392 * @param report the Apogee export report 393 */ 394 public void checkMandatoryDataForContent(Content content, List<String> mandatoryData, ExportReport report) 395 { 396 for (String dataPath : mandatoryData) 397 { 398 if (VERSION_APOGEE_ATTRIBUTE_NAME.equals(dataPath)) 399 { 400 _checkVersionApogee(content, report); 401 } 402 else if (content.hasValue(dataPath)) 403 { 404 if (org.ametys.runtime.model.type.ModelItemTypeConstants.STRING_TYPE_ID.equals(content.getType(dataPath).getId())) 405 { 406 _checkSimpleData(content, dataPath, report); 407 } 408 else if (ModelItemTypeConstants.CONTENT_ELEMENT_TYPE_ID.equals(content.getType(dataPath).getId())) 409 { 410 _checkTableRef(content, dataPath, report); 411 } 412 } 413 else 414 { 415 _addMandatoryDataPathAndReport(content, dataPath, report); 416 } 417 } 418 } 419 420 /** 421 * Check if the content has a value for the simple data 422 * @param content the content to check 423 * @param dataPath the data path 424 * @param report the Apogee export report 425 */ 426 protected void _checkSimpleData(Content content, String dataPath, ExportReport report) 427 { 428 if (content.isMultiple(dataPath)) 429 { 430 String[] values = content.getValue(dataPath); 431 if (values.length == 0 || StringUtils.isBlank(values[0])) 432 { 433 _addMandatoryDataPathAndReport(content, dataPath, report); 434 } 435 } 436 else 437 { 438 String value = content.getValue(dataPath); 439 if (StringUtils.isBlank(value)) 440 { 441 _addMandatoryDataPathAndReport(content, dataPath, report); 442 } 443 } 444 } 445 446 /** 447 * Check if the content has a value for the simple table ref data 448 * @param content the content to check 449 * @param dataPath the data path 450 * @param report the Apogee export report 451 */ 452 protected void _checkTableRef(Content content, String dataPath, ExportReport report) 453 { 454 // We handle only simple table ref 455 if (!content.isMultiple(dataPath)) 456 { 457 ContentValue value = content.getValue(dataPath); 458 Content refContent = value.getContent(); 459 if (!value.hasValue(CODE_APOGEE_ATTRIBUTE_NAME)) 460 { 461 _addMandatoryDataPathAndReport(refContent, CODE_APOGEE_ATTRIBUTE_NAME, report); 462 } 463 } 464 } 465 466 /** 467 * Check if the version Apogee exist in the Apogee code 468 * @param content the content 469 * @param report the Apogee export report 470 */ 471 protected void _checkVersionApogee(Content content, ExportReport report) 472 { 473 if (!content.hasValue(CODE_APOGEE_ATTRIBUTE_NAME)) 474 { 475 report.addInvalidDataPath(content, new I18nizableText("plugin.odf-sync", "PLUGINS_ODF_SYNC_EXPORT_APOGEE_MANDATORY_VERSION")); 476 } 477 else 478 { 479 String codeApogee = content.getValue(CODE_APOGEE_ATTRIBUTE_NAME); 480 if (!codeApogee.contains("-") || StringUtils.isBlank(StringUtils.substringAfterLast(codeApogee, "-"))) 481 { 482 report.addInvalidDataPath(content, new I18nizableText("plugin.odf-sync", "PLUGINS_ODF_SYNC_EXPORT_APOGEE_MANDATORY_VERSION")); 483 } 484 } 485 } 486 487 /** 488 * Check if the orgUnits has a value for all mandatory data 489 * @param content the content to check 490 * @param orgUnits the list of orgUnit to check 491 * @param mandatoryData the list of mandatory data path 492 * @param report the Apogee export report 493 */ 494 public void checkMandatoryDataForOrgunits(Content content, List<String> orgUnits, List<String> mandatoryData, ExportReport report) 495 { 496 if (orgUnits.size() == 0) 497 { 498 // No orgUnits, course data is invalid 499 _addMandatoryDataPathAndReport(content, "orgUnit", report); 500 } 501 else 502 { 503 for (String orgUnitId : orgUnits) 504 { 505 OrgUnit orgUnit = _resolver.resolveById(orgUnitId); 506 checkMandatoryDataForContent(orgUnit, mandatoryData, report); 507 } 508 } 509 } 510 511 /** 512 * Create a course list in Apogee 513 * @param courseList the course list to create 514 * @param parentApogee the parent in Apogee 515 * @param creationService the service to create element in Apogee 516 * @param report the Apogee export report 517 * @throws RemoteException if an export error occurred 518 */ 519 protected void _createCourseList(CourseList courseList, Content parentApogee, CreationSEMetierServiceInterface creationService, ExportReport report) throws RemoteException 520 { 521 // Create ELPs before the list 522 for (Course course : courseList.getCourses()) 523 { 524 _createCourse(course, courseList, creationService, report); 525 } 526 527 String codELP = getCodeApogee(parentApogee); 528 String codLSE = getCodeApogee(courseList); 529 530 _apogeeWS.createLSE(courseList, null, codLSE, creationService); 531 532 Long nbELP = null; 533 Double ectsMin = 0D; 534 Double ectsMax = 0D; 535 String choiceTypeAmetys = courseList.getValue("choiceType"); 536 if (choiceTypeAmetys.equals("CHOICE")) 537 { 538 nbELP = courseList.getValue("min"); 539 540 List<Double> ectsList = courseList.getCourses() 541 .stream() 542 .map(Course::getEcts) 543 .sorted() 544 .collect(Collectors.toList()); 545 546 for (int i = 0; i < nbELP; i++) 547 { 548 ectsMin += ectsList.get(0); 549 } 550 551 ectsMin = (Math.floor(ectsMin) == 0D) ? 1D : Math.floor(ectsMin); 552 553 for (int i = 1; i <= nbELP; i++) 554 { 555 ectsMax += ectsList.get(ectsList.size() - i); 556 } 557 558 ectsMax = (Math.ceil(ectsMax) == 0D || ectsMin > ectsMax) ? 1D : Math.ceil(ectsMax); 559 } 560 561 _apogeeWS.createLinkETPELPLSE(null, null, codLSE, codELP, nbELP, ectsMin, ectsMax, creationService); 562 } 563 564 /** 565 * Create a course in Apogee 566 * @param course the course to create 567 * @param parentApogee the parent in Apogee 568 * @param creationService the service to create element in Apogee 569 * @param report the Apogee export report 570 * @throws RemoteException if an export error occurred 571 */ 572 protected void _createCourse(Course course, Content parentApogee, CreationSEMetierServiceInterface creationService, ExportReport report) throws RemoteException 573 { 574 String codELP = getCodeApogee(course); 575 _apogeeWS.createELP(course, null, codELP, creationService); 576 577 for (CourseList courseList : course.getCourseLists()) 578 { 579 _createCourseList(courseList, course, creationService, report); 580 } 581 } 582 583 /** 584 * Check if the container is a semester and set the exportStatus to CONTENT_STRUCTURE_INVALID if the container nature code cannot be found 585 * @param containerNatureCode The container nature code 586 * @param report The ExportReport that contains the exportStatus 587 * @return true if the container is a semester, false if it is not, and null if the container nature code could not be retrieved 588 */ 589 protected boolean checkContainerSemester(String containerNatureCode, ExportReport report) 590 { 591 if (containerNatureCode.isBlank()) 592 { 593 //The structure is not handled by this export because the container has no type 594 report.setStatus(ExportStatus.CONTENT_STRUCTURE_INVALID); 595 return false; 596 } 597 598 return "semestre".equals(containerNatureCode); 599 } 600 601 /** 602 * Check if the container is a year and set the exportStatus to CONTENT_STRUCTURE_INVALID if the container nature code cannot be found 603 * @param containerNatureCode The container nature code 604 * @param report The ExportReport that contains the exportStatus 605 * @return true if the container is a year, false if it is not, and null if the container nature code could not be retrieved 606 */ 607 protected boolean checkContainerYear(String containerNatureCode, ExportReport report) 608 { 609 if (containerNatureCode.isBlank()) 610 { 611 //The structure is not handled by this export because the container has no type 612 report.setStatus(ExportStatus.CONTENT_STRUCTURE_INVALID); 613 return false; 614 } 615 616 return "annee".equals(containerNatureCode); 617 } 618 619 private void _addMandatoryDataPathAndReport(Content content, String dataPath, ExportReport report) 620 { 621 I18nizableText invalidMessage = new I18nizableText( 622 "plugin.odf-sync", 623 "PLUGINS_ODF_SYNC_EXPORT_APOGEE_MANDATORY_FIELD", 624 Map.of("fieldName", content.getDefinition(dataPath).getLabel()) 625 ); 626 report.addInvalidDataPath(content, invalidMessage); 627 } 628}