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:
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:
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.
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:
- Parse and validate parameters
- Locate the specified backing bean and call the method to uptain the binary content
- Stream the result back to the browser using the specified parameters
<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
Thanks.
@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.