Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Error in streaming dynamic resource. null

Important Notice : This issue has been fixed as of PrimeFaces 5.2 final (Community Release) released on April 8, 2015. As such if you happened to use that version or newer, you would not need to fiddle around with a temporary workaround.

The earlier given example can now safely be modified as follows.

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");
        byte[] bytes = Utils.isNumber(id) ? service.findImageById(Long.parseLong(id)) : null;
        return bytes == null ? null : new DefaultStreamedContent(new ByteArrayInputStream(bytes));
    }
}

I have moved images to the database (MySQL) in the form of BLOB (LONGBLOB) after I got tired of manipulating/managing images stored in the disk file system.

Accordingly, I'm displaying images in <p:dataTable> as follows (blatantly copied from here :) ).

<p:column headerText="Header">
    <p:graphicImage value="#{bannerBean.image}" height="200" width="200">
        <f:param name="id" value="#{row.bannerId}"/>
    </p:graphicImage>
<p:column>

The bean that retrieves images is as follows.

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

    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");
            byte[] bytes = service.findImageById(Long.parseLong(id));
            return bytes==null? new DefaultStreamedContent():new DefaultStreamedContent(new ByteArrayInputStream(bytes));
        }
    }
}

This works fine as long as there are images in each row of the underlying database table.

The BLOB type column in the database is however, optional in some cases and hence, it can contain null values as well.

If this column in any row/s in the database is null then, the following exception is thrown.

SEVERE:   Error in streaming dynamic resource. null
WARNING:   StandardWrapperValve[Faces Servlet]: Servlet.service() for servlet Faces Servlet threw exception
java.lang.NullPointerException
    at org.primefaces.application.PrimeResourceHandler.handleResourceRequest(PrimeResourceHandler.java:127)
    at javax.faces.application.ResourceHandlerWrapper.handleResourceRequest(ResourceHandlerWrapper.java:153)
    at javax.faces.webapp.FacesServlet.service(FacesServlet.java:643)
    at org.apache.catalina.core.StandardWrapper.service(StandardWrapper.java:1682)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:344)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:214)
    at org.primefaces.webapp.filter.FileUploadFilter.doFilter(FileUploadFilter.java:70)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:256)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:214)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:316)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:160)
    at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:734)
    at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:673)
    at com.sun.enterprise.web.WebPipeline.invoke(WebPipeline.java:99)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:174)
    at org.apache.catalina.connector.CoyoteAdapter.doService(CoyoteAdapter.java:357)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:260)
    at com.sun.enterprise.v3.services.impl.ContainerMapper.service(ContainerMapper.java:188)
    at org.glassfish.grizzly.http.server.HttpHandler.runService(HttpHandler.java:191)
    at org.glassfish.grizzly.http.server.HttpHandler.doHandle(HttpHandler.java:168)
    at org.glassfish.grizzly.http.server.HttpServerFilter.handleRead(HttpServerFilter.java:189)
    at org.glassfish.grizzly.filterchain.ExecutorResolver$9.execute(ExecutorResolver.java:119)
    at org.glassfish.grizzly.filterchain.DefaultFilterChain.executeFilter(DefaultFilterChain.java:288)
    at org.glassfish.grizzly.filterchain.DefaultFilterChain.executeChainPart(DefaultFilterChain.java:206)
    at org.glassfish.grizzly.filterchain.DefaultFilterChain.execute(DefaultFilterChain.java:136)
    at org.glassfish.grizzly.filterchain.DefaultFilterChain.process(DefaultFilterChain.java:114)
    at org.glassfish.grizzly.ProcessorExecutor.execute(ProcessorExecutor.java:77)
    at org.glassfish.grizzly.nio.transport.TCPNIOTransport.fireIOEvent(TCPNIOTransport.java:838)
    at org.glassfish.grizzly.strategies.AbstractIOStrategy.fireIOEvent(AbstractIOStrategy.java:113)
    at org.glassfish.grizzly.strategies.WorkerThreadIOStrategy.run0(WorkerThreadIOStrategy.java:115)
    at org.glassfish.grizzly.strategies.WorkerThreadIOStrategy.access$100(WorkerThreadIOStrategy.java:55)
    at org.glassfish.grizzly.strategies.WorkerThreadIOStrategy$WorkerThreadRunnable.run(WorkerThreadIOStrategy.java:135)
    at org.glassfish.grizzly.threadpool.AbstractThreadPool$Worker.doWork(AbstractThreadPool.java:564)
    at org.glassfish.grizzly.threadpool.AbstractThreadPool$Worker.run(AbstractThreadPool.java:544)
    at java.lang.Thread.run(Thread.java:722)

So how to manage null BLOBs so that this exception disappears?

Returning new DefaultStreamedContent(new ByteArrayInputStream(new byte[0])), in case, the byte array in the managed bean is null would suppress the exception but this after all, should not be a solution. Is this a desired solution?


The EJB method that returns a byte array though completely unnecessary, in this case.

public byte[] findImageById(Long id)
{
    CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
    CriteriaQuery<byte[]>criteriaQuery=criteriaBuilder.createQuery(byte[].class);
    Root<BannerImages> root = criteriaQuery.from(BannerImages.class);
    criteriaQuery.multiselect(root.get(BannerImages_.bannerImage));
    ParameterExpression<Long>parameterExpression=criteriaBuilder.parameter(Long.class);
    criteriaQuery.where(criteriaBuilder.equal(root.get(BannerImages_.bannerId), parameterExpression));
    List<byte[]> list = entityManager.createQuery(criteriaQuery).setParameter(parameterExpression, id).getResultList();
    return list!=null&&!list.isEmpty()?list.get(0):null;
}
like image 438
Tiny Avatar asked May 26 '14 15:05

Tiny


2 Answers

This is a bug (at least, a functional/technical design mistake) in PrimeResourceHandler. It shouldn't have assumed the dynamic resource or its content to be never null. It should have conditionally checked if that was the case and then simply have returned a HTTP 404 "Not Found" response.

In other words, instead of

85    streamedContent = (StreamedContent) ve.getValue(eLContext);
86 
87    externalContext.setResponseStatus(200);
88    externalContext.setResponseContentType(streamedContent.getContentType());

they should have done

85    streamedContent = (StreamedContent) ve.getValue(eLContext);
86 
87    if (streamedContent == null || streamedContent.getStream() == null) {
88        externalContext.responseSendError(HttpServletResponse.SC_NOT_FOUND, ((HttpServletRequest) externalContext.getRequest()).getRequestURI());
89        return;
90    }
91
92    externalContext.setResponseStatus(200);
93    externalContext.setResponseContentType(streamedContent.getContentType());

This way you can just return null or an empty StreamedContent from the getImage() method in order to generate a decent 404.

Well, what can you do?

  1. Report it to them and hope that they'll fix it. Update They fixed it in version 5.2.

  2. And/or, put a copy of PrimeResourceHandler class in the Java source folder of your webapp project, in exactly its own org.primefaces.application package and then just edit it to include the mentioned change and finally just build/deploy your webapp project as WAR as usual. Classes in /WEB-INF/classes have higher classloading precedence over those in JARs in /WEB-INF/lib, so the modified one will be used instead.

like image 52
BalusC Avatar answered Oct 19 '22 17:10

BalusC


I would suggest to include an attribute in the object the <p:dataTable> is iterating on to signify if an image exists. That way, there are no unnecessary (or null return) calls to BannerBean.getImage().

Example:

<p:column headerText="Header">
    <p:graphicImage value="#{bannerBean.image}" height="200" width="200"
            rendered="#{row.hasImage}">
        <f:param name="id" value="#{row.bannerId}"/>
    </p:graphicImage>
<p:column>

Another option is to grab the Primefaces source code, edit PrimeResourceHandler.java, and build it. (see the wiki)


An alternative solution would be to setup your own Serlvet to provide the images.

  • Main benefit is that browsers can cache the image (you can specify cache timeout if wanted).
  • Be aware of SQL Injection & other security attacks.
  • Attach some sort of login/permission check for additional security. (if needed)

Example:

<p:column headerText="Header">
    <p:graphicImage value="#{request.contextPath}/images/banner/?id=#{row.bannerId}"
            height="200" width="200" />
<p:column>
@WebServlet(name = "Retrieve Banner Images", urlPatterns = "/images/banner/*")
public class BannerImageServlet extends HttpServlet
{
    @EJB
    private final BannerBeanLocal service;

    protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
        String[] ids = req.getParameterValues("id");
        if(ids != null && ids.length == 1) {
            byte[] bytes = service.findImageById(Long.parseLong(ids[0]));
            if(bytes != null) {
                // see link #3 below
            }
        }
    }
}

Sources / useful links:

  1. #{request.contextPath}
  2. @WebServlet tutorial
  3. how to send byte stream back to the client
like image 25
srbs Avatar answered Oct 19 '22 17:10

srbs