001/* 002 * Copyright 2018 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.odfpilotage.report.impl; 017 018import java.io.File; 019import java.io.FileOutputStream; 020import java.io.IOException; 021import java.util.Comparator; 022import java.util.List; 023import java.util.Map; 024import java.util.Map.Entry; 025import java.util.TreeMap; 026 027import javax.xml.transform.Result; 028import javax.xml.transform.TransformerConfigurationException; 029import javax.xml.transform.TransformerFactory; 030import javax.xml.transform.sax.SAXTransformerFactory; 031import javax.xml.transform.sax.TransformerHandler; 032import javax.xml.transform.stream.StreamResult; 033 034import org.apache.cocoon.xml.AttributesImpl; 035import org.apache.cocoon.xml.XMLUtils; 036import org.apache.commons.io.FileUtils; 037import org.apache.commons.lang3.StringUtils; 038import org.xml.sax.SAXException; 039 040import org.ametys.odf.ProgramItem; 041import org.ametys.odf.course.Course; 042import org.ametys.odf.courselist.CourseList; 043import org.ametys.odf.courselist.CourseList.ChoiceType; 044import org.ametys.odf.coursepart.CoursePart; 045import org.ametys.odf.orgunit.OrgUnit; 046import org.ametys.odf.program.Container; 047import org.ametys.odf.program.Program; 048import org.ametys.odf.program.SubProgram; 049import org.ametys.plugins.repository.AmetysObjectIterable; 050import org.ametys.plugins.repository.AmetysObjectIterator; 051import org.ametys.plugins.repository.AmetysRepositoryException; 052import org.ametys.plugins.repository.UnknownAmetysObjectException; 053import org.ametys.plugins.repository.metadata.UnknownMetadataException; 054import org.ametys.runtime.i18n.I18nizableText; 055import org.ametys.runtime.parameter.ParameterHelper; 056 057/** 058 * Pilotage report for Apogée. 059 */ 060public class ApogeeReport extends AbstractReport 061{ 062 private int _order; 063 064 @Override 065 protected void _launchByOrgUnit(String uaiCode, String catalog, String lang) throws Exception 066 { 067 _writeApogeeReport(uaiCode, catalog, lang); 068 } 069 070 @Override 071 protected String getType() 072 { 073 return "apogee"; 074 } 075 076 @Override 077 protected String getReportName() 078 { 079 return _i18nUtils.translate(new I18nizableText("plugin.odf-pilotage", "PLUGINS_ODF_PILOTAGE_APOGEE_LABEL")); 080 } 081 082 /** 083 * Create the Apogee report for one organization unit 084 * @param uaiCode The UAI code of the org unit 085 * @param catalog The catalog 086 * @param lang The language 087 * @throws SAXException if an error occurs while saxing 088 * @throws IOException if an I/O exception occurs 089 * @throws TransformerConfigurationException if the SAX transformer is not configured properly 090 */ 091 protected void _writeApogeeReport(String uaiCode, String catalog, String lang) throws SAXException, IOException, TransformerConfigurationException 092 { 093 SAXTransformerFactory factory = (SAXTransformerFactory) TransformerFactory.newInstance(); 094 String fileName = catalog + "-" + lang + "-" + _reportHelper.getAccronymOrUaiCode(uaiCode) + "-" + "[" + getType() + "-" + _currentFormattedDate + "]"; 095 096 // Delete old files 097 File xmlFile = new File(_tmpFolder, fileName + ".xml"); 098 FileUtils.deleteQuietly(xmlFile); 099 100 TransformerHandler handler = factory.newTransformerHandler(); 101 102 // Write XML file 103 try (FileOutputStream fos = new FileOutputStream(xmlFile)) 104 { 105 try 106 { 107 // Prepare the transformation 108 Result result = new StreamResult(fos); 109 handler.setResult(result); 110 handler.startDocument(); 111 112 AttributesImpl attrs = new AttributesImpl(); 113 attrs.addCDATAAttribute("type", getType()); 114 attrs.addCDATAAttribute("date", _reportHelper.getReadableCurrentDate()); 115 XMLUtils.startElement(handler, "report", attrs); 116 117 // SAX tree 118 _generateReport(handler, uaiCode, lang, catalog); 119 } 120 finally 121 { 122 XMLUtils.endElement(handler, "report"); 123 handler.endDocument(); 124 } 125 } 126 127 // Convert the report to HTML-XLS 128 convertReport(_tmpFolder, fileName + ".xls", xmlFile); 129 } 130 131 /** 132 * SAX the XML of the report 133 * @param handler the transformer handler 134 * @param uaiCode the uai code of the organization unit to process 135 * @param lang the lang of the programs 136 * @param catalog the catalog of the programs 137 * @throws SAXException if an error occurs while generating the SAX events 138 */ 139 private void _generateReport(TransformerHandler handler, String uaiCode, String lang, String catalog) throws SAXException 140 { 141 if (StringUtils.isEmpty(uaiCode)) 142 { 143 throw new IllegalArgumentException("Cannot process the Apogee report without the uai code.The processing of the Apogee report is aborted."); 144 } 145 146 try 147 { 148 _reportHelper.saxNaturesEnseignement(handler, getLogger()); 149 150 Map<Program, Object> contentsTree = _getStructure(uaiCode, lang, catalog); 151 if (contentsTree.size() > 0) 152 { 153 _order = 1; 154 _saxTree(handler, contentsTree); 155 } 156 } 157 catch (Exception e) 158 { 159 getLogger().error("An error occurred while processing the apogee report. Its generation has been aborted.", e); 160 } 161 } 162 163 /** 164 * Generate the data structure that will be used to create the report 165 * @param uaiCode the uai code of the organization unit 166 * @param lang the lang of programs 167 * @param catalog the catalog of programs 168 * @return The structure 169 */ 170 private Map<Program, Object> _getStructure(String uaiCode, String lang, String catalog) 171 { 172 AmetysObjectIterable<OrgUnit> orgUnits = _reportHelper.getRootOrgUnitsByUaiCode(uaiCode); 173 AmetysObjectIterator<OrgUnit> orgUnitsIterator = orgUnits.iterator(); 174 if (!orgUnitsIterator.hasNext()) 175 { 176 throw new IllegalArgumentException("Unable to find any organization unit with the uai code : " + uaiCode + ".The processing of the Apogee report is aborted."); 177 } 178 179 Map<Program, Object> programTree = new TreeMap<>(new ProgramTitleComparator()); 180 181 OrgUnit rootOrgUnit = orgUnitsIterator.next(); 182 // On ne récupère que les composantes, enfant direct du root org unit, et on ignore les départements. 183 if (rootOrgUnit.getParentOrgUnit() != null && rootOrgUnit.getParentOrgUnit().getParentOrgUnit() == null) 184 { 185 List<String> orgUnitIds = _reportHelper.getSubOrgUnits(rootOrgUnit); 186 187 // Chercher les programmes concernés par la composante sélectionnée et ses enfants 188 for (String orgUnitId : orgUnitIds) 189 { 190 AmetysObjectIterable<Program> programs = _reportHelper.getProgramsByOrgUnitId(orgUnitId, lang, catalog); 191 AmetysObjectIterator<Program> programsIterator = programs.iterator(); 192 while (programsIterator.hasNext()) 193 { 194 Program program = programsIterator.next(); 195 Map<ProgramItem, Object> courses = _reportHelper.getCoursesFromContent(program); 196 if (courses != null) 197 { 198 programTree.put(program, courses); 199 } 200 } 201 } 202 } 203 204 return programTree; 205 } 206 207 /** 208 * Sax the information related to the courses of the tree 209 * @param handler the transformer handler 210 * @param programTree the program tree to sax 211 * @throws SAXException if an error occurs when SAXing 212 */ 213 @SuppressWarnings("unchecked") 214 private void _saxTree(TransformerHandler handler, Map<Program, Object> programTree) throws SAXException 215 { 216 for (Entry<Program, Object> programEntry : programTree.entrySet()) 217 { 218 if (programEntry.getValue() != null && programEntry.getValue() instanceof Map<?, ?>) 219 { 220 _saxCourseFromTree(handler, (Map<ProgramItem, Object>) programEntry.getValue(), programEntry.getKey()); 221 } 222 } 223 } 224 225 226 private void _saxCourseFromTree(TransformerHandler handler, Map<ProgramItem, Object> programTree, Program program) throws SAXException 227 { 228 _saxCourseFromTree(handler, programTree, program, null, null, null, null, null, null, 1, false, ""); 229 } 230 231 private void _saxCourseFromTree(TransformerHandler handler, Map<ProgramItem, Object> tree, Program program, SubProgram subprogram, Container containerYear, Container containerSemester, CourseList list, Integer listPosition, Course parentCourse, int level, boolean lastLevel, String courseHierarchy) throws SAXException 232 { 233 int courseListPosition = 0; 234 for (Entry<ProgramItem, Object> entry : tree.entrySet()) 235 { 236 ProgramItem child = entry.getKey(); 237 @SuppressWarnings("unchecked") 238 Map<ProgramItem, Object> subTree = (Map<ProgramItem, Object>) entry.getValue(); 239 240 if (child instanceof Course) 241 { 242 Course childCourse = (Course) child; 243 String path = courseHierarchy + " > " + childCourse.getTitle(); 244 _saxCourse(handler, program, subprogram, containerYear, containerSemester, list, listPosition, (Course) child, parentCourse, level, subTree == null ? true : false, path); 245 246 if (subTree != null) 247 { 248 _saxCourseFromTree(handler, subTree, program, subprogram, containerYear, containerSemester, list, listPosition, childCourse, level + 1, lastLevel, path); 249 } 250 } 251 252 if (subTree != null) 253 { 254 if (child instanceof Program) 255 { 256 _saxCourseFromTree(handler, subTree, (Program) child, subprogram, containerYear, containerSemester, list, listPosition, parentCourse, level, lastLevel, courseHierarchy); 257 } 258 else if (child instanceof Container) 259 { 260 Container container = (Container) child; 261 String containerNature = _refTableHelper.getItemCode(container.getNature()); 262 263 if ("annee".equals(containerNature)) 264 { 265 _saxCourseFromTree(handler, subTree, program, subprogram, container, containerSemester, list, listPosition, parentCourse, level, lastLevel, courseHierarchy); 266 } 267 else if ("semestre".equals(containerNature)) 268 { 269 _saxCourseFromTree(handler, subTree, program, subprogram, containerYear, container, list, listPosition, parentCourse, level, lastLevel, courseHierarchy); 270 } 271 else 272 { 273 _saxCourseFromTree(handler, subTree, program, subprogram, containerYear, containerSemester, list, listPosition, parentCourse, level, lastLevel, courseHierarchy); 274 } 275 } 276 else if (child instanceof SubProgram) 277 { 278 _saxCourseFromTree(handler, subTree, program, (SubProgram) child, containerYear, containerSemester, list, listPosition, parentCourse, level, lastLevel, courseHierarchy); 279 } 280 else if (child instanceof CourseList) 281 { 282 courseListPosition++; 283 CourseList childCourseList = (CourseList) child; 284 String path = courseHierarchy.equals("") ? childCourseList.getTitle() : courseHierarchy + " > " + childCourseList.getTitle(); 285 _saxCourseFromTree(handler, subTree, program, subprogram, containerYear, containerSemester, (CourseList) child, courseListPosition, parentCourse, level, lastLevel, path); 286 } 287 } 288 } 289 } 290 291 private String _getHierarchy(Program program, SubProgram subprogram, Container containerYear, Container containerSemester, String courseHierarchy) 292 { 293 String hierarchy = program.getTitle(); 294 if (subprogram != null) 295 { 296 hierarchy += " > " + subprogram.getTitle(); 297 } 298 299 if (containerYear != null) 300 { 301 hierarchy += " > " + containerYear.getTitle(); 302 } 303 304 if (containerSemester != null) 305 { 306 hierarchy += " > " + containerSemester.getTitle(); 307 } 308 309 hierarchy += " > " + courseHierarchy; 310 311 return hierarchy; 312 } 313 314 private void _saxCourse(TransformerHandler handler, Program program, SubProgram subprogram, Container containerYear, Container containerSemester, CourseList list, Integer listPosition, Course course, Course parentCourse, int level, boolean lastLevel, String courseHierarchy) throws SAXException 315 { 316 if (course != null) 317 { 318 String hierarchy = _getHierarchy(program, subprogram, containerYear, containerSemester, courseHierarchy); 319 320 XMLUtils.startElement(handler, "course"); 321 322 // Ordre 323 XMLUtils.createElement(handler, "ordre", String.valueOf(_order)); 324 325 // Composante 326 _saxOrgUnits(handler, program); 327 328 // Formation 329 XMLUtils.createElement(handler, "formation", program.getTitle()); 330 XMLUtils.createElement(handler, "formationCode", program.getCode()); 331 332 _saxSubProgram(handler, subprogram); 333 334 _saxContainer(handler, containerYear, parentCourse); 335 336 _saxCourseList(handler, list, listPosition); 337 338 // A des fils 339 boolean aDesFils = course.hasCourseLists(); 340 XMLUtils.createElement(handler, "aDesFils", aDesFils ? "X" : ""); 341 342 long courseListsSize = course.getParentCourseLists().size(); 343 344 // Partagé 345 XMLUtils.createElement(handler, "partage", courseListsSize > 1 ? "X" : ""); 346 347 // Nb occurences 348 XMLUtils.createElement(handler, "occurences", _reportHelper.formatNumberToSax(courseListsSize)); 349 350 Container etape = _getEtapePorteuse(course, hierarchy); 351 352 String porte = _getPorte(etape, containerYear); 353 354 // Porté ("X" si l'ELP est porté par la formation (Etape porteuse=COD_ETP), vide sinon) 355 XMLUtils.createElement(handler, "porte", porte); 356 357 // Niveau 358 XMLUtils.createElement(handler, "niveau", "niv" + level); 359 360 // Date de création 361 XMLUtils.createElement(handler, "creationDate", ParameterHelper.valueToString(course.getCreationDate())); 362 363 // Code Apogée 364 XMLUtils.createElement(handler, "codeApogee", course.getMetadataHolder().getString("elpCode", "")); 365 366 // Nature de l'élément 367 XMLUtils.createElement(handler, "nature", _refTableHelper.getItemCode(course.getCourseType())); 368 369 // Libellé court 370 XMLUtils.createElement(handler, "libelleCourt", course.getMetadataHolder().getString("shortLabel", "")); 371 372 // Libellé 373 XMLUtils.createElement(handler, "libelle", course.getTitle()); 374 375 // Code Ametys (ELP) 376 XMLUtils.createElement(handler, "elpCode", course.getCode()); 377 378 // Lieu 379 _reportHelper.saxMultipleEnumeratedMetadata(handler, course, "campus", "campus"); 380 381 // Crédits ECTS 382 XMLUtils.createElement(handler, "ects", String.valueOf(course.getEcts())); 383 384 String teachingActivity = _refTableHelper.getItemCode(course.getTeachingActivity()); 385 String stage = teachingActivity.equals("SA") ? "X" : ""; 386 387 // Element stage 388 XMLUtils.createElement(handler, "stage", stage); 389 390 // Code semestre 391 String periode = _refTableHelper.getItemCode(course.getTeachingTerm()); 392 XMLUtils.createElement(handler, "periode", "s10".equals(periode) ? "s0" : periode); 393 394 OrgUnit orgUnit = _getOrgUnit(course, hierarchy); 395 396 // Code composante 397 XMLUtils.createElement(handler, "codeComposante", orgUnit != null ? orgUnit.getMetadataHolder().getString("codCmp", "") : ""); 398 399 // Code CIP 400 XMLUtils.createElement(handler, "codeCIP", orgUnit != null ? orgUnit.getMetadataHolder().getString("codCipApogee", "") : ""); 401 402 // Code ANU 403 long codeAnu = course.getMetadataHolder().getLong("CodeAnu", 0); 404 XMLUtils.createElement(handler, "CodeAnu", codeAnu > 0 ? String.valueOf(codeAnu) : StringUtils.EMPTY); 405 406 // Calcul des charges 407 XMLUtils.createElement(handler, "calculCharges", aDesFils ? "" : "X"); 408 409 // Heures d'enseignement 410 for (CoursePart coursePart : course.getCourseParts()) 411 { 412 AttributesImpl attr = new AttributesImpl(); 413 attr.addCDATAAttribute("nature", coursePart.getNature()); 414 XMLUtils.createElement(handler, "volumeHoraire", attr, String.valueOf(coursePart.getNumberOfHours())); 415 } 416 417 // Etape porteuse 418 XMLUtils.createElement(handler, "etapePorteuse", lastLevel && etape != null ? etape.getMetadataHolder().getString("etpCode", "") : ""); 419 420 // VET Etape porteuse 421 XMLUtils.createElement(handler, "vetEtapePorteuse", lastLevel && etape != null ? etape.getMetadataHolder().getString("vrsEtpCode", "") : ""); 422 423 // Discipline 424 XMLUtils.createElement(handler, "discipline", course.getMetadataHolder().getString("discipline", "")); 425 426 saxAdditionalCourseData(handler, course); 427 428 XMLUtils.endElement(handler, "course"); 429 430 _order++; 431 } 432 } 433 434 /** 435 * Sax a additional data of a {@link Course}. 436 * @param handler The handler 437 * @param course The course to SAX 438 * @throws UnknownMetadataException if an error occurs 439 * @throws AmetysRepositoryException if an error occurs 440 * @throws SAXException if an error occurs 441 */ 442 protected void saxAdditionalCourseData(TransformerHandler handler, Course course) throws UnknownMetadataException, AmetysRepositoryException, SAXException 443 { 444 // Do nothing by default 445 } 446 447 private void _saxContainer(TransformerHandler handler, Container container, Course parentCourse) throws UnknownMetadataException, AmetysRepositoryException, SAXException 448 { 449 if (container != null) 450 { 451 // Année 452 XMLUtils.createElement(handler, "annee", container.getTitle()); 453 // COD_ETP 454 XMLUtils.createElement(handler, "COD_ETP", container.getMetadataHolder().getString("etpCode", "")); 455 // COD_VRS_ETP 456 XMLUtils.createElement(handler, "COD_VRS_ETP", container.getMetadataHolder().getString("vrsEtpCode", "")); 457 // Code Ametys ELP père 458 XMLUtils.createElement(handler, "codeELPPere", parentCourse != null ? parentCourse.getCode() : ""); 459 } 460 } 461 462 private void _saxSubProgram(TransformerHandler handler, SubProgram subprogram) throws UnknownMetadataException, AmetysRepositoryException, SAXException 463 { 464 if (subprogram != null) 465 { 466 // Parcours 467 XMLUtils.createElement(handler, "parcours", subprogram.getTitle()); 468 XMLUtils.createElement(handler, "parcoursCode", subprogram.getCode()); 469 } 470 } 471 472 private void _saxOrgUnits(TransformerHandler handler, Program program) throws SAXException 473 { 474 StringBuilder sb = new StringBuilder(); 475 List<String> orgUnits = program.getOrgUnits(); 476 for (String orgUnitId : orgUnits) 477 { 478 try 479 { 480 OrgUnit orgUnit = _resolver.resolveById(orgUnitId); 481 if (sb.length() > 0) 482 { 483 sb.append(", "); 484 } 485 sb.append(orgUnit.getTitle()); 486 sb.append(" ("); 487 sb.append(orgUnit.getUAICode()); 488 sb.append(")"); 489 } 490 catch (UnknownAmetysObjectException e) 491 { 492 getLogger().info("La composante référencée par la formation {} ({}) n'a pas été trouvée.", program.getTitle(), program.getCode()); 493 } 494 } 495 XMLUtils.createElement(handler, "orgUnit", sb.toString()); 496 } 497 498 private String _getPorte(Container etape, Container containerYear) 499 { 500 String porte = ""; 501 if (etape != null && containerYear != null) 502 { 503 String etpCode = containerYear.getMetadataHolder().getString("etpCode", ""); 504 if (StringUtils.isNotEmpty(etpCode)) 505 { 506 porte = etpCode.equals(etape.getMetadataHolder().getString("etpCode", "")) ? "X" : ""; 507 } 508 } 509 return porte; 510 } 511 512 private OrgUnit _getOrgUnit(Course course, String hierarchy) 513 { 514 OrgUnit orgUnit = null; 515 516 List<String> courseOrgUnits = course.getOrgUnits(); 517 if (!courseOrgUnits.isEmpty()) 518 { 519 try 520 { 521 orgUnit = _resolver.resolveById(courseOrgUnits.get(0)); 522 } 523 catch (UnknownAmetysObjectException e) 524 { 525 getLogger().info("La composante référencée par l'élément pédagogique {} ({}) n'a pas été trouvée.", hierarchy, course.getCode()); 526 } 527 528 if (courseOrgUnits.size() > 1) 529 { 530 getLogger().warn("L'élément pédagogique {} ({}) référence plus d'une composante.", hierarchy, course.getCode()); 531 } 532 } 533 534 return orgUnit; 535 } 536 537 private Container _getEtapePorteuse(Course course, String hierarchy) 538 { 539 String etapePorteuse = course.getMetadataHolder().getString("etapePorteuse", ""); 540 Container etape = null; 541 if (StringUtils.isNotEmpty(etapePorteuse)) 542 { 543 try 544 { 545 etape = _resolver.resolveById(etapePorteuse); 546 } 547 catch (UnknownAmetysObjectException e) 548 { 549 getLogger().info("L'étape porteuse référencée par l'élément pédagogique {} ({}) n'a pas été trouvée.", hierarchy, course.getCode()); 550 } 551 } 552 553 return etape; 554 } 555 556 private void _saxCourseList(TransformerHandler handler, CourseList list, Integer position) throws SAXException 557 { 558 if (list != null) 559 { 560 XMLUtils.createElement(handler, "list", "Lst" + position); 561 _saxChoiceList(handler, list); 562 } 563 } 564 565 private void _saxChoiceList(TransformerHandler handler, CourseList list) throws SAXException 566 { 567 // Type 568 ChoiceType typeList = list.getType(); 569 if (typeList != null) 570 { 571 if (typeList.name() != null && (typeList.name().equals(ChoiceType.CHOICE.toString()) || typeList.name().equals(ChoiceType.MANDATORY.toString()) || typeList.name().equals(ChoiceType.OPTIONAL.toString()))) 572 { 573 String typeListAsString = ""; 574 if (typeList.name().equals(ChoiceType.CHOICE.toString())) 575 { 576 typeListAsString = "X"; 577 } 578 else if (typeList.name().equals(ChoiceType.MANDATORY.toString())) 579 { 580 typeListAsString = "O"; 581 } 582 else if (typeList.name().equals(ChoiceType.OPTIONAL.toString())) 583 { 584 typeListAsString = "F"; 585 } 586 587 XMLUtils.createElement(handler, "typeList", typeListAsString); 588 } 589 590 // Min-Max (Ne remplir que pour le type "CHOICE" (ne mettre que le min)) 591 if (typeList.name() != null && typeList.name().equals(ChoiceType.CHOICE.toString())) 592 { 593 XMLUtils.createElement(handler, "minmax", _reportHelper.formatNumberToSax(list.getMinNumberOfCourses())); 594 } 595 } 596 } 597 598 class ProgramTitleComparator implements Comparator<Program> 599 { 600 @Override 601 public int compare(Program p1, Program p2) 602 { 603 604 return p1.getTitle().compareTo(p2.getTitle()); 605 } 606 } 607}