001/* 002 * Copyright 2019 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.odf.content; 017 018import java.io.IOException; 019import java.util.ArrayList; 020import java.util.List; 021import java.util.Locale; 022import java.util.Optional; 023import java.util.Set; 024 025import org.apache.avalon.framework.service.ServiceException; 026import org.apache.avalon.framework.service.ServiceManager; 027import org.apache.cocoon.ProcessingException; 028import org.apache.cocoon.environment.ObjectModelHelper; 029import org.apache.cocoon.environment.Request; 030import org.apache.cocoon.generation.ServiceableGenerator; 031import org.apache.cocoon.xml.AttributesImpl; 032import org.apache.cocoon.xml.XMLUtils; 033import org.apache.commons.lang.StringUtils; 034import org.xml.sax.SAXException; 035 036import org.ametys.cms.contenttype.ContentTypesHelper; 037import org.ametys.cms.repository.Content; 038import org.ametys.odf.NoLiveVersionException; 039import org.ametys.odf.ODFHelper; 040import org.ametys.odf.ProgramItem; 041import org.ametys.odf.course.Course; 042import org.ametys.odf.courselist.CourseList; 043import org.ametys.odf.coursepart.CoursePart; 044import org.ametys.odf.enumeration.OdfReferenceTableEntry; 045import org.ametys.odf.enumeration.OdfReferenceTableHelper; 046import org.ametys.odf.program.AbstractProgram; 047import org.ametys.odf.program.Container; 048import org.ametys.odf.program.Program; 049import org.ametys.odf.program.SubProgram; 050import org.ametys.odf.skill.ODFSkillsHelper; 051import org.ametys.plugins.repository.AmetysObjectResolver; 052import org.ametys.plugins.repository.AmetysRepositoryException; 053import org.ametys.plugins.repository.jcr.DefaultAmetysObject; 054import org.ametys.runtime.model.View; 055import org.ametys.runtime.model.exception.BadItemTypeException; 056import org.ametys.runtime.model.type.DataContext; 057 058/** 059 * SAX the structure (ie. the child program items) of a {@link ProgramItem} 060 * 061 */ 062public class ProgramItemStructureGenerator extends ServiceableGenerator 063{ 064 private static final Set<String> __ALLOWED_VIEW_NAMES = Set.of("main", "pdf"); 065 066 /** The ODF helper */ 067 protected ODFHelper _odfHelper; 068 /** Helper for ODF reference table */ 069 protected OdfReferenceTableHelper _odfReferenceTableHelper; 070 /** The content types helper */ 071 protected ContentTypesHelper _cTypesHelper; 072 /** The ODF skills helper */ 073 protected ODFSkillsHelper _odfSkillsHelper; 074 /** The Ametys object resolver */ 075 protected AmetysObjectResolver _resolver; 076 077 @Override 078 public void service(ServiceManager smanager) throws ServiceException 079 { 080 _odfHelper = (ODFHelper) smanager.lookup(ODFHelper.ROLE); 081 _odfReferenceTableHelper = (OdfReferenceTableHelper) smanager.lookup(OdfReferenceTableHelper.ROLE); 082 _cTypesHelper = (ContentTypesHelper) smanager.lookup(ContentTypesHelper.ROLE); 083 _odfSkillsHelper = (ODFSkillsHelper) smanager.lookup(ODFSkillsHelper.ROLE); 084 _resolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE); 085 } 086 087 public void generate() throws IOException, SAXException, ProcessingException 088 { 089 Request request = ObjectModelHelper.getRequest(objectModel); 090 Content content = (Content) request.getAttribute(Content.class.getName()); 091 092 if (content == null) 093 { 094 String contentId = parameters.getParameter("contentId", null); 095 if (StringUtils.isBlank(contentId)) 096 { 097 throw new IllegalArgumentException("Content is missing in request attribute or parameters"); 098 } 099 content = _resolver.resolveById(contentId); 100 } 101 102 String viewName = parameters.getParameter("viewName", StringUtils.EMPTY); 103 String fallbackViewName = parameters.getParameter("fallbackViewName", StringUtils.EMPTY); 104 105 View view = _cTypesHelper.getViewWithFallback(viewName, fallbackViewName, content); 106 107 contentHandler.startDocument(); 108 109 if (view != null && __ALLOWED_VIEW_NAMES.contains(view.getName())) 110 { 111 if (content instanceof ProgramItem) 112 { 113 XMLUtils.startElement(contentHandler, "structure"); 114 saxProgramItem((ProgramItem) content); 115 XMLUtils.endElement(contentHandler, "structure"); 116 } 117 else 118 { 119 getLogger().warn("Cannot get the structure of a non program item '" + content.getId() + "'"); 120 } 121 } 122 123 contentHandler.endDocument(); 124 } 125 126 /** 127 * SAX a program item with its child program items 128 * @param programItem the program item 129 * @throws SAXException if an error occurs while saxing 130 */ 131 protected void saxProgramItem(ProgramItem programItem) throws SAXException 132 { 133 if (programItem instanceof Program) 134 { 135 saxProgram((Program) programItem); 136 } 137 else if (programItem instanceof SubProgram) 138 { 139 saxSubProgram((SubProgram) programItem); 140 } 141 else if (programItem instanceof Container) 142 { 143 saxContainer((Container) programItem); 144 } 145 else if (programItem instanceof CourseList) 146 { 147 saxCourseList((CourseList) programItem); 148 } 149 else if (programItem instanceof Course) 150 { 151 saxCourse((Course) programItem); 152 } 153 } 154 155 /** 156 * SAX the child program items 157 * @param programItem the program item 158 * @throws SAXException if an error occurs while saxing 159 */ 160 protected void saxChildProgramItems(ProgramItem programItem) throws SAXException 161 { 162 List<ProgramItem> childProgramItems = _odfHelper.getChildProgramItems(programItem); 163 for (ProgramItem childProgramItem : childProgramItems) 164 { 165 try 166 { 167 _odfHelper.switchToLiveVersionIfNeeded((DefaultAmetysObject) childProgramItem); 168 saxProgramItem(childProgramItem); 169 } 170 catch (NoLiveVersionException e) 171 { 172 // Just ignore the program item 173 } 174 } 175 } 176 177 /** 178 * SAX a program 179 * @param program the subprogram to SAX 180 * @throws SAXException if an error occurs 181 */ 182 protected void saxProgram(Program program) throws SAXException 183 { 184 AttributesImpl attrs = new AttributesImpl(); 185 _saxCommonAttributes(program, attrs); 186 187 XMLUtils.startElement(contentHandler, "program", attrs); 188 189 XMLUtils.startElement(contentHandler, "attributes"); 190 _saxStructureViewIfExists(program); 191 XMLUtils.endElement(contentHandler, "attributes"); 192 193 saxChildProgramItems(program); 194 XMLUtils.endElement(contentHandler, "program"); 195 } 196 197 /** 198 * SAX a subprogram 199 * @param subProgram the subprogram to SAX 200 * @throws SAXException if an error occurs 201 */ 202 protected void saxSubProgram(SubProgram subProgram) throws SAXException 203 { 204 AttributesImpl attrs = new AttributesImpl(); 205 _saxCommonAttributes(subProgram, attrs); 206 207 XMLUtils.startElement(contentHandler, "subprogram", attrs); 208 209 XMLUtils.startElement(contentHandler, "attributes"); 210 _saxReferenceTableItem(subProgram.getEcts(), AbstractProgram.ECTS, subProgram.getLanguage()); 211 _saxStructureViewIfExists(subProgram); 212 XMLUtils.endElement(contentHandler, "attributes"); 213 214 saxChildProgramItems(subProgram); 215 216 XMLUtils.endElement(contentHandler, "subprogram"); 217 } 218 219 /** 220 * SAX a container 221 * @param container the container to SAX 222 * @throws SAXException if an error occurs while saxing 223 */ 224 protected void saxContainer(Container container) throws SAXException 225 { 226 AttributesImpl attrs = new AttributesImpl(); 227 _saxCommonAttributes(container, attrs); 228 229 XMLUtils.startElement(contentHandler, "container", attrs); 230 231 XMLUtils.startElement(contentHandler, "attributes"); 232 _saxReferenceTableItem(container.getNature(), Container.NATURE, container.getLanguage()); 233 _saxReferenceTableItem(container.getPeriod(), Container.PERIOD, container.getLanguage()); 234 235 _saxStructureViewIfExists(container); 236 237 XMLUtils.endElement(contentHandler, "attributes"); 238 239 saxChildProgramItems(container); 240 241 XMLUtils.endElement(contentHandler, "container"); 242 } 243 244 /** 245 * SAX a course list 246 * @param cl the course list to SAX 247 * @throws SAXException if an error occurs while saxing 248 */ 249 protected void saxCourseList(CourseList cl) throws SAXException 250 { 251 AttributesImpl attrs = new AttributesImpl(); 252 _saxCommonAttributes(cl, attrs); 253 254 XMLUtils.startElement(contentHandler, "courselist", attrs); 255 256 XMLUtils.startElement(contentHandler, "attributes"); 257 _saxStructureViewIfExists(cl); 258 XMLUtils.endElement(contentHandler, "attributes"); 259 260 saxChildProgramItems(cl); 261 262 XMLUtils.endElement(contentHandler, "courselist"); 263 } 264 265 /** 266 * SAX a course 267 * @param course the container to SAX 268 * @throws SAXException if an error occurs while saxing 269 */ 270 protected void saxCourse(Course course) throws SAXException 271 { 272 AttributesImpl attrs = new AttributesImpl(); 273 _saxCommonAttributes(course, attrs); 274 275 XMLUtils.startElement(contentHandler, "course", attrs); 276 277 XMLUtils.startElement(contentHandler, "attributes"); 278 _saxReferenceTableItem(course.getCourseType(), Course.COURSE_TYPE, course.getLanguage()); 279 _saxStructureViewIfExists(course); 280 XMLUtils.endElement(contentHandler, "attributes"); 281 282 saxChildProgramItems(course); 283 284 saxCourseParts(course); 285 286 XMLUtils.endElement(contentHandler, "course"); 287 } 288 289 /** 290 * SAX a course part 291 * @param course The course 292 * @throws SAXException if an error occurs 293 */ 294 protected void saxCourseParts(Course course) throws SAXException 295 { 296 List<CoursePart> courseParts = course.getCourseParts(); 297 298 double totalHours = 0; 299 List<CoursePart> liveCourseParts = new ArrayList<>(); 300 for (CoursePart coursePart : courseParts) 301 { 302 try 303 { 304 _odfHelper.switchToLiveVersionIfNeeded(coursePart); 305 totalHours += coursePart.getNumberOfHours(); 306 liveCourseParts.add(coursePart); 307 } 308 catch (NoLiveVersionException e) 309 { 310 getLogger().warn("Some hours of " + course.toString() + " are not added because the course part " + coursePart.toString() + " does not have a live version."); 311 } 312 } 313 314 AttributesImpl attrs = new AttributesImpl(); 315 attrs.addCDATAAttribute("totalHours", String.valueOf(totalHours)); 316 XMLUtils.startElement(contentHandler, "courseparts", attrs); 317 318 for (CoursePart coursePart : liveCourseParts) 319 { 320 saxCoursePart(coursePart); 321 } 322 323 XMLUtils.endElement(contentHandler, "courseparts"); 324 } 325 326 /** 327 * SAX a course part 328 * @param coursePart The course part to SAX 329 * @throws SAXException if an error occurs 330 */ 331 protected void saxCoursePart(CoursePart coursePart) throws SAXException 332 { 333 AttributesImpl attrs = new AttributesImpl(); 334 attrs.addCDATAAttribute("id", coursePart.getId()); 335 attrs.addCDATAAttribute("title", coursePart.getTitle()); 336 _addAttrIfNotEmpty(attrs, "code", coursePart.getCode()); 337 338 XMLUtils.startElement(contentHandler, "coursepart", attrs); 339 340 XMLUtils.startElement(contentHandler, "attributes"); 341 _saxReferenceTableItem(coursePart.getNature(), CoursePart.NATURE, coursePart.getLanguage()); 342 343 _saxStructureViewIfExists(coursePart); 344 345 XMLUtils.endElement(contentHandler, "attributes"); 346 347 XMLUtils.endElement(contentHandler, "coursepart"); 348 } 349 350 /** 351 * SAX the 'structure' view if exists 352 * @param content the content 353 * @throws SAXException if an error occurs 354 */ 355 protected void _saxStructureViewIfExists(Content content) throws SAXException 356 { 357 View view = _cTypesHelper.getView("structure", content.getTypes(), content.getMixinTypes()); 358 if (view != null) 359 { 360 try 361 { 362 content.dataToSAX(contentHandler, view, DataContext.newInstance().withLocale(new Locale(content.getLanguage())).withEmptyValues(false)); 363 } 364 catch (BadItemTypeException | AmetysRepositoryException e) 365 { 366 throw new SAXException("Fail to sax the 'structure' view for content " + content.getId(), e); 367 } 368 } 369 } 370 371 /** 372 * SAX the common attributes for program item 373 * @param programItem the program item 374 * @param attrs the attributes 375 */ 376 protected void _saxCommonAttributes(ProgramItem programItem, AttributesImpl attrs) 377 { 378 attrs.addCDATAAttribute("title", ((Content) programItem).getTitle()); 379 attrs.addCDATAAttribute("id", programItem.getId()); 380 attrs.addCDATAAttribute("code", programItem.getCode()); 381 attrs.addCDATAAttribute("name", programItem.getName()); 382 383 boolean excludedFromSkills = _odfSkillsHelper.isExcluded(programItem); 384 if (excludedFromSkills) 385 { 386 attrs.addCDATAAttribute("excludedFromSkills", String.valueOf(excludedFromSkills)); 387 } 388 } 389 390 /** 391 * SAX the item of a reference table 392 * @param itemId the item id 393 * @param tagName the tag name 394 * @param lang the language to use 395 * @throws SAXException if an error occurs while saxing 396 */ 397 protected void _saxReferenceTableItem(String itemId, String tagName, String lang) throws SAXException 398 { 399 OdfReferenceTableEntry item = Optional.ofNullable(itemId) 400 .filter(StringUtils::isNotEmpty) 401 .map(_odfReferenceTableHelper::getItem) 402 .orElse(null); 403 404 if (item != null) 405 { 406 AttributesImpl attrs = new AttributesImpl(); 407 attrs.addCDATAAttribute("id", item.getId()); 408 _addAttrIfNotEmpty(attrs, "code", item.getCode()); 409 410 XMLUtils.createElement(contentHandler, tagName, attrs, item.getLabel(lang)); 411 } 412 } 413 414 /** 415 * Add an attribute if its not null or empty. 416 * @param attrs The attributes 417 * @param attrName The attribute name 418 * @param attrValue The attribute value 419 */ 420 protected void _addAttrIfNotEmpty(AttributesImpl attrs, String attrName, String attrValue) 421 { 422 if (StringUtils.isNotEmpty(attrValue)) 423 { 424 attrs.addCDATAAttribute(attrName, attrValue); 425 } 426 } 427 428}