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.lang.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 Content content = (Content) programItem; 124 _odfHelper.switchToLiveVersion((DefaultContent) content); 125 _saxProgramItem(programItem, "programItem", List.of(programItem), null); 126 127 boolean includeSubPrograms = (boolean) request.getAttribute(EducationalBookletSchedulable.PARAM_INCLUDE_SUBPROGRAMS); 128 129 if (includeSubPrograms) 130 { 131 for (SubProgram subProgram : _getChildSubPrograms(programItem)) 132 { 133 _saxProgramItem(subProgram, "subprogram", List.of(programItem, subProgram), programItem); 134 } 135 } 136 137 for (Course course : _getChildCourses(programItem)) 138 { 139 _saxProgramItem(course, "course", List.of(course), programItem); 140 } 141 } 142 catch (NoLiveVersionException e) 143 { 144 throw new IllegalArgumentException("The program item with id " + programItemId + " has no live version."); 145 } 146 147 XMLUtils.endElement(contentHandler, "booklet"); 148 contentHandler.endDocument(); 149 } 150 151 /** 152 * Sax the catalog of program item 153 * @param programItem the program item 154 * @throws SAXException if an error occurred 155 */ 156 protected void _saxCatalog(ProgramItem programItem) throws SAXException 157 { 158 String catalogName = programItem.getCatalog(); 159 Catalog catalog = _catalogManager.getCatalog(catalogName); 160 161 if (catalog != null) 162 { 163 AttributesImpl attrs = new AttributesImpl(); 164 attrs.addCDATAAttribute("name", catalogName); 165 XMLUtils.createElement(contentHandler, "catalog", attrs, catalog.getTitle()); 166 } 167 } 168 169 /** 170 * Get all child courses from the program item 171 * @param programItem the program item 172 * @return the set of child courses 173 * @throws MalformedURLException if an error occurred 174 * @throws IOException if an error occurred 175 * @throws SAXException if an error occurred 176 */ 177 protected Set<Course> _getChildCourses(ProgramItem programItem) throws MalformedURLException, IOException, SAXException 178 { 179 Set<Course> courses = new LinkedHashSet<>(); 180 for (ProgramItem childProgramItem : _odfHelper.getChildProgramItems(programItem)) 181 { 182 try 183 { 184 _odfHelper.switchToLiveVersion((DefaultAmetysObject) childProgramItem); 185 if (childProgramItem instanceof Course) 186 { 187 courses.add((Course) childProgramItem); 188 } 189 courses.addAll(_getChildCourses(childProgramItem)); 190 } 191 catch (NoLiveVersionException e) 192 { 193 getLogger().warn("Cannot add the course to the educational booklet : Live label is required but there is no Live version for content " + childProgramItem.getId()); 194 } 195 } 196 197 return courses; 198 } 199 200 /** 201 * Get the direct child {@link SubProgram}s of a {@link ProgramItem} 202 * @param programItem the program item 203 * @return the subprograms 204 */ 205 protected Set<SubProgram> _getChildSubPrograms(ProgramItem programItem) 206 { 207 return _odfHelper.getChildProgramItems(programItem) 208 .stream() 209 .filter(SubProgram.class::isInstance) 210 .map(SubProgram.class::cast) 211 .collect(Collectors.toSet()); 212 } 213 214 /** 215 * Sax a {@link ProgramItem} as fo 216 * @param programItem the program item 217 * @param tagName the xml tag name 218 * @param ancestorPath The path of this program item (computed from the initial program item). Can be a partial path. 219 * @param rootProgramItem The root program item ancestor for this booklet 220 * @throws MalformedURLException if an error occurred 221 * @throws IOException if an error occurred 222 * @throws SAXException if an error occurred 223 */ 224 protected void _saxProgramItem(ProgramItem programItem, String tagName, List<ProgramItem> ancestorPath, ProgramItem rootProgramItem) throws MalformedURLException, IOException, SAXException 225 { 226 AttributesImpl attrs = new AttributesImpl(); 227 attrs.addCDATAAttribute("id", programItem.getId()); 228 attrs.addCDATAAttribute("name", programItem.getName()); 229 attrs.addCDATAAttribute("title", ((Content) programItem).getTitle()); 230 attrs.addCDATAAttribute("code", programItem.getCode()); 231 232 XMLUtils.startElement(contentHandler, tagName, attrs); 233 _saxContentAsFo((Content) programItem, ancestorPath, rootProgramItem); 234 XMLUtils.endElement(contentHandler, tagName); 235 } 236 237 /** 238 * Sax content as fo 239 * @param content the content 240 * @param ancestorPath The path of this program item (computed from the initial program item). Can be a partial path. 241 * @param rootAncestor The root program item ancestor for this booklet 242 * @throws MalformedURLException if an error occurred 243 * @throws IOException if an error occurred 244 * @throws SAXException if an error occurred 245 */ 246 protected void _saxContentAsFo(Content content, List<ProgramItem> ancestorPath, ProgramItem rootAncestor) throws MalformedURLException, IOException, SAXException 247 { 248 XMLUtils.startElement(contentHandler, "fo"); 249 SitemapSource src = null; 250 251 Request request = ContextHelper.getRequest(_context); 252 253 try 254 { 255 request.setAttribute(EducationalPathHelper.PROGRAM_ITEM_ANCESTOR_PATH_REQUEST_ATTR, ancestorPath); 256 if (rootAncestor != null) 257 { 258 request.setAttribute(EducationalPathHelper.ROOT_PROGRAM_ITEM_REQUEST_ATTR, rootAncestor); 259 } 260 261 Map<String, Object> pdfParameters = new HashMap<>(); 262 pdfParameters.put("versionLabel", CmsConstants.LIVE_LABEL); 263 pdfParameters.put("exportMode", __EXPORT_MODE); 264 265 String uri = "cocoon://_plugins/odf/_content/" + content.getName() + ".fo"; 266 src = (SitemapSource) resolver.resolveURI(uri, null, pdfParameters); 267 src.toSAX(new IgnoreRootHandler(contentHandler)); 268 } 269 catch (UnknownAmetysObjectException e) 270 { 271 // The content may be archived 272 } 273 finally 274 { 275 resolver.release(src); 276 277 request.removeAttribute(EducationalPathHelper.PROGRAM_ITEM_ANCESTOR_PATH_REQUEST_ATTR); 278 request.removeAttribute(EducationalPathHelper.ROOT_PROGRAM_ITEM_REQUEST_ATTR); 279 } 280 281 XMLUtils.endElement(contentHandler, "fo"); 282 } 283}