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.util.LinkedHashMap; 021import java.util.Map; 022import java.util.Map.Entry; 023import java.util.Optional; 024import java.util.Set; 025 026import javax.xml.transform.Result; 027import javax.xml.transform.TransformerFactory; 028import javax.xml.transform.sax.SAXTransformerFactory; 029import javax.xml.transform.sax.TransformerHandler; 030import javax.xml.transform.stream.StreamResult; 031 032import org.apache.cocoon.xml.AttributesImpl; 033import org.apache.cocoon.xml.XMLUtils; 034import org.apache.commons.io.FileUtils; 035import org.apache.commons.lang3.StringUtils; 036import org.xml.sax.SAXException; 037 038import org.ametys.cms.data.ContentValue; 039import org.ametys.cms.repository.ModifiableContent; 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.program.Container; 046import org.ametys.odf.program.Program; 047import org.ametys.odf.program.SubProgram; 048import org.ametys.plugins.repository.UnknownAmetysObjectException; 049import org.ametys.plugins.repository.jcr.NameHelper; 050 051/** 052 * Class to generate course extract as DOC. 053 */ 054public class MaquetteExtract extends AbstractExtract 055{ 056 @Override 057 protected String getType() 058 { 059 return "maquette"; 060 } 061 062 @Override 063 protected Set<String> getSupportedOutputFormats() 064 { 065 return Set.of(OUTPUT_FORMAT_DOC, OUTPUT_FORMAT_XLS); 066 } 067 068 @Override 069 protected void _saxProgram(Program program) 070 { 071 Map<ProgramItem, Object> programTree = _reportHelper.getCoursesFromContent(program); 072 if (programTree != null) 073 { 074 _saxProgramsTree(programTree, program); 075 } 076 else 077 { 078 getLogger().info("La formation '{}' ne contient pas d'éléments pédagogiques", program.getTitle()); 079 } 080 } 081 082 /** 083 * Get the report filename for a given program 084 * @param program The program 085 * @return the file name 086 */ 087 private String _getReportFileName(Program program) 088 { 089 StringBuilder sb = new StringBuilder(); 090 091 sb.append("maquette-"); 092 093 // Catalog 094 sb.append(program.getCatalog()); 095 sb.append("-"); 096 097 // Lang 098 sb.append(program.getLanguage()); 099 sb.append("-"); 100 101 // Mention or title 102 String mentionId = program.getMention(); 103 if (StringUtils.isBlank(mentionId)) 104 { 105 sb.append(program.getTitle()); 106 } 107 else 108 { 109 sb.append(_refTableHelper.getItemLabel(mentionId, program.getLanguage())); 110 } 111 112 // Code Ametys 113 String code = program.getCode(); 114 if (StringUtils.isNotBlank(code)) 115 { 116 sb.append("-"); 117 sb.append(code); 118 } 119 120 // Date 121 sb.append("-"); 122 sb.append(_currentFormattedDate); 123 124 return NameHelper.filterName(sb.toString()); 125 } 126 127 private void _saxProgramsTree(Map<ProgramItem, Object> programTree, Program program) 128 { 129 SAXTransformerFactory factory = (SAXTransformerFactory) TransformerFactory.newInstance(); 130 131 String fileName = _getReportFileName(program); 132 133 File xmlFile = new File(_tmpFolder, fileName + ".xml"); 134 FileUtils.deleteQuietly(xmlFile); 135 136 try (FileOutputStream fos = new FileOutputStream(xmlFile)) 137 { 138 TransformerHandler handler = factory.newTransformerHandler(); 139 140 Result result = new StreamResult(fos); 141 handler.setResult(result); 142 143 handler.startDocument(); 144 145 AttributesImpl attrs = new AttributesImpl(); 146 attrs.addCDATAAttribute("xmlns:i18n", "http://apache.org/cocoon/i18n/2.1"); 147 attrs.addCDATAAttribute("type", getType()); 148 attrs.addCDATAAttribute("date", _reportHelper.getReadableCurrentDate()); 149 XMLUtils.startElement(handler, "report", attrs); 150 151 _saxReport(handler, program, programTree); 152 153 XMLUtils.endElement(handler, "report"); 154 handler.endDocument(); 155 156 // Convert the report to configured output format 157 convertReport(_tmpFolder, fileName, xmlFile); 158 } 159 catch (Exception e) 160 { 161 getLogger().error("An error occured while generating 'Maquette' extract for program '{}' ({})", program.getTitle(), program.getCode(), e); 162 } 163 finally 164 { 165 FileUtils.deleteQuietly(xmlFile); 166 } 167 } 168 169 private void _saxReport(TransformerHandler handler, Program program, Map<ProgramItem, Object> programTree) throws SAXException 170 { 171 _reportHelper.saxNaturesEnseignement(handler, getLogger()); 172 173 AttributesImpl attr = new AttributesImpl(); 174 175 attr.addCDATAAttribute("formation", program.getTitle()); 176 attr.addCDATAAttribute("COD_DIP", _reportHelper.getCodeDIP(program)); 177 attr.addCDATAAttribute("COD_VDI", _reportHelper.getCodeVRSVDI(program)); 178 179 XMLUtils.startElement(handler, "program", attr); 180 181 _saxProgramTree(handler, programTree); 182 183 XMLUtils.endElement(handler, "program"); 184 } 185 186 @SuppressWarnings("unchecked") 187 private void _saxProgramTree(TransformerHandler handler, Map<ProgramItem, Object> programTree) throws SAXException 188 { 189 if (programTree != null) 190 { 191 for (Entry<ProgramItem, Object> node : programTree.entrySet()) 192 { 193 if (node.getKey() instanceof SubProgram) 194 { 195 _saxSubProgram(handler, (SubProgram) node.getKey(), (Map<ProgramItem, Object>) node.getValue()); 196 } 197 else if (node.getKey() instanceof Container) 198 { 199 _saxContainer(handler, (Container) node.getKey(), (Map<ProgramItem, Object>) node.getValue()); 200 } 201 else if (node.getKey() instanceof Course) 202 { 203 _saxCourse(handler, (Course) node.getKey(), (Map<ProgramItem, Object>) node.getValue()); 204 } 205 else if (node.getKey() instanceof CourseList) 206 { 207 if (_saxProgramTreeOnCourseList(handler, programTree, node)) 208 { 209 break; 210 } 211 } 212 } 213 } 214 } 215 216 @SuppressWarnings("unchecked") 217 private boolean _saxProgramTreeOnCourseList(TransformerHandler handler, Map<ProgramItem, Object> programTree, Entry<ProgramItem, Object> node) throws SAXException 218 { 219 if (programTree.size() > 1) 220 { 221 Map<ProgramItem, Object> orderedMandatoryLists = new LinkedHashMap<>(); 222 Map<ProgramItem, Object> orderedChoiceLists = new LinkedHashMap<>(); 223 Map<ProgramItem, Object> orderedOptionalLists = new LinkedHashMap<>(); 224 for (Entry<ProgramItem, Object> nodeList : programTree.entrySet()) 225 { 226 if (nodeList.getKey() instanceof CourseList) 227 { 228 CourseList list = (CourseList) nodeList.getKey(); 229 if (list.getType().equals(CourseList.ChoiceType.MANDATORY)) 230 { 231 orderedMandatoryLists.put(list, nodeList.getValue()); 232 } 233 else if (list.getType().equals(CourseList.ChoiceType.CHOICE)) 234 { 235 orderedChoiceLists.put(list, nodeList.getValue()); 236 } 237 else 238 { 239 orderedOptionalLists.put(list, nodeList.getValue()); 240 } 241 } 242 else if (nodeList.getKey() instanceof SubProgram) 243 { 244 _saxSubProgram(handler, (SubProgram) nodeList.getKey(), (Map<ProgramItem, Object>) nodeList.getValue()); 245 } 246 else if (nodeList.getKey() instanceof Container) 247 { 248 _saxContainer(handler, (Container) nodeList.getKey(), (Map<ProgramItem, Object>) nodeList.getValue()); 249 } 250 251 } 252 253 for (Entry<ProgramItem, Object> nodeList : orderedMandatoryLists.entrySet()) 254 { 255 _saxCourseList(handler, (CourseList) nodeList.getKey(), (Map<ProgramItem, Object>) nodeList.getValue()); 256 } 257 for (Entry<ProgramItem, Object> nodeList : orderedChoiceLists.entrySet()) 258 { 259 _saxCourseList(handler, (CourseList) nodeList.getKey(), (Map<ProgramItem, Object>) nodeList.getValue()); 260 } 261 for (Entry<ProgramItem, Object> nodeList : orderedOptionalLists.entrySet()) 262 { 263 _saxCourseList(handler, (CourseList) nodeList.getKey(), (Map<ProgramItem, Object>) nodeList.getValue()); 264 } 265 return true; 266 } 267 else 268 { 269 _saxCourseList(handler, (CourseList) node.getKey(), (Map<ProgramItem, Object>) node.getValue()); 270 } 271 return false; 272 } 273 274 private void _saxSubProgram(TransformerHandler handler, SubProgram subprogram, Map<ProgramItem, Object> programTree) throws SAXException 275 { 276 AttributesImpl attr = new AttributesImpl(); 277 278 attr.addCDATAAttribute("parcours", subprogram.getTitle()); 279 280 XMLUtils.startElement(handler, "subprogram", attr); 281 282 _saxProgramTree(handler, programTree); 283 284 XMLUtils.endElement(handler, "subprogram"); 285 } 286 287 private void _saxContainer(TransformerHandler handler, Container container, Map<ProgramItem, Object> programTree) throws SAXException 288 { 289 AttributesImpl attr = new AttributesImpl(); 290 291 attr.addCDATAAttribute("id", container.getId()); 292 attr.addCDATAAttribute("libelle", container.getTitle()); 293 294 String containerNature = _refTableHelper.getItemCode(container.getNature()); 295 attr.addCDATAAttribute("type", containerNature); 296 if ("annee".equals(containerNature)) 297 { 298 attr.addCDATAAttribute("COD_ETP", container.getValue("etpCode", false, StringUtils.EMPTY)); 299 } 300 301 long codeAnu = container.getValue("CodeAnu", false, 0L); 302 if (codeAnu > 0) 303 { 304 attr.addCDATAAttribute("HeaderCodeAnu", "Maquette " + String.valueOf(codeAnu) + "-" + String.valueOf(codeAnu + 1)); 305 } 306 307 XMLUtils.startElement(handler, "container", attr); 308 309 _saxProgramTree(handler, programTree); 310 311 XMLUtils.endElement(handler, "container"); 312 } 313 314 private void _saxCourse(TransformerHandler handler, Course course, Map<ProgramItem, Object> programTree) throws SAXException 315 { 316 AttributesImpl attr = new AttributesImpl(); 317 318 attr.addCDATAAttribute("CodeAmetysELP", course.getCode()); 319 attr.addCDATAAttribute("CodeApogee", course.getValue("elpCode", false, StringUtils.EMPTY)); 320 attr.addCDATAAttribute("natureElement", _refTableHelper.getItemCode(course.getCourseType())); 321 attr.addCDATAAttribute("ects", String.valueOf(course.getEcts())); 322 323 Container containerEtape = Optional.ofNullable((ContentValue) course.getValue("etapePorteuse")) 324 .flatMap(contentValue -> _getEtapePorteuseIfExists(contentValue, course)) 325 .map(Container.class::cast) 326 .orElse(null); 327 328 if (containerEtape != null) 329 { 330 attr.addCDATAAttribute("etapePorteuse", containerEtape.getTitle()); 331 attr.addCDATAAttribute("idEtapePorteuse", containerEtape.getId()); 332 if (containerEtape.hasValue("etpCode")) 333 { 334 attr.addCDATAAttribute("codeEtapePorteuse", containerEtape.getValue("etpCode")); 335 } 336 } 337 338 if (course.hasValue("CodeAnu")) 339 { 340 long codeAnu = course.getValue("CodeAnu"); 341 attr.addCDATAAttribute("HeaderCodeAnu", "Maquette " + String.valueOf(codeAnu) + "-" + String.valueOf(codeAnu + 1)); 342 } 343 344 XMLUtils.startElement(handler, "course", attr); 345 346 String shortLabel = course.getValue("shortLabel"); 347 if (StringUtils.isNotBlank(shortLabel)) 348 { 349 XMLUtils.createElement(handler, "shortLabel", shortLabel); 350 } 351 XMLUtils.createElement(handler, "libelle", course.getTitle()); 352 353 long courseListsSize = course.getParentCourseLists().size(); 354 355 // Partagé 356 XMLUtils.createElement(handler, "partage", courseListsSize > 1 ? "X" : ""); 357 358 // Nb occurrences 359 XMLUtils.createElement(handler, "occurrences", _reportHelper.formatNumberToSax(courseListsSize)); 360 361 // Heures d'enseignement 362 for (CoursePart coursePart : course.getCourseParts()) 363 { 364 attr = new AttributesImpl(); 365 attr.addCDATAAttribute("nature", coursePart.getNature()); 366 XMLUtils.createElement(handler, "volumeHoraire", attr, String.valueOf(coursePart.getNumberOfHours())); 367 } 368 369 _saxProgramTree(handler, programTree); 370 371 XMLUtils.endElement(handler, "course"); 372 373 } 374 375 private Optional<ModifiableContent> _getEtapePorteuseIfExists(ContentValue etapePorteuse, Course course) 376 { 377 try 378 { 379 return Optional.ofNullable(etapePorteuse.getContent()); 380 } 381 catch (UnknownAmetysObjectException e) 382 { 383 getLogger().error("Course {} {}", course.getId(), e.getMessage(), e); 384 return Optional.empty(); 385 } 386 } 387 388 private void _saxCourseList(TransformerHandler handler, CourseList courseList, Map<ProgramItem, Object> programTree) throws SAXException 389 { 390 AttributesImpl attr = new AttributesImpl(); 391 ChoiceType typeList = courseList.getType(); 392 if (typeList.name().equals(ChoiceType.CHOICE.toString()) && courseList.getMinNumberOfCourses() > 0) 393 { 394 attr.addCDATAAttribute("choice", String.valueOf(courseList.getMinNumberOfCourses())); 395 } 396 else if (typeList.name().equals(ChoiceType.OPTIONAL.toString())) 397 { 398 attr.addCDATAAttribute("optional", "true"); 399 } 400 XMLUtils.startElement(handler, "courselist", attr); 401 402 _saxProgramTree(handler, programTree); 403 404 XMLUtils.endElement(handler, "courselist"); 405 } 406}