Tuesday January 10, 2006
My brother has been visiting me this last couple of weeks.
It's been really nice and fun - see the picture on the right for my
recycling bin a couple of days ago...
Last night we took a look at an application he has developed using Creator 2 (EA2). It's a nice little web application for his company that lets customers download patches based on privileges, installed software, etc. It had some weird bugs. So I went looking in the forums, and sure enough, there's a real Gotcha! with implementing live file downloading. Several people had tried and the solution was not posted. So, a blog entry might be in order!
There are many use cases for live file downloading. (By "live file" I mean that you are not just providing a static hyperlink to a file you are just deploying with your web application - you instead want to provide the file to the browser when they click on something, but the contents of the file might be generated on the fly, or at least the actual file to be downloaded is determined at runtime).
For example, you may want to let users click to download and view a PDF report based on current data in the web application, or perhaps even use something like POI to generate Excel-formatted spreadsheet files.
The first thing you have to do is write an action handler. This is invoked when the user clicks the download link or button. Double click on the component to get a skeleton, then write something like this:
public String button1_action() {
String filename = "foo.pdf"; // Filename suggested in browser Save As dialog
String contentType = "application/pdf"; // For dialog, try application/x-download
byte[] data = ; // File contents to be written. Sorry, YOU have to do this part!
FacesContext fc = FacesContext.getCurrentInstance();
HttpServletResponse response = (HttpServletResponse)fc.getExternalContext().getResponse();
response.setHeader("Content-disposition", "attachment; filename=" + filename);
response.setContentLength(data.length);
response.setContentType(contentType);
ServletOutputStream out = response.getOutputStream();
out.write(data);
fc.responseComplete();
return null;
}
You may have been tempted to add the above code in the action handler for a hyperlink (or link action). That seems really natural - most "download links" on the web are just that - actual links. Indeed, that's what my brother had done.
And that's the gotcha. If you do that, the above code will work, but as soon as you've clicked the link to download, your web app starts to act funny - if you click on any other hyperlinks, the download will be initiated again! It's as if the above code "corrupts" the webapp.
The solution is really simple. Just use a download button instead of a link! If you hook the download code up to a button action handler, everything will work as expected. And having a button rather than a link does make some sense when what you're doing is asking for something to be generated (a live file) rather than simply referencing a static resource.
<speculation range="wild">
JSF jumps through some hoops to make HTML hyperlinks behave like
proper "actions" - via tricks like using an input hidden field
in the form etc. This might be what's causing the weird link
anomaly. I will check with the JSF guys to see if this has been addressed
in newer JSF releases.
</speculation>
Posted by Mounir MADRANE on January 11, 2006 at 03:39 AM PST #
I don't understand -- what is the problem with navigation? In the sample code above, your action handler returns null. That means "stay on the current page". If you have defined a navigation case "foo" (in the navigation editor), you can return "foo" from the action handler to navigate to the page pointed to by the "foo" case in the navigation editor, and so on.
I suspect I didn't understand your question correctly.
Posted by Tor Norbye on January 11, 2006 at 10:09 PM PST #
Posted by Mounir MADRANE on January 12, 2006 at 02:44 AM PST #
Posted by 192.18.42.11 on January 12, 2006 at 02:24 PM PST #