Wie stelle ich einen Dateidownload von einer JSF-Backing-Bean bereit?

Gibt es eine Möglichkeit, einen Dateidownload von einer JSF-Backing-Bean-Aktionsmethode bereitzustellen? Ich habe viele Dinge ausprobiert. Das Hauptproblem ist, dass ich nicht herausfinden kann, wie ich den OutputStream der Antwort bekomme, um den OutputStream zu schreiben. Ich weiß, wie man es mit einem Servlet , aber dies kann nicht von einem JSF-Formular aus aufgerufen werden und erfordert eine neue Anfrage.

Wie kann ich den OutputStream der Antwort vom aktuellen FacesContext ?

   

Einführung

Sie können alles über ExternalContext . In JSF 1.x können Sie das Raw-Objekt HttpServletResponse von ExternalContext#getResponse() . In JSF 2.x können Sie die Reihe neuer Delegate-Methoden wie ExternalContext#getResponseOutputStream() ohne die HttpServletResponse unter den JSF-Hauben HttpServletResponse .

In der Antwort sollten Sie den Content-Type Header festlegen, damit der Client weiß, welche Anwendung der bereitgestellten Datei zugeordnet werden soll. Und Sie sollten den Content-Length Header festlegen, damit der Client den Download-Fortschritt berechnen kann, andernfalls wird es unbekannt sein. Und Sie sollten den Content-Disposition Header auf attachment festlegen, wenn Sie ein Dialogfeld Speichern unter möchten, andernfalls versucht der Client, es inline anzuzeigen. Schreiben Sie schließlich den Dateiinhalt in den Antwort-Ausgabestream.

Am wichtigsten ist es, FacesContext#responseComplete() , um JSF mitzuteilen, dass es keine Navigation und kein Rendering durchführen soll, nachdem Sie die Datei in die Antwort geschrieben haben. Andernfalls wird das Ende der Antwort mit dem HTML-Inhalt der Seite verunreinigt. oder in älteren JSF-Versionen erhalten Sie eine IllegalStateException mit einer Nachricht wie getoutputstream() has already been called for this response wenn die JSF-Implementierung getWriter() , um HTML zu rendern.

Generisches JSF 2.x Beispiel

 public void download() throws IOException { FacesContext fc = FacesContext.getCurrentInstance(); ExternalContext ec = fc.getExternalContext(); ec.responseReset(); // Some JSF component library or some Filter might have set some headers in the buffer beforehand. We want to get rid of them, else it may collide. ec.setResponseContentType(contentType); // Check http://www.iana.org/assignments/media-types for all types. Use if necessary ExternalContext#getMimeType() for auto-detection based on filename. ec.setResponseContentLength(contentLength); // Set it with the file size. This header is optional. It will work if it's omitted, but the download progress will be unknown. ec.setResponseHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\""); // The Save As popup magic is done here. You can give it any file name you want, this only won't work in MSIE, it will use current request URL as file name instead. OutputStream output = ec.getResponseOutputStream(); // Now you can write the InputStream of the file to the above OutputStream the usual way. // ... fc.responseComplete(); // Important! Otherwise JSF will attempt to render the response which obviously will fail since it's already written with a file and closed. } 

Generisches JSF 1.x Beispiel

 public void download() throws IOException { FacesContext fc = FacesContext.getCurrentInstance(); HttpServletResponse response = (HttpServletResponse) fc.getExternalContext().getResponse(); response.reset(); // Some JSF component library or some Filter might have set some headers in the buffer beforehand. We want to get rid of them, else it may collide. response.setContentType(contentType); // Check http://www.iana.org/assignments/media-types for all types. Use if necessary ServletContext#getMimeType() for auto-detection based on filename. response.setContentLength(contentLength); // Set it with the file size. This header is optional. It will work if it's omitted, but the download progress will be unknown. response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\""); // The Save As popup magic is done here. You can give it any file name you want, this only won't work in MSIE, it will use current request URL as file name instead. OutputStream output = response.getOutputStream(); // Now you can write the InputStream of the file to the above OutputStream the usual way. // ... fc.responseComplete(); // Important! Otherwise JSF will attempt to render the response which obviously will fail since it's already written with a file and closed. } 

Beispiel für eine herkömmliche statische Datei

Falls Sie eine statische Datei vom Dateisystem des lokalen Datenträgers streamen müssen, ersetzen Sie den Code wie folgt:

 File file = new File("/path/to/file.ext"); String fileName = file.getName(); String contentType = ec.getMimeType(fileName); // JSF 1.x: ((ServletContext) ec.getContext()).getMimeType(fileName); int contentLength = (int) file.length(); // ... Files.copy(file.toPath(), output); 

Beispiel für eine gemeinsame dynamische Datei

Falls Sie eine dynamisch generierte Datei wie PDF oder XLS streamen müssen, geben Sie einfach dort eine output an, wo die verwendete API einen OutputStream erwartet.

ZB iText PDF:

 String fileName = "dynamic.pdf"; String contentType = "application/pdf"; // ... Document document = new Document(); PdfWriter writer = PdfWriter.getInstance(document, output); document.open(); // Build PDF content here. document.close(); 

ZB Apache POI HSSF:

 String fileName = "dynamic.xls"; String contentType = "application/vnd.ms-excel"; // ... HSSFWorkbook workbook = new HSSFWorkbook(); // Build XLS content here. workbook.write(output); workbook.close(); 

Beachten Sie, dass Sie die Inhaltslänge hier nicht festlegen können. Sie müssen also die Zeile entfernen, um die Länge des Antwortinhalts festzulegen. Dies ist technisch kein Problem, der einzige Nachteil ist, dass dem Endnutzer ein unbekannter Download-Fortschritt präsentiert wird. Falls dies wichtig ist, müssen Sie zuerst in eine lokale (temporäre) Datei schreiben und diese dann wie im vorherigen Kapitel beschrieben bereitstellen.

Schalten Sie Ajax aus!

Sie müssen nur sicherstellen, dass die Aktionsmethode nicht von einer Ajax-Anforderung aufgerufen wird, sondern von einer normalen Anforderung beim mit und . Ajax-Anfragen werden von JavaScript gehandhabt, was wiederum aus Sicherheitsgründen keine Möglichkeiten bietet, einen Dialog Speichern unter mit dem Inhalt der Ajax-Antwort zu erzwingen.

Sie zB PrimeFaces , müssen Sie sicherstellen, dass Sie explizit ajax über das Attribut ajax="false" deaktivieren. Wenn Sie ICefaces verwenden, müssen Sie ein in der Befehlskomponente verschachteln.

Dienstprogrammmethode

Wenn Sie die JSF-Dienstprogrammbibliothek OmniFaces verwenden , können Sie eine der drei praktischen Faces#sendFile() -Methoden verwenden, die entweder eine File oder einen InputStream oder ein byte[] und angeben, ob die Datei als heruntergeladen werden soll Anhang ( true ) oder Inline ( false ).

 public void download() throws IOException { Faces.sendFile(file, true); } 

Ja, dieser Code ist vollständig wie er ist. Sie müssen responseComplete() usw. nicht selbst aufrufen. Diese Methode behandelt auch IE-spezifische Header und UTF-8-Dateinamen. Sie können den Quellcode hier finden .

 public void download() throws IOException { File file = new File("file.txt"); FacesContext facesContext = FacesContext.getCurrentInstance(); HttpServletResponse response = (HttpServletResponse) facesContext.getExternalContext().getResponse(); response.reset(); response.setHeader("Content-Type", "application/octet-stream"); response.setHeader("Content-Disposition", "attachment;filename=file.txt"); OutputStream responseOutputStream = response.getOutputStream(); InputStream fileInputStream = new FileInputStream(file); byte[] bytesBuffer = new byte[2048]; int bytesRead; while ((bytesRead = fileInputStream.read(bytesBuffer)) > 0) { responseOutputStream.write(bytesBuffer, 0, bytesRead); } responseOutputStream.flush(); fileInputStream.close(); responseOutputStream.close(); facesContext.responseComplete(); } 

Das hat bei mir funktioniert:

 public void downloadFile(String filename) throws IOException { final FacesContext fc = FacesContext.getCurrentInstance(); final ExternalContext externalContext = fc.getExternalContext(); final File file = new File(filename); externalContext.responseReset(); externalContext.setResponseContentType(ContentType.APPLICATION_OCTET_STREAM.getMimeType()); externalContext.setResponseContentLength(Long.valueOf(file.lastModified()).intValue()); externalContext.setResponseHeader("Content-Disposition", "attachment;filename=" + file.getName()); final HttpServletResponse response = (HttpServletResponse) externalContext.getResponse(); FileInputStream input = new FileInputStream(file); byte[] buffer = new byte[1024]; final ServletOutputStream out = response.getOutputStream(); while ((input.read(buffer)) != -1) { out.write(buffer); } out.flush(); fc.responseComplete(); } 

Hier ist das komplette Code-Snippet http://bharatonjava.wordpress.com/2013/02/01/downloading-file-in-jsf-2/

  @ManagedBean(name = "formBean") @SessionScoped public class FormBean implements Serializable { private static final long serialVersionUID = 1L; /** * Download file. */ public void downloadFile() throws IOException { File file = new File("C:\\docs\\instructions.txt"); InputStream fis = new FileInputStream(file); byte[] buf = new byte[1024]; int offset = 0; int numRead = 0; while ((offset < buf.length) && ((numRead = fis.read(buf, offset, buf.length -offset)) >= 0)) { offset += numRead; } fis.close(); HttpServletResponse response = (HttpServletResponse) FacesContext.getCurrentInstance() .getExternalContext().getResponse(); response.setContentType("application/octet-stream"); response.setHeader("Content-Disposition", "attachment;filename=instructions.txt"); response.getOutputStream().write(buf); response.getOutputStream().flush(); response.getOutputStream().close(); FacesContext.getCurrentInstance().responseComplete(); } } 

Sie können die Dateileselogik ändern, wenn Sie möchten, dass die Datei zur Laufzeit generiert wird.