13

I want to dynamically load images from a database withing a PrimeFaces data table. Code looks like as follows which is based on this PF forum topic:

<p:dataTable id="tablaInventario" var="inv" value="#{registrarPedidoController.inventarioList}" paginator="true" rows="10"
    selection="#{registrarPedidoController.inventarioSelected}" selectionMode="single"                                     
    update="tablaInventario tablaDetalle total totalDesc" dblClickSelect="false" paginatorPosition="bottom">
    <p:column sortBy="producto.codigo" filterBy="producto.codigo">
        <f:facet name="header">#{msg.codigo}</f:facet>
        #{inv.producto.codProducto}
    </p:column>                            
    <p:column>
        <f:facet name="header">Foto</f:facet>
        <p:graphicImage id="photo" value="#{registrarPedidoController.streamedImageById}" cache="FALSE">
            <f:param name="inv" value="#{inv.id}" />
        </p:graphicImage>                                
    </p:column>
</p:dataTable>

with

public StreamedContent getStreamedImageById() {
    DefaultStreamedContent image = null;
    String get = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap().get("inv");
    System.out.println("[Param]: " + get); // This prints null.
    Long id = new Long(get);
    List<Inventario> listInventarios = controladorRegistrarPedido.listInventarios();

    for (Inventario i : listInventarios) {
        if (i.getId().compareTo(id) == 0) {
            byte[] foto = i.getProducto().getFoto();
            image = new DefaultStreamedContent(new ByteArrayInputStream(foto), "image/png");
        }
    }

    return image;
}

However I can't get it work. My param is passing "null" to my backing bean. How is this caused and how can I solve it?

I am using Netbeans 6.9.1, JSF 2.0 and Primefaces 2.2.RC2.

I went on using BalusC first solution, it worked fine but images aren't being rendered in the UI. Exceptions Glassfish is throwing up:

WARNING: StandardWrapperValve[Faces Servlet]: PWC1406: Servlet.service() for servlet Faces Servlet threw exception
java.lang.NullPointerException
        at com.sun.faces.mgbean.BeanManager$ScopeManager$ViewScopeHandler.isInScope(BeanManager.java:552)

Well seems I get working thanks to BalusC. I've to used RequestScoped, SessionScoped or ApplicationScoped for managing the getStreamedImageId. However in the UI is always setting the default image (for the null cases) and not as expected the image that correspondes to each row. The new code is:

public StreamedContent streamedById(Long id) {
    DefaultStreamedContent image = null;

    System.out.println("[ID inventario]: " + id);

    List<Inventario> listInventarios = controladorRegistrarPedido.listInventarios();
    for (Inventario i : listInventarios) {
        if (i.getId().equals(id)) {
            byte[] foto = i.getProducto().getFoto();
            if (foto != null) {
                System.out.println("   [Foto]: " + foto);
                image = new DefaultStreamedContent(new ByteArrayInputStream(foto), "image/png");
                break;
            }
        }


    }
    if (image == null) {
        System.out.println("       [Image null]");
        byte[] foto = listInventarios.get(0).getProducto().getFoto();
        image = new DefaultStreamedContent(new ByteArrayInputStream(foto), "image/png");
    }

    System.out.println("   [Foto Streamed]: " + image);

    return image;

}
Santiago Agüero
  • 2,933
  • 7
  • 23
  • 36

3 Answers3

35

The <p:graphicImage> will call the getter method twice. First time is when the <img> element is to be rendered to HTML and thus requires an URL in the src attribute. If you just return new DefaultStreamedContent(), then it will autogenerate the right URL in src attribute. Second time is when the browser really requests the image, this is the moment when you should return the actual image.

So, the getter method should basically look like this:

public StreamedContent getStreamedImageById() {
    FacesContext context = FacesContext.getCurrentInstance();

    if (context.getCurrentPhaseId() == PhaseId.RENDER_RESPONSE) {
        // So, we're rendering the view. Return a stub StreamedContent so that it will generate right URL.
        return new DefaultStreamedContent();
    }
    else {
        // So, browser is requesting the image. Get ID value from actual request param.
        String id = context.getExternalContext().getRequestParameterMap().get("id");
        Image image = service.find(Long.valueOf(id));
        return new DefaultStreamedContent(new ByteArrayInputStream(image.getBytes()));
    }
}
BalusC
  • 992,635
  • 352
  • 3,478
  • 3,452
  • 3
    BalusC excellent answer with great quality. I went with the first solution, it's working the long parameter, and I'm getting the image from the database, however is not being rendered in the UI, I checked Glassfish log and some exceptions are present, any idea? (I added to my post.) – Santiago Agüero Nov 29 '11 at 02:51
  • 2
    I'd start with sharing the exceptions as they are usually already the complete answer at its own. The type, message and 1st line of stacktrace is usually sufficient information. – BalusC Nov 29 '11 at 02:52
  • Done, good advices, is it good to know that people like you make stackoverflow a great wiki community. Keep it up! – Santiago Agüero Nov 29 '11 at 03:16
  • 4
    The bean which is serving the `p:graphicImage` content needs to be request or session scoped (or even application scoped if it's stateless anyway). The view scope is not supported. You need to split the method from your view scoped bean into a separate bean in the proper scope. This has also the additional benefit that it's better reuseable elsewhere. – BalusC Nov 29 '11 at 03:19
  • 1
    Yes! That's a good point, I think I'm almost ready. Now exceptions are not being thrown, however images are not being rendered (however if I put a hardcoded 1 in registrarPedidoController.getStreamedImageById(1) in the UI it works (brings the same image of course). I see my log and there is a strange situation, there is a point where it is looking for id equal to 0 (when I don't have that in my database); should I managed this cases or null images? What should I return for those cases in getStreamedImageById method? – Santiago Agüero Nov 29 '11 at 04:02
  • Well, it happen to be I would have to managed the null images, and return a default one. However now I'm getting that image for all the cases (I mean, in the UI is only displaying that image!). I updated my post with the new getStreamedImageById method if it helps.. – Santiago Agüero Nov 29 '11 at 05:04
  • The primefaces implementation of graphicimage is kind of buggy. If you create some logging printouts you will see that your bean is called multiple times. The first times the parameter will be correct, but the last time (the one that matters) it will be null. What happens is basically that the images are fetched in an entirely new request, and at that time inv.id is not defined. This is the reason to use f:param in the first place, but that doesn't work properly as you've noticed. It's possible to make it work by also using an f:attribute tag in addition to the f:param one (one will be set). – MatsT Nov 29 '11 at 09:44
  • But in the end I think you're better off making a servlet that works by directly returning a StreamedContent object based upon an id passed directly in the url instead of via en ELExpression. – MatsT Nov 29 '11 at 09:46
  • Thanks for your advices MatsT. I'll look forward for improvements (maybe in Primefaces 3.x?) Cheers! – Santiago Agüero Nov 30 '11 at 22:38
  • Very good discussion but I dont understand why view scope is not supported. – vinay Oct 09 '14 at 14:35
  • @vinay: Simply because there's no means of a JSF view when the browser downloads the image. It's exactly like as when you enter image's URL straight in browser's address bar. The bean would then behave like request scoped. It's therefore pointless to declare the bean as view scoped. – BalusC Oct 09 '14 at 15:12
  • @BalusC Makes sense. Thank you for all your help. – vinay Oct 09 '14 at 15:49
  • Thanks very much @BalusC. I spent 2 days to overcome this problem – Stathis Andronikos Nov 26 '14 at 12:21
  • @BalusC I have similar problem. However, when I use your solution I only get a stub image if the page was not fully refreshed which does not always happen. Do you have any solution for that? – CodeSamurai777 Apr 22 '15 at 08:59
  • @Balusc What is service in the line Image image = service.find(Long.valueOf(id)); – subhakar patnala Jul 31 '15 at 12:16
  • @subhakarpatnala: just your local business service which finds the image by given ID in the datastore (DB, FS, other service, etc) and returns an entity with the image content as property. JSF absolutely doesn't care about how you do that, as long as it's capable of taking the given argument and returning the expected result. See also a.o http://stackoverflow.com/questions/13011392/jsf-service-layer – BalusC Jul 31 '15 at 12:38
  • @BalusC your suggestion works fine but when I press f5 the images dissapear – Pablo Aleman Mar 05 '19 at 02:02
3

images are saved in the database as byte[] if we save them through hibernate. I uploaded the images with <p:fileUpload... tag then I save the image alongwith other data values using hibernate.

On second page, I'm displaying the whole table data (of course with images) using

<p:dataTable  var="data" value="#{three.all}" ....

and dynamic Images using

<p:graphicImage   alt="image"  value="#{three.getImage(data)}" cache="false"  >
                <f:param id="image_id" name="image_id" value="#{data.number}" />
</p:graphicImage></p:dataTable>

Here "three" is the name of Backing Bean. In method getAll(), I'm retrieving data from table through hibernate and in the same method, I've created a HashMap<Integer, byte[]>. HashMap is an instance variable of the bean and The Bean is SessionScoped. I'm putting the images (which are in byte[] form) alongwith an integer image_id.

code:

    for (int i=0; i<utlst.size(); i++ ){
             images.put(utlst.get(i).getNumber(), utlst.get(i).getImage());}
//utlst is the object retrieved from database. number is user-id.

In the view getImage.xhtml, <p:graphicImage alt="image" value="#{three.getImage(data)}" cache="false" > it calls the method getImage(data /*I am passing current object of the list which is being iterated by*/ )

code of getImage:

public StreamedContent getImage(Util ut) throws IOException {
       //Util is the pojo

          String image_id = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap().get("image_id");
          System.out.println("image_id: " + image_id);

          if (image_id == null) {

                defaultImage=new DefaultStreamedContent(FacesContext.getCurrentInstance().getExternalContext().getResourceAsStream("/Capture.PNG"), "image/png");

            return defaultImage; 
        }


         image= new DefaultStreamedContent(new ByteArrayInputStream(images.get(Integer.valueOf(image_id))), "image/png");

        return image;
    }

just keep your dynamic Images with ids in A HashMap in the session then they will be correctly streamed.

Thanks & regards, Zeeshan

2

In PrimeFaces 3.2 the bug is still present. I do the workaround with

<p:graphicImage value="#{company.charting}">
    <f:param id="a" name="a" value="#{cc.attrs.a}" />
    <f:param id="b" name="b" value="#{cc.attrs.b}" />
</p:graphicImage>

and

ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext();
String a= externalContext.getRequestParameterMap().get("a");
String b= externalContext.getRequestParameterMap().get("b");

But even with this the bean is called 2 times. But in the second call variable a + b is filled ;-)

Damn bug

atamanroman
  • 10,631
  • 6
  • 51
  • 77
xyx
  • 83
  • 2
  • 8
  • thanks this was useful. In one of the request the parameter is null, and in the other one it's filled, so with only one attribute it's enough. just have to check at the begining that the param is not null. – damian May 09 '12 at 19:04