Reputation: 1668
I'm using this code to download files from JSF page.
public String downloadFile(String fileName) {
try {
String reportPath = "/opt/download" + File.separator + selectedDownloadValue + File.separator + fileName;
FacesContext facesContext = FacesContext.getCurrentInstance();
HttpServletResponse response = (HttpServletResponse) facesContext.getExternalContext().getResponse();
// use this code if the package is located insight the WAR package
File file = new File(reportPath);
if (!file.exists()) {
response.sendError(HttpServletResponse.SC_NOT_FOUND, "No file " + reportPath);
}
int DEFAULT_BUFFER_SIZE = 10240;
response.setBufferSize(DEFAULT_BUFFER_SIZE);
response.setHeader("Content-Length", String.valueOf(file.length())); // Display file size during download
response.setHeader("Content-Type", "application/octet-stream");
response.setHeader("Content-Transfer-Encoding", "Binary");
response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
BufferedInputStream bIn = new BufferedInputStream(new FileInputStream(file));
int rLength = -1;
byte[] buffer = new byte[1000];
while ((rLength = bIn.read(buffer, 0, 100)) != -1) {
response.getOutputStream().write(buffer, 0, rLength);
}
FacesContext.getCurrentInstance().responseComplete();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
But for some reason the file that I download is with extension some_file.exe.xht
I tried to add additional headers but without result.
Maybe before download I need to get file extension? Is there any solution?
Upvotes: 0
Views: 1150
Reputation: 1108692
For best cross browser compatibility as to obtaining a download with the right filename, particularly if you'd like to cover Internet Explorer as well, the filename has to be the path name of the actual URL triggering the download. This will not work with JSF forms because they by default submit to the URL of the JSF page itself.
The best way to achieve that is to create a standalone file servlet which listens on a prefix URL pattern like /file/*
, /download/*
, etc, so that the filename can be supplied as path name of the URL. Based on your current JSF approach, below is how the standalone file servlet could look like, with here and there some improvements to reduce boilerplate and nonsense, and a bug fix as to 404 behavior:
@WebServlet("/download/*")
public class FileServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String fileName = request.getPathInfo().substring(1);
String selectedDownloadValue = request.getParameter("selectedDownloadValue");
String reportPath = "/opt/download/" + selectedDownloadValue + "/" + fileName;
File file = new File(reportPath);
if (!file.exists()) {
response.sendError(HttpServletResponse.SC_NOT_FOUND, "No file " + reportPath);
return;
}
response.setHeader("Content-Length", String.valueOf(file.length()));
response.setHeader("Content-Type", getServletContext().getMimeType(fileName));
response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
Files.copy(file.toPath(), response.getOutputStream());
}
}
No further configuration/change is necessary provided that you're already on minimally Java 7 and Java EE 6.
The above servlet is available by /download/filename.ext?selectedDownloadValue=foo
and will be provided in Save As as filename.ext
across all browsers, including Internet Explorer, who stubbornly ignores the filename in Content-Disposition
header and actually prefers the path name in URL as default filename.
Now, in order to invoke the above servlet from your JSF backing bean, simply do a redirect.
public void downloadFile(String fileName) throws IOException {
ExternalContext ec = FacesContext.getCurrentInstance().getExternalContext();
ec.redirect(ec.getRequestContextPath() + "/download/" + fileName + "?selectedDownloadValue=" + URLEncoder.encode(selectedDownloadValue, "UTF-8"));
}
If possible as per your business requirements (i.e. no JSF form/conversion/validation/*somemagic* necessary; all is just "static"), then you could even just link to it directly by a plain link or a GET form without the need for an intermediating JSF backing bean.
<h:outputLink value="#{request.contextPath}/download/#{bean.fileName}">
<f:param name="selectedDownloadValue" value="foo" />
Download
</h:outputLink>
<form action="#{request.contextPath}/download/#{bean.fileName}">
<input type="hidden" name="selectedDownloadValue" value="foo" />
<input type="submit" value="Download" />
</form>
Upvotes: 3