For our example, we assume that the web tier of the Media Mania store handles the interactions with the customer while he is browsing and shopping. The web tier manages the customer's name and contents of his cart. The web tier might manage the cart using persistent classes or simply maintain the cart as a session state. When the customer chooses to check out, the web tier delegates this important function to the EJB tier of the application.
For this purpose, we implement a stateless session bean, called CashierBean, with a checkout( ) business method. We use the stateless-session-bean pattern because it best models the semantics of a store cashier. During the time a customer is checking out, the cashier devotes all of her time to that customer. Once a customer walks away from the cashier, the cashier forgets all about that customer in order to help the next one. Any information needed from the transaction must be stored persistently during the interaction with the customer.
A stateless session bean is the most efficient type of bean for this purpose because there is no client state that needs to be maintained between business methods. Any currently idle bean can service any incoming request from any client. Therefore, these beans can be managed by the application server easily, based on workload. If more requests arrive for a particular type of bean than there are beans available, the server can create more quickly. Similarly, if there are too many idle beans, they can quickly be destroyed because there is no persistent state to save.
When you develop a session bean that uses JDO, you associate each instance of the bean with an instance of the PersistenceManagerFactory that you look up when you initialize the session bean during setSessionContext( ).
The bean class contains instance variables that hold the associated PersistenceManager and PersistenceManagerFactory.
public class CashierBean implements javax.ejb.SessionBean { private javax.ejb.SessionContext context; private PersistenceManagerFactory pmf; private PersistenceManager pm; private static String pmfName = "java:comp/env/jdo/MediaManiaPMF";
When the container calls setSessionContext( ) to initialize the bean, we look up the PersistenceManagerFactory via JNDI. The name of the PersistenceManagerFactory is hardcoded into the bean, but JNDI uses an indirection to find the actual PersistenceManagerFactory. The PersistenceManagerFactory represents the same datastore for all beans sharing the same datastore resource. This allows the PersistenceManagerFactory to manage the association between the distributed transaction and the PersistenceManager:
public void setSessionContext(javax.ejb.SessionContext aContext) { context = aContext; try { Context ic = new InitialContext( ); pmf = (PersistenceManagerFactory)ic.lookup(pmfName); } catch (NamingException ex) { throw new EJBException("setSessionContext", ex); } }
This simple bean uses only one PersistenceManagerFactory. If your application requires more than one PersistenceManagerFactory, each of them should be looked up during setSessionContext( ) and saved into its own field.
During assembly of the application, the assembler defines the resource-ref element in the session element that describes the CashierBean in the ejb-jar.xml file. The resource-ref identifies the PersistenceManagerFactory as a resource; the res-ref-name is the JNDI name in the session bean's JNDI context:
<resource-ref> <res-ref-name>jdo/MediaManiaPMF</res-ref-name> <res-type>javax.jdo.PersistenceManagerFactory</res-type> <res-auth>Container</res-auth> </resource-ref>
During deployment of the bean, the deployer associates the res-ref-name given in the deployment descriptor with the actual PersistenceManagerFactory constructed by a server implementation-specific process. The association is indirect; the name coded into the application is in the session bean's JNDI context and is mapped to the actual resource name. This allows different applications to use the same name to refer to different resources or to use different names to refer to the same resource.
The server-resource configuration process, while not standard, typically requires the deployer to write a server-resource definition file containing the PersistenceManagerFactory class name, properties, and JNDI lookup name. For example:
<persistence-manager-factory-resource> <jndi-name>jdo/MediaManiaPMF</jndi-name> <factory-class-name>com.sun.jdori.FOStorePMF</factory-class-name> <property key="ConnectionURL" value="fostore://mmserv/MediaManiaDB"/> <property key="ConnectionUserName" value="fortune"/> <property key="ConnectionPassword" value="silence"/> </persistence-manager-factory-resource>
The server typically implements the resource configuration at server initialization by getting the factory class name as a String and obtaining a corresponding class instance using Class.forName( ). The server turns each property's name in the property list into a method name by using the JavaBeans pattern of capitalizing the first character of the property name and prepending set to the name. Then, the server looks up the method using Class.getMethod( ) and invokes the method with the property value as a parameter. After the server sets all properties, it binds the configured object to the name specified in the jndi-name element. This binding allows the bean's Context.lookup( ) method in setSessionContext( ) to find the resource during server operation.
We continue the implementation of our bean with the business method. The signature of the checkout( ) method is complex, but it illustrates a best practice for remote methods. Instead of decomposing the checkout process into several methods, the single checkout( ) method takes as parameters all the information needed to perform the operation. The benefit of this decomposition is that the transaction and security checks occur only once per checkout, regardless of the number of items checked out.
The only initialization we assume in Example 17-1 is that the pmf field has the appropriate PersistenceManagerFactory for this application.
public void checkout( java.lang.String lastName, java.lang.String firstName, java.util.Collection rentals, java.util.Collection purchases) throws java.rmi.RemoteException { PersistenceManager pm = pmf.getPersistenceManager(); [1] Customer customer = StoreQueries.getCustomer(pm, firstName, lastName); [2] Iterator it = rentals.iterator( ); while (it.hasNext( )) { RentalValueObject rvo = (RentalValueObject)it.next( ); RentalItem ri = StoreQueries.getRentalItem [3] (pm, rvo.serialNumber); Rental rental = new Rental(customer, new Date( ), ri); customer.addTransaction(rental); customer.addRental(rental); } it = purchases.iterator( ); while (it.hasNext( )) { PurchaseValueObject pvo = (PurchaseValueObject)it.next( ); MediaItem mediaItem = StoreQueries.getMediaItem( [4] pm, pvo.title, pvo.format); Purchase purchase = new Purchase(customer, new Date( ), mediaItem); customer.addTransaction(purchase); } pm.close( ); [5] }
We use static methods defined in StoreQueries to find the Customer by first and last name (line [2]), find a RentalItem by serial number (line [3]), and find a MediaItem by title and format (line [4]). This static-method pattern allows us to keep the application classes free of any references to the JDO interfaces. Of course, when you design your persistent classes, you may find it useful to put these finder methods directly into the persistent classes.
In the checkout( ) method, the customer is identified uniquely by first name and last name, and the rentals and purchases are represented by collections of value objects.
A value object is a design pattern for representing complex data that can be serialized and sent by value from one process to another. In our case, the value objects are used only to hold data values; all the information needed to identify a specific rental or purchase item is contained in the corresponding value object. Since the data elements need no abstraction, the value-object classes are implemented to have no behavior and all their fields are public. The compiler generates a public no-arg constructor for each class:
public class MediaValueObject implements java.io.Serializable { public String title; } public class RentalValueObject extends MediaValueObject { public String serialNumber; } public class PurchaseValueObject extends MediaValueObject { public String format; }
The strings and value objects in the parameter list of the checkout( ) method can be serialized and sent by value using any of a number of protocols, including SOAP, RMI, and IIOP. The details of which protocol is used are not important to the implementation of the business logic.
In the checkout( ) method, we update the datastore and insert new instances. Therefore, we need to have an active JDO transaction. The simplest implementation technique is to use container-managed transactions, in which the container manages the transactions for us. In order for the container to begin a new transaction for the business method automatically, the deployer must declare in the deployment descriptor that the business method requires transactions. This descriptor specifies that checkout( ) requires an active transaction, and the container will start a transaction if one is not already active. The container-transaction element is contained in the assembly-descriptor element of the ejb-jar element in the ejb-jar.xml file:
<container-transaction> <method> <ejb-name>CashierBean</ejb-name> <method-name>checkout</method-name> </method> <trans-attribute>Required</trans-attribute> </container-transaction>
Because we marked the checkout( ) method in the deployment descriptor of the CashierBean with trans-attribute given the value Required, the checkout( ) method has transactional behavior. Before the container calls the method, it automatically obtains a UserTransaction and begins a transaction if one is not already in progress. This gives maximum flexibility for the reuse of components. If a new component is implemented with a method defined as requiring transactions, the new method can call the checkout( ) method and the container will simply verify that there is already a transaction in progress.
When the checkout( ) method calls getPersistenceManager( ) on the PersistenceManagerFactory (on line [1] of Example 17-1), the JDO implementation determines the UserTransaction associated with the thread of control of the caller and checks if there is an active transaction. If there is already a PersistenceManager associated with an active UserTransaction, the JDO implementation returns it. If not, the JDO implementation constructs a new PersistenceManager, associates it with the active UserTransaction, and begins the JDO Transaction in which we perform all of the queries and updates.
When we close the PersistenceManager (on line [5] of Example 17-1), all of the changed and new instances remain in the PersistenceManager cache. The PersistenceManager will remain active until the container completes the transaction. In this case, the container completes the transaction as soon as the checkout( ) method returns. Since we are using container-managed transactions, we never use the JDO Transaction methods.
Now, we fill in the required methods according to the EJB specification for stateless session beans. The ejbActivate( ) and ejbPassivate( ) methods are used for stateful session beans, and the ejbCreate( ) and ejbRemove( ) methods are empty since there is no special behavior required when creating or removing our stateless session bean:
public void ejbActivate( ) { } public void ejbPassivate( ) { } public void ejbRemove( ) { } public void ejbCreate( ) { }
Now that we have seen how to implement a simple session bean using JDO, we will describe the lifecycle and special requirements for all kinds of session beans. Figure 17-1 shows the lifecycle for stateless session beans.
The fields of a JDO session bean of any type include:
A reference to the PersistenceManagerFactory, which is initialized by the setSessionContext( ) method. This method looks up the PersistenceManagerFactory by JNDI access to the object identified in the deployment descriptor.
A reference to the PersistenceManager, which is acquired by each business method and closed at the end of the business method.
A reference to the SessionContext, which is initialized by the method setSessionContext( ).
Stateful session beans are service objects that are created for a particular user, and they may have a state associated with that user between business methods. A business-method invocation on a reference to a stateful session bean is dispatched to the specific instance created by the user.
The timeworn example of a stateful session bean is the online shopping cart; the cart that keeps track of all the items purchased at an online purveyor contains all the information needed when you go to check out. Every item you have picked from the shelves and all the special discounts you've chosen are put into the cart. No matter when you stop shopping or when you return, your cart still contains the items that you put into it.
But the burden of managing the cart belongs to the server. And, since stateful session beans are created for a specific user, the beans' state takes up precious memory space. If the cart's owner doesn't use the cart for an extended period of time, the server has to deal with storing the contents persistently.
There are a number of other implications that you should consider before using stateful session beans:
The create method for the stateful session bean can take parameters specific to the intended use, so you can create beans with different behavior based on create parameters. A stateless session bean has only one create method, and therefore only one type of bean may be created.
The bean is dedicated to the particular user and is therefore bound to a specific server process. Load-balancing techniques, if implemented by the server at all, are complicated and may require special deployment descriptors.
If the server needs to manage memory usage in the JVM, it can passivate the bean, but only after a potentially expensive serialization process to persistent storage (usually a file in a local directory). Management of this memory and persistent storage can be a significant resource drain on the server. Because memory and persistent storage are scarce resources, the lifecycle allows the server to destroy a bean that has not been used for some amount of time, called the timeout period. After the timeout period expires, your bean might be destroyed without notice.
Implementing the ejbActivate( ) and ejbPassivate( ) methods is your responsibility as the bean developer. Any state that can't simply be serialized must be saved at ejbPassivate( ) and restored at ejbActivate( ). Although ejbPassivate( ) will not be called while a transaction is active, the bean might time out, and your implementation must take this into account.
You can't preserve a JDO state using serialization, as JDO implementations don't support serialization for JDO-implementation artifacts such as those that implement PersistenceManager and Transaction. This means that your bean can save only the object identities of persistent instances, not object references, and your bean then has to restore them using getObjectById( ) in business methods.
Otherwise, the behavior of stateful session beans using container-managed transactions is the same as for stateless session beans. In particular, all business methods in the bean interface acquire a PersistenceManager at the beginning of the method and close it at the end of the method.
Figure 17-2 shows the lifecycle of a stateful session bean.