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