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.

JSF and PDF
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:


ServletName?BackingBeanName.MethodName[;ContentType][;FileName]


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" >
</iframe>




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.

Update:
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.

Comments

Anonymous said…
Sounds cool, but make sure it's airtight and parameters are sanitized and/or checked against an approved list of values so people can't access random files, invoke arbitrary bean methods and such. Keep up the good work!
Anonymous said…
Question: When you use this code, does it render the PDF in the HTML or does it spawn a save/open dialog. I get the latter when I need the former.

Thanks.
Casper Bang said…
@Anonymous1: Yes, sanitization is not covered. It's up to you to determine the dangers if any. The BinaryJSFServlet will only consume no-arg methods which returns a byte array on backingbeans which already has its context setup though.

@Anonymous2: It simply gets a hold of the raw PDF file. It is your browser (and its plugins) that determine what happens. If you specify a file name with BinaryJSFServlet, it will be sent as an attachment (usually spawning a save as dialog), if not, it will usually be opened in-line but capable browsers and plugins such as Firefox with Adobe Reader.

Popular posts from this blog

Oracle SQLDeveloper 4 on Debian/Ubuntu/Mint

Beware of SQLite and the Turkish Locale

Rejsekort Scanner