Como fornecer um download de arquivo de um bean de apoio do JSF?

Existe alguma maneira de fornecer um download de arquivo de um método de ação do bean de apoio do JSF? Eu tentei muitas coisas. O principal problema é que não consigo descobrir como obter o OutputStream da resposta para gravar o conteúdo do arquivo. Eu sei como fazer isso com um Servlet , mas isso não pode ser invocado a partir de um formulário JSF e requer uma nova solicitação.

Como posso obter o OutputStream da resposta do FacesContext atual?

Introdução

Você pode obter tudo através do ExternalContext . No JSF 1.x, você pode obter o object HttpServletResponse bruto por ExternalContext#getResponse() . No JSF 2.x, você pode usar o grupo de novos methods delegates como ExternalContext#getResponseOutputStream() sem a necessidade de pegar o HttpServletResponse sob os capôs ​​do JSF.

Na resposta, você deve definir o header Content-Type para que o cliente saiba qual aplicativo associar ao arquivo fornecido. E você deve definir o header Content-Length para que o cliente possa calcular o progresso do download, caso contrário, ele será desconhecido. E, você deve definir o header Content-Disposition para o attachment se você quiser um diálogo Salvar Como , caso contrário, o cliente tentará exibi-lo em linha. Por fim, basta gravar o conteúdo do arquivo no stream de saída de resposta.

A parte mais importante é chamar FacesContext#responseComplete() para informar ao JSF que ele não deve executar navegação e renderização depois de ter escrito o arquivo para a resposta, caso contrário, o final da resposta será poluído com o conteúdo HTML da página, ou em versões mais antigas do JSF, você obterá uma IllegalStateException com uma mensagem como getoutputstream() has already been called for this response quando a implementação do JSF chamar getWriter() para renderizar HTML.

Exemplo genérico do JSF 2.x

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

Exemplo genérico do JSF 1.x

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

Exemplo de arquivo estático comum

Caso você precise transmitir um arquivo estático do sistema de arquivos do disco local, substitua o código conforme abaixo:

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

Exemplo de arquivo dynamic comum

Caso você precise transmitir um arquivo gerado dinamicamente, como PDF ou XLS, simplesmente forneça a output onde a API sendo usada espera um OutputStream .

Por exemplo, 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(); 

Por exemplo, 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(); 

Note que você não pode definir o tamanho do conteúdo aqui. Então você precisa remover a linha para definir o tamanho do conteúdo da resposta. Isto tecnicamente não é problema, a única desvantagem é que o usuário final terá um progresso de download desconhecido. Caso isso seja importante, você precisará primeiro gravar em um arquivo local (temporário) e depois fornecê-lo como mostrado no capítulo anterior.

Desligue o ajax!

Você só precisa ter certeza de que o método de ação não é chamado por uma solicitação ajax, mas que é chamado por uma solicitação normal à medida que você triggers com e . As solicitações do Ajax são tratadas pelo JavaScript, que por sua vez, devido a razões de segurança, não possui resources para forçar um diálogo Salvar como com o conteúdo da resposta do ajax.

Caso você esteja usando, por exemplo, PrimeFaces , então você precisa ter certeza de que você explicitamente desativará ajax através do atributo ajax="false" . Caso você esteja usando ICEfaces, então você precisa aninhar um no componente de comando.

Método utilitário

Se você estiver usando a biblioteca de utilitários JSF OmniFaces , poderá usar um dos três methods convenientes Faces#sendFile() usando um File , ou um InputStream ou um byte[] , e especificando se o arquivo deve ser baixado como um anexo ( true ) ou inline ( false ).

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

Sim, este código está completo como está. Você não precisa invocar responseComplete() e assim por diante. Esse método também lida corretamente com headers específicos do IE e nomes de arquivos UTF-8. Você pode encontrar o código fonte aqui .

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

Isto é o que funcionou para mim:

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

aqui está o trecho de código 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(); } } 

Você pode alterar a lógica de leitura de arquivos, caso deseje que o arquivo seja gerado em tempo de execução.