TOC PREV NEXT INDEX






 


 




Server-initiated Rendering API


One of the unique, powerful features of ICEfaces is the ability to trigger updates to the client's user interface based on dynamic state changes within the application. It is possible for the application to request updates for one or more clients based on a very simple low-level rendering API.

However, even though the low-level API is simple, it is fairly easy to use incorrectly, which can result in:

The purpose of the Server-initiated Rendering API is to provide an effective way for developers to leverage the power of the server-side rendering feature of ICEfaces, without exposure to any of the potential pitfalls of using the low-level rendering API.

PersistentFacesState.render()

At the most basic level, server-initiated rendering relies on the PersistentFacesState. Each client that interacts with an ICEfaces application can be referenced with a unique PersistentFacesState.

For example, if you had a request-scoped managed bean named User, you would do something like this:

public class User {
 

 
	private PersistentFacesState state;
 

 
	public User() {
 
		state = PersistentFacesState.getInstance();
 
	}
 
}
 

Once you have the reference to the PersistentFacesState, you can then use it to initiate a render call whenever the state of the application requires it. The method to use is executeAndRender():

	state.executeAndRender(); 
 

The executeAndRender() method runs all phases of the JSF lifecycle. When this is done, the ICEfaces framework detects any changes that should be sent to the client, packages them up, and sends them on their way. The PersistentFacesState also exposes separate execute() and render() methods but executeAndRender() is the recommended API to ensure PhaseListeners are executed for all phases. This is critical with some third party frameworks (such as, Seam).

Figure 13 below illustrates the use of the low-level render() API.

Figure 13 Low-level Server-initiated Rendering






Note: It is important to keep in mind that the ICEfaces framework synchronizes operations during a server-initiated render call to ensure that the server-side DOM remains uncorrupted. While a render is in progress, subsequent calls will block waiting for the current render pass to complete.
Rendering Considerations

The Server-initiated Rendering API is designed to avoid potential pitfalls that can occur when using the PersistentFacesState.executeAndRender() call. Specifically, the implementation addresses the following characteristics of dynamic server-initiated rendering.

Concurrency

It is highly recommended to only call the executeAndRender() method from a separate thread to avoid deadlock and other potential problems. For example, client updates can be induced from regular interaction with the user interface. This type of interaction goes through the normal JSF life cycle, including the render phase. Calling a server-initiated render from the same thread that is currently calling a render (based on user interaction) can lead to unexpected application behavior. The Server-initiated rendering implementation in ICEfaces uses a thread pool to address concurrency issues and to provide bounded thread usage in large-scale deployments.

Performance

Calling the executeAndRender() method is relatively expensive so you want to ensure that you only call it when required. This is an important consideration if your application can update its state in several different places. You may find yourself sprinkling render calls throughout the code. Done incorrectly, this can lead to render calls being queued up unnecessarily and more render calls executing than actually needed. The issue is compounded with the number of clients, as application state changes may require the executeAndRender() method to be called on multiple users-potentially all the currently active users of the application. In these cases, it is additionally imperative that only the minimum number of render calls be executed. The Server-initiated Rendering implementation in ICEfaces coalesces render requests to ensure that the minimum number of render calls are executed despite multiple concurrent render requests being generated in the application.

Scalability

Concurrency and performance issues both directly influence scalability. As mentioned, server-side render calls should be called in a separate thread within the web application. However, creating one or more separate threads for every potential user of the system can adversely affect scalability. As the number of users goes up, thread context switching can adversely affect performance. And since rendering is expensive, too many/frequent render calls can overwhelm the server CPU(s), reducing the number of users that your application can support. The Server-initiated Rendering implementation in ICEfaces uses a thread pool for rendering, bounds thread usage in the application, and facilitates application performance tuning for large-scale deployments.

Rendering Exceptions

Server-initiated rendering does not always succeed, and can fail for a variety of reasons including recoverable causes, such as a slow client failing to accept recent page updates, and unrecoverable causes, such as an attempt to render a view with no valid session. Rendering failure is reported to the application by the following exceptions:

RenderingException

The RenderingException exception is thrown whenever rendering does not succeed. In this state, the client has not received the recent set of updates, but may or may not be able to receive updates in the future. The application should consider different strategies for TransientRenderingException and FatalRenderingException subclasses.

TransientRenderingException

The TransientRenderingException exception is thrown whenever rendering does not succeed, but may succeed in the future. This is typically due to the client being heavily loaded or on a slow connection. In this state, the client will not be able to receive updates until it refreshes the page, and the application should consider a back-off strategy on rendering requests with the particular client.

FatalRenderingException

The FatalRenderingException exception is thrown whenever rendering does not succeed and is typically due to the client no longer being connected (such as the session being expired). In this state, the client will not be able to receive updates until it reconnects, and the server should clean up any resources associated with the particular client.

Server-initiated Rendering Architecture

The server-initiated rendering architecture is illustrated in Figure 14 below.

Figure 14 Group Renderers



The key elements of the architecture are:
Renderable
A request-scoped bean that implements the Renderable interface and associates the bean with a specific PersistentFacesState. Typically, there will be a single Renderable per client.
RenderManager
An application-scoped bean that manages all rendering requests through the RenderHub and a set of named GroupAsyncRenderers.
GroupAsyncRenderer
Supports rendering of a group of Renderables. GroupAsyncRenderers can support on-demand, interval, and delayed rendering of a group.

The following sections examine each of these elements in detail.

Renderable Interface

The Renderable interface is very simple:

public interface Renderable { 
 
    public PersistentFacesState getState(); 
 
    public void renderingException(RenderingException renderingException); 
 
} 
 

 

The typical usage is that a request-scoped or session-scoped managed-bean implements the Renderable interface and provides a getter for accessing the reference to the PersistentFacesState that was retrieved in the constructor. The general recommendation is to implement the Renderable implementation as a request-scoped bean if possible. If the bean is request-scoped, then the instance of PersistenceFacesState can be retrieved from the constructor. If the bean is session-scoped, then it's possible for the PersistentFacesState reference to change over the duration of the session. In this case, the state should be retrieved from the constructor as well as from any valid getter methods that appear on the relevant pages. This ensures that the reference to the state is always the current one.

Since the rendering is all done via a thread pool, the interface also defines a callback for any RenderingExceptions that occur during the render call. Modifying our earlier example of a User class, assuming it's request-scoped, it now looks like this:

public class User implements Renderable { 
 
	
 
	private PersistentFacesState state; 
 
	
 
	public User() { 
 
		state = PersistentFacesState.getInstance(); 
 
	} 
 
	
 
	public PersistentFacesState getState(){ 
 
		return state; 
 
	} 
 
	
 
	public void renderingException(RenderingException renderingException){ 
 
		//Logic for handling rendering exceptions can differ depending 
 
		//on the application. 
 
	} 
 
} 
 

 

Now that the User can be referred to as a Renderable, you can use instances of User with the RenderManager and/or the various implementations of GroupAsyncRenderers.

RenderManager Class

There should only be a single RenderManager per ICEfaces application. The easiest way to ensure this with JSF is to create an application-scoped, managed-bean in the faces-config.xml configuration file and pass the reference into one or more of your managed beans. To continue our example, you could create a RenderManager and provide a reference to each User by setting up the following in the
faces-config.xml file.

<managed-bean> 
 
	<managed-bean-name>renderMgr</managed-bean-name> 
 
	<managed-bean-class> 
 
		com.icesoft.faces.async.render.RenderManager
 
	</managed-bean-class> 
 
	<managed-bean-scope>application</managed-bean-scope> 
 
</managed-bean> 
 

 
<managed-bean> 
 
	<managed-bean-name>user</managed-bean-name> 
 
	<managed-bean-class>
 
		com.icesoft.app.User
 
	</managed-bean-class> 
 
	<managed-bean-scope>session</managed-bean-scope> 
 
	<managed-property> 
 
		<property-name>renderManager</property-name> 
 
		<value>#{renderMgr}</value> 
 
	</managed-property>
 
</managed-bean>
 

 

The User class needs a setter method to accommodate this:

public class User implements Renderable { 
 
	
 
	private PersistentFacesState state; 
 
	private RenderManager renderManager; 
 
	
 
	public User() { 
 
		state = PersistentFacesState.getInstance(); 
 
	} 
 
	
 
	public PersistentFacesState getState(){ 
 
		return state; 
 
	} 
 
	
 
	public void renderingException(RenderingException renderingException){ 
 
		//Logic for handling rendering exceptions can differ depending 
 
		//on the application. 
 
	} 
 
	
 
	public void setRenderManager( RenderManager renderManager ){ 
 
		this.renderManager = renderManager; 
 
	} 
 

 
} 
 

 

Once you have a reference to the RenderManager, you can request a render to be directly performed on instances that implement the Renderable interface.

renderManager.requestRender( aRenderable );
 

 
GroupAsyncRenderer Implementations

Being able to render individual users in a safe and scalable manner is useful for many types of applications. However, what if you want to request a render for a group or all users of an application? As an example, consider a chat application or a chat component in your application. There could be many users in the same chat group and any update to the chat transcript should result in all users getting notified.

To handle group rendering requests in a scalable fashion, the Rendering API provides implementations of the GroupAsyncRenderer base class. There are currently three implementations of GroupAsyncRenderer you can use. Each implementation allows you to add and remove Renderable instances from their collection.

The best way to get one of the GroupAsyncRenderer implementations is to use one of the methods provided by the RenderManager:

RenderManager.getOnDemandRenderer(String name); 
 
RenderManager.getIntervalRenderer(String name); 
 
RenderManager.getDelayRenderer(String name); 
 

 

As you can see, a GroupAsyncRenderer has a name, which the RenderManager uses to track each GroupAsyncRenderer so that each request using the unique name returns the same instance. That way, it is easy to get a reference to the same renderer from different parts of your application.

To expand our previous example of a User, we can augment our code to use a named GroupAsyncRenderer that can be called on demand when a stock update occurs. In the example code, we are using a fictitious stockEventListener method to listen for events that indicate a stock has changed. The trigger for this event should be from a thread outside the normal operation of the application. An EJB MessageBean receiving a JMS message would be a typical example.

When this event occurs, we'll call requestRender() on the GroupAsyncRenderer. Every user that is a member of that group will have a render call executed. We'll also add a member variable and getter for storing and retrieving the highest stock.

public class User implements Renderable { 
 
	
 
	private PersistentFacesState state; 
 
	private RenderManager renderManager; 
 
	private OnDemandRenderer stockGroup;
 
	private String highestStock; 
 
	
 
	public User() { 
 
		state = PersistentFacesState.getInstance(); 
 
	} 
 
	
 
	public PersistentFacesState getState(){ 
 
		return state; 
 
	} 
 
	
 
	public void renderingException(RenderingException renderingException){ 
 
		//Logic for handling rendering exceptions can differ depending 
 
		//on the application. Here if we have a problem, we'll just remove
 
		//our Renderable from the render group so that there are no further
 
		//attempts to render this user.
 
		stockGroup.remove(this); 
 
	} 
 
	
 
	public void setRenderManager( RenderManager renderManager ){ 
 
		this.renderManager = renderManager; 
 
		stockGroup = renderManager.getOnDemandRenderer( "stockGroup" );
 
		stockGroup.add(this); 
 
	} 
 

 
	public void stockEventListener( StockEvent event ){ 
 
		if( event instanceof StockValueChangedEvent ){
 
			highestStock = calculateHighestStock(); 
 
			stockGroup.requestRender(); 
 
		}
 
	} 
 
	
 
	public String getHighestStock(){
 
		return hightestStock;	
 
	}
 
}
 

 

As a final recommendation, in order to properly clean up Renderables and group renderers in your application, you should consider implementing the DisposableBean interface. For more details on implementing DisposableBeans, see The DisposableBean Interface. Continuing with our previous example, our UserBean would look like this:

public class User implements Renderable { 
 
	
 
	private PersistentFacesState state; 
 
	private RenderManager renderManager; 
 
	private OnDemandRenderer stockGroup;
 
	private String highestStock; 
 
	
 
	public User() { 
 
		state = PersistentFacesState.getInstance(); 
 
	} 
 
	
 
	public PersistentFacesState getState(){ 
 
		return state; 
 
	} 
 
	
 
	public void renderingException(RenderingException renderingException){ 
 
		//Logic for handling rendering exceptions can differ depending 
 
		//on the application. Here if we have a problem, we'll just remove
 
		//our Renderable from the render group so that there are no further
 
		//attempts to render this user.
 
		stockGroup.remove(this); 
 
	} 
 
	
 
	public void setRenderManager( RenderManager renderManager ){ 
 
		this.renderManager = renderManager; 
 
		stockGroup = renderManager.getOnDemandRenderer( "stockGroup" );
 
		stockGroup.add(this); 
 
	} 
 

 
	public void stockEventListener( StockEvent event ){ 
 
		if( event instanceof StockValueChangedEvent ){
 
			highestStock = calculateHighestStock(); 
 
			stockGroup.requestRender(); 
 
		}
 
	} 
 
	
 
	public String getHighestStock(){
 
		return hightestStock;	
 
	}
 
	
 
	public void dispose() throws Exception {
 
        stockGroup.remove(this);
 
    }
 

 
} 
 

 


Copyright 2005-2008. ICEsoft Technologies, Inc.
http://www.icesoft.com

TOC PREV NEXT INDEX