Creating downloadable files

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>

Comments:

Hello, I am trying to navigate after an export data from JSF (I use POI to generate Excel-formatted spreadsheet files "CSV"). For that I use response.getOutPutStream() which works good (the export works), but after this export, JSF lost the navigation (when I click on "h:commandLink" or "h:commandButton" to do other Action ...). I think that my data and my " <t:saveState id="xxxManagerBean" value="xxxManagerBean"> " are lost . code for export: //I use a download button : "h:commandButton immediate="true" action="#{myManagerBean.exportAction}" " //FunctionAction. public String exportAction() { FacesContext context = FacesContext.getCurrentInstance(); HttpServletResponse response = (HttpServletResponse)context.getExternalContext().getResponse(); response.setContentType("application/x-zip"); response.setContentType("Cache-Control", "no-store"); response.setContentType("Pragma", ""); response.setHeader("Content-disposition", "attachment; filename=file.zip"); try { exportExcel = new CSVExport(response.getOutputStream(), "nameFile.csv"); exportExcel.setCharSeparator(..); exportExcel.exportData("List" or "ResultSet"); exportExcel.closeFile(); response.getOutputStream().flush(); response.getOutputStream().close(); context.responseComplete(); } catch (IOException e) { e.printStackTrace(); } return null; } Perhaps, this problem is caused by "context.responseComplete();" or the saveState lost data when I work white response !!!. There is the same problem whith the "<h:commandLink target="myNewPage"/>", I lost my navigation. Thanks in Advance.

Posted by Mounir MADRANE on January 10, 2006 at 07:39 PM 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 02:09 PM PST #

Thanks for reply, In my JSF code, I have a tomahawk tag [t:saveState id="xxxManagerBean" value="xxxManagerBean"]. The export data functions correctly, but the navigation is lost because my data are lost after the export. I think that the tomahawk tag [t:saveState ... ] does not keep good the values after export. The same problem occurs as you use the attribute Target in [h:commandLink target="myNewPage"/>"]. after export, I lost my navigation. Thanks in Advance.

Posted by Mounir MADRANE on January 11, 2006 at 06:44 PM PST #

Sorry, I don't know anything about Tomahawk. You cannot always mix and match web frameworks -- JSF relies on redirects for navigation so if tomahawk is messing with state it could cause problems. It might be possible to make this work but I don't know anything about it - sorry.

Posted by guest on January 12, 2006 at 06:24 AM PST #

Post a Comment:
Comments are closed for this entry.
About

Tor Norbye

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today