Sunday, 25 November 2012

Advanced PrimeFaces Graphic Image renderer for dynamic content

PrimeFaces has a component to render graphic resources and has added support for dynamic generated content.
There is a special StreamedContent class for this, so that you stream the generated resources from the JSF application to the browser.

But there are a few limitations for the component when it comes to dynamic generated content.  And they are not related to PrimeFaces but to the communication model between the browser and the application server.
Since there were lately a few questions in the PrimeFaces forum that had to do with these limitations, I decided to create an advanced renderer for the component. But there are a few warnings in using it.
For the impatient ones, you can find the project on GitHub.

How does the PrimeFaces Graphic Image renderer work?

The PrimeFaces Graphic Image renderer generates a html img tag where the src attribute points back to the application.  The URL contains a unique id and stores within the http session of the user, the EL expression linked to that unique id.
The EL expression comes from the value attribute of the p:graphicImage tag that the developer wrote in the JSF file and which points to the StreamedContent value that contains the dynamic generated content.
The browser interprets the html page and ask for the image content based on the URL found in the src attribute.

Since this URL points back to the JSF application, the PrimeFaces resource handler gets a chance to handle the request.  It recognize that the browser asks for a graphic resource with dynamic content based on the URL parts and parameters.
Based on the unique id which is available as parameter in the URL, it can take the corresponding EL and stream the content to the browser.

At this last step, the limitations can be hit in some use cases. Since the resource handler has no idea whatsoever of the page where the dynamic content is located, which bean is involved etc...  It can only rely on the EL expression the renderer has placed in the http session.  It is possible that this EL expression is only valid within a certain context.

The most striking example is the use of a composite component attribute.  Something like #{cc.attr.image} can be perfectly valid within the context of a custom composite component but it is clearly not when arbitrary executed.
The same reasoning goes for the dataTable component.  Whenever you use the alias defined in the var attribute for indicating the dynamic content, the PrimeFaces resource handler will be unable to retrieve the contents.
And the renderer can't determine the  EL expression that can give us the reference to the StreamedContent

Solution

The solution that I implemented is that we store the contents of the dynamic generated content on a location that can is always accessible, without the restriction of a context. This means we need to copy the contents to another location, the file system in our case.
The renderer copies the StreamedContent to a file in the System temp directory. For scaling reasons, this is a better alternative then storing the content directly in the http session area. We now only need to keep the file name next to the unique id instead of the EL expression.

On the other side, there is also another resource handler defined that uses the contents of the file to serve the browser request for the image resource.
The temporary files are removed when the http session is invalidated so that we don't have a problem with the disk storage size. We can't do it earlier because the browser can request the image multiple times as the user goes back in the screen flow or asks for a refresh.

Usage warnings

There is one issue with the above described solution, there is a performance drop.  It all depends on the size of the images of course but we need to write the content to disk, an additional step, and the resource handler serve it from disk which is slower then memory access.
For optimal performance, the storage on the disk is performed by using java.nio.channel.Channels.  That should guaranty to most efficient solution.

The clearing of the files is done when http session is invalidated by a HttpSessionBindingListener.  And although we can be pretty sure that the files gets removed, if the server implementation is correct, the files are kept during the user session.  When you have an application with a lot of dynamic generated content and the user works for a long time with your application, a lot of files can be created.

Intelligence of the advanced renderer

Therefore, there is some intelligence build into the Graphic Image renderer.  The scenario where the content is stored on disk is by default only used when the graphicImage tag is used within a custom composite component or a component that implements the UIData interface like dataTable.
These 2 cases are the situations where there is a lot of chance that the standard algorithm of PrimeFaces will fail. If this is not the case, the standard PrimeFaces functionality will be used (without the storage on disk).

Intelligent default functionality is fine but sometimes you need to take control yourself. This can be achieved by nesting a advancedRenderering tag within the graphicImage tag.  By specifying a boolean value for the value attribute, you can control the usage of the advanced rendering functionality.

Use cases where this can be handy.
1) You are using the graphicImage tag within a custom composite component but you as developer know that the EL you have specified can be used outside of the component (it is 'general' and not tied to a certain context) then you can disable the advanced rendering.
2) You have created the dynamic content with a bean on a request scope.  The resource handler can perfectly access the bean but in most cases the generated content is lost.  Here you can ask for the advanced rendering so that at render time, the image is stored on disk ready for streaming to the browser.

Code

The code can be found on GitHub and is released under the Apache License, Version 2.0.

1 comment:

  1. HI,

    thanks much for renderer. It helped me using streamed content in composite components.

    I added test for value == null into AdvancedGraphicImage Renderer.getImageSrc, as I am rendering not-yet initialized images in my component.

    ReplyDelete