1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
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 }