001/* 002 * Copyright 2022 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 */ 016 017package org.ametys.plugins.odfsync.pegase.ws.structure; 018 019import java.io.IOException; 020import java.math.BigDecimal; 021import java.util.HashMap; 022import java.util.HashSet; 023import java.util.List; 024import java.util.Map; 025import java.util.Set; 026import java.util.UUID; 027import java.util.regex.Pattern; 028 029import org.apache.avalon.framework.activity.Initializable; 030import org.apache.avalon.framework.component.Component; 031import org.apache.avalon.framework.service.ServiceException; 032import org.apache.avalon.framework.service.ServiceManager; 033import org.apache.commons.lang3.StringUtils; 034 035import org.ametys.cms.data.ContentValue; 036import org.ametys.cms.data.RichTextHelper; 037import org.ametys.cms.repository.Content; 038import org.ametys.odf.ODFHelper; 039import org.ametys.odf.ProgramItem; 040import org.ametys.odf.course.Course; 041import org.ametys.odf.course.CourseFactory; 042import org.ametys.odf.courselist.CourseList; 043import org.ametys.odf.courselist.CourseList.ChoiceType; 044import org.ametys.odf.enumeration.OdfReferenceTableEntry; 045import org.ametys.odf.program.AbstractProgram; 046import org.ametys.odf.program.Container; 047import org.ametys.odf.program.Program; 048import org.ametys.odf.program.ProgramFactory; 049import org.ametys.odf.program.ProgramPart; 050import org.ametys.odf.program.SubProgram; 051import org.ametys.plugins.odfsync.export.AbstractExportStructure; 052import org.ametys.plugins.odfsync.export.ExportReport; 053import org.ametys.plugins.odfsync.export.ExportReport.ExportStatus; 054import org.ametys.plugins.odfsync.export.ExportReport.ProblemTypes; 055import org.ametys.plugins.odfsync.pegase.ws.PegaseApiManager; 056import org.ametys.plugins.odfsync.pegase.ws.PegaseExportException; 057import org.ametys.plugins.odfsync.pegase.ws.PegaseHelper; 058import org.ametys.plugins.repository.AmetysRepositoryException; 059import org.ametys.plugins.repository.data.UnknownDataException; 060import org.ametys.runtime.config.Config; 061import org.ametys.runtime.i18n.I18nizableText; 062 063import fr.pcscol.pegase.odf.ApiException; 064import fr.pcscol.pegase.odf.api.ObjetsMaquetteApi; 065import fr.pcscol.pegase.odf.model.Contexte; 066import fr.pcscol.pegase.odf.model.CreerFormationRequest; 067import fr.pcscol.pegase.odf.model.CreerGroupementRequest; 068import fr.pcscol.pegase.odf.model.CreerObjetFormationRequest; 069import fr.pcscol.pegase.odf.model.CreerObjetMaquetteRequest; 070import fr.pcscol.pegase.odf.model.DescripteursAglae; 071import fr.pcscol.pegase.odf.model.DescripteursEnqueteRequest; 072import fr.pcscol.pegase.odf.model.DescripteursFormationRequest; 073import fr.pcscol.pegase.odf.model.DescripteursFormationSyllabus; 074import fr.pcscol.pegase.odf.model.DescripteursGroupementRequest; 075import fr.pcscol.pegase.odf.model.DescripteursObjetFormationRequest; 076import fr.pcscol.pegase.odf.model.DescripteursObjetFormationSyllabus; 077import fr.pcscol.pegase.odf.model.DescripteursObjetMaquetteRequest; 078import fr.pcscol.pegase.odf.model.DescripteursSiseRequest; 079import fr.pcscol.pegase.odf.model.DescripteursSyllabus; 080import fr.pcscol.pegase.odf.model.Enfant; 081import fr.pcscol.pegase.odf.model.EnfantsStructure; 082import fr.pcscol.pegase.odf.model.MaquetteStructure; 083import fr.pcscol.pegase.odf.model.ObjetMaquetteDetail; 084import fr.pcscol.pegase.odf.model.ObjetMaquetteStructure; 085import fr.pcscol.pegase.odf.model.ObjetMaquetteSummary; 086import fr.pcscol.pegase.odf.model.Pageable; 087import fr.pcscol.pegase.odf.model.PagedObjetMaquetteSummaries; 088import fr.pcscol.pegase.odf.model.PlageDeChoix; 089import fr.pcscol.pegase.odf.model.TypeObjetMaquette; 090 091/** 092 * The structure to export the program in Pegase 093 */ 094public class PegaseProgramStructure extends AbstractExportStructure implements Component, Initializable 095{ 096 /** Role */ 097 public static final String ROLE = PegaseProgramStructure.class.getName(); 098 099 /* Constants */ 100 /** The attribute name for the Pégase code */ 101 private static final String __CODE_PEGASE_ATTRIBUTE_NAME = "pegaseCode"; 102 /** The pattern of a Pégase code */ 103 private static final Pattern __CODE_PATTERN = Pattern.compile("^[A-Z0-9\\-]{3,30}$"); 104 /** The attribute name for the Pégase Sync code (UUID) */ 105 private static final String __PEGASE_SYNC_CODE = "pegaseSyncCode"; 106 107 /** The ects attribute path */ 108 private static final String __ECTS_DATA_PATH = "ects"; 109 110 /** The type ID for a subProgram in pegase */ 111 private static final String __PARCOURS_TYPE_ID = "PARCOURS-TYPE"; 112 /** The type ID for a year in pegase */ 113 private static final String __ANNEE_TYPE_ID = "ANNEE"; 114 /** The type ID for a semester in pegase */ 115 private static final String __SEMESTRE_TYPE_ID = "SEMESTRE"; 116 117 /** The mandatory attributes by content type */ 118 private static final Map<String, Set<String>> __MANDATORY_ATTRIBUTES_BY_CONTENT_TYPE = new HashMap<>(); 119 static 120 { 121 __MANDATORY_ATTRIBUTES_BY_CONTENT_TYPE.put( 122 ProgramFactory.PROGRAM_CONTENT_TYPE, 123 Set.of( 124 AbstractProgram.EDUCATION_KIND 125 ) 126 ); 127 __MANDATORY_ATTRIBUTES_BY_CONTENT_TYPE.put( 128 CourseFactory.COURSE_CONTENT_TYPE, 129 Set.of(Course.COURSE_TYPE) 130 ); 131 } 132 133 /* Components */ 134 private PegaseApiManager _pegaseApiManager; 135 private ODFHelper _odfHelper; 136 private RichTextHelper _richTextHelper; 137 private PegaseHelper _pegaseHelper; 138 139 /* Pégase configuration */ 140 private boolean _isActive; 141 private String _structureCode; 142 private boolean _trustAmetys; 143 144 145 @Override 146 public void service(ServiceManager manager) throws ServiceException 147 { 148 super.service(manager); 149 150 _odfHelper = (ODFHelper) manager.lookup(ODFHelper.ROLE); 151 _pegaseApiManager = (PegaseApiManager) manager.lookup(PegaseApiManager.ROLE); 152 _richTextHelper = (RichTextHelper) manager.lookup(RichTextHelper.ROLE); 153 _pegaseHelper = (PegaseHelper) manager.lookup(PegaseHelper.ROLE); 154 } 155 156 public void initialize() throws Exception 157 { 158 _isActive = Config.getInstance().getValue("pegase.activate", true, false); 159 if (_isActive) 160 { 161 _structureCode = Config.getInstance().getValue("pegase.structure.code"); 162 _trustAmetys = Config.getInstance().getValue("pegase.trust", true, false); 163 } 164 } 165 166 private String _getEtag(ObjetsMaquetteApi objetsMaquetteApi) 167 { 168 List<String> etags = objetsMaquetteApi.getApiClient().getResponseHeaders().get("ETag"); 169 170 // Defaults to version 1 171 String etag = "1"; 172 if (etags != null && !etags.isEmpty()) 173 { 174 etag = etags.get(0); 175 } 176 177 return etag; 178 } 179 180 private Pageable _getPageable(int page, int taille, List<String> tri) 181 { 182 Pageable pageable = new Pageable(); 183 pageable.setPage(page); 184 pageable.setTaille(taille); 185 pageable.setTri(tri); 186 187 return pageable; 188 } 189 190 /** 191 * Checks if the program has all the required fields 192 * @param program the program to check 193 * @param report the Pegase export report 194 */ 195 @Override 196 public void checkProgram(Program program, ExportReport report) 197 { 198 if (!_isActive) 199 { 200 throw new UnsupportedOperationException("Pégase is not active in the configuration, you cannot check the program for the export."); 201 } 202 203 try 204 { 205 _checkProgramItem(program, report); 206 } 207 catch (PegaseExportException e) 208 { 209 report.setStatus(ExportStatus.ERROR); 210 getLogger().error("An error occured while checking the program", e); 211 } 212 } 213 214 /** 215 * Checks if the program item has all the required fields 216 * @param programItem the program item to check 217 * @param report the Pegase export report 218 * @throws PegaseExportException If an error occurs while retrieving the Pégase espace 219 */ 220 private void _checkProgramItem(ProgramItem programItem, ExportReport report) throws PegaseExportException 221 { 222 // Check program item attributes 223 _checkAttributes((Content) programItem, report); 224 225 // Check children 226 for (ProgramItem child : _odfHelper.getChildProgramItems(programItem)) 227 { 228 _checkProgramItem(child, report); 229 } 230 } 231 232 private void _checkAttributes(Content content, ExportReport report) throws PegaseExportException 233 { 234 // If it is a Program, check the codes and the coherence with Pégase 235 if (content instanceof Program) 236 { 237 _checkPegaseCodesForProgram(content, report); 238 } 239 // If it is a Program, only check the codes 240 else if (content instanceof ProgramItem) 241 { 242 _checkPegaseCodes(content, report); 243 } 244 245 // Check the other mandatory attributes by type 246 String contentType = content.getTypes()[0]; 247 Set<String> mandatoryAttributes = __MANDATORY_ATTRIBUTES_BY_CONTENT_TYPE.getOrDefault(contentType, Set.of()); 248 for (String attribute : mandatoryAttributes) 249 { 250 if (!content.hasValue(attribute)) 251 { 252 _addMandatoryDataPathAndReport(content, attribute, report); 253 } 254 } 255 } 256 257 private void _checkPegaseCodesForProgram(Content content, ExportReport report) throws PegaseExportException 258 { 259 UUID syncCode = null; 260 261 // Check if the Pegase sync code is present and valid 262 boolean hasValidSyncCode = false; 263 if (content.hasValue(__PEGASE_SYNC_CODE)) 264 { 265 syncCode = _getUuidIfValid(content.getValue(__PEGASE_SYNC_CODE)); 266 267 hasValidSyncCode = syncCode != null; 268 if (!hasValidSyncCode) 269 { 270 report.addInvalidDataPath(content, new I18nizableText("plugin.odf-sync", "PLUGINS_ODF_SYNC_EXPORT_PEGASE_INVALID_SYNC_CODE")); 271 } 272 } 273 274 // Check if the Pegase code is present 275 boolean hasValidPegaseCode = false; 276 if (content.hasValue(__CODE_PEGASE_ATTRIBUTE_NAME)) 277 { 278 hasValidPegaseCode = _isValidPegaseCode(content.getValue(__CODE_PEGASE_ATTRIBUTE_NAME)); 279 if (!hasValidPegaseCode) 280 { 281 report.addInvalidDataPath(content, new I18nizableText("plugin.odf-sync", "PLUGINS_ODF_SYNC_EXPORT_PEGASE_INVALID_CODE")); 282 } 283 } 284 285 // If both codes are present and valid, check their coherence with each other and with Pégase 286 if (hasValidSyncCode && hasValidPegaseCode) 287 { 288 _checkProgramCoherenceWithTwoCodes(content.getValue(__CODE_PEGASE_ATTRIBUTE_NAME), syncCode, report); 289 } 290 // If only the sync codes is present and valid, check its coherence with Pégase 291 else if (hasValidSyncCode && !hasValidPegaseCode) 292 { 293 _getProgramAndcheckCoherenceForUUID(syncCode, report); 294 } 295 // If only the Pégase codes is present and valid, check its coherence with Pégase 296 else if (!hasValidSyncCode && hasValidPegaseCode) 297 { 298 _checkProgramCoherenceByCode(content.getValue(__CODE_PEGASE_ATTRIBUTE_NAME), report); 299 } 300 // If neither of the codes is present, report the problem 301 else 302 { 303 _addMandatoryPegaseCodeAndReport(content, report); 304 } 305 } 306 307 /** 308 * Check if an object or a non editable program for the UUID and if the pegaseCode requested is coherent 309 * @param report The report 310 * @param pegaseCode The code wanted 311 * @param pegaseSyncCode The UUID of the Pégase object requested 312 * @throws PegaseExportException If an error occurs while retriving the espaceId 313 */ 314 private void _checkProgramCoherenceWithTwoCodes(String pegaseCode, UUID pegaseSyncCode, ExportReport report) throws PegaseExportException 315 { 316 ObjetMaquetteSummary programFound = _getProgramAndcheckCoherenceForUUID(pegaseSyncCode, report); 317 318 // If a program was found and was coherent with the UUID, check that the pegaseCode requested is coherent with the PegaseCode of the program found 319 if (programFound != null 320 && !ExportStatus.NON_EDITABLE_PROGRAM_ALREADY_EXISTS.equals(report.getStatus()) 321 && !pegaseCode.equals(programFound.getCode())) 322 { 323 report.setStatus(ExportStatus.NON_EDITABLE_PROGRAM_ALREADY_EXISTS); 324 } 325 } 326 327 /** 328 * Check if an object or a non editable program already exists for the UUID and retrives it 329 * @param report The report 330 * @param syncCode The UUID of the Pégase object requested 331 * @throws PegaseExportException If an error occurs while retriving the espaceId 332 */ 333 private ObjetMaquetteSummary _getProgramAndcheckCoherenceForUUID(UUID syncCode, ExportReport report) throws PegaseExportException 334 { 335 try 336 { 337 ObjetsMaquetteApi objetsMaquetteApi = _pegaseApiManager.getObjetsMaquetteApi(); 338 339 // Get the objects with the same ID (syncCode) as the one requested 340 PagedObjetMaquetteSummaries pagedObjetMaquetteSummaries = objetsMaquetteApi.rechercherObjetMaquette(_structureCode, _getPageable(0, 10, List.of()), null, _pegaseHelper.getEspaceId(_structureCode).toString(), List.of(), null, null, List.of(syncCode), null, null, null, null); 341 342 // Check that it can be edited 343 if (pagedObjetMaquetteSummaries.getTotalElements() == 1) 344 { 345 ObjetMaquetteSummary objetMaquetteSummary = pagedObjetMaquetteSummaries.getItems().get(0); 346 347 // If the object is not a Program or it is already validated, then it is not editable 348 if (!TypeObjetMaquette.FORMATION.equals(objetMaquetteSummary.getTypeObjetMaquette()) 349 || objetMaquetteSummary.getValideInAnyContexte()) 350 { 351 report.setStatus(ExportStatus.NON_EDITABLE_PROGRAM_ALREADY_EXISTS); 352 } 353 354 return objetMaquetteSummary; 355 } 356 // Should not be possible, there can only be one object with the ID 357 else 358 { 359 report.setStatus(ExportStatus.NON_EDITABLE_PROGRAM_ALREADY_EXISTS); 360 return null; 361 } 362 } 363 catch (IOException | ApiException e) 364 { 365 report.setStatus(ExportStatus.ERROR); 366 getLogger().warn("Une erreur est survenue lors la recherche de l'élément de code de synchronization Pégase '{}' pour vérifier son existance préalable dans Pégase", syncCode, e); 367 } 368 369 return null; 370 } 371 372 /** 373 * Check if an object or a non editable program already exists for the PegaseCode wanted 374 * @param report The report 375 * @param pegaseCode The code wanted 376 * @throws PegaseExportException If an error occurred while retrieving the Pégase espace 377 */ 378 private void _checkProgramCoherenceByCode(String pegaseCode, ExportReport report) throws PegaseExportException 379 { 380 try 381 { 382 ObjetsMaquetteApi objetsMaquetteApi = _pegaseApiManager.getObjetsMaquetteApi(); 383 384 // Get the objects with the same code as the one requested 385 PagedObjetMaquetteSummaries pagedObjetMaquetteSummaries = objetsMaquetteApi.rechercherObjetMaquette(_structureCode, _getPageable(0, 10, List.of()), pegaseCode, _pegaseHelper.getEspaceId(_structureCode).toString(), List.of(), null, null, List.of(), null, null, null, null); 386 387 Long numberOfResults = pagedObjetMaquetteSummaries.getTotalElements(); 388 // If there is one result, check if it is coherent 389 if (numberOfResults == 1) 390 { 391 ObjetMaquetteSummary objetMaquetteSummary = pagedObjetMaquetteSummaries.getItems().get(0); 392 393 // If the object found is not a program or if it is validated, then it is not editable 394 if (!TypeObjetMaquette.FORMATION.equals(objetMaquetteSummary.getTypeObjetMaquette()) 395 || objetMaquetteSummary.getValideInAnyContexte()) 396 { 397 report.setStatus(ExportStatus.NON_EDITABLE_PROGRAM_ALREADY_EXISTS); 398 } 399 } 400 // If there are more than one result found, it is not editable 401 else if (numberOfResults > 1) 402 { 403 report.setStatus(ExportStatus.NON_EDITABLE_PROGRAM_ALREADY_EXISTS); 404 } 405 // If there are no results, the program is exportable to Pégase (since no sync code was requested) 406 } 407 catch (IOException | ApiException e) 408 { 409 report.setStatus(ExportStatus.ERROR); 410 getLogger().warn("Une erreur est survenue lors la recherche de l'élément de code de synchronization Pégase '{}' pour vérifier son existance préalable dans Pégase", pegaseCode, e); 411 } 412 } 413 414 /** 415 * Check if there is at least one of the two Pegase codes 416 * @param report The report 417 * @param content The content 418 */ 419 private void _checkPegaseCodes(Content content, ExportReport report) 420 { 421 // If there is no pegase sync code or if it is invalid, check the pegaseCode 422 if (!content.hasValue(__PEGASE_SYNC_CODE) || !(_getUuidIfValid(content.getValue(__PEGASE_SYNC_CODE)) != null)) 423 { 424 // If there is no pegase code or it is invalid, report the missing fields 425 if (!content.hasValue(__CODE_PEGASE_ATTRIBUTE_NAME) || !_isValidPegaseCode(content.getValue(__CODE_PEGASE_ATTRIBUTE_NAME))) 426 { 427 _addMandatoryPegaseCodeAndReport(content, report); 428 } 429 } 430 } 431 432 private UUID _getUuidIfValid(String uuidToCheck) 433 { 434 try 435 { 436 return UUID.fromString(uuidToCheck); 437 } 438 catch (Exception e) 439 { 440 return null; 441 } 442 } 443 444 private boolean _isValidPegaseCode(String attribute) 445 { 446 return __CODE_PATTERN.matcher(attribute).matches(); 447 } 448 449 private void _addMandatoryDataPathAndReport(Content content, String dataPath, ExportReport report) 450 { 451 I18nizableText invalidMessage = new I18nizableText( 452 "plugin.odf-sync", 453 "PLUGINS_ODF_SYNC_EXPORT_PEGASE_MANDATORY_FIELD", 454 Map.of("fieldName", content.getDefinition(dataPath).getLabel()) 455 ); 456 report.addInvalidDataPath(content, invalidMessage); 457 } 458 459 private void _addMandatoryPegaseCodeAndReport(Content content, ExportReport report) 460 { 461 I18nizableText invalidMessage = new I18nizableText( 462 "plugin.odf-sync", 463 "PLUGINS_ODF_SYNC_EXPORT_PEGASE_MANDATORY_FIELDS", 464 Map.of("fieldName", content.getDefinition(__PEGASE_SYNC_CODE).getLabel(), 465 "secondFieldName", content.getDefinition(__CODE_PEGASE_ATTRIBUTE_NAME).getLabel()) 466 ); 467 report.addInvalidDataPath(content, invalidMessage); 468 } 469 470 private Set<ProgramItem> _getAllChildrenProgramItems(ProgramItem programItem) 471 { 472 return _getAllChildrenProgramItems(programItem, new HashSet<>()); 473 } 474 475 476 private Set<ProgramItem> _getAllChildrenProgramItems(ProgramItem programItem, Set<ProgramItem> allChildrenProgramItems) 477 { 478 allChildrenProgramItems.add(programItem); 479 480 for (ProgramItem child : _odfHelper.getChildProgramItems(programItem)) 481 { 482 if (!allChildrenProgramItems.contains(child)) 483 { 484 allChildrenProgramItems.addAll(_getAllChildrenProgramItems(child, allChildrenProgramItems)); 485 } 486 } 487 488 return allChildrenProgramItems; 489 } 490 491 private ObjetMaquetteAndEtagIfExists _getPegaseProgramIfAlreadyExists(Program program, ExportReport report) throws PegaseExportException 492 { 493 return _getPegaseObjetMaquetteDetailIfAlreadyExists(program, TypeObjetMaquette.FORMATION, report); 494 } 495 496 private ObjetMaquetteAndEtagIfExists _getPegaseObjetFormationIfAlreadyExists(Content content, ExportReport report) throws PegaseExportException 497 { 498 return _getPegaseObjetMaquetteDetailIfAlreadyExists(content, TypeObjetMaquette.OBJET_FORMATION, report); 499 } 500 501 private ObjetMaquetteAndEtagIfExists _getPegaseGroupIfAlreadyExists(Content content, ExportReport report) throws PegaseExportException 502 { 503 return _getPegaseObjetMaquetteDetailIfAlreadyExists(content, TypeObjetMaquette.GROUPEMENT, report); 504 } 505 506 private ObjetMaquetteAndEtagIfExists _getPegaseObjetMaquetteDetailIfAlreadyExists(Content content, TypeObjetMaquette type, ExportReport report) throws PegaseExportException 507 { 508 try 509 { 510 // If there is a sync code, it has been verified to be valid, try to get the Pégase object 511 if (content.hasValue(__PEGASE_SYNC_CODE)) 512 { 513 String id = content.getValue(__PEGASE_SYNC_CODE); 514 try 515 { 516 ObjetsMaquetteApi objetsMaquetteApi = _pegaseApiManager.getObjetsMaquetteApi(); 517 ObjetMaquetteDetail objetMaquetteFound = objetsMaquetteApi.lireObjetMaquette(_structureCode, UUID.fromString(id)); 518 519 // If the object found is not in the right type, throw an exception 520 if (!type.equals(objetMaquetteFound.getTypeObjetMaquette())) 521 { 522 throw new PegaseExportException("Erreur lors de l'export de " + content.getTitle() + ". Un élément avec l'identifiant Pégase existe déjà et n'est pas mutualisable ou modifiable"); 523 } 524 525 return new ObjetMaquetteAndEtagIfExists(objetMaquetteFound, _getEtag(objetsMaquetteApi)); 526 } 527 catch (ApiException e) 528 { 529 throw new PegaseExportException("Un code de synchronisation ('" + id + "') Pégase a été entré pour l'élément " + content.getTitle() + " mais aucun élément ne correspond à ce code dans Pégase.", e); 530 } 531 } 532 else if (content.hasValue(__CODE_PEGASE_ATTRIBUTE_NAME)) 533 { 534 String pegaseCode = content.getValue(__CODE_PEGASE_ATTRIBUTE_NAME); 535 536 return _getObjetMaquetteDetailFromPaged(content, pegaseCode, type, report); 537 } 538 } 539 catch (ApiException | IOException e) 540 { 541 throw new PegaseExportException("Une erreur est survenue en essayant de récupérer un élément déjà existant pour l'élément '" + content.getTitle() + "'.", e); 542 } 543 544 return new ObjetMaquetteAndEtagIfExists(null, null); 545 } 546 547 private ObjetMaquetteAndEtagIfExists _getObjetMaquetteDetailFromPaged(Content content, String pegaseCode, TypeObjetMaquette type, ExportReport report) throws ApiException, IOException, PegaseExportException 548 { 549 PagedObjetMaquetteSummaries pagedObjetsMaquetteFound = _pegaseApiManager.getObjetsMaquetteApi().rechercherObjetMaquette(_structureCode, _getPageable(0, Integer.MAX_VALUE, List.of()), pegaseCode, _pegaseHelper.getEspaceId(_structureCode).toString(), List.of(), null, null, List.of(), null, false, false, false); 550 551 ObjetMaquetteAndEtagIfExists objetMaquetteFound = _lookForObjetMaquetteInPage(content, pagedObjetsMaquetteFound, pegaseCode, type, report); 552 553 if (objetMaquetteFound != null) 554 { 555 return objetMaquetteFound; 556 } 557 558 for (int page = 0; page < pagedObjetsMaquetteFound.getTotalPages(); page++) 559 { 560 objetMaquetteFound = _lookForObjetMaquetteInPage(content, page, Integer.MAX_VALUE, pegaseCode, type, report); 561 562 if (objetMaquetteFound != null) 563 { 564 return objetMaquetteFound; 565 } 566 } 567 568 return new ObjetMaquetteAndEtagIfExists(null, null); 569 } 570 571 private ObjetMaquetteAndEtagIfExists _lookForObjetMaquetteInPage(Content content, int page, int taille, String pegaseCode, TypeObjetMaquette type, ExportReport report) throws ApiException, IOException, PegaseExportException 572 { 573 PagedObjetMaquetteSummaries pagedObjetsMaquetteFound = _pegaseApiManager.getObjetsMaquetteApi().rechercherObjetMaquette(_structureCode, _getPageable(page, taille, List.of()), pegaseCode, _pegaseHelper.getEspaceId(_structureCode).toString(), List.of(), null, null, List.of(), null, false, false, false); 574 575 return _lookForObjetMaquetteInPage(content, pagedObjetsMaquetteFound, pegaseCode, type, report); 576 } 577 578 private ObjetMaquetteAndEtagIfExists _lookForObjetMaquetteInPage(Content content, PagedObjetMaquetteSummaries pagedObjetsMaquetteFound, String pegaseCode, TypeObjetMaquette type, ExportReport report) throws ApiException, IOException, UnknownDataException, AmetysRepositoryException, PegaseExportException 579 { 580 List<ObjetMaquetteSummary> items = pagedObjetsMaquetteFound.getItems(); 581 if (items != null) 582 { 583 for (ObjetMaquetteSummary objetMaquette : items) 584 { 585 if (pegaseCode.equals(objetMaquette.getCode())) 586 { 587 if (objetMaquette.getTypeObjetMaquette().equals(type) && !objetMaquette.getValideInAnyContexte()) 588 { 589 ObjetsMaquetteApi objetsMaquetteApi = _pegaseApiManager.getObjetsMaquetteApi(); 590 return new ObjetMaquetteAndEtagIfExists(objetsMaquetteApi.lireObjetMaquette(_structureCode, objetMaquette.getId()), _getEtag(objetsMaquetteApi)); 591 } 592 else 593 { 594 report.updateExportReport(ExportStatus.ERROR, ProblemTypes.ELEMENT_ALREADY_EXIST); 595 throw new PegaseExportException("Erreur lors de l'export de " + content.getTitle() + ". Un élément avec l'identifiant ou le code Pégase existe déjà et n'est pas mutualisable ou modifiable"); 596 } 597 } 598 } 599 } 600 601 return new ObjetMaquetteAndEtagIfExists(null, null); 602 } 603 604 private void _initCreerObjetMaquetteRequest(CreerObjetMaquetteRequest creerObjetMaquetteRequest, TypeObjetMaquette typeObjetMaquette, Boolean mutualise, Content content) throws PegaseExportException 605 { 606 creerObjetMaquetteRequest.setCode(content.getValue(__CODE_PEGASE_ATTRIBUTE_NAME)); 607 creerObjetMaquetteRequest.setTypeObjetMaquette(typeObjetMaquette); 608 creerObjetMaquetteRequest.setEspaceId(_pegaseHelper.getEspaceId(_structureCode)); 609 creerObjetMaquetteRequest.setMutualise(mutualise); 610 } 611 612 private Enfant _createPegaseChild(UUID pegaseId, boolean mandatory) 613 { 614 Enfant pegaseChild = new Enfant(); 615 616 pegaseChild.setId(pegaseId.toString()); 617 pegaseChild.setObligatoire(mandatory); 618 619 return pegaseChild; 620 } 621 622 /** 623 * Create a program in Pegase 624 * @param program the program to export 625 * @param report the Pegase export report 626 */ 627 @Override 628 public void createProgram(Program program, ExportReport report) 629 { 630 if (!_isActive) 631 { 632 throw new UnsupportedOperationException("Pégase is not active in the configuration, you cannot import or synchronize a program in Pégase."); 633 } 634 635 int nbTotal = _getAllChildrenProgramItems(program).size(); 636 637 report.setNbTotal(nbTotal); 638 639 try 640 { 641 // Create the Program in Pegase 642 ObjetMaquetteDetail pegaseProgram = _createOrUpdateProgram(program, report); 643 report.addElementExported(program); 644 645 // Get the children of the Ametys program 646 List<ProgramPart> children = program.getProgramPartChildren(); 647 648 // The map of the children to link to the program and their own children 649 Map<String, PegaseChildWithChildren> childrenToLink = new HashMap<>(); 650 651 for (ProgramPart child : children) 652 { 653 // Create the pegase instance of the child 654 PegaseChildWithChildren enfant = _createChild((Content) child, false, report); 655 656 // Add the Enfant (created from the Pegase Id of the child) to the list of "Enfant" to link, if it is not already attached 657 if (enfant != null) 658 { 659 childrenToLink.put(enfant.pegaseChild().getId(), enfant); 660 } 661 } 662 663 _detachAndAttachAllChildren(program, pegaseProgram.getId(), childrenToLink, report); 664 } 665 catch (Exception e) 666 { 667 report.updateStatus(ExportStatus.ERROR); 668 getLogger().error("Une erreur est survenue lors de l'export de la formation '{}' ({}) dans Pégase", program.getTitle(), program.getId(), e); 669 } 670 } 671 672 private ObjetMaquetteDetail _createOrUpdateProgram(Program program, ExportReport report) throws PegaseExportException 673 { 674 try 675 { 676 ObjetMaquetteAndEtagIfExists objetMaquetteAndEtagIfExists = _getPegaseProgramIfAlreadyExists(program, report); 677 678 ObjetMaquetteDetail objetMaquetteDetailFound = objetMaquetteAndEtagIfExists.objetMaquetteDetail(); 679 680 // Program does not already exists, need to create it 681 if (objetMaquetteDetailFound == null) 682 { 683 return _createProgram(program); 684 } 685 // Program already exists, need to update it 686 else 687 { 688 // Get the previous etag (number of version) required to update the program 689 String etag = objetMaquetteAndEtagIfExists.etag(); 690 return _updateProgram(program, objetMaquetteDetailFound, etag); 691 } 692 } 693 catch (Exception e) 694 { 695 report.updateExportReport(ExportStatus.ERROR, ProblemTypes.API_ERROR); 696 697 throw new PegaseExportException("Une erreur est survenue lors de la création ou modification de la formation dans Pégase", e); 698 } 699 } 700 701 private ObjetMaquetteDetail _createProgram(Program program) throws ApiException, IOException, PegaseExportException 702 { 703 ObjetsMaquetteApi objetsMaquetteApi = _pegaseApiManager.getObjetsMaquetteApi(); 704 705 CreerFormationRequest creerFormationRequest = new CreerFormationRequest(); 706 _initCreerObjetMaquetteRequest(creerFormationRequest, TypeObjetMaquette.FORMATION, false, program); 707 708 // Update descripteurs formation 709 creerFormationRequest.setDescripteursObjetMaquette(_getDescripteursFormationRequest(program)); 710 711 ObjetMaquetteDetail programCreated = objetsMaquetteApi.creerObjetMaquette(_structureCode, creerFormationRequest); 712 713 String etag = _getEtag(objetsMaquetteApi); 714 715 return _updateDescripteurProgram(program, programCreated.getId(), etag, objetsMaquetteApi); 716 } 717 718 private ObjetMaquetteDetail _updateProgram(Program program, ObjetMaquetteDetail objetMaquetteDetailFound, String etag) throws IOException, ApiException, PegaseExportException 719 { 720 ObjetsMaquetteApi objetsMaquetteApi = _pegaseApiManager.getObjetsMaquetteApi(); 721 722 // Update descripteurs formation 723 DescripteursFormationRequest descripteursFormationRequest = _getDescripteursFormationRequest(program); 724 725 // If no updates were necessary, return the former objetMaquetteDetail 726 UUID pegaseProgramId = objetMaquetteDetailFound.getId(); 727 objetsMaquetteApi.modifierDescripteursObjetMaquette(_structureCode, pegaseProgramId, etag, descripteursFormationRequest); 728 729 return _updateDescripteurProgram(program, pegaseProgramId, _getEtag(objetsMaquetteApi), objetsMaquetteApi); 730 } 731 732 private ObjetMaquetteDetail _updateDescripteurProgram(AbstractProgram program, UUID pegaseId, String etag, ObjetsMaquetteApi objetsMaquetteApi) throws ApiException, IOException, PegaseExportException 733 { 734 _updateDescripteursEnqueteFormation(program, pegaseId, etag, objetsMaquetteApi); 735 736 return _updateDescripteursSyllabus(program, pegaseId, TypeObjetMaquette.FORMATION, new DescripteursFormationSyllabus()); 737 } 738 739 private DescripteursFormationRequest _getDescripteursFormationRequest(Program program) 740 { 741 DescripteursFormationRequest descripteursFormationRequest = new DescripteursFormationRequest(); 742 743 String programTitle = program.getTitle(); 744 745 descripteursFormationRequest.setLibelle(StringUtils.truncate(programTitle, 50)); 746 descripteursFormationRequest.setLibelleLong(StringUtils.truncate(programTitle, 150)); 747 descripteursFormationRequest.setEcts(_getEctsFromContent(program)); 748 749 // Education king -> Type 750 descripteursFormationRequest.setType(_pegaseHelper.getPegaseCodeForField(program, AbstractProgram.EDUCATION_KIND)); 751 752 return descripteursFormationRequest; 753 } 754 755 private ObjetMaquetteDetail _updateDescripteursSyllabus(Content content, UUID pegaseProgramId, TypeObjetMaquette type, DescripteursSyllabus descripteursSyllabus) throws ApiException, IOException, PegaseExportException 756 { 757 ObjetsMaquetteApi objetsMaquetteApi = _pegaseApiManager.getObjetsMaquetteApi(); 758 759 String description = null; 760 if (content.hasValue("description")) 761 { 762 description = _richTextHelper.richTextToString(content.getValue("description")); 763 } 764 else if (content.hasValue("presentation")) 765 { 766 description = _richTextHelper.richTextToString(content.getValue("presentation")); 767 } 768 769 if (description != null) 770 { 771 descripteursSyllabus.setDescription(StringUtils.truncate(description, 2000)); // la description doit faire moins de 2000 caractères 772 } 773 774 if (content.hasValue("objectives")) 775 { 776 descripteursSyllabus.setObjectif(StringUtils.truncate(_richTextHelper.richTextToString(content.getValue("objectives")), 2000)); // l'objectif doit faire moins de 2000 caractères 777 } 778 779 if (content.hasValue("neededPrerequisite")) 780 { 781 descripteursSyllabus.setPrerequisPedagogique(StringUtils.truncate(_richTextHelper.richTextToString(content.getValue("neededPrerequisite")), 2000)); // les prérequis doit faire moins de 2000 caractères 782 } 783 784 // Retrieve the created object to get its current Etag 785 ObjetMaquetteAndEtagIfExists objetMaquetteExistingAndEtag = _getPegaseObjetMaquetteDetailIfAlreadyExists(content, type, new ExportReport(content)); 786 787 return objetsMaquetteApi.modifierDescripteursSyllabusObjetMaquette(_structureCode, pegaseProgramId, objetMaquetteExistingAndEtag.etag(), descripteursSyllabus); 788 } 789 790 private ObjetMaquetteDetail _updateDescripteursEnqueteFormation(AbstractProgram program, UUID pegaseProgramId, String etag, ObjetsMaquetteApi objetsMaquetteApi) throws ApiException 791 { 792 DescripteursSiseRequest descripteursSiseRequest = new DescripteursSiseRequest(); 793 794 // Degree -> typeDiplome 795 descripteursSiseRequest.setTypeDiplome(_pegaseHelper.getPegaseCodeForField(program, AbstractProgram.DEGREE)); 796 // EducationLevel -> niveauFormation 797 descripteursSiseRequest.setNiveauDiplomeSise(_pegaseHelper.getPegaseCodeForField(program, AbstractProgram.LEVEL)); 798 // RncpLevel -> niveauDiplome 799 descripteursSiseRequest.setNiveauDiplome(_pegaseHelper.getPegaseCodeForField(program, AbstractProgram.RNCP_LEVEL)); 800 // Domain 801 descripteursSiseRequest.setDomaineFormation(_pegaseHelper.getPegaseCodeForFirstValue(program, AbstractProgram.DOMAIN)); 802 // Mention 803 descripteursSiseRequest.setMention(_pegaseHelper.getPegaseCodeForField(program, AbstractProgram.MENTION)); 804 // ProgramFields -> champFormation 805 descripteursSiseRequest.setChampFormation(_pegaseHelper.getPegaseCodeForField(program, AbstractProgram.PROGRAM_FIELD)); 806 807 // Update descripteurs enquete 808 DescripteursEnqueteRequest descripteursEnqueteRequest = new DescripteursEnqueteRequest(); 809 descripteursEnqueteRequest.setDescripteursSise(descripteursSiseRequest); 810 811 DescripteursAglae descripteursAglae = new DescripteursAglae(); 812 descripteursAglae.setHabilitePourBoursesAglae(false); 813 descripteursEnqueteRequest.setDescripteursAglae(descripteursAglae); 814 815 return objetsMaquetteApi.modifierDescripteursEnqueteObjetMaquette(_structureCode, pegaseProgramId, etag, descripteursEnqueteRequest); 816 } 817 818 private BigDecimal _getEctsFromContent(Content content) 819 { 820 if (content.hasValue(__ECTS_DATA_PATH)) 821 { 822 Object value = content.getValue(__ECTS_DATA_PATH); 823 if (value instanceof Double ects) 824 { 825 return BigDecimal.valueOf(ects); 826 } 827 else if (value instanceof ContentValue contentValue) 828 { 829 try 830 { 831 BigDecimal ects = contentValue.getContentIfExists() 832 .map(c -> c.<String>getValue(OdfReferenceTableEntry.CODE)) 833 .map(Double::parseDouble) 834 .map(BigDecimal::valueOf) 835 .orElse(null); 836 return ects; 837 } 838 catch (NumberFormatException e) 839 { 840 getLogger().debug("The ects value '{}' is not a double, therefore, it is not compatible with Pégase ects and will not be exported", contentValue); 841 } 842 843 } 844 } 845 846 return null; 847 } 848 849 private PegaseChildWithChildren _createChild(Content content, boolean parentMutualise, ExportReport report) 850 { 851 boolean success = true; 852 try 853 { 854 // Conteneur : semestre, année -> OF 855 if (content instanceof Container container) 856 { 857 return _createOFFromContainer(container, parentMutualise, report); 858 } 859 // Parcours -> OF 860 if (content instanceof SubProgram subProgram) 861 { 862 return _createObjetFormationAndChildren(subProgram, __PARCOURS_TYPE_ID, parentMutualise, report); 863 } 864 // ELP -> OF 865 if (content instanceof Course course) 866 { 867 return _createObjetFormationAndChildren(course, _pegaseHelper.getPegaseCodeForFirstValue(course, Course.COURSE_TYPE), parentMutualise, report); 868 } 869 // liste d'ELP -> Groupement 870 if (content instanceof CourseList) 871 { 872 return _createGroupFromCourseList((CourseList) content, parentMutualise, report); 873 } 874 875 return null; 876 } 877 catch (Exception ex) 878 { 879 success = false; 880 getLogger().error("Erreur lors de l'export de l'élément '{}'", content.getTitle(), ex); 881 return null; 882 } 883 finally 884 { 885 if (success) 886 { 887 report.addElementExported(content); 888 } 889 } 890 } 891 892 /** 893 * Creates a Pégase objet formation from a container 894 * @param container The container 895 * @param parentMutualise If the element's parent is mutualised 896 * @param report The report 897 * @return An {@link Enfant} object, Pégase child 898 * @throws PegaseExportException If an error occurs 899 * @throws IOException If an API error occurs 900 * @throws ApiException If an error occurs 901 */ 902 private PegaseChildWithChildren _createOFFromContainer(Container container, boolean parentMutualise, ExportReport report) throws PegaseExportException, IOException, ApiException 903 { 904 String type = null; 905 906 String natureCode = getContainerNatureCode(container); 907 boolean isYear = "annee".equals(natureCode); 908 boolean isSemester = "semestre".equals(natureCode); 909 910 // If it is a container of nature : semester of year, export it as an OF of type semester or year 911 if (isYear || isSemester) 912 { 913 type = isYear ? __ANNEE_TYPE_ID : __SEMESTRE_TYPE_ID; 914 915 return _createObjetFormationAndChildren(container, type, parentMutualise, report); 916 } 917 918 // Else, ignore it 919 return null; 920 } 921 922 /** 923 * Creates a Pégase objet formation from a content 924 * @param content The content 925 * @param parentMutualise If the element's parent is mutualised 926 * @param report The report 927 * @return An {@link Enfant} object, Pégase child 928 * @throws PegaseExportException If an error occurs 929 * @throws IOException If an API error occurs 930 * @throws ApiException If an error occurs 931 */ 932 private PegaseChildWithChildren _createObjetFormationAndChildren(Content content, String type, boolean parentMutualise, ExportReport report) throws IOException, PegaseExportException, ApiException 933 { 934 // Create or update the object 935 ObjetMaquetteDetail objetMaquetteDetailCreated = _createOrUpdateObjetFormation(content, type, parentMutualise, report); 936 937 _updateDescripteursSyllabus(content, objetMaquetteDetailCreated.getId(), TypeObjetMaquette.OBJET_FORMATION, new DescripteursObjetFormationSyllabus()); 938 939 // Create the children if there are any 940 ChildrenToAttachForObjectRequest childrenToAttach = _createChildrenIfAny(content, objetMaquetteDetailCreated, report); 941 942 UUID objetMaquetteDetailCreatedId = objetMaquetteDetailCreated.getId(); 943 944 // Create and return an Enfant object from the child created 945 return new PegaseChildWithChildren(_createPegaseChild(objetMaquetteDetailCreatedId, true), childrenToAttach); 946 947 } 948 949 private ObjetMaquetteDetail _createOrUpdateObjetFormation(Content content, String type, boolean parentMutualise, ExportReport report) throws PegaseExportException 950 { 951 try 952 { 953 // Check if the object already exists 954 ObjetMaquetteAndEtagIfExists objetMaquetteAndEtagIfExists = _getPegaseObjetFormationIfAlreadyExists(content, report); 955 956 ObjetMaquetteDetail objetMaquetteDetail = objetMaquetteAndEtagIfExists.objetMaquetteDetail(); 957 // If it already exists, update the object 958 if (objetMaquetteDetail != null) 959 { 960 return _updateObjetFormation(objetMaquetteAndEtagIfExists, content, type, parentMutualise, report); 961 } 962 // If it did not already exist, create the object 963 else 964 { 965 return _createObjetFormation(content, type, parentMutualise, report); 966 } 967 } 968 catch (Exception e) 969 { 970 report.updateExportReport(ExportStatus.ERROR, ProblemTypes.API_ERROR); 971 972 throw new PegaseExportException("Une erreur est survenue lors de la récupération de l'objet Pégase déjà existant", e); 973 } 974 } 975 976 private PegaseChildWithChildren _createGroupFromCourseList(CourseList courseList, boolean parentMutualise, ExportReport report) throws PegaseExportException 977 { 978 ChoiceType type = courseList.getType(); 979 boolean mandatory = !ChoiceType.OPTIONAL.equals(type); 980 PlageDeChoix plageDeChoix = ChoiceType.CHOICE.equals(type) ? _buildPlageDeChoix(courseList) : null; 981 982 ObjetMaquetteDetail objetMaquetteDetailCreated = _createOrUpdateGroup(courseList, parentMutualise, plageDeChoix, report); 983 984 ChildrenToAttachForObjectRequest childrenToAttach = _createChildrenIfAny(courseList, objetMaquetteDetailCreated, report); 985 986 UUID objetMaquetteDetailCreatedId = objetMaquetteDetailCreated.getId(); 987 988 return new PegaseChildWithChildren(_createPegaseChild(objetMaquetteDetailCreatedId, mandatory), childrenToAttach); 989 } 990 991 private ObjetMaquetteDetail _createOrUpdateGroup(CourseList courseList, boolean parentMutualise, PlageDeChoix plageDeChoix, ExportReport report) throws PegaseExportException 992 { 993 try 994 { 995 // Check if the object already exists 996 ObjetMaquetteAndEtagIfExists objetMaquetteAndEtagIfExists = _getPegaseGroupIfAlreadyExists(courseList, report); 997 998 ObjetMaquetteDetail objetMaquetteDetail = objetMaquetteAndEtagIfExists.objetMaquetteDetail(); 999 // If it already exists, update the object 1000 if (objetMaquetteDetail != null) 1001 { 1002 return _updateGroup(objetMaquetteAndEtagIfExists, courseList, parentMutualise, plageDeChoix, report); 1003 } 1004 // If it did not already exist, create the object 1005 else 1006 { 1007 return _createGroup(courseList, parentMutualise, plageDeChoix, report); 1008 } 1009 } 1010 catch (Exception e) 1011 { 1012 report.updateExportReport(ExportStatus.ERROR, ProblemTypes.API_ERROR); 1013 1014 throw new PegaseExportException("Une erreur est survenue lors de la récupération de l'objet Pégase déjà existant", e); 1015 } 1016 } 1017 1018 private ObjetMaquetteDetail _updateGroup(ObjetMaquetteAndEtagIfExists objetMaquetteAndEtagIfExists, Content content, boolean parentMutualise, PlageDeChoix plageDeChoix, ExportReport report) throws PegaseExportException, IOException 1019 { 1020 DescripteursGroupementRequest descripteursGroupementRequest = new DescripteursGroupementRequest(); 1021 1022 String label = content.getTitle(); 1023 descripteursGroupementRequest.setLibelle(StringUtils.truncate(label, 50)); 1024 descripteursGroupementRequest.setLibelleLong(StringUtils.truncate(label, 150)); 1025 descripteursGroupementRequest.setPlageDeChoix(plageDeChoix); 1026 1027 return _updateObjetMaquette(objetMaquetteAndEtagIfExists, content, parentMutualise, descripteursGroupementRequest, report); 1028 } 1029 1030 private ObjetMaquetteDetail _createGroup(CourseList courseList, boolean parentMutualise, PlageDeChoix plageDeChoix, ExportReport report) throws PegaseExportException 1031 { 1032 DescripteursGroupementRequest descripteursGroupementRequest = new DescripteursGroupementRequest(); 1033 if (plageDeChoix != null) 1034 { 1035 descripteursGroupementRequest.setPlageDeChoix(plageDeChoix); 1036 } 1037 1038 CreerGroupementRequest creerGroupementRequest = new CreerGroupementRequest(); 1039 1040 return _createObjetMaquette(courseList, TypeObjetMaquette.GROUPEMENT, parentMutualise, descripteursGroupementRequest, creerGroupementRequest, report); 1041 } 1042 1043 private ChildrenToAttachForObjectRequest _createChildrenIfAny(Content content, ObjetMaquetteDetail objetMaquetteDetailCreated, ExportReport report) throws UnknownDataException, AmetysRepositoryException 1044 { 1045 Map<String, PegaseChildWithChildren> children = new HashMap<>(); 1046 if (content instanceof ProgramItem programItem) 1047 { 1048 // Create the children 1049 children = _createAllChildren(_odfHelper.getChildProgramItems(programItem), objetMaquetteDetailCreated.getMutualise(), report); 1050 } 1051 1052 return new ChildrenToAttachForObjectRequest(content, objetMaquetteDetailCreated.getId(), children); 1053 } 1054 1055 private Map<String, PegaseChildWithChildren> _createAllChildren(List<ProgramItem> programItem, boolean parentMutualise, ExportReport report) 1056 { 1057 Map<String, PegaseChildWithChildren> children = new HashMap<>(); 1058 1059 for (ProgramItem childContent : programItem) 1060 { 1061 PegaseChildWithChildren child = _createChild((Content) childContent, parentMutualise, report); 1062 1063 // If the child was created, add it to the list of children to attach 1064 if (child != null) 1065 { 1066 children.put(child.pegaseChild().getId(), child); 1067 } 1068 } 1069 1070 return children; 1071 } 1072 1073 private void _detachAndAttachAllChildren(Content content, UUID pegaseParentID, Map<String, PegaseChildWithChildren> children, ExportReport report) throws PegaseExportException 1074 { 1075 try 1076 { 1077 // The children that are not going to be related to the program anymore 1078 Map<String, PegaseChildWithChildren> childrenToCheck = Map.copyOf(children); 1079 1080 _detachChildren(content, pegaseParentID, children, report); 1081 1082 // For each child that is not directly related to the program anymore, attach its own children recursively 1083 for (PegaseChildWithChildren childToAttach : childrenToCheck.values()) 1084 { 1085 if (!children.containsValue(childToAttach)) 1086 { 1087 ChildrenToAttachForObjectRequest childrenToAttach = childToAttach.childrenToAttachRequest(); 1088 _attachAllChildren(childrenToAttach.parentContent(), childrenToAttach.parentId(), childrenToAttach.children(), report); 1089 } 1090 } 1091 1092 _attachAllChildren(content, pegaseParentID, children, report); 1093 } 1094 catch (Exception e) 1095 { 1096 throw new PegaseExportException("Une erreur est survenue lors de l'attachement des enfants de la formation '" + content.getTitle() + "'"); 1097 } 1098 } 1099 1100 private void _detachChildren(Content content, UUID pegaseParentObjectId, Map<String, PegaseChildWithChildren> children, ExportReport report) throws UnknownDataException, AmetysRepositoryException, PegaseExportException 1101 { 1102 try 1103 { 1104 // Detach children recursively 1105 for (PegaseChildWithChildren childToAttach : children.values()) 1106 { 1107 ChildrenToAttachForObjectRequest childrenToAttach = childToAttach.childrenToAttachRequest(); 1108 _detachChildren(childrenToAttach.parentContent(), childrenToAttach.parentId(), childrenToAttach.children(), report); 1109 } 1110 1111 MaquetteStructure structureMaquetteParent = _pegaseApiManager.getMaquettesApi().lireStructureMaquette(_structureCode, pegaseParentObjectId); 1112 1113 ObjetMaquetteStructure parentRacine = structureMaquetteParent.getRacine(); 1114 List<EnfantsStructure> childrenAlreadyAttached = parentRacine.getEnfants(); 1115 1116 // If there are children already attached and the option trustAmetys is checked, remove every child already attached from the list of children to attach 1117 if (childrenAlreadyAttached != null) 1118 { 1119 for (EnfantsStructure child : childrenAlreadyAttached) 1120 { 1121 UUID childId = child.getObjetMaquette().getId(); 1122 String stringChildId = childId.toString(); 1123 1124 // Detach children in Pégase in order to reattach them correctly 1125 if (children.keySet().contains(stringChildId)) 1126 { 1127 _pegaseApiManager.getObjetsMaquetteApi().retirerEnfant(_structureCode, pegaseParentObjectId, childId); 1128 } 1129 // If trust ametys is selected, detach children attached in Pégase which are not attached in Ametys 1130 else if (_trustAmetys) 1131 { 1132 try 1133 { 1134 _pegaseApiManager.getObjetsMaquetteApi().retirerEnfant(_structureCode, pegaseParentObjectId, childId); 1135 } 1136 catch (ApiException e) 1137 { 1138 report.updateExportReport(ExportStatus.ERROR, ProblemTypes.API_ERROR, content); 1139 getLogger().warn("L'enfant de code Pégase '{}' de l'élément '{}' n'a pas pu être détaché dans Pégase", child.getId(), content.getTitle(), e); 1140 } 1141 } 1142 } 1143 } 1144 } 1145 catch (ApiException e) 1146 { 1147 report.updateExportReport(ExportStatus.WARN, ProblemTypes.API_ERROR, content); 1148 1149 getLogger().warn("Les liens avec les enfants de l'élément '{}' n'ont pas pu être traités car les enfants depuis Pégase n'ont pas pu être récupérés.", content.getTitle(), e); 1150 } 1151 catch (Exception e) 1152 { 1153 throw new PegaseExportException("Erreur lors de l'attachement des enfants de l'élément " + content.getTitle() + ".", e); 1154 } 1155 } 1156 1157 private void _attachAllChildren(Content content, UUID pegaseParentId, Map<String, PegaseChildWithChildren> children, ExportReport report) throws UnknownDataException, AmetysRepositoryException, PegaseExportException, IOException 1158 { 1159 // If there are children that need to be attached 1160 if (!children.isEmpty()) 1161 { 1162 // For each child, attach its own children recursively 1163 for (PegaseChildWithChildren childToAttach : children.values()) 1164 { 1165 ChildrenToAttachForObjectRequest childrenToAttach = childToAttach.childrenToAttachRequest(); 1166 _attachAllChildren(childrenToAttach.parentContent(), childrenToAttach.parentId(), childrenToAttach.children(), report); 1167 } 1168 1169 for (PegaseChildWithChildren child : children.values()) 1170 { 1171 Enfant pegaseChildToAttach = child.pegaseChild(); 1172 try 1173 { 1174 _changeMutualiseIfNecessary(UUID.fromString(pegaseChildToAttach.getId()), pegaseParentId); 1175 1176 // Try to attach them 1177 _pegaseApiManager.getObjetsMaquetteApi().ajouterEnfant(_structureCode, pegaseParentId, pegaseChildToAttach); 1178 } 1179 catch (ApiException ex) 1180 { 1181 report.updateExportReport(ExportStatus.WARN, ProblemTypes.LINKS_MISSING, content); 1182 1183 getLogger().warn("Une erreur est survenue lors de l'attachement de l'enfant ({}) à l'élément ({}) dans Pégase", pegaseChildToAttach.getId(), content.getTitle(), ex); 1184 } 1185 } 1186 } 1187 } 1188 1189 private ObjetMaquetteAndEtagIfExists _changeMutualiseIfNecessary(UUID childId, UUID parentID) throws IOException, ApiException 1190 { 1191 ObjetsMaquetteApi objetsMaquetteApi = _pegaseApiManager.getObjetsMaquetteApi(); 1192 ObjetMaquetteDetail childObjetMaquetteDetail = objetsMaquetteApi.lireObjetMaquette(_structureCode, childId); 1193 String etag = _getEtag(objetsMaquetteApi); 1194 ObjetMaquetteAndEtagIfExists objetMaquetteAndEtagIfExists = new ObjetMaquetteAndEtagIfExists(childObjetMaquetteDetail, etag); 1195 1196 List<Contexte> contextes = childObjetMaquetteDetail.getContextes(); 1197 1198 // If the object has no parents (orphan), it does not need to become mutualised, and if it has multiple parents, it is already mutualised 1199 // If the object has exactly one parent 1200 if (!childObjetMaquetteDetail.getMutualise() && contextes.size() == 1) 1201 { 1202 List<UUID> chemins = contextes.get(0).getChemin(); 1203 int numberOfChemins = chemins.size(); 1204 1205 // If the direct parent is not the program we are attaching it to, set mutualise to true 1206 // If there is only one element in chemins, then it is the child itself 1207 if (numberOfChemins > 1 && !parentID.equals(chemins.get(numberOfChemins - 1))) 1208 { 1209 return _makeHierarchyMutualised(objetMaquetteAndEtagIfExists); 1210 } 1211 } 1212 1213 return objetMaquetteAndEtagIfExists; 1214 } 1215 1216 private ObjetMaquetteAndEtagIfExists _makeHierarchyMutualised(ObjetMaquetteAndEtagIfExists objetMaquetteAndEtagIfExists) throws ApiException, IOException 1217 { 1218 ObjetMaquetteDetail objetMaquetteDetail = objetMaquetteAndEtagIfExists.objetMaquetteDetail(); 1219 1220 ObjetsMaquetteApi objetsMaquetteApi = null; 1221 1222 // If there are any, make the children mutualise 1223 if (objetMaquetteDetail.getEnfants().size() != 0) 1224 { 1225 MaquetteStructure structureMaquetteParent = _pegaseApiManager.getMaquettesApi().lireStructureMaquette(_structureCode, objetMaquetteDetail.getId()); 1226 1227 objetsMaquetteApi = _pegaseApiManager.getObjetsMaquetteApi(); 1228 ObjetMaquetteStructure racine = structureMaquetteParent.getRacine(); 1229 1230 List<EnfantsStructure> children = racine.getEnfants(); 1231 1232 for (EnfantsStructure child : children) 1233 { 1234 // Need to retrieve the objet maquette again to get the Etag 1235 ObjetMaquetteDetail childObjetMaquette = objetsMaquetteApi.lireObjetMaquette(_structureCode, child.getObjetMaquette().getId()); 1236 // Make the child hierarchy mutualise recusively 1237 _makeHierarchyMutualised(new ObjetMaquetteAndEtagIfExists(childObjetMaquette, _getEtag(objetsMaquetteApi))); 1238 } 1239 } 1240 else 1241 { 1242 // Retrieve the API again to reset the "external" attribute to false 1243 objetsMaquetteApi = _pegaseApiManager.getObjetsMaquetteApi(); 1244 } 1245 1246 objetMaquetteDetail = objetsMaquetteApi.rendreMutualise(_structureCode, objetMaquetteDetail.getId(), objetMaquetteAndEtagIfExists.etag()); 1247 1248 return new ObjetMaquetteAndEtagIfExists(objetMaquetteDetail, _getEtag(objetsMaquetteApi)); 1249 } 1250 1251 private ObjetMaquetteDetail _updateObjetFormation(ObjetMaquetteAndEtagIfExists objetMaquetteAndEtagIfExists, Content content, String type, boolean parentMutualise, ExportReport report) throws PegaseExportException, IOException 1252 { 1253 DescripteursObjetFormationRequest descripteursObjetFormationRequest = new DescripteursObjetFormationRequest(); 1254 1255 String label = content.getTitle(); 1256 descripteursObjetFormationRequest.setLibelle(StringUtils.truncate(label, 50)); 1257 descripteursObjetFormationRequest.setLibelleLong(StringUtils.truncate(label, 150)); 1258 1259 // Set the formation type (ANNEE, SEMESTRE, UE...) 1260 descripteursObjetFormationRequest.setType(type); 1261 1262 descripteursObjetFormationRequest.setEcts(_getEctsFromContent(content)); 1263 1264 return _updateObjetMaquette(objetMaquetteAndEtagIfExists, content, parentMutualise, descripteursObjetFormationRequest, report); 1265 } 1266 1267 private ObjetMaquetteDetail _updateObjetMaquette(ObjetMaquetteAndEtagIfExists objetMaquetteAndEtagIfExists, Content content, boolean parentMutualise, DescripteursObjetMaquetteRequest descObjetMaquette, ExportReport report) throws PegaseExportException, IOException 1268 { 1269 try 1270 { 1271 ObjetMaquetteAndEtagIfExists objetMaquetteAndEtagIfExistsMutualised = objetMaquetteAndEtagIfExists; 1272 // If the parent is mutualized, make the object mutualized too, and retrieve the new Etag and object from the update 1273 if (parentMutualise) 1274 { 1275 objetMaquetteAndEtagIfExistsMutualised = _makeHierarchyMutualised(objetMaquetteAndEtagIfExists); 1276 } 1277 1278 ObjetMaquetteDetail objetMaquetteDetailFound = objetMaquetteAndEtagIfExistsMutualised.objetMaquetteDetail(); 1279 ObjetsMaquetteApi objetsMaquetteApi = _pegaseApiManager.getObjetsMaquetteApi(); 1280 1281 ObjetMaquetteDetail objetMaquetteDetail = objetsMaquetteApi.modifierDescripteursObjetMaquette(_structureCode, objetMaquetteDetailFound.getId(), objetMaquetteAndEtagIfExistsMutualised.etag(), descObjetMaquette); 1282 1283 return objetMaquetteDetail; 1284 } 1285 catch (ApiException e) 1286 { 1287 report.updateExportReport(ExportStatus.ERROR, ProblemTypes.ELEMENT_NOT_EXPORTED); 1288 1289 throw new PegaseExportException("L'élément " + content.getTitle() + " n'a pas pu être exportée car sa modification dans Pégase a posé un problème", e); 1290 } 1291 } 1292 1293 private ObjetMaquetteDetail _createObjetFormation(Content content, String type, boolean parentMutualise, ExportReport report) throws PegaseExportException 1294 { 1295 DescripteursObjetFormationRequest descripteursObjetFormationRequest = new DescripteursObjetFormationRequest(); 1296 descripteursObjetFormationRequest.setType(type); 1297 1298 descripteursObjetFormationRequest.setEcts(_getEctsFromContent(content)); 1299 1300 CreerObjetFormationRequest creerObjetFormationRequest = new CreerObjetFormationRequest(); 1301 1302 return _createObjetMaquette(content, TypeObjetMaquette.OBJET_FORMATION, parentMutualise, descripteursObjetFormationRequest, creerObjetFormationRequest, report); 1303 } 1304 1305 private ObjetMaquetteDetail _createObjetMaquette(Content content, TypeObjetMaquette typeObjetMaquette, boolean parentMutualise, DescripteursObjetMaquetteRequest descripteursObjetMaquetteRequest, CreerObjetMaquetteRequest creerObjetMaquetteRequest, ExportReport report) throws PegaseExportException 1306 { 1307 try 1308 { 1309 ObjetsMaquetteApi objetsMaquetteApi = _pegaseApiManager.getObjetsMaquetteApi(); 1310 1311 _initCreerObjetMaquetteRequest(creerObjetMaquetteRequest, typeObjetMaquette, parentMutualise, content); 1312 1313 String label = content.getTitle(); 1314 descripteursObjetMaquetteRequest.setLibelle(StringUtils.truncate(label, 50)); 1315 descripteursObjetMaquetteRequest.setLibelleLong(StringUtils.truncate(label, 150)); 1316 1317 creerObjetMaquetteRequest.setDescripteursObjetMaquette(descripteursObjetMaquetteRequest); 1318 1319 ObjetMaquetteDetail objetMaquetteDetail = objetsMaquetteApi.creerObjetMaquette(_structureCode, creerObjetMaquetteRequest); 1320 1321 return objetMaquetteDetail; 1322 } 1323 catch (IOException e) 1324 { 1325 report.updateExportReport(ExportStatus.ERROR, ProblemTypes.API_ERROR); 1326 1327 throw new PegaseExportException("Une erreur est survenue lors de la création de l'objet Pégase", e); 1328 } 1329 catch (ApiException e) 1330 { 1331 report.updateExportReport(ExportStatus.ERROR, ProblemTypes.ELEMENT_NOT_EXPORTED); 1332 1333 throw new PegaseExportException("L'élément " + content.getTitle() + " n'a pas pu être exporté dû à un problème rencontré avec Pégase", e); 1334 } 1335 } 1336 1337 private PlageDeChoix _buildPlageDeChoix(CourseList courseList) 1338 { 1339 PlageDeChoix plageDeChoix = new PlageDeChoix(); 1340 1341 Long min = courseList.getValue(CourseList.MIN_COURSES, false, 0L); 1342 plageDeChoix.setMin(min.intValue()); 1343 1344 Long max = courseList.getValue(CourseList.MAX_COURSES, false, min); 1345 plageDeChoix.setMax(max.intValue()); 1346 1347 return plageDeChoix; 1348 } 1349 1350 /** 1351 * If the objet exists, store the objetMaquette detail and its Etag 1352 * @param objetMaquetteDetail The objet maquette detail 1353 * @param etag The etag of the object (its version) 1354 */ 1355 protected record ObjetMaquetteAndEtagIfExists (ObjetMaquetteDetail objetMaquetteDetail, String etag) { /* empty */ } 1356 1357 /** 1358 * Store the parameters necessary to attach all children of an object 1359 * @param parentContent The content of the object 1360 * @param parentId The pegase ID of the object 1361 * @param children The children of the object 1362 */ 1363 protected record ChildrenToAttachForObjectRequest (Content parentContent, UUID parentId, Map<String, PegaseChildWithChildren> children) { /* empty */ } 1364 1365 /** 1366 * Store the Pegase child and its children to attach request 1367 * @param pegaseChild The pegase child 1368 * @param childrenToAttachRequest The children to attach request parameters as a {@link ChildrenToAttachForObjectRequest} object 1369 */ 1370 protected record PegaseChildWithChildren (Enfant pegaseChild, ChildrenToAttachForObjectRequest childrenToAttachRequest) { /* empty */ } 1371}