Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Display dynamic image from database or remote source with p:graphicImage and StreamedContent

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 
like image 875
minhltnt Avatar asked Nov 21 '11 04:11

minhltnt


2 Answers

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.

like image 102
BalusC Avatar answered Oct 06 '22 01:10

BalusC


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);   
like image 43
rcheuk Avatar answered Oct 05 '22 23:10

rcheuk