The Stateful Session Bean (Enterprise JavaBeans 3.1)

 

While the strengths of the stateless session bean lie in its speed and efficiency, stateful session beans are built as a server-side extension of the client. Each SFSB is dedicated to one client for the life of the bean instance; it acts on behalf of that client as its agent (see Figure 6-1). Stateful session beans are not swapped among EJB objects, nor are they kept in an instance pool like their stateless session counterparts. Once a stateful session bean is instantiated and assigned to an EJB object, it is dedicated to that EJB object for its entire lifecycle.*

Client relationship with the EJB Container and backing bean instances

Figure 6-1. Client relationship with the EJB Container and backing bean instances

Stateful session beans maintain conversational state, which means that the instance variables of the bean class can maintain data specific to the client between method invocations. This makes it possible for methods to be interdependent such that changes made to the bean’s state in one method call can affect the results of subsequent method invocations. Therefore, every method call from a client must be serviced by the same instance (at least conceptually), so the bean instance’s state can be predicted from one method invocation to the next. In contrast, stateless session beans don’t maintain client-specific data from one method call to the next, so any instance can be used to service any method call from any client.

* This is a conceptual model. Some EJB containers may actually use instance swapping with stateful session beans but make it appear as if the same instance is servicing all requests. Conceptually, however, the same stateful session bean instance services all requests.

Although stateful session beans maintain conversational state, they are not themselves persistent; the state of a SFSB is lost when the session is removed, the session times out, or the server restarts. Persistent state in EJB is modeled by the entity bean.

Because SFSBs are often considered extensions of the client, we may think of a client as being composed from a combination of operations and state. Each task may rely on some information gathered or changed by a previous operation. A GUI client is a perfect example: when you fill in the fields on a GUI client, you are creating conversational state. Pressing a button executes an operation that might fill in more fields, based on the information you entered previously. The information in the fields is conversational state.

Stateful session beans allow you to encapsulate some of the business logic and conversational state of a client and move it to the server. Moving this logic to the server thins the client application and makes the system as a whole easier to manage. The stateful session bean acts as an agent for the client, managing processes or taskflow to accomplish a set of tasks; it manages the interactions of other beans in addition to direct data access over several operations to accomplish a complex set of tasks. By encapsulating and managing taskflow on behalf of the client, stateful beans present a simplified interface that hides the details of many interdependent operations on the database and other beans from the client.


The Lifecycle of a Stateful Session Bean

The biggest difference between the stateful session bean and the other bean types is that stateful session beans do not use instance pooling. Stateful session beans are dedicated to one client for their entire lives, so swapping or pooling of instances isn’t pos-sible.f When they are idle, stateful session bean instances are simply evicted from memory. The EJB object remains connected to the client, but the bean instance is dereferenced and garbage-collected during inactive periods. This means that each stateful bean must be passivated before it is evicted in order to preserve the conversational state of the instance, and it must be activated to restore its state when the EJB object becomes active again.

Some vendors use pooling with stateful session beans, but that is a proprietary implementation and should not affect the specified lifecycle of the stateful session bean.

The bean’s perception of its lifecycle depends on whether it implements a special interface called javax.ejb.SessionSynchronization. This interface defines an additional set of callback methods that notify the bean of its participation in transactions. A bean that implements SessionSynchronization can cache database data across several method calls before making an update. We have not discussed transactions in detail yet.This section describes the lifecycle of stateful session beans that do not implement the SessionSynchronization interface.

The lifecycle of a stateful session bean has three states: Does Not Exist, Method-Ready, and Passivated. This sounds a lot like a stateless session bean, but the Method-Ready state is significantly different from the Method-Ready Pool of stateless beans. Figure 6-2 shows the state diagram for stateful session beans.

Stateful session bean lifecycle

Figure 6-2. Stateful session bean lifecycle

The Does Not Exist State

A stateful bean instance in the Does Not Exist state has not been instantiated yet. It doesn’t exist in the system’s memory.

The Method-Ready State

The Method-Ready state is the state in which the bean instance can service requests from its clients. This section explores the instance’s transition into and out of the Method-Ready state.

Transitioning into the Method-Ready state

When a client invokes the first method on the stateful session bean reference, the bean’s lifecycle begins. The container invokes newInstance() on the bean class, creating a new instance of the bean. Next, the container injects any dependencies into the bean instance. At this point, the bean instance is assigned to the client referencing it. Finally, just like stateless session beans, the container invokes any @PostConstruct callbacks if there is a method in the bean class that has this annotation applied. Once @PostConstruct has completed, the container continues with the actual method call.

Life in the Method-Ready state

While in the Method-Ready state, the bean instance is free to receive method invocations from the client, which may involve controlling the taskflow of other beans or accessing the database directly. During this time, the bean can maintain conversational state and open resources in its instance variables.

Transitioning out of the Method-Ready state

Bean instances leave the Method-Ready state to enter either the Passivated state or the Does Not Exist state. Depending on how the client uses the stateful bean, the EJB container’s load, and the passivation algorithm used by the vendor, a bean instance may be passivated (and activated) several times in its life, or not at all. If the bean is removed, it enters the Does Not Exist state. A client application can remove a bean by invoking a business interface method annotated as @Remove.

The container can also move the bean instance from the Method-Ready state to the Does Not Exist state if the bean times out. Timeouts are declared at deployment time in a vendor-specific manner. When a timeout occurs in the Method-Ready state, the container may, but is not required to, call any @PreDestroy callback methods. A stateful bean cannot time out while a transaction is in progress.

The Passivated State

During the lifetime of a stateful session bean, there may be periods of inactivity when the bean instance is not servicing methods from the client. To conserve resources, the container can passivate the bean instance by preserving its conversational state and evicting the bean instance from memory. A bean’s conversational state may consist of primitive values, objects that are serializable, and the following special types:

• javax.ejb.SessionContext

• javax.jta.UserTransaction (bean transaction interface)

• javax.naming.Context (only when it references the JNDI ENC)

• javax.persistence.EntityManager

• javax.persistence.EntityManagerFactory

• References to managed resource factories (e.g., javax.sql.DataSource)

• References to other EJBs

The types in this list (and their subtypes) are handled specially by the passivation mechanism. They do not need to be serializable; they will be maintained through passivation and restored automatically when the bean instance is activated.

When a bean is about to be passivated, a method on the bean class may be annotated with @PrePassivate to receive a callback for this event. This can be used to alert the bean instance that it is about to enter the Passivated state. At this time, the bean instance should close any open resources and set all nontransient, nonserializable fields to null. This prevents problems from occurring when the bean is serialized. Transient fields are simply ignored.

How does the container store the bean’s conversational state? It’s largely up to the container. Containers can use standard Java serialization to preserve the bean instance, or some other mechanism that achieves the same result. Some vendors, for example, simply read the values of the fields and store them in a cache. The container is required to preserve remote references to other beans with the conversational state. When the bean is activated, the container must restore any bean references automatically. The container must also restore any references to the special types listed earlier.

When the client makes a request on an EJB object whose bean is passivated, the container activates the instance. This involves deserializing the bean instance and reconstructing the SessionContext reference, bean references, and managed resource factories held by the instance before it was passivated. When a bean’s conversational state has been successfully restored, an @PostActivate callback method is invoked on the bean instance if one is declared on the bean class. The bean instance should open any resources that cannot be passivated and initialize the values of any transient fields within the @PostActivate method. Once @PostActivate is complete, the bean is back in the Method-Ready state and is available to service client requests delegated by the EJB object.

The activation of a bean instance follows the rules of Java serialization, regardless of how the bean’s state was actually stored. The exception to this is transient fields. In Java serialization, transient fields are set to their default values when an object is deserialized; primitive numbers become zero, Boolean fields false, and object references null. In EJB, transient fields can contain arbitrary values when the bean is activated. The values held by transient fields following activation are unpredictable across vendor implementations, so do not depend on them to be initialized. Instead, use an @PostActivate callback method to reset their values.

The container can also move the bean instance from the Passivated state to the Does Not Exist state if the bean times out. When a timeout occurs in the Passivated state, any @PreDestroy callback methods are not invoked.

System exceptions

Whenever a system exception is thrown by a bean method, the container invalidates the EJB object and destroys the bean instance. The bean instance moves directly to the Does Not Exist state, and any @PreDestroy call methods are not invoked.^

A system exception is any unchecked exception not annotated as an @Application Exception, including EJBException.


Example: The File TransferEJB

The File Transfer Protocol (FTP) defines a common language for exchanging data over network boundaries. Defined in RFC-959 (http://www.ietf.org/rfc/rfc959.txt), the specification mandates conversational state as part of the communication scheme; not all information required to service a request is present in the request itself. Because a session’s invocation history plays a role in how an FTP client will operate, the SLSB is not a valid implementation choice. These requirements lie right in the wheelhouse of the stateful session bean.

Most of the business logic contained in the bean implementation class has been stripped from the text in order to keep the focus upon SFSB semantics.

The simple operations we’ll implement are:

• Print current directory (pwd)

• Make directory (mkdir)

• Change to directory (cd)

This will be enough to connect to an FTP server and interact with it a bit to show how conversational state is central to the design of the SFSB. Readers interested in extending the example to send files over the wire are encouraged to do so.

It’s important to consider the additional lifecycle phases of the stateful session bean, as compared with the simpler SLSB. Not all of a bean’s internal state is Serializable, and in the case of our FTP client, we need to explicitly handle the connection to the server. We won’t attempt to serialize this out during passivation; we need to safely close all related resources and then reinitialize the value upon activation. The passivation and activation process must be completely transparent to the client such that from the caller’s perspective, the SFSB must act as if these had never occurred.

Yes, this is a hole in the specification.

When it’s completed all tasks, the client must signal when it’s done with the session. This allows the container to perform cleanup operations and reduce the overhead inherent with hanging onto bean instances that are no longer needed.

The Contract: Business Interfaces

The operations explained in the previous section imply a contract:

tmp9572_thumb2_thumb3tmp9573_thumb2

There are five functions total to connect to, disconnect from, and play with directory structure of the FTP server. We build upon this base to create a remote business view:

tmp9574_thumb1_thumb2

The endSession() method here is specific to an EJB client, so we’ve excluded it from the POJO-friendly FileTransferCommonBusiness and instead placed it directly into the remote business interface. As noted in the JavaDoc, invocation of this method will tell the container that the client is done with the session and may discard its associated target instance after firing any @PreDestroy callbacks.

Exceptions

We’ll include one general-purpose unchecked exception to denote a problem in executing the requested FTP command. Because this is a system-level problem from which a client typically cannot recover, it’s not marked as @ApplicationException: public class FileTransferException extends RuntimeException{…}

Bean Implementation Class

The bean implementation class will contain all of the business logic for our FTP client. The following imports are used:

tmp9575_thumb_thumb

The @PostConstruct and @PreDestroy annotations apply lifecycle callbacks when bean instances are created and destroyed, respectively. @PrePassivate and @PostActivate denote methods to be invoked during the stateful session passivation/activation phases.

@Remove is not a callback, but rather marks that when the client invokes its method, the session should be retired. Finally, we’ll use the Apache Commons FTP Client (http:// commons.apache.org/net/) to handle the finer points of the FTP protocol for us. First comes the bean declaration:

tmp9576_thumb1_thumb1

Here we simply mark this class as a stateful session bean via the @Stateful annotation and assign it an explicit name such that we can construct the portable Global JNDI name in testing later. Also, we’ll hardcode the host and port to be used in connecting to the FTP server. Typically this would be externalized, but for brevity’s sake we’ll keep things simple in this example:

tmp9577_thumb_thumb

Next, we’ll account for the instance members that will comprise our SFSB’s internal state:

tmp9578_thumb_thumb

The client will be our delegate for all FTP operations. Because we cannot serialize it during passivation, we’ll have to null this out during passivation and automatically reconnect upon activation. Although it may be possible to achieve the same goal implicitly using the Java transient keyword, the EJB specification advises against this because not all containers will rely upon Java Serialization for the passivation process.

We’ll also manually track the present working directory, our location within the FTP server, because once we go through the passivation/activation cycle we’ll need to reconnect and move back to the same location.

Our lifecycle callbacks will account for the operations that must take place during instance construction, passivation, activation, and session removal:

tmp9579_thumb1_thumb1

The @PostConstruct and @PostActivate annotations denote to the server that it should call connect() after instance creation and activation (wakeup from passivation). Here we create a new client delegate, connect and log into the remote server, and finally switch into the present working directory (if we had one before passivation). After this

callback has been made, our SFSB instance and session has been initialized and is ready for service:

tmp9580_thumb_thumb

Due to the @PrePassivate and @PreDestroy annotations, the callback disconnect() is invoked by the container before passivation or the instance is removed from service. This is where we clean up any underlying resources by logging out and disconnecting from the FTP server.

We have the expected accessor and mutator methods:

tmp9581_thumb_thumb

Now all that’s left is to do is implement the business interface methods that power the logic of the client. For brevity’s sake, we’ll omit the true code here; again:

tmp9582_thumb_thumbtmp9583_thumb1_thumb

POJO Testing Outside the Container

By directly creating an instance of our bean implementation class, we can use the FileTransferEJB as a POJO. Of course, we won’t be relying upon services such as injection and lifecycle callbacks, but we can mock the container’s responsibilities by invoking these manually. Consider the following setup in a JUnit test class:

tmp9584_thumb_thumb1

Before each test case is run, we’ll create a new POJO instance and manually connect. This essentially takes the place of the instance creation and @PostConstruct lifecycle callback performed by the EJB Container:

tmp9585_thumb_thumb

After each test completes, we can mock the @PreDestroy callback to disconnect from the FTP server.

That covers our test setup; now let’s ensure that our passivation logic is intact. Again, we can do this by explicitly invoking lifecycle callback methods as if our test client were the EJB Container:

tmp9586_thumb_thumbtmp9587_thumb1_thumb2

This test is actually very simple. It:

1. Obtains the initialized FTP client from the test setup

2. Changes the working directory

3. Manually invokes the pre-passivation callback

4. Copies the instance via serialization (as the container may do during passivation and activation)

5. Manually invokes the post-activation callback

6. Ensures that the FTP client is connected and in the same working directory as before the passivation process

Testing passivation and activation manually is a wise habit, as it’s difficult to get this fine-grained control in integration tests to true EJBs run within the context of the container. The specification does not define a mechanism to request that the container start passivation or activation upon a session.

Integration Testing

It’s also important to consider context during testing, so we wouldn’t be doing our job if we omitted integration tests to assert that our SFSB is working as a true EJB. This will also give us the opportunity to prove that the session isolation and removal contracts are holding true. Let’s assume we have a method available in our test case:

tmp9588_thumb_thumb1

This will use the test’s JNDI Context to obtain a new Proxy, hence creating a new user session. We’ll use createNewSession() in our tests.

It’s important for a client to release its session once complete with work, allowing the server to reclaim the resources used in maintaining its state. We can write a test to validate that signaling we’re done triggers the session removal:

tmp9589_thumb1_thumb2tmp9590_thumb1_thumb1

As this test illustrates, invoking upon an SFSB’s business method after session removal will result in a javax.ejb.NoSuchEJBException. This confirms that a call to endSession() correctly starts the removal of the current session and backing bean instance.

We may also show how the Container maintains conversational state for each session in an isolated context. Operations in discrete sessions may not leak out:

tmp9591_thumb1_thumb2tmp9592_thumb1_thumb1

The stateless session bean is not capable of handling this test reliably. It’s expected that subsequent calls to pwd() will remember the current location, changed previously via calls to cd(). Our SFSB is built primarily to handle conversational cases like the one testSessionIsolation() illustrates.

Next post:

Previous post: