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