TOC PREV NEXT INDEX

Server-initiated Rendering (Ajax Push) APIs


One of the unique and powerful features of ICEfaces is the ability to trigger updates to the client's user interface based on dynamic state changes within the application. Using APIs provided by the ICEfaces framework, it is possible for the application developer to request updates for one or more clients when the application state changes in a relevant way.

ICEfaces provides two different rendering APIs for application developers to choose from:

The two APIs serve slightly different needs. The RenderManager API, which has been a part of ICEfaces since its inception, is a powerful and flexible API that developers can use for fine-grained control of Ajax Push requests. The SessionRendering API, introduced in ICEfaces v1.8, is simpler and more focused on providing easy access to Ajax Push. Both APIs are built on top of the same rendering infrastructure allowing the developer to switch from one to the other with confidence as requirements change.

PersistentFacesState.render()

Before we discuss the RenderManager and SessionRenderer APIs in more detail, you will need to understand some lower-level concepts. 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 JSF managed bean called 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 life-cycle. 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 12 illustrates the use of the low-level render() API.

However, even though the low-level API appears simple, it is fairly easy to use incorrectly. This can result in one or more of the following:

The purpose of the RenderManager and SessionRenderer APIs 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.

Figure 12 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-side rendering APIs are 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.

SessionRenderer

The key motivation for the SessionRenderer API is simplicity - to make Ajax Push features as accessible and effortless as possible. Specifically, the key advantages for the application developer are:

The trade-off for the simplicity is a bit less flexibility. Specifically:

As a testament to its simplicity, the SessionRenderer API is small and self-explanatory:

SessionRenderer.addCurrentSession(String groupName); 
 
SessionRenderer.removeCurrentSession(String groupName); 
 
SessionRenderer.render(String groupName); 
 

The addCurrentSession and removeCurrentSession methods are used to manage group membership and leverage the user's session to determine the currently active view. Render groups are maintained using unique String names. A group can contain one or many different sessions and sessions can be in one or more groups. Calling the render method with the appropriate group name will render all the members of that group. For example, in the constructor of one of your backing beans you could add the current session to a named group:

SessionRenderer.addCurrentSession("chatRoomA");
 

Then, whenever an update relevant to that group needs to be pushed out, you would simply call:

SessionRenderer.render("chatRoomA"); 
 

You can also use a default group name for all active sessions that would update all registered sessions:

SessionRenderer.render(SessionRenderer.ALL_SESSIONS);
 

The work of cleaning up group membership when beans go out of scope or when exceptions are generated during a render pass is handled automatically.

RenderManager API

The RenderManager API for doing Ajax Push is suitable if your rendering needs are a bit more sophisticated or require some additional flexibility. When using this API, you can achieve these benefits:

Refer to Using ICEfaces in Clustered Environments for details.

Of course, with added features and control comes some additional complexity. The following sections give more of a background on the technical details of doing Ajax Push and how to use the RenderManager API for implementing Ajax Push in your application.

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 13.

Figure 13 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 is 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 is 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>request</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, DisposableBean { 
 
	
 
	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-2009. ICEsoft Technologies, Inc.
TOC PREV NEXT INDEX