[ Team LiB ] |
Working with Stateful Session BeansIn the previous section, we looked at the different aspects of writing and deploying a Stateless Session Bean. In this section, we look at the Stateful Session Bean. As we go along, you'll see that developing a Stateful Session Bean is very similar to developing its Stateless counterpart. Because of this, you'll find that this section refers you to sections of the Stateless Session Beans discussion wherever relevant. However, you must understand the conceptual differences between these two types of Session Beans. Unlike its Stateless counterpart, a Stateful Session Bean instance is associated with a client and cannot be shared across different clients. It represents a single client on the application server context. It acts as an extension of the client within the application server, and performs tasks on behalf of a single client. To achieve this, Stateful Session Beans can maintain conversational state, which is specific to a client. Also, Stateful Session Beans aren't pooled as Stateless Session Beans are. The container creates a new instance of a Stateful Session Bean whenever you request one. Having said that, you can use handles to share Stateful Session Beans, but that isn't often done in practice. For our case study, we must develop a shopping cart bean, which will enable the user to book seats on several flights and pay for them. This is a typical case in which Stateful Session Beans would come in handy. The state that's maintained in the bean will be the total amount that must be charged. Our bean will utilize the Stateless Session Bean that we already developed to perform the actual reservation task. Similar to Stateless Session Beans, Stateful Session Beans also have the following components:
Remember, you have to include a set of remote interfaces, local interfaces, or both in your bean deployment. The following sections describe these components in detail. The Remote InterfaceThe first interface we'll create is the remote interface. Unlike the AirlineReservation EJB, the AirlineShoppingCart EJB requires a set of remote interfaces because the clients of this bean might be accessing it remotely. This interface enables the client to communicate with the Stateful Bean. Using the same naming convention adopted earlier, we'll name our remote interface AirlineShoppingCartRemoteObject. To decide what methods this interface should provide, let's first identify how the client will use the shopping cart bean. As you might remember, the shopping cart bean will behave as an extension to the client. The client will first instantiate the bean and search for the flights using business methods provided by the bean. It will subsequently use setters in the bean to set the flight number and the number of seats required. Following this, the client will reserve the seats by invoking the reserveSeats method on the bean. The client will repeat this set of operations for as many flights the user desires to book seats on. Upon completion, the client will request the bean—the total amount that should be charged to the client. This gives us all the information we need to create the remote interface. We'll extend this interface from the javax.ejb.EJBObject interface. The code snippet follows: import java.rmi.RemoteException; import java.util.Properties; import javax.ejb.EJBObject; public interface AirlineShoppingCartRemoteObject extends EJBObject { public void setOrigin( String origin ) throws RemoteException ; public void setDestination( String destination ) throws RemoteException ; public void setFlightNumber( int flightNum ) throws RemoteException ; public void setNumSeats( int numSeats ) throws RemoteException ; public int[] searchFlights() throws RemoteException, AirlineShoppingCartException; public void reserveSeats() throws RemoteException, AirlineShoppingCartException; public Properties getFlightInfo() throws RemoteException, AirlineShoppingCartException; public double getTotalCost() throws RemoteException; } This tells us how the client will invoke the shopping cart bean. As is already obvious, the Stateful Bean does not require that client-specific information be always passed into each business method. It enables us to store client-specific state; therefore, we can have setter methods set the relevant values in the bean before invoking the business methods. Note that, in this case, the client cannot invoke the business methods (namely searchFlights, reserveSeats or getFlightInfo) without invoking the setters. In other words, our business methods do not work on their own; they expect other methods to have been invoked before the business method has been invoked. Is this the only correct way to code a Stateful Session Bean? Certainly not. This bean was coded this way only to demonstrate this capability. In certain cases, this mechanism offers many advantages. For instance, assume that output from one business method drives the inputs to the next business method invoked by the client. For instance, output from the getFlightInfo might drive the input to the reserveSeats method. This means that the only information that the client has to provide is the number of seats. The discussion about serializable objects for parameters and return types also holds true for Stateful Session Beans. Parameters to the methods and return values must be serializable so that they can be sent over the wire. Also, all methods in this interface throw RemoteException to satisfy the requirement for RMI communication. You can read more about the types of exceptions that can be thrown by business methods in the "More About Exceptions" section later in this chapter. The remote interface extends from the EJBObject interface. Several methods of the EJBObject interface hold similar meanings when invoked in the context of a Stateful Session Bean, as they do with Stateless Session Beans. However, there are a few differences as described in the following list:
The Remote Home InterfaceThe remote home interface is very similar to the remote home interface of a Stateless Session Bean. The remote home interface is a factory for the remote object, and it extends from the javax.ejb.EJBHome interface. However, unlike a Stateless Session Bean, the remote home interface for a Stateful Session Bean can contain more than one create method. This is because containers do not maintain pools of Stateful Session Beans. These bean instances are created whenever a client invokes the create method on the home instance. Therefore, invoking the create method translates into a corresponding ejbCreate method on the bean class. Because a Stateful Session Bean instance is always associated with a client, the client can choose how the bean instance has to be created, which is unlike a Stateless Session Bean. Each of the create methods returns the remote interface of the bean. You can even have suffixes for the create methods, thus providing more meaning to the method name. For instance, suppose that we enhance the functionality of our application to support bookings for platinum customers. To differentiate the types of customers, we can either add a Boolean value and invoke the appropriate setter, or alternatively, add another create method called createCartForPlatinumCustomer to make it more obvious. The remote home interface extends from the javax.ejb.EJBHome interface, just like Stateless Session Beans. For the sake of our shopping cart bean, let's produce two create methods: one that doesn't take any parameters, and a second that takes the origin and destination airports. The second isn't necessary in our case because the bean provides setters, which can be invoked to achieve the same result, but it will help to illustrate the point. The following code snippet illustrates the remote home interface: import javax.ejb.EJBObject; import java.rmi.RemoteException; public interface AirlineShoppingCartRemoteHome extends EJBObject { public AirlineShoppingCartRemoteObject create() throws RemoteException, CreateException; public AirlineShoppingCartRemoteObject create( String origin, String destination ) throws RemoteException, CreateException; } As before, our create methods throw exceptions of type RemoteException and CreateException. The bean usually uses the CreateException to indicate to the client that an error occurred while creating the instance. RemoteExceptions are typically used by the underlying RMI implementation to indicate whether any failure occurs while accessing the bean. Note that the create methods are free to throw more application exceptions. The Local Interface and Local Home InterfaceNext, we create a set of local interfaces for the AirlineShoppingCart EJB. We don't discuss this in detail, but it's sufficient for you to understand that the concepts for creating local interfaces are very similar to those for creating Stateless Session Beans. However, the methods that go into these interfaces do resemble their remote counterparts for the AirlineShoppingCart EJB, which we created in the preceding sections. As a reminder, local interfaces differ from remote interfaces in the following ways:
The Bean ClassThe Bean class provides the bean implementation. The container invokes corresponding business methods on the bean implementation when client requests are made on the RMI stub. This class does not directly implement any of the interfaces that we've already discussed; it simply provides methods that handle both life cycle methods as well as business methods. Let's first take a look at the skeleton of the Bean class that we'll be writing, as shown in Listing 21.5. We haven't yet coded the logic within the business methods. Listing 21.5 Implementation for a Stateful Beanpublic class AirlineShoppingCartBean implements SessionBean { SessionContext context = null; public AirlineShoppingCartBean() { super(); } public void setSessionContext(SessionContext aContext) throws RemoteException { System.out.println(" setSessionContext called "); context = aContext; } public void ejbCreate() throws CreateException { System.out.println("ejbCreate called"); // Create the prepared statements } public void ejbCreate(String origin, String destination) throws CreateException { System.out.println("ejbCreate called"); } public void ejbRemove() { System.out.println("ejbRemove called"); } public void ejbActivate() { System.out.println("ejbActivate called"); } public void ejbPassivate() { System.out.println("ejbPassivate called"); } public Properties getFlightInfo() throws RemoteException, AirlineShoppingCartException { return null; } public double getTotalCost() throws RemoteException { return 0; } public void reserveSeats() throws AirlineShoppingCartException { } public int[] searchFlights() throws AirlineShoppingCartException { return null; } public void setDestination(String destination) { } public void setFlightNumber(int flightNum) { } public void setNumSeats(int numSeats) { } public void setOrigin(String origin) { } } The rules for coding the Bean class are the same as we discussed for Stateless Session Beans. The different methods that were discussed in that context also hold true in the context of Stateful Session Beans. However, a few concepts are slightly different for Stateful Session Beans. We highlight only those concepts in the following list.
Of course, apart from these methods, the bean classes should provide implementations for all the business methods defined in the object interfaces. We don't list any code from the business methods here, but you can find the implementation class in the file com/wlsunleashed/ejb/session/stateful/AirlineShoppingCartBean.java. Let's look at a snippet of code from this bean implementation to illustrate the process by which another bean (the AirlineReservation bean) present within the same container is invoked. Consider the following snippet of code from the bean implementation: // The initialization routine called by ejbCreate private void initialize() throws CreateException { .... try { Context ctx = new InitialContext(); reservationHome = (AirlineReservationLocalHome) ctx.lookup( "LocalAirlineReservationBean"); } catch (Exception ex) { throw new CreateException(ex.getMessage()); } .... } public void reserveSeats() throws AirlineShoppingCartException { .... try { AirlineReservationLocalObject reservationObject = reservationHome.create(); reservationObject.reserveSeats(flightNumber, numSeats); Properties props = getFlightInfo(); double unitPrice = Double.parseDouble( (String)props.get("TOTAL_COST_PER_SEAT")); totalCost += unitPrice; } catch (Exception ex) { throw new AirlineShoppingCartException(ex.getMessage()); } .... } This code snippet is interesting because of the fact that the shopping cart bean behaves as a client to the AirlineReservation EJB that we created earlier. It's clear from the code that we look up the home object at the time of initialization of the bean. We then use this home instance to create a local object instance in the business method. For a detailed discussion of invoking EJBs, refer to the earlier discussion on Stateless Session Beans. Deployment Descriptors of a Stateful Session BeanYou have to essentially create the same two deployment descriptors for deploying Stateful Session Beans as you did for Stateless Session Beans. Most of the discussion of deployment descriptors in the earlier sections of this chapter also holds true for Stateful Session Beans. Of course, there are some differences between the deployment descriptors of the two. This section highlights only these differences. To get the complete picture, please ensure that you read this section in conjunction with the section on deployment descriptors for Stateless Session Beans and the introduction provided in Chapter 20. ejb-jar.xmlThe ejb-jar.xml file looks very similar to the one from Stateless Session Beans. The only difference between these two descriptors is the <session-type> tag (under <ejb-jar><enterprise-beans><session>), which has to be set to Stateful (rather than Stateless) for Stateful Session Beans. Listing 21.6 shows the code of the ejb-jar.xml file, which we use for the AirlineShoppingCart EJB. Listing 21.6 ejb-jar.xml for a Stateful Session Bean<!DOCTYPE ejb-jar PUBLIC '-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0/ /EN''http://java.sun.com/dtd/ejb-jar_2_0.dtd'> <ejb-jar> <enterprise-beans> <session> <ejb-name>AirlineShoppingCartBean</ejb-name> <home> com.wls70unleashed.ejb.session.stateful. AirlineShoppingCartRemoteHome </home> <remote> com.wls70unleashed.ejb.session.stateful. AirlineShoppingCartRemoteObject </remote> <ejb-class> com.wls70unleashed.ejb.session.stateful. AirlineShoppingCartBean </ejb-class> <session-type>Stateful</session-type> <transaction-type>Container</transaction-type> <ejb-local-ref> <ejb-ref-name>ejb/ReservationStatelessEJB</ejb-ref-name> <ejb-ref-type>Session</ejb-ref-type> <local-home> com.wls70unleashed.ejb.session.stateless. AirlineReservationLocalHome </local-home> <local> com.wls70unleashed.ejb.session.stateless. AirlineReservationLocalObject </local> <ejb-link>AirlineReservationBean</ejb-link> </ejb-local-ref> </session> </enterprise-beans> <assembly-descriptor> </assembly-descriptor> </ejb-jar> weblogic-ejb-jar.xmlThe second deployment descriptor is the weblogic-ejb-jar.xml file. This descriptor also looks quite similar to that used for Stateless Session Beans. However, in the place of the <stateless-session-descriptor> stanza (under <weblogic-ejb-jar><weblogic-enterprise-bean>), this descriptor now contains the stanza <stateful-session-descriptor>. For example: <!DOCTYPE weblogic-ejb-jar PUBLIC '-//BEA Systems, Inc.//DTD WebLogic 7.0.0 EJB//EN''http: //www.bea.com/servers/wls700/dtd/weblogic700-ejb-jar.dtd'> <weblogic-ejb-jar> <weblogic-enterprise-bean> <ejb-name>AirlineShoppingCartBean</ejb-name> <stateful-session-descriptor> <stateful-session-cache> </stateful-session-cache> <stateful-session-clustering> </stateful-session-clustering> </stateful-session-descriptor> <transaction-descriptor> </transaction-descriptor> <reference-descriptor> <ejb-local-reference-description> <ejb-ref-name>ejb/ReservationStatelessEJB</ejb-ref-name> <jndi-name>LocalAirlineReservationBean</jndi-name> </ejb-local-reference-description> </reference-descriptor> <jndi-name>AirlineShoppingCartBean</jndi-name> </weblogic-enterprise-bean> </weblogic-ejb-jar> This stanza can contain the following elements:
The <stateful-session-clustering> stanza relates to clustering, and you can read more about this stanza in the section "Clustering a Stateful Session Bean" that appears later in this chapter. <stateful-session-cache> describes the caching strategy of the Stateful Session Bean. A Stateful Session Bean cache is a collection of bean instances created by clients at any given time. The container maintains this cache using the parameters defined under this stanza. This stanza contains the following elements
Deploying a Stateful Session BeanCreating the deployment descriptors and deploying the bean to WebLogic Server is exactly the same as for Stateless Session Beans. You can refer the sections earlier in this chapter for a detailed discussion on these tasks. The build script packaged with this book also takes care of building the AirlineShoppingCart bean. Both the EJBs are built and packaged in the same JAR file. So, if you've already built and deployed the EAR file, you've also deployed the Stateful Bean. Accessing a Stateful Session Bean from a ClientLet's create a JSP that will access the Stateful Session Bean and enable users to reserve seats on the flights. From the client's perspective, accessing a Stateful Session Bean is no different from accessing a Stateless Session Bean. The client simply uses the business methods published by the object interfaces, just as it did with Stateless Session Beans. The client to the AirlineShoppingCart EJB is comprised of the following files:
NOTE If you used the Ant script we provided and deployed the EAR file, the Web application containing these files is already part of the EAR file. You can directly access the HTML file by typing http://localhost:7001/AirlineReservation/SearchFlights.html in your browser window after deploying the EAR file. One very interesting side note about the JSP client is that if you look closely at the code, you'll find that the JSP (along with its helper classes) uses the EJB by invoking the local interfaces. As with Stateless Session Beans, other colocated objects can access Stateful Session Beans using local interfaces, if defined. This obviously improves performance by completely avoiding network calls. This works perfectly well if the client is packaged within the same application as the EJB. This is the reason we deploy the application as an EAR file. If you didn't do this, but deployed the EJB independent of the WAR file, the JSP cannot use local interfaces. Life Cycle of a Stateful Session BeanFigure 21.2 illustrates the lifecycle of a Stateful Session Bean, within the container. Figure 21.2. Stateful Session Bean Life cycle.As you can see from the figure, the bean instance may be in three states: Does Not Exist, Method Ready, And Passive. As already mentioned, Stateful Session Beans do not use instance pooling. Therefore, the method ready state of the Stateful Session Bean isn't the same as that of a Stateless Session Bean. Each Stateful Session Bean instance is tied to only one client, and when the client is done with the bean instance, the instance is evicted from memory to save resources. The bean instance does not initially exist; in other words, it's present in the Does Not Exist state. At this point, when a client tries to create a bean instance by invoking a create method on its home interface, the container creates a bean instance by calling the newInstance method on the bean class. The container then sets the session context by invoking the setSessionContext method. The container invokes the appropriate ejbCreate method on the bean instance and allows the bean to initialize itself. The bean is now said to have transitioned to the Method Ready state, and is available for the client to invoke its methods. While in this state, the bean instance can maintain conversational state with the client. When a business method is invoked outside of a transaction, the bean goes into a busy state. While in this state, the container will not entertain additional client calls. Even if you've set the <allow-concurrent-calls> property in weblogic-ejb-jar.xml (under <weblogic-ejb-jar><weblogic-enterprise-bean><stateful-session-descriptor>) to true, the container will block this call until the first call has completed successfully. After the business method has completed, the bean returns to the Method Ready state. Stateful Session Beans are, by definition, not transactional components. This means that when a transaction rolls back, it has no effect on the state of the bean instance (that is, the values stored within the bean). However, you can make the bean transactional by making the Stateful Session Bean implement the javax.ejb.SessionSynchronization interface. This interface holds three callback methods: afterBegin, beforeCompletion, and afterCompletion. We look at this interface in more detail in the section "Container-Managed Transactions " that appears later in this chapter. For now, it's sufficient to know that the container invokes these methods at appropriate times when a business method is invoked within a transaction. When a business method is invoked within the boundaries of a transaction, the container first invokes the afterBegin callback method. This tells the bean instance that the transaction has begun, and it may perform additional initialization routines during this time. The bean instance goes to an In Transaction state. All business methods that are invoked at this time will return the bean back to the In Transaction state. Once the transaction is committed or rolled back, the instance moves back to the Method Ready state. During this process, two more callback methods, beforeCompletion and afterCompletion, are invoked in the bean instance. While performing any business method, both inside and outside a transaction, if the container traps an unhandled exception or a RuntimeException is thrown from the bean instance, it evicts the bean and moves it straight to the Does Not Exist state. When the container needs to free up resources, it may passivate bean instances. At this time, it invokes the ejbPassivate method on the bean instance to provide to it with an opportunity to clear up its resources. The EJB specification requires the bean instance to set all variables that aren't serializable to null, and to close any open resources in this method. Any field that's declared transient will not be serialized. On completion of this method call, the bean instance moves to the Passive state. If a client request comes in for the bean instance while it's in the Passive state, the container activates the bean and invokes the ejbActivate method on the instance. At this point, the bean may reinitialize itself. The bean instance then moves back to the Method Ready state. While in the passive state, the bean instance might timeout before a client request comes in. When this happens, the bean moves back to the Does Not Exist state. This timeout value can be controlled by using the <idle-timeout-seconds> tag in the weblogic-ejb-jar.xml file, under <weblogic-ejb-jar><weblogic-enterprise-bean><stateful-session-descriptor><stateful-session-cache>. Refer to Chapter 20 for a detailed discussion of passivation and activation of beans. If the client invokes the remove method on the bean when it's in the Method Ready state, the container evicts the bean and the bean moves back to Does Not Exist state. |
[ Team LiB ] |