Come fornire un download di file da un bean di supporto JSF?

Esiste un modo per fornire un download di file da un metodo di azione del bean backing JSF? Ho provato un sacco di cose. Il problema principale è che non riesco a capire come ottenere l’ OutputStream della risposta per scrivere il contenuto del file. So come farlo con un Servlet , ma questo non può essere invocato da un modulo JSF e richiede una nuova richiesta.

Come posso ottenere l’ OutputStream della risposta dall’attuale FacesContext ?

    introduzione

    Puoi ottenere tutto tramite ExternalContext . In JSF 1.x, è ansible ottenere l’object HttpServletResponse ExternalContext#getResponse() da ExternalContext#getResponse() . In JSF 2.x, è ansible utilizzare il gruppo di nuovi metodi delegate come ExternalContext#getResponseOutputStream() senza la necessità di afferrare HttpServletResponse da sotto i cappucci JSF.

    Sulla risposta, è necessario impostare l’intestazione Content-Type modo che il client sappia quale applicazione associare al file fornito. E, dovresti impostare l’intestazione Content-Length modo che il client possa calcolare l’avanzamento del download, altrimenti sarà sconosciuto. E, dovresti impostare l’intestazione Content-Disposition per l’ attachment se desideri una finestra di dialogo Salva con nome , altrimenti il ​​client tenterà di visualizzarlo in linea. Infine basta scrivere il contenuto del file sul stream di output della risposta.

    La parte più importante è chiamare FacesContext#responseComplete() per informare JSF che non dovrebbe eseguire la navigazione e il rendering dopo aver scritto il file nella risposta, altrimenti la fine della risposta sarà inquinata con il contenuto HTML della pagina, o nelle versioni JSF precedenti, si otterrà un IllegalStateException con un messaggio come getoutputstream() has already been called for this response quando l’implementazione JSF chiama getWriter() per il rendering HTML.

    Esempio JSF 2.x generico

     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. } 

    Esempio JSF 1.x generico

     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. } 

    Esempio di file statico comune

    Nel caso in cui sia necessario eseguire lo streaming di un file statico dal file system del disco locale, sostituire il codice come indicato di seguito:

     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); 

    Esempio di file dinamico comune

    Nel caso in cui sia necessario eseguire lo streaming di un file generato dynamicmente, come PDF o XLS, fornire semplicemente l’ output lì dove l’API utilizzata si aspetta un OutputStream .

    Ad esempio 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(); 

    Ad esempio 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(); 

    Si noti che non è ansible impostare la lunghezza del contenuto qui. Quindi è necessario rimuovere la linea per impostare la lunghezza del contenuto della risposta. Questo non è tecnicamente un problema, l’unico svantaggio è che l’utente finale presenterà un progresso di download sconosciuto. Nel caso in cui ciò sia importante, è necessario innanzitutto scrivere su un file locale (temporaneo) e quindi fornirlo come mostrato nel capitolo precedente.

    Distriggers ajax!

    Devi solo assicurarti che il metodo di azione non venga chiamato da una richiesta Ajax, ma che sia chiamato da una normale richiesta mentre fai fuoco con e . Le richieste Ajax sono gestite da JavaScript che a sua volta, a causa di motivi di sicurezza, non è stato in grado di forzare un dialogo Salva con il contenuto della risposta ajax.

    Nel caso in cui tu stia utilizzando, ad esempio PrimeFaces , devi assicurarti di distriggersre esplicitamente ajax tramite l’attributo ajax="false" . Nel caso in cui si utilizzi ICEfaces, è necessario nidificare nel componente di comando.

    Metodo di utilità

    Se si utilizza la libreria di utilità JSF OmniFaces , è ansible utilizzare uno dei tre metodi convenienti Faces#sendFile() utilizzando un File o un InputStream o un byte[] e specificando se il file deve essere scaricato come attachment ( true ) o inline ( false ).

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

    Sì, questo codice è completo così com’è. Non è necessario invocare responseComplete() e così via. Questo metodo tratta anche correttamente intestazioni specifiche di IE e nomi di file UTF-8. Puoi trovare il codice sorgente qui .

     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(); } 

    Questo è ciò che ha funzionato per me:

     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(); } 

    ecco lo snippet di codice completo 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(); } } 

    È ansible modificare la logica di lettura del file nel caso in cui si desideri generare il file in fase di runtime.