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