View Javadoc

1   /**
2    * Copyright 2012 Hippo.
3    * 
4    * This file is part of HST PDF Renderer.
5    * 
6    * HST PDF Renderer is free software: you can redistribute it and/or modify it 
7    * under the terms of the GNU General Public License as published by the Free 
8    * Software Foundation, either version 3 of the License, or (at your option) 
9    * any later version.
10   * 
11   * HST PDF Renderer is distributed in the hope that it will be useful, but 
12   * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
13   * or FITNESS FOR A PARTICULAR PURPOSE.
14   * See the GNU General Public License for more details.
15   * 
16   * You should have received a copy of the GNU General Public License along with
17   * HST PDF Renderer. If not, see http://www.gnu.org/licenses/.
18   */
19  package org.onehippo.forge.hst.pdf.renderer.servlet;
20  
21  import java.io.ByteArrayInputStream;
22  import java.io.CharArrayReader;
23  import java.io.File;
24  import java.io.IOException;
25  import java.io.InputStream;
26  import java.io.InputStreamReader;
27  import java.io.OutputStream;
28  import java.io.Reader;
29  import java.net.URI;
30  import java.util.ArrayList;
31  import java.util.List;
32  import java.util.Properties;
33  
34  import javax.servlet.Filter;
35  import javax.servlet.FilterChain;
36  import javax.servlet.FilterConfig;
37  import javax.servlet.ServletException;
38  import javax.servlet.ServletRequest;
39  import javax.servlet.ServletResponse;
40  import javax.servlet.http.HttpServletRequest;
41  import javax.servlet.http.HttpServletResponse;
42  
43  import org.apache.commons.io.IOUtils;
44  import org.apache.commons.lang.StringUtils;
45  import org.apache.commons.lang.math.NumberUtils;
46  import org.hippoecm.hst.configuration.hosting.VirtualHost;
47  import org.hippoecm.hst.core.request.HstRequestContext;
48  import org.hippoecm.hst.util.HstRequestUtils;
49  import org.onehippo.forge.hst.pdf.renderer.HtmlPDFRenderer;
50  import org.slf4j.Logger;
51  import org.slf4j.LoggerFactory;
52  import org.xhtmlrenderer.extend.UserAgentCallback;
53  
54  /**
55   * HtmlPDFRenderingFilter
56   */
57  public class HtmlPDFRenderingFilter implements Filter {
58  
59      public static final String TIDY_PROPS_PARAM = "tidy.props";
60      public static final String CSS_URI_PARAM = "css.uris";
61      public static final String BUFFER_SIZE_PARAM = "buffer.size";
62      public static final String USER_AGENT_CALLBACK_CLASS_PARAM = "user.agent.callback.class";
63      public static final String FONT_PATHS_PARAM = "font.paths";
64  
65      private static Logger log = LoggerFactory.getLogger(HtmlPDFRenderingFilter.class);
66  
67      private HtmlPDFRenderer pdfRenderer;
68  
69      @Override
70      public void init(FilterConfig filterConfig) throws ServletException {
71          Properties tidyProps = new Properties();
72          String param = StringUtils.trim(filterConfig.getInitParameter(TIDY_PROPS_PARAM));
73  
74          if (!StringUtils.isEmpty(param)) {
75              InputStream input = null;
76              try {
77                  input = filterConfig.getServletContext().getResourceAsStream(param);
78                  tidyProps.load(input);
79              } catch (Exception e) {
80                  log.error("Failed to parse Tidy properties from '" + param + "'.", e);
81              } finally {
82                  IOUtils.closeQuietly(input);
83              }
84          }
85  
86          pdfRenderer = new HtmlPDFRenderer(tidyProps);
87  
88          param = StringUtils.trim(filterConfig.getInitParameter(CSS_URI_PARAM));
89  
90          if (!StringUtils.isEmpty(param)) {
91              String [] cssURIParams = StringUtils.split(param, ";, \t\r\n");
92              List<URI> cssURIList = new ArrayList<URI>();
93  
94              for (String cssURIParam : cssURIParams) {
95                  if (StringUtils.startsWith(cssURIParam, "file:") || StringUtils.startsWith(cssURIParam, "http:") || StringUtils.startsWith(cssURIParam, "https:") || StringUtils.startsWith(cssURIParam, "ftp:") || StringUtils.startsWith(cssURIParam, "sftp:")) {
96                      cssURIList.add(URI.create(param));
97                  } else {
98                      File cssFile = null;
99  
100                     if (StringUtils.startsWith(cssURIParam, "/")) {
101                         cssFile = new File(filterConfig.getServletContext().getRealPath(cssURIParam));
102                     } else {
103                         cssFile = new File(cssURIParam);
104                     }
105 
106                     if (!cssFile.isFile()) {
107                         log.error("Cannot find the css file: {}", cssFile);
108                     } else {
109                         cssURIList.add(cssFile.toURI());
110                     }
111                 }
112             }
113 
114             if (!cssURIList.isEmpty()) {
115                 pdfRenderer.setCssURIs(cssURIList.toArray(new URI[cssURIList.size()]));
116             }
117         }
118 
119         param = StringUtils.trim(filterConfig.getInitParameter(BUFFER_SIZE_PARAM));
120 
121         if (!StringUtils.isEmpty(param)) {
122             pdfRenderer.setBufferSize(Math.max(512, NumberUtils.toInt(param, 4096)));
123         }
124 
125         param = StringUtils.trim(filterConfig.getInitParameter(USER_AGENT_CALLBACK_CLASS_PARAM));
126 
127         if (!StringUtils.isEmpty(param)) {
128             try {
129                 Class<?> userAgentCallBackClass = Thread.currentThread().getContextClassLoader().loadClass(param);
130 
131                 if (!UserAgentCallback.class.isAssignableFrom(userAgentCallBackClass)) {
132                     log.error("The class, '{}' is not an type of '{}'.", param, UserAgentCallback.class);
133                 } else {
134                     pdfRenderer.setUserAgentCallback((UserAgentCallback) userAgentCallBackClass.newInstance());
135                 }
136             } catch (Exception e) {
137                 log.error("Failed to set userAgentClassCallback object", e);
138             }
139         }
140 
141         param = StringUtils.trim(filterConfig.getInitParameter(FONT_PATHS_PARAM));
142 
143         if (!StringUtils.isEmpty(param)) {
144             String [] fontPaths = StringUtils.split(param, ";, \t\r\n");
145             pdfRenderer.setFontPaths(fontPaths);
146         }
147     }
148 
149     @Override
150     public void destroy() {
151     }
152 
153     @Override
154     public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
155             ServletException {
156         if (!(response instanceof HttpServletResponse)) {
157             chain.doFilter(request, response);
158             return;
159         }
160 
161         if (response.isCommitted()) {
162             log.warn("The servlet response is already committed for the request: '{}'.", ((HttpServletRequest) request).getRequestURI());
163             chain.doFilter(request, response);
164             return;
165         }
166 
167         ContentCapturingHttpServletResponse capturingResponse = null;
168         InputStream htmlInputStream = null;
169         Reader htmlReader = null;
170         OutputStream pdfOutputStream = null;
171 
172         try {
173             capturingResponse = new ContentCapturingHttpServletResponse((HttpServletResponse) response);
174             chain.doFilter(request, capturingResponse);
175 
176             if (capturingResponse.isWrittenToPrintWriter()) {
177                 htmlReader = new CharArrayReader(capturingResponse.toCharArray());
178             } else {
179                 htmlInputStream = new ByteArrayInputStream(capturingResponse.toByteArray());
180                 String characterEncoding = StringUtils.defaultIfBlank(capturingResponse.getCharacterEncoding(), "UTF-8");
181                 htmlReader = new InputStreamReader(htmlInputStream, characterEncoding);
182             }
183 
184             capturingResponse.close();
185             capturingResponse = null;
186 
187             response.setContentType("application/pdf");
188 
189             StringBuilder headerValue = new StringBuilder("attachment");
190             headerValue.append("; filename=\"").append(getDocumentFilename((HttpServletRequest) request)).append("\"");
191             ((HttpServletResponse) response).addHeader("Content-Disposition", headerValue.toString());
192 
193             pdfOutputStream = response.getOutputStream();
194             pdfRenderer.renderHtmlToPDF(htmlReader, true, pdfOutputStream, getDocumentURL((HttpServletRequest) request), getExternalLinkBaseURL((HttpServletRequest) request));
195         } catch (Exception e) {
196             
197         } finally {
198             if (capturingResponse != null) {
199                 capturingResponse.close();
200             }
201 
202             IOUtils.closeQuietly(pdfOutputStream);
203             IOUtils.closeQuietly(htmlReader);
204             IOUtils.closeQuietly(htmlInputStream);
205         }
206     }
207 
208     private String getDocumentFilename(HttpServletRequest request) {
209         String requestURI = request.getRequestURI();
210         String fileName = StringUtils.trim(StringUtils.substringAfterLast(requestURI, "/"));
211 
212         if (StringUtils.isEmpty(fileName)) {
213             return "download.pdf";
214         } else {
215             return fileName + ".pdf";
216         }
217     }
218 
219     private String getDocumentBaseURL(HttpServletRequest request) {
220         StringBuilder sb = new StringBuilder(40);
221 
222         String scheme = request.getScheme();
223         String serverName = request.getServerName();
224         int port = request.getServerPort();
225 
226         if ("https".equals(scheme)) {
227             if (port == 443) {
228                 sb.append(scheme).append("://").append(serverName);
229             } else {
230                 sb.append(scheme).append("://").append(serverName).append(':').append(port);
231             }
232         } else {
233             if (port == 80) {
234                 sb.append(scheme).append("://").append(serverName);
235             } else {
236                 sb.append(scheme).append("://").append(serverName).append(':').append(port);
237             }
238         }
239 
240         return sb.toString();
241     }
242 
243     private String getDocumentURL(HttpServletRequest request) {
244         StringBuilder sb = new StringBuilder(40);
245         sb.append(getDocumentBaseURL(request));
246         sb.append(request.getRequestURI());
247         return sb.toString();
248     }
249 
250     private String getExternalLinkBaseURL(HttpServletRequest request) {
251         HstRequestContext requestContext = HstRequestUtils.getHstRequestContext(request);
252 
253         if (requestContext == null) {
254             return getDocumentBaseURL(request);
255         }
256 
257         VirtualHost virtualHost = requestContext.getVirtualHost();
258 
259         if (virtualHost == null) {
260             return getDocumentBaseURL(request);
261         }
262 
263         return virtualHost.getBaseURL(request);
264     }
265 
266 }