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