2014-09-05 29 views
5

Jesteśmy strumieniowe plik binarny dla naszych użytkowników, zgodnie z procedurą opracowana w SO pytanie How to provide a file download from a JSF backing bean?Jak przesyłać strumieniowo plik do pobrania i wyświetlać komunikat twarzy JSF?

w ogóle workflow działa zgodnie z przeznaczeniem, ale podczas generowania pliku eksportu mogą wystąpić wydobywalne błędy i chcemy aby wyświetlić je jako ostrzeżenie dla użytkownika. Sam plik nadal będzie generowany w takim przypadku. Chcemy, aby ten eksport był kontynuowany, aby kontynuować wyświetlanie komunikatów.

Tylko po to, aby położyć nacisk na to: Tak, jest coś nie w porządku z danymi, ale nasi użytkownicy chcą, aby eksport kontynuował i otrzymywał ten wadliwy plik. Następnie chcą rzucić okiem na plik, skontaktować się z jego sprzedawcą i wysłać mu wiadomość o wadzie.

W każdym razie potrzebuję eksportu.

Ale to nie działa tak, jak tego chcemy. Stworzyłem uproszczony przykład ilustrujący nasze podejście.

Jako alternatywę rozważamy Groszek, który będzie przechowywać wiadomości i wyświetlać je po eksporcie. Prawdopodobnie istnieje sposób wbudowania mechanizmów JSF, aby to osiągnąć.

Controller

import java.io.ByteArrayInputStream; 
import java.io.IOException; 
import java.io.OutputStream; 
import javax.faces.application.FacesMessage; 
import javax.faces.bean.ManagedBean; 
import javax.faces.bean.RequestScoped; 
import javax.faces.context.ExternalContext; 
import javax.faces.context.FacesContext; 
import org.apache.tomcat.util.http.fileupload.util.Streams; 

@ManagedBean 
@RequestScoped 
public class ExportController { 

    public void export() { 
     FacesContext fc = FacesContext.getCurrentInstance(); 
     ExternalContext ec = fc.getExternalContext(); 

     byte[] exportContent = "Hy Buddys, thanks for the help!".getBytes(); 
     // here something bad happens that the user should know about 
     // but this message does not go out to the user 
     fc.addMessage(null, new FacesMessage("record 2 was flawed")); 

     ec.responseReset(); 
     ec.setResponseContentType("text/plain"); 
     ec.setResponseContentLength(exportContent.length); 
     String attachmentName = "attachment; filename=\"export.txt\""; 
     ec.setResponseHeader("Content-Disposition", attachmentName); 
     try { 
      OutputStream output = ec.getResponseOutputStream(); 
      Streams.copy(new ByteArrayInputStream(exportContent), output, false); 
     } catch (IOException ex) { 
      ex.printStackTrace(); 
     } 

     fc.responseComplete(); 
    } 
} 

JSF Page

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> 
<html xmlns="http://www.w3.org/1999/xhtml" 
     xmlns:h="http://java.sun.com/jsf/html" 
     xmlns:f="http://java.sun.com/jsf/core" 
     xmlns:ui="http://java.sun.com/jsf/facelets" 
     xmlns:p="http://primefaces.org/ui"> 

    <f:view contentType="text/html"> 
     <h:body> 
      <h:form prependId="false"> 
       <h:messages id="messages" /> 
       <h:commandButton id="download" value="Download" 
           actionListener="#{exportController.export()}" /> 
      </h:form> 
     </h:body> 
    </f:view> 
</html> 
+0

+1 dla prawidłowego przykładu SSCCE. –

+0

'org.apache.tomcat.util.http.fileupload.util.Streams' jest specyficzne dla Tomcat. – BalusC

Odpowiedz

6

Skoro faktycznie wykonujący odpowiedź pobierania pliku, a nie JSF jeden, to nie jest możliwe, aby wiadomość do dodania podczas gdy ma miejsce to samo żądanie. Najbardziej czystym rozwiązaniem dla mnie, unikając hacky asynchronicznych żądań jest użycie fasoli @ViewScoped i wykonanie zadania w dwóch etapach. Tak więc, aby mieć przycisk do przygotowania pliku, powiadamiania użytkownika później i pozwalając mu ściągnąć ją, gdy będzie gotowa:

@ManagedBean 
@ViewScoped 
public class ExportController implements Serializable { 

    private byte[] exportContent; 

    public boolean isReady() { 
     return exportContent != null; 
    } 

    public void export() { 
     FacesContext fc = FacesContext.getCurrentInstance(); 
     ExternalContext ec = fc.getExternalContext(); 
     ec.responseReset(); 
     ec.setResponseContentType("text/plain"); 
     ec.setResponseContentLength(exportContent.length); 
     String attachmentName = "attachment; filename=\"export.txt\""; 
     ec.setResponseHeader("Content-Disposition", attachmentName); 
     try { 
      OutputStream output = ec.getResponseOutputStream(); 
      Streams.copy(new ByteArrayInputStream(exportContent), output, false); 
     } catch (IOException ex) { 
      ex.printStackTrace(); 
     } 

     fc.responseComplete(); 
    } 

    public void prepareFile() { 
     exportContent = "Hy Buddys, thanks for the help!".getBytes(); 
     // here something bad happens that the user should know about 
     FacesContext.getCurrentInstance().addMessage(null, 
       new FacesMessage("record 2 was flawed")); 
    } 
} 
<html xmlns="http://www.w3.org/1999/xhtml" 
    xmlns:h="http://java.sun.com/jsf/html" 
    xmlns:f="http://java.sun.com/jsf/core" 
    xmlns:ui="http://java.sun.com/jsf/facelets" 
    xmlns:p="http://primefaces.org/ui"> 

<f:view contentType="text/html"> 
    <h:body> 
     <h:form> 
      <h:messages id="messages" /> 
      <h:commandButton value="Prepare" 
       action="#{exportController.prepareFile}" /> 
      <h:commandButton id="download" value="Download" 
       disabled="#{not exportController.ready}" 
       action="#{exportController.export()}" /> 
     </h:form> 
    </h:body> 
</f:view> 
</html> 

Uwaga rozwiązanie to może być ważne dla małych plików (cała ich zawartość jest przechowywane w pamięci, podczas gdy użytkownik trzyma w tym samym widoku). Jeśli jednak zamierzasz używać dużych plików (lub dużej liczby użytkowników), najlepiej przechowywać zawartość w pliku tymczasowym i wyświetlać link do niego zamiast przycisku pobierania. Tak właśnie sugeruje @BalusC w odnośniku poniżej.

Zobacz także:

+2

Możesz użyć JS, aby wywołać następną akcję. – BalusC

+0

@BalusC byłoby miło zobaczyć twoje rozwiązanie JS. Czy możesz zamieścić link? – douglaslps

-1

Możesz spróbować tego: Dla primefaces, można użyć polecenia zdalnego zamiast linku polecenia, a nazywają go z nazwy onSuccess. W przeciwnym razie podaj widget var do linku komend i wywołaj metodę click.

+0

Dodanie więcej opisu i przydatnych treści w celu udzielenia wsparcia byłoby pomocne.W przeciwnym razie zostanie to po prostu usunięte. –