Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

BLOB images are displayed only on refreshing a page after an update via p:dataTable is made in PrimeFaces

I'm displaying images stored in the form of BLOB in MySQL on a <p:graphicImage> as follows.

<p:dataTable var="row" value="#{testManagedBean}" lazy="true" editable="true" rows="10">
    <p:column headerText="id">
        <h:outputText value="#{row.brandId}"/>
    </p:column>

    <p:column headerText="Image">
        <p:cellEditor>
            <f:facet name="output">
                <p:graphicImage value="#{brandBean.image}" height="100" width="100">
                    <f:param name="id" value="#{row.brandId}"/>
                </p:graphicImage>
            </f:facet>
            <f:facet name="input">
                <p:graphicImage id="image" value="#{brandBean.image}" height="100" width="100">
                    <f:param name="id" value="#{row.brandId}"/>
                </p:graphicImage>
            </f:facet>
        </p:cellEditor>
    </p:column>

    <p:column headerText="Edit" width="50">
        <p:rowEditor/>
    </p:column>
</p:dataTable>

While editing of a row, a <p:fileUpload> is displayed on a <p:overlayPanel>. This and many other things are omitted in this example for the sake of simplicity as they are not related to the concrete problem.

The associated JSF managed bean:

@ManagedBean
@ViewScoped
public final class TestManagedBean extends LazyDataModel<Brand> implements Serializable
{
    @EJB
    private final TestBeanLocal service=null;
    private static final long serialVersionUID = 1L;

    @Override
    public List<Brand> load(int first, int pageSize, String sortField, SortOrder sortOrder, Map<String, Object> filters) {
        setRowCount(3);
        return service.getList();
    }
}

The bean that retrieves images from the database based on a unique row identifier - BrandBean.

@ManagedBean
@ApplicationScoped
public final class BrandBean
{
    @EJB
    private final BrandBeanLocal service=null;
    public BrandBean() {}

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

        if (context.getCurrentPhaseId() == PhaseId.RENDER_RESPONSE) {
            return new DefaultStreamedContent();
        }
        else {
            String id = context.getExternalContext().getRequestParameterMap().get("id");
            System.out.println("id = "+id);
            byte[] bytes = service.findImageById(Long.parseLong(id));
            return bytes==null? new DefaultStreamedContent(new ByteArrayInputStream(new byte[0])):new DefaultStreamedContent(new ByteArrayInputStream(bytes));
        }
    }
}

When a row is updated (after it is edited) by clicking a tick located (indicated by <p:rowEditor>) in the last column of the data table, the getImage() method in the BrandBean is invoked as it should.

This happens correctly in an application running on GlassFish server 4.0 using PrimeFaces 5.0 and JSF 2.2.6.

A new image will be displayed in the data table immediately after a row is updated in the data table (and consequently in the database).


There is another application running on Tomcat server 8.0.5 using Spring 4.0.0 GA in which the getImage() method is not invoked after a row held by the data table is updated resulting in still displaying the old image (not the newly updated one) in the data table (even though the changes are correctly propagated to the database).

The newly updated image is displayed only when the page refreshed by pressing F5 (on most browsers). It is even not displayed on page load (entering a URL into the address bar and then pressing the enter key).

In other words, when a row in a data table is updated by clicking the tick indicated by <p:rowEditor>, the getImage() method is not invoked (hence, the new image is not fetched from the database to be displayed on <p:graphicImage>). This method is invoked only when the page is refreshed/reloaded by pressing the F5 shortcut key.


Why does this happen? How to show a newly updated image immediately after a row is updated?

Superficially, this should neither be related to Spring nor JPA (the update operation is correctly propagated to the database after clicking the tick). This should rather be related to Tomcat server.

like image 548
Tiny Avatar asked May 30 '14 04:05

Tiny


1 Answers

The newly updated image is displayed only when the page refreshed by pressing F5 (on most browsers). It is even not displayed on page load (entering a URL into the address bar and then pressing the enter key).

The image is being cached by the webbrowser. Resources are cached on a per-URL basis via the cache-related instructions set in the response headers. Your concrete problem is caused because the resource URL is still the same and the webbrowser isn't aware that the resource has changed in the server side. The OmniFaces CacheControlFilter showcase page explains caching in detail (note: the filter is not the solution to this problem).

You basically need to force the webbrowser to re-request the resource by changing the URL. One of most common approaches for this kind of situation, whereby a cacheable resource is suddenly changed and its change needs to be immediately reflected to all clients, is appending the "last modified" timestamp of the image to the query string of the URL. Given that you're using JPA, so this should do:

  • Add a lastModified column to the brand table:

    ALTER TABLE brand ADD COLUMN lastModified TIMESTAMP DEFAULT now();
    
  • Extend the Brand entity with the appropriate property and a @PreUpdate which sets it:

    @Column @Temporal(TemporalType.TIMESTAMP)
    private Date lastModified;
    
    @PreUpdate
    public void onUpdate() {
        lastModified = new Date();
    }
    
    // +getter+setter
    

    (a @PreUpdate annotated method is invoked by JPA right before every UPDATE query)

  • Append it to the image URL (the parameter name v is a hint to "version"):

    <p:graphicImage value="#{brandBean.image}" ...>
        <f:param name="id" value="#{row.brandId}" />
        <f:param name="v" value="#{row.lastModified.time}" />
    </p:graphicImage>
    

    (I would here rename row to brand for clarity and brandId to id to deduplicate)

  • Finally, if you're using PrimeFaces 5.0 or newer, then you also need to disable server side caching:

    <p:graphicImage value="#{brandBean.image}" cache="false" ...>
    

Design notice: if the image is not necessarily updated on every update of Brand, then split it off to another table Image and let Brand have a FK (@ManyToOne or @OneToOne) to Image. This also makes the property "image" reusable across various entities in the webapp.

like image 200
BalusC Avatar answered Oct 21 '22 18:10

BalusC