21 October 2008

Sending binary data from Freemarker

There are probably many better alternatives to writing binary content with Freemarker: for example write your data to the filesystem and let apache serve it. But sometimes you have no choice and you need to serve your png or jpg or doc or pdf from a freemarker template.

The solution involves setting the content type, like when sending javascript from freemarker. Then we need an object having access to the data with a method to write it for us.

send-png-image.ftl
${res.setContentType("image/png")}${action.writeData(res.getOutputStream())}
The framework you are using should have injected the HttpResponse object into the Freemarker context. For example, Webwork/Struts2 creates a "res" variable which you can access from your Freemarker templates. The writeData implementation need not differ much from this:
SendImage.java
 ...

public void writeData(OutputStream os) throws IOException {
   os.write(this.getByteArray(this.image));
 }

...

It would be nice to simply write ${res.outputStream.write(byteArray)} in Freemarker, and dispense with the Java method - but Freemarker wraps the byte[] byteArray in a SequenceAdapter (ooh, a Design Pattern), and doesn't unwrap it again when we just want the byte array. So we need a writeData method or equivalent in java.

The same principle works for other kinds of binary content - photos, movies, documents, pdfs, etc - set the right content-type. If you want the browser to open a "Save As ..." dialog instead of displaying the content inline, add this after the content-type declaration:

${res.setHeader("Content-Disposition", "attachment; filename=${fileName}" )}

Special Note for writing Excel

Set your content-type as follows: ${res.setContentType("application/vnd.ms-excel; charset=UTF-8")}, and then write your data as a HTML table. That's all! Trivial! No POI, no hacks, no bridging to VB. The only thing you need worry about is escaping some of the data so Excel doesn't misinterpret it.

No comments:

Post a Comment