2

When I try to load my page including a primefaces media pdf the PDF is not loaded. I generate the PDF in my postconstruct and keep the streamedcontent in a seperate variable. In my JSF I call the getStream method that returns the streamedcontent.

JSF page:

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
      xmlns:h="http://xmlns.jcp.org/jsf/html"
      xmlns:c="http://java.sun.com/jsp/jstl/core"
      xmlns:p="http://primefaces.org/ui"
      xmlns:f="http://xmlns.jcp.org/jsf/core">
<ui:composition template="/templates/header.xhtml">
    <ui:define name="content">
        <f:metadata>
            <f:viewParam name="invoiceID" value="#{invoiceBean.invoiceID}"/>
        </f:metadata>
        <ui:param name="invoiceID" value="#{invoiceBean.invoiceID}"/>
        <h4 style="text-align: center;"><h:outputText
                value="#{msgs['invoice.thankYou']}"/></h4>
        <div class="card">
            <p:media value="#{invoiceBean.stream}" player="pdf" width="100%" height="800px">
                Your browser can't display pdf,
                <h:outputLink
                        value="#{invoiceBean.streamedContent}">click
                </h:outputLink>
                to download pdf instead.
            </p:media>
        </div>
    </ui:define>
</ui:composition>
</html>

Bean:

@Model
@Getter
@Setter
public class InvoiceBean {
    @Inject
    InvoiceService invoiceService;
    @Inject
    HttpServletRequest httpServletRequest;
    private Invoice invoice;
    private String invoiceID;
    private StreamedContent streamedContent;

    @PostConstruct
    public void initInvoice() {
        User user = getLoggedInUser();
        invoiceID = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap().get("invoiceID");
        invoice = invoiceService.getInvoice(Long.parseLong(invoiceID));
        PDFGenerator pdf = new PDFGenerator(invoice);
        streamedContent = pdf.getStreamedContent();
    }

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

        if (context.getCurrentPhaseId() == PhaseId.RENDER_RESPONSE) {
            return new DefaultStreamedContent();
        } else {
            return streamedContent;
        }
    }
}
Jasper de Vries
  • 19,370
  • 6
  • 64
  • 102
johnlofty
  • 21
  • 1
  • 2

1 Answers1

1

Because the stream is dynamic, this post from @BalusC is very useful to resolve this issue. In particular "never declare StreamedContent nor any InputStream or even UploadedFile as a bean property; only create it brand-new in the getter of a stateless @ApplicationScoped bean when the webbrowser actually requests the image content."

The p:media tag needs to disable the cache.

                <p:media value="#{pdfViewController.pdf}" player="pdf" cache="false"
                    width="100%" height="500px" >
                    Your browser can't display pdf.
                </p:media>
                <br/>
                <h:form>
                    <p:commandButton value="Download" icon="pi pi-arrow-down" ajax="false">
                        <p:fileDownload value="#{pdfViewController.pdfDownload}" />
                    </p:commandButton>
                </h:form>

The backing bean needs to do all the work in the getter, not in the PostConstruct method. (See the comments in @BalusC's post about this.) I was able to use both Request (per Showcase) and Session beans, but the PrimeFaces documentation warns against ViewScoped.

@Named
@RequestScoped
public class PdfViewController implements java.io.Serializable {
    public StreamedContent getPdf() {
        return DefaultStreamedContent.builder()
                .contentType("application/pdf")
                .stream(() -> makePDFStream())
                .build();
    }

    protected ByteArrayInputStream makePDFStream() {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        String dt = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss-n"));
        makePDF(baos, "This message was created at " + dt);
        return new ByteArrayInputStream(baos.toByteArray());
    }

    // use iText to create a PDF document "on the fly"
    protected void makePDF(OutputStream os, String message) {
        PdfDocument pdf = new PdfDocument(new PdfWriter(os));
        try (Document document = new Document(pdf)) {
            String line = "Hello! Welcome to iTextPdf";
            document.add(new Paragraph(line));
            document.add(new LineSeparator((new SolidLine())));
            PdfFont font = PdfFontFactory.createFont(StandardFonts.TIMES_ITALIC);
            Text msgText = new Text(message).setFont(font).setFontSize(10);
            document.add(new Paragraph().add(msgText));
        } catch (IOException ex) {
            LOG.log(Level.SEVERE, "PDF document creation error", ex);
        }
        // os is automatically written and closed when document is autoclosed
    }
}

In order to support the download option and because the bean is @request, the stream is recreated when needed. It was also necessary to include the .name("...)", whereas the p:media value= tag failed if name is specified, hence it needs a separate method.

    public StreamedContent getPdfDownload() {
        return DefaultStreamedContent.builder()
                .name("temp.pdf")
                .contentType("application/pdf")
                .stream(() -> makePDFStream())
                .build();
    }

Tested with PrimeFaces v8 and Wildfly v21 (w/Mojarra).

Brooksie
  • 361
  • 2
  • 5