Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

File download using RichFaces

I got the following to work already:

  1. User can upload a file (i.e. a compressed archive)
  2. User can uncompress the file on the server
  3. User can execute some stuff on these files, which results in more files to be generated

Now I need to get step 4 to work:

  • User can download the files to his own computer again

Can anyone give me a hint? I tried to understand the stuff I found on Google, but it does not work quite as expected. Do I have to set a content type? When I set application/octet stream only txt and csv files would display correctly (in the browser, not as download popup as I wanted) other files would not work...

JSP:

<a4j:commandLink value="Download" action="#{appController.downloadFile}" rendered="#{!file.directory}">
   <f:param name="file" value="#{file.absoluteFilename}" />
</a4j:commandLink>

appController:

public String downloadFile() {
    String filename = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap().get("file");
    File file = new File(filename);
    HttpServletResponse response = (HttpServletResponse) FacesContext.getCurrentInstance().getExternalContext().getResponse();  

    writeOutContent(response, file, file.getName());

    FacesContext.getCurrentInstance().responseComplete();
    return null;
}

private void writeOutContent(final HttpServletResponse res, final File content, final String theFilename) {
    if (content == null) {
        return;
    }
    try {
        res.setHeader("Pragma", "no-cache");
        res.setDateHeader("Expires", 0);
        res.setHeader("Content-disposition", "attachment; filename=" + theFilename);
        FileInputStream fis = new FileInputStream(content);
        ServletOutputStream os = res.getOutputStream();
        int bt = fis.read();
        while (bt != -1) {
            os.write(bt);
            bt = fis.read();
        }
        os.flush();
        fis.close();
        os.close();
    } catch (final IOException ex) {
        Logger.getLogger(ApplicationController.class.getName()).log(Level.SEVERE, null, ex);
    }
}
like image 216
brandstaetter Avatar asked Feb 01 '12 16:02

brandstaetter


2 Answers

Your concrete problem is that you're attempting to download files by Ajax. This is not correct. JavaScript can't deal with binary responses nor has it any facilities to force a Save As dialogue. You need to make it a normal synchronous request instead so that it's the webbrowser itself who has to deal with it.

<h:commandLink value="Download" action="#{appController.downloadFile}" rendered="#{!file.directory}">
   <f:param name="file" value="#{file.absoluteFilename}" />
</h:commandLink>

As to setting the content type, if you have a file name with extension at your hands, you could use ServletContext#getMimeType() to resolve it based on <mime-mapping> in web.xml (either the server's default one or your webapp's one).

ServletContext servletContext = (ServletContext) externalContext.getContext();
String contentType = servletContext.getMimeType(file.getName());

if (contentType == null) {
    contentType = "application/octet-stream";
}

response.setContentType(contentType);
// ...

(note that I assume that you're using JSF 1.x, seeing the way how you obtained the servlet response, you could since JSF 2.x otherwise also use ExternalContext#getMimeType())

like image 169
BalusC Avatar answered Sep 22 '22 13:09

BalusC


I have done the step 4 some weeks ago, and let me give you some advices:

  1. Use a link html tag component. For this, I recommend the a4j:htmlCommandLink tag component (it's like the common h:commandLink with the difference that the <f:param /> components are always rendered, you can check more in the component documentation).

  2. If you don't know the type of the file to download (), then you must set the Content as application/octet-stream.

  3. After setting up the file to download to your response, you should set that the response has been completed.

I'll put my Backing Bean code for this request:

public void descargaArchivo() {
    //sorry but the programming standard says that we MUST declare
    //our variables at the beginning of any function =(
    HttpServletResponse objResponse;
    FileInputStream objFileInputStream;
    String strNombreCompletoArchivo, strNombreArchivo;
    byte[] arrDatosArchivo;
    try {
        //Here I get the <f:param> with the full name of the file. It encapsulates
        // the Faces.getCurrentInstance...  call.
        strNombreCompletoArchivo = UManejadorSesionWeb.obtieneParametro("nombreCompletoArchivo");
        //The function obtieneNombreArchivo retrieves the name of the file
        //based on the full name and the file separator (/ for Windows, \ for Linux)
        strNombreArchivo = UFuncionesGenerales.obtieneNombreArchivo(strNombreCompletoArchivo);
        //Getting the response from Faces.getCurrentInstance... 
        objResponse = UManejadorSesionWeb.obtieneHttpResponse();
        //Setting up the response content type and header (don't set the length!)
        objResponse.setContentType("application/octet-stream");
        objResponse.setHeader("Content-Disposition", "attachment; filename=\"" + strNombreArchivo + "\"");
        //Create the FileInputStream for the file to download
        objFileInputStream = new FileInputStream(strNombreCompletoArchivo);
        //Setting the file on the response
        arrDatosArchivo = new byte[UConstante.BUFFER_SIZE];
        while(objFileInputStream.read(arrDatosArchivo, 0, UConstante.BUFFER_SIZE) != -1) {
           objResponse.getOutputStream().write(arrDatosArchivo, 0, UConstante.BUFFER_SIZE);
        }
        objFileInputStream.close();
        objResponse.getOutputStream().flush();
        objResponse.getOutputStream().close();
        //Telling the framework that the response has been completed.
        FacesContext.getCurrentInstance().responseComplete();
    } catch (Exception objEx) {
        //manage the errors...
    }
}
//The constant used for byte array size
public class UConstante {
    public static final int BUFFER_SIZE = 2048;
}

The jsp fragment I used looks like this:

<a4j:htmlCommandLink  rendered="#{documento.rutaDestino != null}"
    action="#{documentoRequerido.descargaArchivo}">
    <f:param name="nombreCompletoArchivo" value="#{documento.rutaDestino}" />
    <h:graphicImage value="/Resource/iconos/mover-abajo.png" styleClass="pic" />
</a4j:htmlCommandLink>

Hope this helps you.

EDIT: I had some spare time, sorry but we are kinda busy in the project. The Java code was updated and tested in IE8, Firefox 9 and 10, and Chrome 16. For the value of buffer size constant, I did some research and found a good answer in this site.

P.S.: I don't take this as a competition, I just try to help people when I can. If my code is not good then thanks for letting me know it, that's the better way for everyone to grow better and healthy :).


EDIT: Thanks to @lisa

To achieve the following manually, just changing this part in the snippet

//strNombreCompletoArchivo = UManejadorSesionWeb.obtieneParametro("nombreCompletoArchivo");
String parameterStr = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap.get("nombreCompletoArchivo");
strNombreCompletoArchivo = parameterStr;
like image 25
Luiggi Mendoza Avatar answered Sep 20 '22 13:09

Luiggi Mendoza