Primefaces P: Media Pdf Not Loading
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...
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=""
xmlns:ui=""
xmlns:h=""
xmlns:c=""
xmlns:p=""
xmlns:f="">
<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;
}
}
}
1 Answer
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).