Downloading files from jsf
Hi
I have a database backed application, and I serve up the filenames/info to the view with a dataTable using a ListDataModel. The problem is how I can download the files that is click on in the table.
The thing Ive been doing so far, is in the selet(), method of the backing bean I get the File, put in the context map, and return a String "download". In the navigation "download" returns to a download.jsf page, witch is pure jsp and that starts the download (like the fileupload example in the MyFaces distribution from Apache).
Problem is that you have to click two times on the "download" link in the table, to get the download starting. Anyone why this happens? Or does anyone have any sugestions how to start a filedownload from a datatable in any way? (Without making a separate download servlet that just gets an id of the file, witch is not an option in this application, security issues).
[926 byte] By [
jomarala] at [2007-10-2 19:45:08]

This is how I handle it and I don't have the two click problem before the file downloads. However, I have noticed that it sometimes takes two clicks afterwards to recover from the event. I'm sure someone else could probably offer an improvement to this, but it's a start.
I have a separate class for File operations, listed here:
public class FileOperations {
public static synchronized void downloadFile(String filename, String fileLocation, String mimeType,
FacesContext facesContext) {
ExternalContext context = facesContext.getExternalContext();
String path = context.getInitParameter("externalFiles") + fileLocation;
String fullFileName = path + filename;
File file = new File(fullFileName);
HttpServletResponse response = (HttpServletResponse) context.getResponse();
response.setHeader("Content-Disposition", "attachment;filename=\"" + filename + "\"");
response.setContentLength((int) file.length());
response.setContentType(mimeType);
try {
FileInputStream in = new FileInputStream(file);
OutputStream out = response.getOutputStream();
// Copy the contents of the file to the output stream
byte[] buf = new byte[1024];
int count;
while ((count = in.read(buf)) >= 0) {
out.write(buf, 0, count);
}
in.close();
out.flush();
out.close();
facesContext.responseComplete();
} catch (IOException ex) {
System.out.println("Error in downloadFile: " + ex.getMessage());
ex.printStackTrace();
}
}
}
Then, in the page, I use a commandLink (in a dataTable or elsewhere) that calls the action method in the backing bean;
<h:commandLink value="Vendor Applications"
action="#{welcome.downloadVendorApp}" immediate="true"
onmouseover="self.status = 'Vendor Applications'; return true;"
onmouseout="self.status = ''; return true;"/>
/*The arguments for this method are: The file name, path to the file, the Mime type, and the FacesContext.
I'm using Shale and extending the AbstractViewController. That's where the getFacesContext() function comes from*/
public String downloadVendorApp() {
FileOperations.downloadFile("Vendor_Application_e_Form.doc", Constants.ROOT, Constants.MIME_DOC,
getFacesContext());
return null;
}
byte[] buf = new byte[1024];
I should do this as follows:
byte[] buf = new byte[file.length()];
This looks somewhat like my first try, this works, but as you say it sometimes takes two clicks afterwards to recover from the event. Or in my case, it always requires two clicks.
This is really annoying, and actually make it quite useless, at least at production level. We are allready having some second thoughts about our choice to go for JSF.
Message was edited by:
jomaral
Put the code in here.PS: JSF is a new tecnology. It's natural that there are some missing issues. And if you are lerning JSF and don't understant well the mecanisms of the framework it's natural to make some misstakes.
//File backer
public String selectDownload()
{
Fil valgt = (Fil) filModel.getRowData();
FilOperasjoner.downloadFile(valgt.getFilnavn(), valgt.getFiltype(),
valgt.getFilstorrelse().intValue(), valgt.getFildata(), false,
getFacesContext());
return null;
}
//fileTable.jsp
<h:dataTable value="#{filebaker.fileModel}" var="fil" border="0">
<h:column>
<f:facet name="header">
<h:outputText value="Filnavn"/>
</f:facet>
<h:outputText value="#{fil.filnavn}" />
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="Download />
</f:facet>
<h:commandButton image="/images/icon_download.gif" value=""action="#{filebacker.selectDownload}" immediate="true"/>
</h:column>
</h:dataTable>
FilOperasjoner.downloadFile is more or less the same as posted above.
public static synchronized void downloadFile(String filename, String mimeType, int length, byte[] data,
boolean open, FacesContext facesContext)
{
.. .. .
}
After downloading a file, JSF need two clicks to recover.
See this example:
byte r[];
GcotNotas gcotNotas = (GcotNotas) getModel().getRowData();
HttpServletResponse httpServletResponse = SessionHelper.getHttpServletResponse();
ServletOutputStream servletOutputStream = httpServletResponse.getOutputStream();
httpServletResponse.setHeader("Content-disposition", "attachment; filename=\"" + gcotNotas.getNomeFicheiro() + "\"");
httpServletResponse.setContentLength((int) gcotNotas.getDocumentAnexo().length());
httpServletResponse.setContentType("application/x-download");
/* Necessary because the user can do an insert and next try to
download in the consult. The getBytes method can be not
accessible if you insert and consult in the same session.
This way we avoid the Blob may not be manipulated from creating
session error.
see: http://forum.hibernate.org/viewtopic.php?t=933977&highlight=blob+may+manipulated+creating+createing+session */
r = new byte[(int) gcotNotas.getDocumentAnexo().length()];
gcotNotas.getDocumentAnexo().getBinaryStream().read(r, 0, (int) gcotNotas.getDocumentAnexo().length());
servletOutputStream.write(r);
FacesContext.getCurrentInstance().responseComplete();
This is more or less the same as I've tried before.
The main differnce is that i set the real mimetype to the
file instead of application/x-download.
We tried another aproach with the MyFaces fileupload example, with the same result. Another aproach with a actionlistener have been tried, same result. We are now just pointing file id's to a downloadservlet(without any security, everyone kan download everyones files) with a outputLink.