Friday, August 15, 2008

JSF and binary content

So I have been learning JSF and JPA at work recently. While I can't say those are the most exciting technologies to me, at least they appear to be (or become) de-facto standards and that means more leverage over the long term (something not always the case when dealing with Java technologies). Using JSF with Facelets can be a little challenging, the tools are only so-so and frankly it bugs me to have to type so much type-unsafe stuff in XML, EL and annotations. Anyway, what this blog entry is about is how you can bridge the gab between JSF page rendering and binary content delivery.

Corporate applications often need to deliver a lot of PDF content, and it is no different where I work. Static PDF documents as well as dynamic ones. Even if frames and iframes are generally frowned upon these days, I find then to be the best way to embed PDF documents inside the browser. However, with JSF you can't just make an xhtml page and expect to be able to stream binary content from it, since the HTTP protocol only allows you to send the header once. Indeed, JSF contains assertions to try and catch this and will complain:

java.lang.IllegalStateException: getWriter() has already been called for this response

You can always use an ActionListener or even an accessor on the backing bean if you just want a link to download a PDF, but embedding one inside a frame or iframe is an entirely different matter. I tried many things, most of them felt like fragile hacks. I will now present the best solution in my opinion, it's generic, it's KISS and it solves the problem.

Back to basics: HttpServlet

It turns out that it is possible, inside a processRequest method of a vanilla HttpServlet, to access session scoped backing beans. This means we just need some way of telling the Servlet about where it can uptain the binary data from and various other meta-data. So I came up with the following simple "API" using HTTP GET parameters:


The Servlet then has to do the following:
  1. Parse and validate parameters
  2. Locate the specified backing bean and call the method to uptain the binary content
  3. Stream the result back to the browser using the specified parameters
The complete source code for the Java Servlet that perform this job, you can get here. To use it, you would add it to your web.xml configuration and then simply call it from your JSF or Facelet files like this:

<iframe name ="frame" scrolling="auto" align="center"
src="BinaryJSFServlet?myBackingBean.createPDF;application/pdf" width="100%" height="700" >

This way, you can still keep your state and logic in the backing beans without having to mess around with JavaScript, ActionListeners or PhaseListeners. Just generate your URL in the *.jsp or *.xhtml file such that it specifies a backing bean, a method name to invoke as well as optional MIME type and file name.

Tim Boudreau mentions a cool technique the code above could make good use of, by relying on a general key/value parser based on dynamic proxies.
Post a Comment