001/* 002 * Copyright 2010 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.program.generators; 017 018import java.io.IOException; 019import java.util.Arrays; 020import java.util.LinkedHashSet; 021import java.util.Locale; 022import java.util.Map; 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.components.source.impl.SitemapSource; 029import org.apache.cocoon.environment.ObjectModelHelper; 030import org.apache.cocoon.environment.Request; 031import org.apache.cocoon.generation.Generator; 032import org.apache.cocoon.xml.AttributesImpl; 033import org.apache.cocoon.xml.XMLUtils; 034import org.apache.commons.lang.StringUtils; 035import org.apache.excalibur.source.SourceResolver; 036import org.xml.sax.SAXException; 037 038import org.ametys.cms.contenttype.ContentType; 039import org.ametys.cms.contenttype.MetadataDefinition; 040import org.ametys.cms.contenttype.MetadataDefinitionHolder; 041import org.ametys.cms.contenttype.MetadataSet; 042import org.ametys.cms.contenttype.RepeaterDefinition; 043import org.ametys.cms.repository.Content; 044import org.ametys.core.util.DateUtils; 045import org.ametys.core.util.IgnoreRootHandler; 046import org.ametys.odf.course.Course; 047import org.ametys.odf.courselist.CourseList; 048import org.ametys.odf.courselist.CourseList.ChoiceType; 049import org.ametys.odf.coursepart.CoursePart; 050import org.ametys.odf.orgunit.OrgUnit; 051import org.ametys.odf.orgunit.OrgUnitFactory; 052import org.ametys.odf.person.Person; 053import org.ametys.odf.person.PersonFactory; 054import org.ametys.odf.program.AbstractProgram; 055import org.ametys.odf.program.Container; 056import org.ametys.odf.program.Program; 057import org.ametys.odf.program.ProgramPart; 058import org.ametys.odf.program.SubProgram; 059import org.ametys.odf.program.TraversableProgramPart; 060import org.ametys.odf.translation.TranslationHelper; 061import org.ametys.plugins.repository.AmetysObject; 062import org.ametys.plugins.repository.UnknownAmetysObjectException; 063import org.ametys.plugins.repository.metadata.CompositeMetadata; 064import org.ametys.plugins.repository.metadata.UnknownMetadataException; 065 066/** 067 * {@link Generator} for rendering raw content data for a {@link Program}. 068 */ 069public class ProgramContentGenerator extends ODFContentGenerator 070{ 071 /** The source resolver */ 072 protected SourceResolver _srcResolver; 073 074 @Override 075 public void service(ServiceManager serviceManager) throws ServiceException 076 { 077 super.service(serviceManager); 078 _srcResolver = (SourceResolver) serviceManager.lookup(SourceResolver.ROLE); 079 } 080 081 @Override 082 protected void _saxOtherData(Content content, Locale defaultLocale) throws SAXException, ProcessingException, IOException 083 { 084 super._saxOtherData(content, defaultLocale); 085 086 boolean isEditionMetadataSet = parameters.getParameterAsBoolean("isEditionMetadataSet", false); 087 if (!isEditionMetadataSet) 088 { 089 if (content instanceof AbstractProgram) 090 { 091 AbstractProgram program = (AbstractProgram) content; 092 093 // Contacts 094 saxPersons(program); 095 096 // Child containers, subprograms or course lists 097 saxChildProgramPart(program, defaultLocale); 098 099 // OrgUnits 100 saxOrgUnits(program); 101 102 // Translations 103 saxTranslation(program); 104 105 // Skill sets 106 saxSkillSets(program, defaultLocale); 107 } 108 } 109 110 Request request = ObjectModelHelper.getRequest(objectModel); 111 request.setAttribute(Content.class.getName(), content); 112 } 113 114 /** 115 * SAX the referenced {@link Person}s 116 * @param content The content to analyze 117 * @throws SAXException if an error occurs while saxing 118 */ 119 protected void saxPersons(Content content) throws SAXException 120 { 121 saxLinkedContents(content, "persons", PersonFactory.PERSON_CONTENT_TYPE, "abstract"); 122 } 123 124 /** 125 * SAX the referenced {@link OrgUnit}s 126 * @param content The content to analyze 127 * @throws SAXException if an error occurs while saxing 128 */ 129 protected void saxOrgUnits(Content content) throws SAXException 130 { 131 saxLinkedContents(content, "orgunits", OrgUnitFactory.ORGUNIT_CONTENT_TYPE, "link"); 132 } 133 134 /** 135 * SAX the referenced {@link ProgramPart}s 136 * @param program The program or subprogram 137 * @param defaultLocale The default locale 138 * @throws SAXException if an error occurs while saxing 139 * @throws IOException if an error occurs 140 * @throws ProcessingException if an error occurs 141 */ 142 protected void saxChildProgramPart(AbstractProgram program, Locale defaultLocale) throws SAXException, ProcessingException, IOException 143 { 144 String[] children = program.getMetadataHolder().getStringArray(TraversableProgramPart.METADATA_CHILD_PROGRAM_PARTS, new String[0]); 145 for (String id : children) 146 { 147 try 148 { 149 AmetysObject child = _resolver.resolveById(id); 150 151 if (child instanceof SubProgram) 152 { 153 saxSubProgram((SubProgram) child, program.getContextPath()); 154 } 155 else if (child instanceof Container) 156 { 157 saxContainer ((Container) child, program.getContextPath(), defaultLocale); 158 } 159 else if (child instanceof CourseList) 160 { 161 saxCourseList((CourseList) child, program.getContextPath(), defaultLocale); 162 } 163 } 164 catch (UnknownAmetysObjectException e) 165 { 166 // The content can reference a non-existing child: ignore the exception. 167 } 168 } 169 } 170 171 /** 172 * SAX the existing translation 173 * @param content The content 174 * @throws SAXException if an error occurs while saxing 175 */ 176 protected void saxTranslation(Content content) throws SAXException 177 { 178 Map<String, String> translations = TranslationHelper.getTranslations(content); 179 if (!translations.isEmpty()) 180 { 181 saxTranslations(translations); 182 } 183 } 184 185 /** 186 * SAX the referenced content types. 187 * @param content The content to analyze 188 * @param tagName The root tagName 189 * @param linkedContentType The content type to search 190 * @param metadataSet The metadata set to parse the found contents 191 * @throws SAXException if an error occurs while saxing 192 */ 193 protected void saxLinkedContents(Content content, String tagName, String linkedContentType, String metadataSet) throws SAXException 194 { 195 Set<String> contentIds = getLinkedContents(content, linkedContentType); 196 197 XMLUtils.startElement(contentHandler, tagName); 198 for (String id : contentIds) 199 { 200 if (StringUtils.isNotEmpty(id)) 201 { 202 try 203 { 204 Content subContent = _resolver.resolveById(id); 205 saxContent(subContent, metadataSet); 206 } 207 catch (IOException e) 208 { 209 throw new SAXException(e); 210 } 211 catch (UnknownAmetysObjectException e) 212 { 213 // The program can reference a non-existing person: ignore the exception. 214 } 215 } 216 } 217 XMLUtils.endElement(contentHandler, tagName); 218 } 219 220 /** 221 * Get the linked contents of the defined content type. 222 * @param content The content to analyze 223 * @param linkedContentType The content type to search 224 * @return A {@link Set} of content ids 225 */ 226 protected Set<String> getLinkedContents(Content content, String linkedContentType) 227 { 228 Set<String> contentIds = new LinkedHashSet<>(); 229 230 for (String cTypeId : content.getTypes()) 231 { 232 ContentType cType = _contentTypeExtensionPoint.getExtension(cTypeId); 233 contentIds.addAll(_getContentIds(content.getMetadataHolder(), cType, linkedContentType)); 234 } 235 236 return contentIds; 237 } 238 239 private Set<String> _getContentIds(CompositeMetadata metadataHolder, MetadataDefinitionHolder metadataDefHolder, String contentType) 240 { 241 Set<String> contentIds = new LinkedHashSet<>(); 242 243 for (String metadataName : metadataDefHolder.getMetadataNames()) 244 { 245 MetadataDefinition metadataDef = metadataDefHolder.getMetadataDefinition(metadataName); 246 247 switch (metadataDef.getType()) 248 { 249 case CONTENT: 250 if (contentType.equals(metadataDef.getContentType())) 251 { 252 String[] values = metadataHolder.getStringArray(metadataName, new String[0]); 253 contentIds.addAll(Arrays.asList(values)); 254 } 255 break; 256 case COMPOSITE: 257 try 258 { 259 CompositeMetadata subMetadataHolder = metadataHolder.getCompositeMetadata(metadataName); 260 261 if (metadataDef instanceof RepeaterDefinition) 262 { 263 String[] entryNames = subMetadataHolder.getMetadataNames(); 264 for (String entryName : entryNames) 265 { 266 CompositeMetadata entryHolder = subMetadataHolder.getCompositeMetadata(entryName); 267 contentIds.addAll(_getContentIds(entryHolder, metadataDef, contentType)); 268 } 269 } 270 else 271 { 272 contentIds.addAll(_getContentIds(subMetadataHolder, metadataDef, contentType)); 273 } 274 } 275 catch (UnknownMetadataException e) 276 { 277 // Nothing 278 } 279 break; 280 281 default: 282 break; 283 } 284 } 285 286 return contentIds; 287 } 288 289 /** 290 * SAX a container 291 * @param container the container to SAX 292 * @param parentPath the parent path 293 * @param defaultLocale The default locale 294 * @throws SAXException if an error occurs 295 * @throws IOException if an error occurs 296 * @throws ProcessingException if an error occurs 297 */ 298 protected void saxContainer(Container container, String parentPath, Locale defaultLocale) throws SAXException, ProcessingException, IOException 299 { 300 AttributesImpl attrs = new AttributesImpl(); 301 attrs.addCDATAAttribute("title", container.getTitle()); 302 attrs.addCDATAAttribute("id", container.getId()); 303 _addAttrIfNotEmpty(attrs, "code", container.getCode()); 304 _addAttrIfNotEmpty(attrs, "nature", container.getNature()); 305 double ects = container.getEcts(); 306 if (ects > 0) 307 { 308 attrs.addCDATAAttribute("ects", String.valueOf(ects)); 309 } 310 311 XMLUtils.startElement(contentHandler, "container", attrs); 312 313 // Children 314 String[] children = container.getMetadataHolder().getStringArray(TraversableProgramPart.METADATA_CHILD_PROGRAM_PARTS, new String[0]); 315 for (String id : children) 316 { 317 try 318 { 319 AmetysObject ao = _resolver.resolveById(id); 320 if (ao instanceof SubProgram) 321 { 322 saxSubProgram((SubProgram) ao, parentPath); 323 } 324 else if (ao instanceof Container) 325 { 326 saxContainer ((Container) ao, parentPath, defaultLocale); 327 } 328 else if (ao instanceof CourseList) 329 { 330 saxCourseList((CourseList) ao, parentPath, defaultLocale); 331 } 332 } 333 catch (UnknownAmetysObjectException e) 334 { 335 // The content can reference a non-existing child (in live for example): ignore the exception. 336 } 337 } 338 339 XMLUtils.endElement(contentHandler, "container"); 340 } 341 342 /** 343 * SAX a sub program 344 * @param subProgram the sub program to SAX 345 * @param parentPath the parent path 346 * @throws SAXException if an error occurs 347 */ 348 protected void saxSubProgram(SubProgram subProgram, String parentPath) throws SAXException 349 { 350 AttributesImpl attrs = new AttributesImpl(); 351 attrs.addCDATAAttribute("title", subProgram.getTitle()); 352 attrs.addCDATAAttribute("id", subProgram.getId()); 353 _addAttrIfNotEmpty(attrs, "code", subProgram.getCode()); 354 _addAttrIfNotEmpty(attrs, "ects", subProgram.getEcts()); 355 356 if (parentPath != null) 357 { 358 attrs.addCDATAAttribute("path", parentPath + "/" + subProgram.getName()); 359 } 360 XMLUtils.startElement(contentHandler, "subprogram", attrs); 361 362 // SAX the XML content of subprogram with nature "Parcours" only 363 if ("P".equals(subProgram.getMetadataHolder().getString(AbstractProgram.EDUCATION_KIND, ""))) 364 { 365 try 366 { 367 saxContent(subProgram, "parcours", "xml", false, true); 368 } 369 catch (IOException e) 370 { 371 throw new SAXException(e); 372 } 373 } 374 375 XMLUtils.endElement(contentHandler, "subprogram"); 376 } 377 378 /** 379 * SAX a course list 380 * @param courseList The course list to SAX 381 * @param parentPath the parent path 382 * @param defaultLocale The default locale 383 * @throws SAXException if an error occurs 384 * @throws IOException if an error occurs 385 * @throws ProcessingException if an error occurs 386 */ 387 protected void saxCourseList(CourseList courseList, String parentPath, Locale defaultLocale) throws SAXException, ProcessingException, IOException 388 { 389 AttributesImpl attrs = new AttributesImpl(); 390 attrs.addCDATAAttribute("title", courseList.getTitle()); 391 _addAttrIfNotEmpty(attrs, "code", courseList.getCode()); 392 393 ChoiceType type = courseList.getType(); 394 if (type != null) 395 { 396 attrs.addCDATAAttribute("type", courseList.getType().toString()); 397 } 398 399 if (ChoiceType.CHOICE.equals(type)) 400 { 401 attrs.addCDATAAttribute("min", String.valueOf(courseList.getMinNumberOfCourses())); 402 attrs.addCDATAAttribute("max", String.valueOf(courseList.getMaxNumberOfCourses())); 403 } 404 405 XMLUtils.startElement(contentHandler, "courseList", attrs); 406 407 for (Course course : courseList.getCourses()) 408 { 409 try 410 { 411 saxCourse(course, parentPath, defaultLocale); 412 } 413 catch (UnknownAmetysObjectException e) 414 { 415 // The content can reference a non-existing course (in live for example): ignore the exception. 416 } 417 } 418 XMLUtils.endElement(contentHandler, "courseList"); 419 } 420 421 /** 422 * SAX a course 423 * @param course the course to SAX 424 * @param parentPath the parent path 425 * @param defaultLocale The default locale 426 * @throws SAXException if an error occurs 427 * @throws IOException if an error occurs 428 * @throws ProcessingException if an error occurs 429 */ 430 protected void saxCourse(Course course, String parentPath, Locale defaultLocale) throws SAXException, ProcessingException, IOException 431 { 432 AttributesImpl attrs = new AttributesImpl(); 433 attrs.addCDATAAttribute("title", course.getTitle()); 434 attrs.addCDATAAttribute("code", course.getCode()); 435 attrs.addCDATAAttribute("id", course.getId()); 436 attrs.addCDATAAttribute("name", course.getName()); 437 438 if (parentPath != null) 439 { 440 attrs.addCDATAAttribute("path", parentPath + "/" + course.getName() + "-" + course.getCode()); 441 } 442 443 MetadataSet metadataSet = _cTypesHelper.getMetadataSetForView("courseList", course.getTypes(), course.getMixinTypes()); 444 445 XMLUtils.startElement(contentHandler, "course", attrs); 446 447 try 448 { 449 if (metadataSet != null) 450 { 451 XMLUtils.startElement(contentHandler, "metadata"); 452 _metadataManager.saxMetadata(contentHandler, course, metadataSet, null); 453 XMLUtils.endElement(contentHandler, "metadata"); 454 } 455 } 456 catch (IOException e) 457 { 458 throw new SAXException(e); 459 } 460 461 // Course list 462 for (CourseList childCl : course.getCourseLists()) 463 { 464 saxCourseList(childCl, parentPath, defaultLocale); 465 } 466 467 // Course parts 468 for (CoursePart coursePart : course.getCourseParts()) 469 { 470 saxCoursePart(coursePart, defaultLocale); 471 } 472 473 XMLUtils.endElement(contentHandler, "course"); 474 } 475 476 /** 477 * SAX the HTML content of a {@link Content} 478 * @param content the content 479 * @param metadataSetName the metadata set name 480 * @throws SAXException If an error occurred saxing the content 481 * @throws IOException If an error occurred resolving the content 482 */ 483 protected void saxContent(Content content, String metadataSetName) throws SAXException, IOException 484 { 485 String format = parameters.getParameter("output-format", "html"); 486 if (StringUtils.isEmpty(format)) 487 { 488 format = "html"; 489 } 490 491 saxContent(content, metadataSetName, format, true, false); 492 } 493 494 /** 495 * SAX a {@link Content} to given format 496 * @param content the content 497 * @param metadataSetName the metadata set name 498 * @param format the output format 499 * @param withContentRoot true to wrap content stream into a root content tag 500 * @param ignoreChildren true to not SAX sub contents 501 * @throws SAXException If an error occurred saxing the content 502 * @throws IOException If an error occurred resolving the content 503 */ 504 protected void saxContent(Content content, String metadataSetName, String format, boolean withContentRoot, boolean ignoreChildren) throws SAXException, IOException 505 { 506 SitemapSource src = null; 507 try 508 { 509 String uri = "cocoon://_content." + format + "?contentId=" + content.getId() + "&metadataSetName=" + metadataSetName + "&output-format=" + format; 510 if (ignoreChildren) 511 { 512 uri += "&ignoreChildren=true"; 513 } 514 515 src = (SitemapSource) _srcResolver.resolveURI(uri); 516 517 if (withContentRoot) 518 { 519 AttributesImpl attrs = new AttributesImpl(); 520 attrs.addCDATAAttribute("id", content.getId()); 521 attrs.addCDATAAttribute("name", content.getName()); 522 attrs.addCDATAAttribute("title", content.getTitle()); 523 attrs.addCDATAAttribute("lastModifiedAt", DateUtils.dateToString(content.getLastModified())); 524 525 XMLUtils.startElement(contentHandler, "content", attrs); 526 } 527 528 src.toSAX(new IgnoreRootHandler(contentHandler)); 529 530 if (withContentRoot) 531 { 532 XMLUtils.endElement(contentHandler, "content"); 533 } 534 } 535 catch (UnknownAmetysObjectException e) 536 { 537 // The content may be archived 538 } 539 finally 540 { 541 _srcResolver.release(src); 542 } 543 } 544 545 /** 546 * Add an attribute if its not null or empty. 547 * @param attrs The attributes 548 * @param attrName The attribute name 549 * @param attrValue The attribute value 550 */ 551 protected void _addAttrIfNotEmpty(AttributesImpl attrs, String attrName, String attrValue) 552 { 553 if (StringUtils.isNotEmpty(attrValue)) 554 { 555 attrs.addCDATAAttribute(attrName, attrValue); 556 } 557 } 558 559 /** 560 * SAX a course part 561 * @param coursePart The course part to SAX 562 * @param defaultLocale The default locale 563 * @throws SAXException if an error occurs 564 * @throws IOException if an error occurs 565 * @throws ProcessingException if an error occurs 566 */ 567 protected void saxCoursePart(CoursePart coursePart, Locale defaultLocale) throws SAXException, ProcessingException, IOException 568 { 569 AttributesImpl attrs = new AttributesImpl(); 570 attrs.addCDATAAttribute("id", coursePart.getId()); 571 attrs.addCDATAAttribute("title", coursePart.getTitle()); 572 _addAttrIfNotEmpty(attrs, "code", coursePart.getCode()); 573 574 XMLUtils.startElement(contentHandler, "coursePart", attrs); 575 MetadataSet metadataSet = _cTypesHelper.getMetadataSetForView("sax", coursePart.getTypes(), coursePart.getMixinTypes()); 576 _saxMetadata(coursePart, metadataSet, defaultLocale); 577 XMLUtils.endElement(contentHandler, "coursePart"); 578 } 579}