001/* 002 * Copyright 2020 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.export.pdf; 017 018import java.io.IOException; 019import java.net.MalformedURLException; 020import java.util.HashMap; 021import java.util.LinkedHashSet; 022import java.util.List; 023import java.util.Map; 024import java.util.Set; 025import java.util.stream.Collectors; 026 027import org.apache.avalon.framework.context.Context; 028import org.apache.avalon.framework.context.ContextException; 029import org.apache.avalon.framework.context.Contextualizable; 030import org.apache.avalon.framework.service.ServiceException; 031import org.apache.avalon.framework.service.ServiceManager; 032import org.apache.cocoon.ProcessingException; 033import org.apache.cocoon.components.ContextHelper; 034import org.apache.cocoon.components.source.impl.SitemapSource; 035import org.apache.cocoon.environment.ObjectModelHelper; 036import org.apache.cocoon.environment.Request; 037import org.apache.cocoon.generation.ServiceableGenerator; 038import org.apache.cocoon.xml.AttributesImpl; 039import org.apache.cocoon.xml.XMLUtils; 040import org.apache.commons.lang3.StringUtils; 041import org.xml.sax.SAXException; 042 043import org.ametys.cms.CmsConstants; 044import org.ametys.cms.repository.Content; 045import org.ametys.cms.repository.DefaultContent; 046import org.ametys.core.util.IgnoreRootHandler; 047import org.ametys.odf.EducationalPathHelper; 048import org.ametys.odf.NoLiveVersionException; 049import org.ametys.odf.ODFHelper; 050import org.ametys.odf.ProgramItem; 051import org.ametys.odf.catalog.Catalog; 052import org.ametys.odf.catalog.CatalogsManager; 053import org.ametys.odf.course.Course; 054import org.ametys.odf.program.SubProgram; 055import org.ametys.odf.schedulable.ArchiveEducationalBookletSchedulable; 056import org.ametys.odf.schedulable.EducationalBookletSchedulable; 057import org.ametys.plugins.repository.AmetysObjectResolver; 058import org.ametys.plugins.repository.UnknownAmetysObjectException; 059import org.ametys.plugins.repository.jcr.DefaultAmetysObject; 060 061 062/** 063 * Generator producing the SAX of subprogram and its courses for the educational booklet 064 */ 065public class EducationalBookletGenerator extends ServiceableGenerator implements Contextualizable 066{ 067 private static final String __EXPORT_MODE = "educational-booklet"; 068 069 /** The Ametys object resolver */ 070 protected AmetysObjectResolver _resolver; 071 072 /** The ODF helper */ 073 protected ODFHelper _odfHelper; 074 075 /** The catalog manager */ 076 protected CatalogsManager _catalogManager; 077 078 /** The avalon context */ 079 protected Context _context; 080 081 @Override 082 public void service(ServiceManager sManager) throws ServiceException 083 { 084 super.service(sManager); 085 _resolver = (AmetysObjectResolver) sManager.lookup(AmetysObjectResolver.ROLE); 086 _odfHelper = (ODFHelper) sManager.lookup(ODFHelper.ROLE); 087 _catalogManager = (CatalogsManager) sManager.lookup(CatalogsManager.ROLE); 088 } 089 090 public void contextualize(Context context) throws ContextException 091 { 092 _context = context; 093 } 094 095 public void generate() throws IOException, SAXException, ProcessingException 096 { 097 Request request = ObjectModelHelper.getRequest(objectModel); 098 099 contentHandler.startDocument(); 100 101 AttributesImpl attrs = new AttributesImpl(); 102 103 String archiveDateAsString = (String) request.getAttribute(ArchiveEducationalBookletSchedulable.PARAM_ARCHIVE_DATE); 104 if (StringUtils.isNotBlank(archiveDateAsString)) 105 { 106 attrs.addCDATAAttribute("isValidated", "true"); 107 attrs.addCDATAAttribute("archiveDate", archiveDateAsString); 108 } 109 else 110 { 111 attrs.addCDATAAttribute("isValidated", "false"); 112 } 113 114 XMLUtils.startElement(contentHandler, "booklet", attrs); 115 116 String programItemId = (String) request.getAttribute(EducationalBookletSchedulable.PARAM_PROGRAM_ITEM_ID); 117 ProgramItem programItem = _resolver.resolveById(programItemId); 118 119 _saxCatalog(programItem); 120 121 try 122 { 123 _odfHelper.switchToLiveVersion((DefaultContent) programItem); 124 _saxProgramItem(programItem, "programItem", List.of(programItem), null); 125 126 boolean includeSubPrograms = (boolean) request.getAttribute(EducationalBookletSchedulable.PARAM_INCLUDE_SUBPROGRAMS); 127 128 if (includeSubPrograms) 129 { 130 for (SubProgram subProgram : _getChildSubPrograms(programItem)) 131 { 132 _saxProgramItem(subProgram, "subprogram", List.of(programItem, subProgram), programItem); 133 } 134 } 135 136 for (Course course : _getChildCourses(programItem)) 137 { 138 _saxProgramItem(course, "course", List.of(course), programItem); 139 } 140 } 141 catch (NoLiveVersionException e) 142 { 143 throw new IllegalArgumentException("The program item with id " + programItemId + " has no live version."); 144 } 145 146 XMLUtils.endElement(contentHandler, "booklet"); 147 contentHandler.endDocument(); 148 } 149 150 /** 151 * Sax the catalog of program item 152 * @param programItem the program item 153 * @throws SAXException if an error occurred 154 */ 155 protected void _saxCatalog(ProgramItem programItem) throws SAXException 156 { 157 String catalogName = programItem.getCatalog(); 158 Catalog catalog = _catalogManager.getCatalog(catalogName); 159 160 if (catalog != null) 161 { 162 AttributesImpl attrs = new AttributesImpl(); 163 attrs.addCDATAAttribute("name", catalogName); 164 XMLUtils.createElement(contentHandler, "catalog", attrs, catalog.getTitle()); 165 } 166 } 167 168 /** 169 * Get all child courses from the program item 170 * @param programItem the program item 171 * @return the set of child courses 172 * @throws MalformedURLException if an error occurred 173 * @throws IOException if an error occurred 174 * @throws SAXException if an error occurred 175 */ 176 protected Set<Course> _getChildCourses(ProgramItem programItem) throws MalformedURLException, IOException, SAXException 177 { 178 Set<Course> courses = new LinkedHashSet<>(); 179 for (ProgramItem childProgramItem : _odfHelper.getChildProgramItems(programItem)) 180 { 181 try 182 { 183 _odfHelper.switchToLiveVersion((DefaultAmetysObject) childProgramItem); 184 if (childProgramItem instanceof Course) 185 { 186 courses.add((Course) childProgramItem); 187 } 188 courses.addAll(_getChildCourses(childProgramItem)); 189 } 190 catch (NoLiveVersionException e) 191 { 192 getLogger().warn("Cannot add the course to the educational booklet : Live label is required but there is no Live version for content " + childProgramItem.getId()); 193 } 194 } 195 196 return courses; 197 } 198 199 /** 200 * Get the direct child {@link SubProgram}s of a {@link ProgramItem} 201 * @param programItem the program item 202 * @return the subprograms 203 */ 204 protected Set<SubProgram> _getChildSubPrograms(ProgramItem programItem) 205 { 206 return _odfHelper.getChildProgramItems(programItem) 207 .stream() 208 .filter(SubProgram.class::isInstance) 209 .map(SubProgram.class::cast) 210 .collect(Collectors.toSet()); 211 } 212 213 /** 214 * Sax a {@link ProgramItem} as fo 215 * @param programItem the program item 216 * @param tagName the xml tag name 217 * @param ancestorPath The path of this program item (computed from the initial program item). Can be a partial path. 218 * @param rootProgramItem The root program item ancestor for this booklet 219 * @throws MalformedURLException if an error occurred 220 * @throws IOException if an error occurred 221 * @throws SAXException if an error occurred 222 */ 223 protected void _saxProgramItem(ProgramItem programItem, String tagName, List<ProgramItem> ancestorPath, ProgramItem rootProgramItem) throws MalformedURLException, IOException, SAXException 224 { 225 AttributesImpl attrs = new AttributesImpl(); 226 attrs.addCDATAAttribute("id", programItem.getId()); 227 attrs.addCDATAAttribute("name", programItem.getName()); 228 attrs.addCDATAAttribute("title", ((Content) programItem).getTitle()); 229 attrs.addCDATAAttribute("code", programItem.getCode()); 230 231 XMLUtils.startElement(contentHandler, tagName, attrs); 232 _saxContentAsFo((Content) programItem, ancestorPath, rootProgramItem); 233 XMLUtils.endElement(contentHandler, tagName); 234 } 235 236 /** 237 * Sax content as fo 238 * @param content the content 239 * @param ancestorPath The path of this program item (computed from the initial program item). Can be a partial path. 240 * @param rootAncestor The root program item ancestor for this booklet 241 * @throws MalformedURLException if an error occurred 242 * @throws IOException if an error occurred 243 * @throws SAXException if an error occurred 244 */ 245 protected void _saxContentAsFo(Content content, List<ProgramItem> ancestorPath, ProgramItem rootAncestor) throws MalformedURLException, IOException, SAXException 246 { 247 XMLUtils.startElement(contentHandler, "fo"); 248 SitemapSource src = null; 249 250 Request request = ContextHelper.getRequest(_context); 251 252 try 253 { 254 request.setAttribute(EducationalPathHelper.PROGRAM_ITEM_ANCESTOR_PATH_REQUEST_ATTR, ancestorPath); 255 if (rootAncestor != null) 256 { 257 request.setAttribute(EducationalPathHelper.ROOT_PROGRAM_ITEM_REQUEST_ATTR, rootAncestor); 258 } 259 260 Map<String, Object> pdfParameters = new HashMap<>(); 261 pdfParameters.put("versionLabel", CmsConstants.LIVE_LABEL); 262 pdfParameters.put("exportMode", __EXPORT_MODE); 263 264 String uri = "cocoon://_plugins/odf/_content/" + content.getName() + ".fo"; 265 src = (SitemapSource) resolver.resolveURI(uri, null, pdfParameters); 266 src.toSAX(new IgnoreRootHandler(contentHandler)); 267 } 268 catch (UnknownAmetysObjectException e) 269 { 270 // The content may be archived 271 } 272 finally 273 { 274 resolver.release(src); 275 276 request.removeAttribute(EducationalPathHelper.PROGRAM_ITEM_ANCESTOR_PATH_REQUEST_ATTR); 277 request.removeAttribute(EducationalPathHelper.ROOT_PROGRAM_ITEM_REQUEST_ATTR); 278 } 279 280 XMLUtils.endElement(contentHandler, "fo"); 281 } 282}