Sun ONE Application Server 7, Enterprise Edition Application Design Guidelines for Storing Session State |
Chapter 2
J2EE ReferencesThis chapter discusses limitations when failover of a HTTP session state occurs and suggests solutions.
This chapter includes the following topics:
Problem Scenario for Stateful Session BeansConsider a cluster set up with two Sun ONE Application Server instances, AS1, and AS2. The application server is configured to persist HTTP session state to the session store. An application is deployed to both the instances, and the load balancer is configured to route requests for this application to the two instances. The cluster is servicing requests for this application. A web client starts a new session, SESS1. The load balancer assigns the SESS1 session to AS1.
When the first request arrives from the web client, the application creates a stateful bean and stores a reference to this stateful session bean as an attribute in the HttpSession object, so that it can be accessed on ensuing requests. At the end of the request, AS1 stores all session states for SESS1 into the session store, including these references.
Subsequently, AS1 becomes unavailable due to some unforeseen failure. The load balancer detects that AS1 is unavailable, and begins routing all requests for SESS1 to AS2. The reference fails to work in the new container because the referenced stateful session bean running in the AS1 EJB container is no longer available in the new AS2 execution environment for SESS1.
Figure 2-1 Problem Scenario
In the absence of such automatic mechanisms to handle proper reconstitution of references (of the type discussed earlier), it is possible to handle the problem in the application tier.
These guidelines do not cover more catastrophic failure scenarios such as the EIS backend becoming unreachable or the unavailability of the JMS provider, and so on. It is assumed that the services that these references are directed to, are available even after any failures in the application server tier.
The pattern discussed in this document illustrates how, with minimal modifications to an application, stateful session bean references that need to be reconstructed after the failover of a session from one server instance to another can be located and managed appropriately.
Note that there is minimum risk involved in implementing and using the solution presented in this document. If the solution is applied incorrectly, exceptions are transmitted to the client just as if the solution was not implemented at all.
Use of ServiceLocator for Factory ObjectsThe example code in this document uses the ServiceLocator pattern for accessing all factory objects (such as EJBHome objects). This pattern hides some of the complexity involved with looking up factory objects but its use is completely optional to the solution.
A brief summary of the ServiceLocator pattern is provided in the following section. For more information visit the following web site.
The ServiceLocator provides access to all the factory objects. Since these factory objects can be safely shared by multiple user sessions, the ServiceLocator may be implemented as a singleton (one ServiceLocator per classloader).
The ServiceLocator design pattern centralizes distributed service object lookups and point of control, and may act as a cache that eliminates redundant lookups. This pattern is used to cache all the factory objects, such as Home objects for enterprise beans.
The ServiceLocator class maintains a table of pairs (JNDI Names, and Home References). The class is used in the following manner:
ServiceLocator sl = ServiceLocator.getInstance(); // get Singleton
EJBLocalHome home = sl.getLocalHome("java:comp/env/ejb/Account");
When sl.getLocalHome() is called, the ServiceLocator checks the table to see if the corresponding Home reference already exists. If it does not, it creates it by using the supplied Java Naming and Directory Service (JNDI) name, and saves it in the table for subsequent use.
For more information on the implementation of ServiceLocator, see Appendix A, “Code Samples.”
General Rule for Service ObjectsMost application programmers take advantage of HttpSession to avoid performing repeated lookup and create calls. Since most of these references support concurrent use, the recommended practice is to store common references in the static storage of a front controller servlet.
For more information on using a front controller servlet, visit the following web site.
In the absence of a controller servlet the reference should be stored in a class member variable, or if the request spans multiple servlets, as an attribute of the HttpRequest. Specific examples are provided in relevant sections below.
EJB Reference TypesThis section includes the following topics:
Managing Stateless Session Bean References
Stateless session beans do not hold any state specific to the current client session, and hence they can be safely discarded and recreated any time. The references to these enterprise beans should not be stored directly in the HttpSession but instead should be stored in a class member variable as shown in the following code example:
HelloLocal hello;
public void jspInit() {
try {
InitialContext ic = new InitialContext();
Object obj = ic.lookup("java:comp/env/ejb/HelloLocal");
HelloLocalHome home = (HelloLocalHome) obj;
hello = home.create();
...
}
Managing Stateful Session Bean References
A stateful session bean's state must be persisted if you expect the bean to survive a session failover. However, Sun ONE Application Server does not support stateful session bean failover in this release. This section discusses issues and suggests alternatives for handling stateful session bean recovery.
New Applications
For new application development, avoid the use of stateful session beans. Simple Java objects or entity beans are recommended. The Java object solution is explained in "Recovering from Failover".
Existing Applications
Existing applications which use stateful session beans that are being ported to the Sun ONE Application Server will most likely need some level of code modifications. In non-failover situations, your application functions properly without code modifications.
Failover Behavior
Stateful session beans in the Sun ONE Application Server cannot survive a session failover. When a session failover occurs, your application may be prone to unexpected behavior or, null pointer exceptions. When an attempt is made to retrieve a stateful session bean from the session on its new server instance, a null is returned. In the worst case scenario, if you are assuming your reference to be valid in subsequent requests after it is created, null pointer exceptions are returned in a session failover situation. You have several options to address this issue, depending on your application requirements.
Detecting Failover
At the most basic level, you need to test for null after each fetch of a stateful session bean reference from the session.
if (session.getAttribute(COUNT_OBJECT_NAME) == null)
{
/* A failover has occurred. Determine how you want to proceed.
This is application dependent. Your options include recreating
the bean, notifying the user that a problem has occurred and they
need to start over, or both.*/
}
Recovering from Failover
If your application requires a more robust solution, it is recommended that you convert your stateful session beans to serializable Java objects and manage those objects in HttpSession. This is a straightforward exercise. As an example, start with a simple stateful session bean, Count. The following code is a bean class code:
public class CountBean public class CountBean
implements javax.ejb.SessionBean {
private javax.ejb.SessionContext context;
// The current counter is our conversational state.
public int val;
public int count() {
return ++val;
}
public void ejbCreate(int val) {
this.val = val;
}
}
To convert this class to a serializable Java object, simply place the ejbCreate method code into the Java object's constructor. Business methods such as count() can be copied, as- is, in the Java object. The results in this case appears as follows:
public class Count implements java.io.Serializable {
// The current counter is our conversational state.
public int val;
/** Creates a new instance of Count */
public Count(int val) {
this.val = val;
}
public int count() {
return ++val;
}
}
Create the Count object from the web client as follows:
count = new Count(iInitialValue);
session.setAttribute(COUNT_OBJECT_NAME, count);
On subsequent requests, you can comfortably retrieve the count object and use it as shown below:
Count count = (Count) session.getAttribute(COUNT_OBJECT_NAME);
int countVal = count.count();
However, there is an issue with this approach if you rely on the session synchronization interface inside your stateful session bean. HttpSession instances do not have a mechanism that allows them to receive transaction notifications. This means that any data that is modified in an HttpSession during a transaction is not reverted if the current transaction is rolled back.
Managing Entity Bean References
The entity bean references are correctly reconstructed when a session is recreated in a new container. For this reason, there is no change required to be made and the entity bean code works as-is.
Although entity bean references do not carry the state associated with a user's session, it may be desirable to work with an entity bean spanning multiple web requests. Instead of finding the bean at the start of every web request, the reference to the bean object can be stored in the session for quick and easy access for future requests. Since the entity bean's home object is stateless and supports concurrent use, it should be stored in a class member variable rather than the session.
ProductLocalHome sharedHome;
public void jspInit() {
try {
InitialContext ic = new InitialContext();
sharedHome = (ProductLocalHome)
ic.lookup("java:comp/env/ejb/MyProduct");...
}
// Now in the body of the JSP or Servlet:
Product product = sharedHome.findByPrimaryKey(productId);
session.setAttribute("productBean", product);
// Then on future requests, simply:
Product product = session.getAttribute("productBean");
Managing JMS ReferencesJava Messaging Service (JMS) provides two interfaces for administered objects: JMS ConnectionFactory and JMS Destination. Both these interfaces encapsulate JMS provider specific information configured at the container level. The JMS ConnectionFactory interface is used by a JMS client to create an authenticated, physical JMS Connection to the JMS provider's server. The JMS Destination interface is used by a JMS client to identify physical destinations (of either Topic or Queue domain) used by the JMS message service. The destinations, connection factories and the connections issued from the ConnectionFactory all support concurrent use and therefore may be used by simultaneous request processing threads in the web container. JMS client code in a web application ultimately access these administered objects through standard JNDI lookup code. For these reasons, the recommended practice is to use the ServiceLocator pattern. For more information see, Appendix A, “Code Samples.”
Another technique for using JMS administered references in a web application is to store common references in the static storage of a controller servlet.
For more information on using a front controller servlet, visit the following web site.
Using the javax.servlet.Servlet.init(javax.servlet.ServletConfig) method as a predictable singleton across a web application's life cycle, you may acquire the required JMS administered objects using ServiceLocator pattern and cache these in the static storage of the servlet. Because JMS ConnectionFactory and Destination may be used concurrently, multiple concurrent request threads in the web application may use these static references directly.
The application may reliably use the static references regardless of the web container instance in the Sun ONE Application Server cluster that a request is processed on.
It may be possible to set a JMS ConnectionFactory or Destination reference as a HttpSession attribute, but there is no value added in doing so.
The only other JMS interface which may be used concurrently is a JMS Connection. Connections are issued by a JMS ConnectionFactory. Although the JMS Connections may be used concurrently (shared by multiple concurrent requests), a JMS session created by the Connection does not support concurrent usage. The JMS sessions and the related interfaces that the session manages (including JMS messages, MessageConsumers, MessageProducers and transactions) are normally scoped to a single request in the web container. Individual requests of a web application normally use a short-lived JMS session lasting no longer than the scope of the request.
In most cases a web application that needs the functionality of sending a JMS message may just issue a common JMS Connection for all requests to share. With the thread and input/output resources required of a JMS Connection, and the ability of the JMS Connection to be shared naturally by concurrent requests, using a shared connection approach works well. For each logical JMS Connection that the application requires, a new connection is created from the correct ConnectionFactory. A proven approach for implementing this is to issue the shared connection during javax.servlet.Servlet.init(javax.servlet.ServletConfig) and then store the shared connection in public static storage of the controller Servlet. See the following code sample.
public class SampleServlet extends javax.servlet.http.HttpServlet
{
public SampleServlet() {
super();
}
private static javax.jms.QueueConnectionFactory
queueConnFactory;private static javax.jms.Queue queue;
private static javax.jms.QueueConnection sharedQueueConn;
public void init(javax.servlet.ServletConfig config)
throws javax.servlet.ServletException {
super.init(config);
try {
WebServiceLocator sl = WebServiceLocator.getInstance();
queueConnFactory = sl.getQueueConnectionFactory(
"java:comp/env/jms/FooQueueConnectionFactory");
queue = sl.getQueue("java:comp/env/jms/FooQueue");
sharedQueueConn =
queueConnFactory.createQueueConnection();}
catch(ServiceLocatorException sle) {
throw new javax.servlet.ServletException(
"Failure bootstrapping common references",sle);
}
catch(javax.jms.JMSException jmse) {
throw new javax.servlet.ServletException(
"Failure issuing common JMS connection",jmse);
}
}
}
Other Reference TypesThis section includes the following topics:
NamingContext
A NamingContext object should never be stored in the HttpSession. It should always be created by calling a new InitialContext().
JDBC Connections
In general, the JDBC connections should not be held beyond a single request. The recommended programming practice is to open and close the connection per Web request, or per method call for enterprise bean methods.
This is important because the application server manages the connections through a connection pool. Since the number of JDBC connections is a precious resource managed by the container, saving the connection for later use in the session precludes the possibility of its reuse by some other session.
Sometimes, this is done to iterate over a large result, set over multiple web requests. However, this is bad programming practice. Instead, convert the result set to a CachedRowSet and save it in HttpSession for subsequent use. While doing this consider setting the limits on the size of CachedRowSet so that it does not become too large.
UserTransaction References
A UserTransaction object should not be stored in the HttpSession.
According to the J2EE specifications, it is illegal to start a transaction in one Web request and commit (or roll-back) it in a subsequent Web request. Hence, the UserTransaction object should not be stored in the HttpSession and reused in a subsequent Web request to implement a long-running transaction. A UserTransaction object does not hold any session specific state. All the state corresponding to the currently active transaction is stored in the thread that is servicing the request. The application developers should consider using the getUserTransaction method provided by the ServiceLocator. The ServiceLocator object should never be stored in the session.
J2EE Connector References
J2EE Connector references are stateless references and should not be stored in HttpSession. However, they do support concurrent use and can be stored in a class member variable. The approach to be used here is identical to that used for stateless session beans.
File and Network References
If you are storing open files and network connections in HTTP sessions, then you need to declare these references as transient. This is something you need to do regardless of whether you are working with a distributed container; because even in a container that is not distributed these references are lost during activation/passivation since they are not serializable.
Prior to each use, test the reference. If the reference is null, you can assume that a passivation or failover has occurred and you must now re-establish the reference.
Impact of Code ChangesFuture versions of Sun ONE Application Server may provide more complete support for failover of J2EE applications. The code changes recommended in this document are not specific to Sun ONE Application Server. You should be able to run the code without modifying the code in future Sun ONE Application Server versions.