9

I'm using Primefaces

p:fileDownload

to download a file which is not in class path.
So I'm passing FileInputStream as parameter to DefaultStreamedContent.
Every thing works fine when my bean is kept at @SessionScoped...,
But

java.io.NotSerializableException: java.io.FileInputStream

is thrown when I keep my bean in @Viewscoped.

My Code:

DownloadBean.java

@ManagedBean
@ViewScoped
public class DownloadBean implements Serializable {

    private StreamedContent dFile;

    public StreamedContent getdFile() {
        return dFile;
    }

    public void setdFile(StreamedContent dFile) {
        this.dFile = dFile;
    }

    /**
     * This Method will be called when download link is clicked
     */
    public void downloadAction()
    {
        File tempFile = new File("C:/temp.txt");
        try {
            dFile = new DefaultStreamedContent(new FileInputStream(tempFile), new MimetypesFileTypeMap().getContentType(tempFile));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }

}

index.xhtml

<h:form>
    <h:commandLink action="#{downloadBean.downloadAction}">
        Download
        <p:fileDownload value="#{downloadBean.dFile}"/>
    </h:commandLink>
</h:form>

Isn't there any method to make it work?

Kishor Prakash
  • 8,011
  • 12
  • 61
  • 92

2 Answers2

15

The NotSerializableException is thrown because the view scope is represented by the JSF view state which can in turn be serialized to HTTP session in case of server side state saving or a HTML hidden input field in case of client side state saving. The FileInputStream can in no way be represented in a serialized form.

If you absolutely need to keep the bean view scoped, then you should not be declaring StreamedContent as an instance variable, but instead recreate it in the getter method. True, doing business logic in a getter method is usually frowned upon, but the StreamedContent is a rather special case. In the action method, you should then only prepare serializable variables which are later to be used during DefaultStreamedContent construction.

@ManagedBean
@ViewScoped
public class DownloadBean implements Serializable {

    private String path;
    private String contentType;

    public void downloadAction() {
        path = "C:/temp.txt";
        contentType = FacesContext.getCurrentInstance().getExternalContext().getMimeType(path);
    }

    public StreamedContent getdFile() throws IOException {
        return new DefaultStreamedContent(new FileInputStream(path), contentType);
    }

}

(note that I also fixed your way to get the content type; you have this way much more freedom to configure mime types via <mime-mapping> entries in web.xml)

The <p:graphicImage> has by the way exactly the same problem with StreamedContent. See also among others Display dynamic image from database with p:graphicImage and StreamedContent.

Community
  • 1
  • 1
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
0

@BalusC, for p:fileDownload, is there a way to offload the creation of the StreamedContent to another object which could then be called directly from JSF? Similar to the way you offload p:graphicImage here. If so what would be the scope of this special object? I'm guessing RequestScoped since there would be no connection between initDownload and getDownload. ApplicationScoped would not be able to keep track of all downloads within a single session, right? I also wonder if creating a new Apache FOP object in every Request is too expensive?

Here's an example:

jsf:

<h:commandButton value="print/download" action="#{streamhelper.initDownload()}">
                        <p:fileDownload value="#{streamhelper.download}"/>
                     <f:param name="html" value="#{bean.html}" />
                     <f:param name="idNum" value="#{bean.idNum}" />                         
                    </h:commandButton>

special object:

@Named("streamhelper") @RequestScoped @Getter @Setter @Slf4j
public class StreamedContentHelper
{
    @PostConstruct @SneakyThrows({NamingException.class})
    public void init(){
        fop = util.getLocator().getObject(util.getLocator().prependPortableName(FOPEngineImpl.class.getSimpleName()));
    }
    
    public void initDownload() throws Exception
    {
        FacesContext context = FacesContext.getCurrentInstance();

        log.trace("context PhaseID: {}", context.getCurrentPhaseId());
            
            String html = context.getExternalContext().getRequestParameterMap().get("html");            
            
            String idNum = context.getExternalContext().getRequestParameterMap().get("idNum");                                                            
                            
            byte[] attachBytes = fop.getPDFBytes(html);                
            InputStream stream = new ByteArrayInputStream(attachBytes);
            stream.mark(0); //remember to this position!
            String filename = String.format("%s-download.pdf", loadNum);
            download = new DefaultStreamedContent(stream, "application/pdf", filename);

    }        
    private StreamedContent download;
    private FOPEngineLocal fop;
    private @Inject Util util;
}
nettie
  • 628
  • 1
  • 11
  • 23