I'm trying to display image bytes which is saved in database as a StreamedContent
in the <p:graphicImage>
as follows:
<p:graphicImage value="#{item.imageF}" width="50" id="grpImage" height="80"/>
private StreamedContent content; // getter and setter public StreamedContent getImageF() { if (student.getImage() != null) { InputStream is = new ByteArrayInputStream(student.getImage()); System.out.println("Byte :"+student.getImage()); content = new DefaultStreamedContent(is, "", student.getStuID()); System.out.println("ddd ------------------------------- " + content); return content; } return content; }
This returns a blank image. How is this caused and how can I solve it?
The stdout prints the following:
INFO: Byte :[B@a2fb48 INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent@b0887b INFO: Byte :[B@a2fb48 INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent@1d06a92 INFO: Byte :[B@d52f0b INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent@39a60 INFO: Byte :[B@d52f0b INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent@8c3daa INFO: Byte :[B@124728a INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent@1dbe05b INFO: Byte :[B@124728a INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent@66a266 INFO: Byte :[B@a2fb48 INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent@1293976 INFO: Byte :[B@a2fb48 INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent@17b7399 INFO: Byte :[B@d52f0b INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent@1e245a5 INFO: Byte :[B@d52f0b INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent@4a7153 INFO: Byte :[B@124728a INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent@1561bfd INFO: Byte :[B@124728a INFO: ddd ------------------------------- org.primefaces.model.DefaultStreamedContent@47a8c2
The <p:graphicImage>
requires a special getter method. It will namely be invoked twice per generated image, each in a completely different HTTP request.
The first HTTP request, which has requested the HTML result of a JSF page, will invoke the getter for the first time in order to generate the HTML <img>
element with the right unique and auto-generated URL in the src
attribute which contains information about which bean and getter exactly should be invoked whenever the webbrowser is about to request the image. Note that the getter does at this moment not need to return the image's contents. It would not be used in any way as that's not how HTML works (images are not "inlined" in HTML output, but they are instead requested separately).
Once the webbrowser retrieves the HTML result as HTTP response, it will parse the HTML source in order to present the result visually to the enduser. Once the webbrowser encounters an <img>
element during parsing the HTML source, then it will send a brand new HTTP request on the URL as specified in its src
attribute in order to download the content of that image and embed it in the visual presentation. This will invoke the getter method for the second time which in turn should return the actual image content.
In your particular case PrimeFaces was apparently either unable to identify and invoke the getter in order to retrieve the actual image content, or the getter didn't return the expected image content. The usage of #{item}
variable name and the lot of calls in the log suggests that you were using it in an <ui:repeat>
or a <h:dataTable>
. Most likely the backing bean is request scoped and the datamodel isn't properly preserved during the request for the image and JSF won't be able to invoke the getter during the right iteration round. A view scoped bean would also not work as the JSF view state is nowhere available when the browser actually requests the image.
To solve this problem, your best bet is to rewrite the getter method as such so that it can be invoked on a per-request basis wherein you pass the unique image identifier as a <f:param>
instead of relying on some backing bean properties which may go "out of sync" during subsequent HTTP requests. It would make completely sense to use a separate application scoped managed bean for this which doesn't have any state. Moreover, an InputStream
can be read only once, not multiple times.
In other words: 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.
E.g.
<p:dataTable value="#{bean.students}" var="student"> <p:column> <p:graphicImage value="#{studentImages.image}"> <f:param name="studentId" value="#{student.id}" /> </p:graphicImage> </p:column> </p:dataTable>
Where the StudentImages
backing bean can look like this:
@Named // Or @ManagedBean @ApplicationScoped public class StudentImages { @EJB private StudentService service; public StreamedContent getImage() 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 image. Return a real StreamedContent with the image bytes. String studentId = context.getExternalContext().getRequestParameterMap().get("studentId"); Student student = studentService.find(Long.valueOf(studentId)); return new DefaultStreamedContent(new ByteArrayInputStream(student.getImage())); } } }
Please note that this is a very special case wherein performing business logic in a getter method is completely legit, considering how the <p:graphicImage>
works under the covers. Invoking business logic in getters is namely usually frowned upon, see also Why JSF calls getters multiple times. Don't use this special case as excuse for other standard (non-special) cases. Please also note that you can't make use of EL 2.2 feature of passing method arguments like so #{studentImages.image(student.id)}
because this argument won't end up in the image URL. Thus you really need to pass them as <f:param>
.
If you happen to use OmniFaces 2.0 or newer, then consider using its <o:graphicImage>
instead which can be used more intuitively, with an application scoped getter method directly delegating to the service method and supporting EL 2.2 method arguments.
Thus so:
<p:dataTable value="#{bean.students}" var="student"> <p:column> <o:graphicImage value="#{studentImages.getImage(student.id)}" /> </p:column> </p:dataTable>
With
@Named // Or @ManagedBean @ApplicationScoped public class StudentImages { @EJB private StudentService service; public byte[] getImage(Long studentId) { return studentService.find(studentId).getImage(); } }
See also the blog on the subject.
Try including a mime type. In your posted example, you have it as "". The blank image may be because it doesn't recognize the stream as a image file since you made that field an empty string. So add a mime type of image/png or image/jpg and see if that works:
String mimeType = "image/jpg"; StreamedContent file = new DefaultStreamedContent(bytes, mimeType, filename);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With