001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software, please see the 018 * company website: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.pdftools; 029 030import org.opencms.file.CmsFile; 031import org.opencms.file.CmsObject; 032import org.opencms.file.CmsResource; 033import org.opencms.file.CmsVfsResourceNotFoundException; 034import org.opencms.file.wrapper.CmsWrappedResource; 035import org.opencms.main.CmsLog; 036import org.opencms.main.CmsResourceInitException; 037import org.opencms.main.CmsRuntimeException; 038import org.opencms.main.I_CmsResourceInit; 039import org.opencms.main.Messages; 040import org.opencms.main.OpenCms; 041import org.opencms.security.CmsSecurityException; 042import org.opencms.util.CmsStringUtil; 043import org.opencms.workplace.CmsWorkplace; 044 045import java.io.ByteArrayInputStream; 046import java.util.Collections; 047import java.util.Locale; 048import java.util.Map; 049 050import javax.servlet.http.HttpServletRequest; 051import javax.servlet.http.HttpServletResponse; 052 053import org.apache.commons.logging.Log; 054 055/** 056 * This resource handler handles URLs of the form /pdflink/{locale}/{formatter-id}/{detailname} and format 057 * the content identified by detailname using the JSP identified by formatter-id to generate XHTML which is then 058 * converted to PDF and returned directly by this handler.<p> 059 * 060 * In Online mode, the generated PDFs are cached on the real file system, while in Offline mode, the PDF data is always 061 * generated on-the-fly.<p> 062 */ 063public class CmsPdfResourceHandler implements I_CmsResourceInit { 064 065 /** Mime type data for different file extensions. */ 066 public static final String IMAGE_MIMETYPECONFIG = "png:image/png|gif:image/gif|jpg:image/jpeg"; 067 068 /** Map of mime types for different file extensions. */ 069 public static final Map<String, String> IMAGE_MIMETYPES = Collections.unmodifiableMap( 070 CmsStringUtil.splitAsMap(IMAGE_MIMETYPECONFIG, "|", ":")); 071 072 /** The logger instance for this class. */ 073 private static final Log LOG = CmsLog.getLog(CmsPdfResourceHandler.class); 074 /** The cache for the generated PDFs. */ 075 private CmsPdfCache m_pdfCache; 076 077 /** The converter used to generate the PDFs. */ 078 private CmsPdfConverter m_pdfConverter = new CmsPdfConverter(); 079 080 /** Cache for thumbnails. */ 081 private CmsPdfThumbnailCache m_thumbnailCache = new CmsPdfThumbnailCache(); 082 083 /** 084 * Creates a new instance.<p> 085 */ 086 public CmsPdfResourceHandler() { 087 088 m_pdfCache = new CmsPdfCache(); 089 } 090 091 /** 092 * @see org.opencms.main.I_CmsResourceInit#initResource(org.opencms.file.CmsResource, org.opencms.file.CmsObject, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) 093 */ 094 public CmsResource initResource( 095 CmsResource resource, 096 CmsObject cms, 097 HttpServletRequest request, 098 HttpServletResponse response) 099 throws CmsResourceInitException, CmsSecurityException { 100 101 // check if the resource was already found or the path starts with '/system/' 102 boolean abort = (resource != null) || cms.getRequestContext().getUri().startsWith(CmsWorkplace.VFS_PATH_SYSTEM); 103 if (abort) { 104 // skip in all cases above 105 return resource; 106 } 107 if (response != null) { 108 String uri = cms.getRequestContext().getUri(); 109 110 try { 111 if (uri.contains(CmsPdfLink.PDF_LINK_PREFIX)) { 112 handlePdfLink(cms, request, response, uri); 113 return null; // this will not be reached because the previous call will throw an exception 114 } else if (uri.contains(CmsPdfThumbnailLink.MARKER)) { 115 handleThumbnailLink(cms, request, response, uri); 116 return null; // this will not be reached because the previous call will throw an exception 117 } else { 118 return null; 119 } 120 } catch (CmsResourceInitException e) { 121 throw e; 122 } catch (CmsSecurityException e) { 123 LOG.warn(e.getLocalizedMessage(), e); 124 throw e; 125 } catch (CmsVfsResourceNotFoundException e) { 126 LOG.warn(e.getLocalizedMessage(), e); 127 return null; 128 } catch (CmsPdfLink.CmsPdfLinkParseException e) { 129 // not a valid PDF link, just continue with the resource init chain 130 LOG.warn(e.getLocalizedMessage(), e); 131 return null; 132 } catch (CmsPdfThumbnailLink.ParseException e) { 133 LOG.warn(e.getLocalizedMessage(), e); 134 return null; 135 } catch (Exception e) { 136 // don't just return null, because we want a useful error message to be displayed 137 LOG.error(e.getLocalizedMessage(), e); 138 throw new CmsRuntimeException( 139 Messages.get().container( 140 Messages.ERR_RESOURCE_INIT_ABORTED_1, 141 CmsPdfResourceHandler.class.getName()), 142 e); 143 } 144 } else { 145 return null; 146 } 147 } 148 149 /** 150 * Handles a link for generating a PDF.<p> 151 * 152 * @param cms the current CMS context 153 * @param request the servlet request 154 * @param response the servlet response 155 * @param uri the current uri 156 * 157 * @throws Exception if something goes wrong 158 * @throws CmsResourceInitException if the resource initialization is cancelled 159 */ 160 protected void handlePdfLink(CmsObject cms, HttpServletRequest request, HttpServletResponse response, String uri) 161 throws Exception { 162 163 CmsPdfLink linkObj = new CmsPdfLink(cms, uri); 164 CmsResource formatter = linkObj.getFormatter(); 165 CmsResource content = linkObj.getContent(); 166 LOG.info("Trying to render " + content.getRootPath() + " using " + formatter.getRootPath()); 167 Locale locale = linkObj.getLocale(); 168 CmsObject cmsForJspExecution = OpenCms.initCmsObject(cms); 169 cmsForJspExecution.getRequestContext().setLocale(locale); 170 cmsForJspExecution.getRequestContext().setSiteRoot(""); 171 byte[] result = null; 172 String cacheParams = formatter.getStructureId() 173 + ";" 174 + formatter.getDateLastModified() 175 + ";" 176 + locale 177 + ";" 178 + request.getQueryString(); 179 String cacheName = m_pdfCache.getCacheName(content, cacheParams); 180 if (cms.getRequestContext().getCurrentProject().isOnlineProject()) { 181 result = m_pdfCache.getCacheContent(cacheName); 182 } 183 if (result == null) { 184 cmsForJspExecution.getRequestContext().setUri(content.getRootPath()); 185 byte[] xhtmlData = CmsPdfFormatterUtils.executeJsp( 186 cmsForJspExecution, 187 request, 188 response, 189 formatter, 190 content); 191 192 LOG.info("Rendered XHTML from " + content.getRootPath() + " using " + formatter.getRootPath()); 193 if (LOG.isDebugEnabled()) { 194 logXhtmlOutput(formatter, content, xhtmlData); 195 } 196 // Use the same CmsObject we used for executing the JSP, because the same site root is needed to resolve external resources like images 197 result = m_pdfConverter.convertXhtmlToPdf(cmsForJspExecution, xhtmlData, "opencms://" + uri); 198 LOG.info("Converted XHTML to PDF, size=" + result.length); 199 m_pdfCache.saveCacheFile(cacheName, result); 200 } else { 201 LOG.info( 202 "Retrieved PDF data from cache for content " 203 + content.getRootPath() 204 + " and formatter " 205 + formatter.getRootPath()); 206 } 207 response.setContentType("application/pdf"); 208 response.getOutputStream().write(result); 209 CmsResourceInitException initEx = new CmsResourceInitException(CmsPdfResourceHandler.class); 210 initEx.setClearErrors(true); 211 throw initEx; 212 } 213 214 /** 215 * Logs the XHTML output.<p> 216 * 217 * @param formatter the formatter 218 * @param content the content resource 219 * @param xhtmlData the XHTML data 220 */ 221 protected void logXhtmlOutput(CmsResource formatter, CmsResource content, byte[] xhtmlData) { 222 223 try { 224 String xhtmlString = new String(xhtmlData, "UTF-8"); 225 LOG.debug( 226 "(PDF generation) The formatter " 227 + formatter.getRootPath() 228 + " generated the following XHTML source from " 229 + content.getRootPath() 230 + ":"); 231 LOG.debug(xhtmlString); 232 } catch (Exception e) { 233 LOG.debug(e.getLocalizedMessage(), e); 234 } 235 } 236 237 /** 238 * Handles a request for a PDF thumbnail.<p> 239 * 240 * @param cms the current CMS context 241 * @param request the servlet request 242 * @param response the servlet response 243 * @param uri the current uri 244 * 245 * @throws Exception if something goes wrong 246 */ 247 private void handleThumbnailLink( 248 CmsObject cms, 249 HttpServletRequest request, 250 HttpServletResponse response, 251 String uri) 252 throws Exception { 253 254 String options = request.getParameter(CmsPdfThumbnailLink.PARAM_OPTIONS); 255 if (CmsStringUtil.isEmptyOrWhitespaceOnly(options)) { 256 options = "w:64"; 257 } 258 CmsPdfThumbnailLink linkObj = new CmsPdfThumbnailLink(cms, uri, options); 259 CmsResource pdf = linkObj.getPdfResource(); 260 CmsFile pdfFile = cms.readFile(pdf); 261 CmsPdfThumbnailGenerator thumbnailGenerator = new CmsPdfThumbnailGenerator(); 262 // use a wrapped resource because we want the cache to store files with the correct (image file) extensions 263 CmsWrappedResource wrapperWithImageExtension = new CmsWrappedResource(pdfFile); 264 wrapperWithImageExtension.setRootPath(pdfFile.getRootPath() + "." + linkObj.getFormat()); 265 String cacheName = m_thumbnailCache.getCacheName( 266 wrapperWithImageExtension.getResource(), 267 options + ";" + linkObj.getFormat()); 268 byte[] imageData = m_thumbnailCache.getCacheContent(cacheName); 269 if (imageData == null) { 270 imageData = thumbnailGenerator.generateThumbnail( 271 new ByteArrayInputStream(pdfFile.getContents()), 272 linkObj.getWidth(), 273 linkObj.getHeight(), 274 linkObj.getFormat(), 275 linkObj.getPage()); 276 m_thumbnailCache.saveCacheFile(cacheName, imageData); 277 } 278 response.setContentType(IMAGE_MIMETYPES.get(linkObj.getFormat())); 279 response.getOutputStream().write(imageData); 280 CmsResourceInitException initEx = new CmsResourceInitException(CmsPdfResourceHandler.class); 281 initEx.setClearErrors(true); 282 throw initEx; 283 284 } 285 286}