PrimeFaces p:media not working with StreamedContent in a @ViewScoped bean

Cong Wang picture Cong Wang · Sep 25, 2013 · Viewed 11k times · Source

I have an issue related to Primefaces4 meida type rendering pdf file in browser. I have successfully tried the example in showcase from primefaces website. Now I want to get a new feature, which offers an tree structure with document nodes in the left panel. User could select one document to display it in the center panel. That means it generates pdf media field in backbean once user selects on one document on the tree.

related code is shown below:

backbean:

@ManagedBean
@ViewScoped
public class DocumentsBean implements Serializable {

private static final long serialVersionUID = 3560539268513760978L;
private TreeNode root;
private String url;
private TreeNode selectedNode; 
private StreamedContent media;

public DocumentsBean() {
    root = new DefaultTreeNode("Root");
}

public TreeNode getRoot() {
    return root;
}

public TreeNode getSelectedNode() {  
    return selectedNode;  
}  

public void setSelectedNode(TreeNode selectedNode) {  
    this.selectedNode = selectedNode;  
}  

public void onNodeSelect(NodeSelectEvent event) {  
    File file = (File) this.selectedNode.getData();
    generatePDF(file);
}

public String getUrl() {
    return url;
}

public void setUrl(String url) {
    this.url = url;
}

public void explore() {
    root = new DefaultTreeNode(new File(this.url), null);
    constructDir(root);
}

/**
 * construct directory and its sub files.
 * @param parent
 */
private void constructDir(TreeNode parent) {
    File file = (File) parent.getData();
    File[] files = file.listFiles();
    for (File f: files) {
        if (f.isFile()) {
            new DefaultTreeNode("document", f, parent);
        } else {
            TreeNode subParent = new DefaultTreeNode(f, parent);
            constructDir(subParent);
        }
    }

}

private void generatePDF(File file) {
    PDFGenerator generator = new PDFGenerator(file);
    File pdf = generator.transformToPDF();

    if (pdf != null) {
        InputStream stream = null;
        try {
            stream = new FileInputStream(pdf);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        media = new DefaultStreamedContent(stream, "application/pdf");
    }

}

public StreamedContent getMedia() {
    return media;
}

}

part of my view:

<p:layoutUnit position="west" size="300" header="Directory Content" resizable="false" collapsible="true">
            <h:form id="docTree_form">
                <p:growl id="messages" showDetail="true" /> 
                <p:tree id="docTree" value="#{documentsBean.root}" var="node" animate="true" selectionMode="single" selection="#{documentsBean.selectedNode}" dynamic="true" cache="true">

                    <p:ajax event="select" update=":pdf_form:media" listener="#{documentsBean.onNodeSelect}" />
                    <p:treeNode expandedIcon="ui-icon-folder-open" collapsedIcon="ui-icon-folder-collapsed">
                        <h:outputText value="#{node.name}" />
                    </p:treeNode>

                    <p:treeNode type="document" icon="ui-icon-document">
                        <h:outputText value="#{node.name}" />
                    </p:treeNode>
                </p:tree>
            </h:form>
        </p:layoutUnit>

        <p:layoutUnit position="center" header="Center" resizable="true">
            <h:form id="pdf_form">
                <p:media id="media" value="#{documentsBean.media}"     player="pdf" width="100%" height="700px">  
                    Your browser can't display pdf 
                </p:media>
            </h:form>
        </p:layoutUnit>

When I run this code, there is no error or exception. However, there is no PDF viewer generated in Firefox. Really strange !

a follow up question based on BalusC comments:

I got this exception when my app is running up:

SEVERE: Servlet.service() for servlet [Faces Servlet] in context with path     [/DocumentViewer_JSF] threw exception
java.lang.NullPointerException
at     org.primefaces.application.PrimeResourceHandler.handleResourceRequest(PrimeResourceHandler.java:114)

I found that this line causing this exception:

return new DefaultStreamedContent();

If I create a real pdf file, the exception is gone. But I really don't want a pdf file displayed if user has not chosen the file.

Answer

BalusC picture BalusC · Sep 25, 2013

Your concrete problem is caused because the webbrowser is actually downloading the PDF file in a physically completely separate HTTP request than the HTTP request which is generating and sending the HTML output based on JSF source code. You probably already know that view scoped beans are tied to a particular JSF view via javax.faces.ViewState hidden input field. If this get changed or is absent, then the request gets a new and different view scoped bean instance.

In other words, while the browser is downloading the PDF file from the server in a separate HTTP request, it isn't using the same @ViewScoped bean instance, but instead getting a brand new and completely independent instance which does not hold the same properties (state) as the one tied to the page and thus the whole StreamedContent is simply null at that point.

This problem with <p:media> and StreamedContent has essentially the same grounds as the problem with <p:graphicImage> and StreamedContent which is answered several times before:

In your particular case, you need to redesign the whole bunch in such way that the DocumentsBean backing bean stores the PDF file in some place (e.g. temp disk, server memory, database, etc) by an unique identifier and then pass exactly that unique identifier along as request parameter to <p:media> as follows:

<p:media value="#{mediaManager.stream}" width="100%" height="700px" player="pdf">
    <f:param name="id" value="#{documentsBean.mediaId}" />
</p:media>

Whereby the MediaManager backing bean look something like this:

@ManagedBean
@ApplicationScoped
public class MediaManager {

    @EJB
    private MediaService service;

    public StreamedContent getStream() throws IOException {
        FacesContext context = FacesContext.getCurrentInstance();

        if (context.getCurrentPhaseId() == PhaseId.RENDER_RESPONSE) {
            // So, we're rendering the HTML. Return a stub StreamedContent so that it will generate right URL.
            return new DefaultStreamedContent();
        } else {
            // So, browser is requesting the media. Return a real StreamedContent with the media bytes.
            String id = context.getExternalContext().getRequestParameterMap().get("id");
            Media media = service.find(Long.valueOf(id));
            return new DefaultStreamedContent(new ByteArrayInputStream(media.getBytes()));
        }
    }

}