KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > faceless > report > PDFFilter


1 // $Id: PDFFilter.java,v 1.24 2004/12/03 15:37:33 mike Exp $
2
//
3
package org.faceless.report;
4
5 import java.util.*;
6 import java.io.*;
7 import java.net.*;
8 import javax.servlet.*;
9 import javax.servlet.http.*;
10 import org.faceless.pdf2.*;
11 import org.faceless.util.*;
12 import org.faceless.report.*;
13 import org.xml.sax.*;
14
15 /**
16  * <p>
17  * The <code>PDFFilter</code> class is an implementation of the Servlet 2.3 Filter
18  * interface, which automatically converts an XML report into a PDF which is
19  * returned to the client. For those still using the Servlet 2.2 architecture,
20  * The {@link PDFProxyServlet} does a similar job.
21  * </p>
22  * <p>
23  * More information on installing filters is available in the Servlet Specification
24  * and has probably been supplied with your Servlet engine. For the impatient,
25  * here's an example setup which would cause all requests to anything in the
26  * <code>/pdf/</code> path of your website to be converted to a PDF. Add the following
27  * lines to your <code>WEB-INF/web.xml</code> file:
28  * <pre>
29  * &lt;filter&gt;
30  * &lt;filter-name&gt;bforeport&lt;/filter-name&gt;
31  * &lt;filter-class&gt;org.faceless.report.PDFFilter&lt;/filter-class&gt;
32  * &lt;/filter&gt;
33  * &lt;filter-mapping&gt;
34  * &lt;filter-name&gt;bforeport&lt;/filter-name&gt;
35  * &lt;url-pattern&gt;/pdf/*&lt;/url-pattern&gt;
36  * &lt;/filter-mapping&gt;
37  * </pre>
38  * <p>
39  * Meta tags that aren't already known to the Report Generator and that
40  * begin with "<code>HTTP-</code>" are added to the response header (minus the
41  * "HTTP-" prefix). An example would be to place
42  * <code>&lt;meta name="HTTP-Expires" value="Mon, 01 Jan 1999 12:00:00 GMT"&gt;</code>
43  * in the head of the XML, which would set the "Expires" header in the HTTP
44  * response.
45  * </p>
46  * <p>
47  * The following custom Meta-Tags may also be used to control the behaviour of
48  * the servlet.
49  * <ul>
50  * <li><code>Servlet-Filename</code> - ask the client browser to save the file as the specified
51  * filename instead of displaying it inline. This uses the <code>Content-Disposition</code>
52  * header, which <i>in theory</i> is accepted by NS4+ and IE5+, although this
53  * <a HREF=http://support.microsoft.com/support/kb/articles/Q281/1/19.asp>known bug</a>
54  * in IE5.5 may prevent the document from being viewed at all. Use with caution unless
55  * you know your audiences browsers.</li>
56  * </ul>
57  * Some initialization parameters can be set in the <code>web.xml</code> file to further
58  * control various internal aspects of the servlet:
59  * <ul>
60  * <li><code>org.xml.sax.driver</code> - may be set to the base class of your SAX parsers
61  * <code>XMLReader</code> implementation, if the generator is unable to locate it.</li>
62  * <li><code>org.faceless.report.flag.WarningUnknownTag</code> - may be set to <code>true</code> or <code>false</code> to generate warnings about unknown tags. Default is "true"</li>
63  * <li><code>org.faceless.report.flag.WarningUnknownAttribute</code> - may be set to <code>true</code> or <code>false</code> to generate warnings about unknown attributes. Default is "true"</li>
64  * <li><code>org.faceless.report.flag.WarningMisplacedText</code> - may be set to <code>true</code> or <code>false</code> to generate warnings about misplaced text. Default is "true"</li>
65  * <li><code>cache-minsize</code> and <code>cache-prefix</code> - can be set to cache parts of the created document to disk.</li>
66  * </ul>
67  * </p>
68  * <p>
69  * These last two parameters were added in version 1.1.19 to cause parts of the document
70  * to be cached to disk. This can reduce memory footprint slightly, although it may slow
71  * things down a little so you must decide whether it's appropriate or not. The
72  * <code>cache-prefix</code> and <code>cache-minsize</code> parameters are passed into the
73  * {@link org.faceless.pdf2.DiskCache} constructor - essentially the <code>prefix</code> should
74  * be a temporary directory, optionally with the first half of a filename. The <code>minsize</code>
75  * parameter sets the minimum size a stream may be before it's considered to be worth caching to disk.
76  * For example, to store streams greater than 8k in the "/tmp/cache" directory and call them
77  * "bfo.1", "bfo.2" and so on, you could do something like this:
78  * </p>
79  * <pre>
80  * &lt;filter&gt;
81  * &lt;filter-name&gt;bforeport&lt;/filter-name&gt;
82  * &lt;filter-class&gt;org.faceless.report.PDFFilter&lt;/filter-class&gt;
83  * &lt;init-param&gt
84  * &lt;param-name&gt;cache-minsize&lt;/param-name&gt;
85  * &lt;param-value&gt;8192&lt;/param-value&gt;
86  * &lt;/init-param&gt
87  * &lt;init-param&gt
88  * &lt;param-name&gt;cache-prefix&lt;/param-name&gt;
89  * &lt;param-value&gt;/tmp/cache/bfo.&lt;/param-value&gt;
90  * &lt;/init-param&gt
91  * &lt;/filter&gt;
92  * </pre>
93  * <p>
94  * This class also implements <code>org.xml.sax.ErrorHandler</code>, to deal with
95  * any errors found during the XML parsing process. Currently all warnings and
96  * errors are fatal, and logged to <code>System.err</code>.
97  * </p>
98  *
99  * @version $Revision: 1.24 $
100  */

101 public class PDFFilter implements Filter, ErrorHandler
102 {
103     private FilterConfig conf=null;
104
105     public void init(FilterConfig config)
106         throws ServletException
107     {
108         this.conf = config;
109     String JavaDoc license = conf.getInitParameter("license");
110     if (license==null) license = conf.getInitParameter("License");
111     if (license!=null) {
112         ReportParser.setLicenseKey(license);
113     }
114
115     String JavaDoc cache = conf.getInitParameter("cache-prefix");
116     if (cache==null) cache = conf.getInitParameter("Cache-prefix");
117     if (cache==null) cache = conf.getInitParameter("Cache-Prefix");
118
119     String JavaDoc cachemin = conf.getInitParameter("cache-minsize");
120     if (cachemin==null) cachemin = conf.getInitParameter("Cache-minsize");
121     if (cachemin==null) cachemin = conf.getInitParameter("Cache-Minsize");
122     if (cachemin==null) cachemin="8192";
123
124     if (cache!=null) {
125         cache=cache.trim();
126         if (cache.length()>0) {
127         PDF.setCache(new DiskCache(cache, Integer.parseInt(cachemin)));
128         }
129     }
130     }
131
132     public void destroy()
133     {
134         this.conf = null;
135     }
136
137     /**
138      * Return the Filter Config. A non-standard method required for WebLogic 6.1
139      */

140     public FilterConfig getFilterConfig()
141     {
142         return conf;
143     }
144
145     /**
146      * Set the Filter Config. A non-standard method required for WebLogic 6.1
147      */

148     public void setFilterConfig(FilterConfig conf)
149     {
150         this.conf=conf;
151     String JavaDoc license = conf.getInitParameter("license");
152     if (license!=null) {
153         org.faceless.report.ReportParser.setLicenseKey(license);
154     }
155     }
156
157     public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
158         throws ServletException, IOException
159     {
160     if (conf==null || !(request instanceof HttpServletRequest && response instanceof HttpServletResponse)) return;
161     // Apparantly some versions of Internet Explorer set the string "contype" as a User-Agent
162
// to help IE determine the type of plugin to load. We haven't seen this behaviour here,
163
// but have added some code to handle this situation just in case.
164
// See http://support.microsoft.com/default.aspx?scid=kb;en-us;293792
165
//
166
boolean iehack = "contype".equals(((HttpServletRequest)request).getHeader("User-Agent"));
167
168     CharResponseWrapper wrapper = new CharResponseWrapper((HttpServletResponse)response);
169
170     chain.doFilter((HttpServletRequest)request, wrapper);
171
172     if (wrapper.getStatus()>=200 && wrapper.getStatus()<=299 && (wrapper.getContentType()!=null && wrapper.getContentType().startsWith("text/xml"))) {
173         if (iehack) {
174         response.setContentType("application/pdf");
175         } else {
176         InputSource xmlin = new InputSource();
177         xmlin.setCharacterStream(new StringReader(wrapper.getString().trim()));
178         String JavaDoc url = ((HttpServletRequest)request).getRequestURL().toString();
179         if (url!=null && url.toLowerCase().startsWith("https:")) {
180             try {
181             URL temp = new URL(url);
182             } catch (Throwable JavaDoc e) {
183             System.err.println("WARNING: HTTPS protocol not recognized by webserver (error is "+e.toString()+"). Switching to HTTP");
184             url = "http:"+url.substring(6);
185             }
186         }
187         xmlin.setSystemId(url);
188
189         PDF pdf=null;
190         try {
191             ReportParser parser = ReportParser.getInstance((String JavaDoc)conf.getInitParameter("org.xml.sax.driver"));
192             parser.setMetaHandler(new MetaCallback((HttpServletRequest)request, (HttpServletResponse)response, this));
193             if ("false".equalsIgnoreCase(conf.getInitParameter("org.faceless.report.flag.WarningUnknownTag"))) {
194             parser.setFlag(ReportParser.WARNING_UNKNOWN_TAG, false);
195             }
196             if ("false".equalsIgnoreCase(conf.getInitParameter("org.faceless.report.flag.WarningUnknownAttribute"))) {
197             parser.setFlag(ReportParser.WARNING_UNKNOWN_ATTRIBUTE, false);
198             }
199             if ("false".equalsIgnoreCase(conf.getInitParameter("org.faceless.report.flag.WarningMisplacedText"))) {
200             parser.setFlag(ReportParser.WARNING_MISPLACED_TEXT, false);
201             }
202             if ("true".equalsIgnoreCase(conf.getInitParameter("org.faceless.report.flag.Debug"))) {
203             parser.setFlag(ReportParser.DEBUG_TO_STDOUT, true);
204             }
205             pdf = parser.parse(xmlin);
206         } catch (SAXException e) {
207             if (e.getException()!=null) {
208             throw new ServletException(e.getException());
209             } else {
210             throw new ServletException(e);
211             }
212         }
213
214         ByteArrayOutputStream bout = new ByteArrayOutputStream();
215         pdf.render(bout);
216
217         response.setContentType("application/pdf");
218         response.setContentLength(bout.size());
219         bout.writeTo(response.getOutputStream());
220         response.getOutputStream().close();
221         }
222     } else {
223             if (wrapper.getContentType()!=null) {
224                 response.setContentType(wrapper.getContentType());
225             }
226
227         response.getWriter().print(wrapper.getString());
228     }
229     }
230
231     private class CharResponseWrapper extends HttpServletResponseWrapper
232     {
233     private static final boolean debug=false;
234         private OutputStreamWrapper bout;
235     private PrintWriter pout;
236     private CharArrayWriter cout;
237     private int status=200;
238     private String JavaDoc ctype;
239
240     private class OutputStreamWrapper extends ServletOutputStream
241     {
242         ByteArrayOutputStream out;
243
244         public OutputStreamWrapper()
245         {
246         out = new ByteArrayOutputStream();
247         }
248
249         public void write(int i)
250         {
251             out.write(i);
252         }
253
254         public String JavaDoc getString(String JavaDoc charset)
255             throws IOException
256         {
257         String JavaDoc s = new String JavaDoc(out.toByteArray(), charset);
258             return s;
259         }
260     }
261
262     public CharResponseWrapper(HttpServletResponse response)
263     {
264         super(response);
265     }
266
267     public String JavaDoc getString()
268         throws IOException
269     {
270         if (bout!=null) {
271         return bout.getString(getCharacterEncoding());
272         } else if (cout!=null) {
273         return cout.toString();
274         } else {
275             return "";
276         }
277     }
278
279     public PrintWriter getWriter()
280         throws IOException
281     {
282         if (debug) System.out.println("--> getWriter()");
283         if (bout!=null) throw new IllegalStateException JavaDoc("Already called getOutputStream");
284         if (cout==null) {
285         cout = new CharArrayWriter();
286         pout = new PrintWriter(cout);
287         }
288         return pout;
289     }
290
291     public ServletOutputStream getOutputStream()
292         throws IOException
293     {
294         if (debug) System.out.println("--> getOutputStream()");
295         if (cout!=null) throw new IllegalStateException JavaDoc("Already called getWriter");
296         if (bout==null) bout = new OutputStreamWrapper();
297         return bout;
298     }
299
300     public void flushBuffer()
301         {
302         if (debug) System.out.println("--> flushBuffer()");
303         }
304
305         public boolean isCommitted()
306         {
307         if (debug) System.out.println("--> isCommitted()");
308             return false;
309         }
310
311         public void reset()
312             throws IllegalStateException JavaDoc
313         {
314         if (debug) System.out.println("--> reset()");
315             cout=null;
316             bout=null;
317             super.reset();
318         }
319
320         public void resetBuffer()
321         {
322         if (debug) System.out.println("--> resetBuffer()");
323             cout=null;
324             bout=null;
325         }
326
327     public void sendError(int sc, String JavaDoc msg)
328         throws IOException
329     {
330         this.status=sc;
331         super.sendError(sc,msg);
332     }
333
334     public void sendError(int sc)
335         throws IOException
336     {
337         this.status=sc;
338         super.sendError(sc);
339     }
340
341     public void setStatus(int sc)
342     {
343         if (debug) System.out.println("--> setStatus("+sc+")");
344         this.status=sc;
345         super.setStatus(sc);
346     }
347
348     public void sendRedirect(String JavaDoc loc)
349         throws IOException
350     {
351         if (debug) System.out.println("--> sendRedirect(\""+loc+"\")");
352         this.status=302;
353         super.sendRedirect(loc);
354     }
355
356     public int getStatus()
357     {
358         return status;
359     }
360
361     public void setContentType(String JavaDoc type)
362     {
363         if (debug) System.out.println("--> setContentType(\""+type+"\")");
364
365             // Tomcat 4.0.4 and others in that series have a problem in that once the charset
366
// is set it can't be unset - and a charset following an "application/pdf" breaks IE.
367
// Here we strip if off before calling super and reinstate it later if the file isn't
368
// converted to PDF after all. Unfortunately Oracle Application Server (at least 10g)
369
// requires a charset to be set or it will throw an exception, so we have to test the
370
// server engine to see what to do.
371

372             this.ctype=type;
373             String JavaDoc server = getFilterConfig().getServletContext().getServerInfo();
374             if (type!=null && !server.startsWith("Oracle Application Server")) {
375                 int i = type.indexOf(';');
376                 if (i>0) type=type.substring(0,i);
377             }
378         super.setContentType(type);
379     }
380
381     public void setHeader(String JavaDoc name, String JavaDoc value)
382     {
383         if (debug) System.out.println("--> setHeader(\""+name+"\", \""+value+"\")");
384         if ("Content-Type".equalsIgnoreCase(name)) {
385         setContentType(value);
386         } else {
387         super.setHeader(name, value);
388         }
389     }
390
391     public void setLocale(Locale locale)
392     {
393         if (debug) System.out.println("--> setLocale("+locale+")");
394         super.setLocale(locale);
395     }
396
397     public String JavaDoc getCharacterEncoding()
398     {
399         String JavaDoc charset="";
400         if (ctype!=null && ctype.indexOf(";")>0 && ctype.indexOf("charset")>0) {
401             int pos = ctype.indexOf("charset")+8;
402         charset = ctype.substring(pos).trim();
403         if (charset.charAt(0)=='=') charset=charset.substring(1).trim();
404         if (charset.indexOf(";")>=0) charset=charset.substring(0,charset.indexOf(";")).trim();
405         }
406         if (debug) System.out.println("--> getCharacterEncoding() - this="+charset+" super="+super.getCharacterEncoding());
407         if (charset.length()==0) charset=super.getCharacterEncoding();
408         return charset;
409     }
410
411     public String JavaDoc getContentType()
412     {
413         if (debug) System.out.println("--> getContentType() --> \""+ctype+"\"");
414         return ctype;
415     }
416     }
417
418     // A class to pass the unknown Meta tags back to a context where
419
// we have a HttpServletResponse to use them
420
//
421
private class MetaCallback implements MetaHandler
422     {
423     private HttpServletRequest reader;
424     private HttpServletResponse writer;
425     private PDFFilter prox;
426
427         public MetaCallback(HttpServletRequest reader, HttpServletResponse writer, PDFFilter prox)
428     {
429         this.reader=reader;
430         this.writer=writer;
431         this.prox=prox;
432     }
433
434     // Whenever this is called, pass the request back to the servlet
435
// to handle it (so the method can be overridden).
436
//
437
public void handleMetaTag(String JavaDoc key, String JavaDoc val)
438         throws SAXException
439     {
440         try {
441         prox.metaTag(reader, writer, key, val);
442         } catch (Exception JavaDoc e) {
443             throw new SAXException(e);
444         }
445     }
446     }
447
448     /**
449      * Handle any meta tags that aren't recognised by the core Report Generator.
450      * This method recognises tags begining with <code>HTTP-</code>, as well as
451      * <code>Servlet-FileName</code>.
452      *
453      * @param request the Servlet request
454      * @param response the Servlet request
455      * @param name the "name" attribute from the meta tag
456      * @param value the "value" attribute from the meta tag
457      */

458     public void metaTag(HttpServletRequest reader, HttpServletResponse writer, String JavaDoc name, String JavaDoc value)
459         throws ServletException, IOException
460     {
461         if (name.startsWith("HTTP-")) {
462         writer.setHeader(name.substring(5), value);
463     } else if (name.equalsIgnoreCase("Servlet-FileName")) {
464         writer.setHeader("Content-Disposition", "attachment; filename=\""+value+"\"");
465     }
466     }
467
468     // SAX error handlers from here on
469

470     public void warning(SAXParseException exception)
471     throws SAXException
472     {
473     System.err.println("PDF WARNING"+(exception.getLineNumber()>=0 ? " AT "+exception.getSystemId()+" line "+exception.getLineNumber() : "")+": "+exception.getMessage());
474         throw exception;
475     }
476
477     public void error(SAXParseException exception)
478     throws SAXException
479     {
480     System.err.println("PDF ERROR"+(exception.getLineNumber()>=0 ? " AT "+exception.getSystemId()+" line "+exception.getLineNumber() : "")+": "+exception.getMessage());
481     throw exception;
482     }
483
484     public void fatalError(SAXParseException exception)
485     throws SAXException
486     {
487     System.err.println("PDF FATAL ERROR"+(exception.getLineNumber()>=0 ? " AT "+exception.getSystemId()+" line "+exception.getLineNumber() : "")+": "+exception.getMessage());
488     throw exception;
489     }
490 }
491
Popular Tags