The rental enterprise application is a straightforward port of the rental Web application. Only a few classes change. The biggest change occurs for the ReservationService whose objects in the rental Web application are local to the controller servlet. In the rental enterprise application, the ReservationService changes to a business interface that is implemented by the ReservationServiceEJB. Likewise, the ReservationServlet changes to accommodate the use of a remote EJB instead of a local object.
The reservation service EJB is implemented as a stateless CMT session bean. It uses the Session-Only pattern. These choices were made primarily for simplicity. Stateless CMT session beans are the simplest beans, and the Session-Only pattern is the simplest way to use them. This type of EJB is also quite sufficient for the rental application.
The ReservationServiceEJB uses a connection factory to get a persistence manager. Since the EJB container is managing transactions, each business method obtains a persistence manager and releases it before returning. See Chapter 6 for a discussion of the design constraints for the various types of EJBs. The service may use either a JDO datastore or optimistic transaction.
As shown in Figure 11-1, several classes and interfaces make up the deployed reservation service EJB. The application supplies the ReservationServiceHome, ReservationServiceRemote, and ReservationService interfaces. It also supplies the bean implementation in the ReservationServiceEJB class. The EJB API supplies the EJBHome, EJBObject, and SessionBean interfaces. The ReservationService interface defines the business methods of the reservation service EJB. The ReservationServiceEJB class implements the ReservationService interface, and the ReservationServiceRemote interface extends it. The application classes shown in Figure 11-1 are found in the com.ysoft.jdo.book.rental.ejb.service and com.ysoft.jdo.book.rental.ejb.service.impl packages.
Figure 11-1: The deployed reservation service EJB
As shown in Figure 11-1, the communication between the servlet and the EJB occurs between two proxies, a stub and a skeleton, that communicate by means of a network protocol such as RMI or RMI-IIOP. Both implement the ReservationServiceRemote interface. The skeleton, which resides on the EJB server, delegates invocations of the business methods to an instance of the ReservationServiceEJB. The EJB container is responsible for generating the stub and skeleton classes. JBoss creates these dynamically.
The ReservationServiceLocator, shown in Figure 11-1, is a utility class that finds a stub that implements the ReservationService interface. The enterprise application's ReservationServlet uses the stub just as the Web application uses a local instance of the ReservationService class.
There are only a few important differences between the Web application's ReservationService class and the enterprise application's ReservationService interface and its implementation in ReservationServiceEJB. The changes can be divided between those that are encapsulated within the implementation of the service and those that alter the public interface of the service. The changes that alter the public interface, in turn, cause changes in the ReservationServlet that uses the service.
There are several encapsulated changes. To begin with, the ReservationServiceEJB must have the life cycle methods required by the javax.ejb.SessionBean interface. Since it will be deployed in a CMT session bean, it must give up the transactional control that it exercised in the local service. The EJB container will start a transaction when the ReservationServlet calls a method in the ReservationService interface, and the container will either commit or roll back the transaction prior to returning control to the servlet.
Another pervasive difference leads to quite a few code changes. In the Web application's ReservationService class, the lifetime of the PersistenceManager and the lifetime of the ReservationService are coincident. In the ReservationServiceEJB, |the lifetime of the persistence manager is coincident with the execution of the business method. Each business method in the EJB obtains a persistence manager, uses it, and closes it before returning. This change leads to a variety of minor changes in initializing queries and closing the persistence manager.
Like the Web application's ReservationService, the ReservationServiceEJB returns persistent objects, but unlike the Web application, the enterprise application's ReservationServlet does not receive persistent objects. Instead, it receives unmanaged application data objects because the skeleton, shown in Figure 11-1, serializes the persistent objects out to the stub. The change from managed to unmanaged objects has effects that are both encapsulated and public.
For the reasons discussed in Chapter 6, the ReservationServiceEJB uses the utility method respond in com.ysoft.jdo.book.common.ejb.EJBHelper to preserialize the persistent objects returned to the ReservationServlet.
Porting the Web application's ReservationService to an EJB forces a couple of changes to the public interface of the ReservationService. To begin with, of course, the mechanism to obtain a reference to an object implementing the service has changed. Whereas the Web application's servlet could simply construct its ReservationService, the enterprise application must obtain a home interface to the EJB and use it to obtain a stub that implements the ReservationService. The get utility method in the ReservationServiceLocator encapsulates this work. In addition, the ReservationService no longer has a close method. Instead, the ReservationServlet releases the remote service by calling the stub's remove method specified in the EJBObject interface. The release utility method in the ReservationServiceLocator encapsulates this work.
The next change occurs in the signatures of the business methods in the ReservationService interface, which may now throw a java.rmi.RemoteException. Each method in the ReservationService interface declares RemoteException in its throws clause. In contrast, the business methods in the Web application's ReservationService do not declare a RemoteException. Indeed, neither does any business method in the ReservationServiceEJB declare a RemoteException in its throws clause. The RemoteException is declared in the enterprise application's ReservationService interface because the corresponding business methods in the stub and skeleton classes are engaged in the business of communicating over the network. Therefore, they throw a RemoteException when they encounter difficulty communicating.
The signature of the flipReservations method in the ReservationService also changes. Like the other business methods in the ReservationService interface, it adds the RemoteException to its throws clause. It also drops the application-defined OptimisticReservationException from its throws clause. Even if the EJB uses JDO's optimistic transactions, there is no opportunity for the flipReservations method in the ReservationServiceEJB to detect an optimistic transaction failure and report it by throwing an OptimisticReservationException. In JDO 1.0, the JDO runtime throws a JDOUserException when it discovers an optimistic lock failure, and in JDO 1.0.1, it throws a JDOOptimisticVerificationException. Since the container commits the transaction after the bean's business method returns, there is no opportunity for the bean developer's code to handle the exception. Since both types of exceptions are unchecked exceptions, the EJB container rolls back the transaction and disposes of the bean instance if the JDO implementation throws either one.
However, the flipReservations method can still detect a difference between the version of a Rental object that the client returns to it and the version of the Rental object that it finds to modify. As a result, the flipReservations method still throws the application-defined ExtendedOptimisticException. When this occurs, the method calls the setRollbackOnly method in the javax.ejb.SessionContext object to ensure that any changes made to persistent objects up until that point cannot be committed.
In the rental Web application, all of the application data classes implement the Serializable interface and two of them, Rental and Customer, implement the SupportsIdentityString interface. As you may recall from the section "The SupportsIdentityString Interface" in Chapter 10, SupportsIdentityString is the application-defined interface that allows an application data class to capture its identity string and use that identity string in equality comparisons. In the enterprise version, the Lighthouse class must also implement the SupportsIdentityString interface.
Why is this change to the Lighthouse class required in the EJB version of the reservation service but not in the servlet version? To answer this question, it is necessary to understand a little more about how the ReservationModel obtains and uses Lighthouse objects.
Before initializing the ReservationModel object, the ReservationServlet calls two methods in the ReservationService. One method returns a collection of Rental objects, and the other method returns a list of Lighthouse objects. The initRentals method in the ReservationModel places each Rental object from the collection in a column under its lighthouse. It does this by looking up the Lighthouse object associated with the Rental object in the list of Lighthouse objects. If the rental's Lighthouse object is the first lighthouse on the list, the rental goes in the first column, and so on. The lookup in the List class is based on the semantics of the equals method.
In the Web application, each ReservationService object is local and uses one persistence manager for its lifetime. While the ReservationServlet processes one HTTP request, it makes multiple calls to the same ReservationService object. Because the service uses one persistence manager and returns the local objects by reference, JDO's uniqueness requirement ensures that the Lighthouse objects returned by one method are the same Lighthouse objects returned by the other method. Because of the uniqueness requirement, there is no reason for the Lighthouse class to override the implementation of the equals method in the Object class, which uses JVM identity (==) to determine equality.
In the enterprise application, the ReservationService is remote and the lifetime of the persistence manager is coincident with the call to the business method. The business methods in the service return objects by value in a serialization stream. As a consequence, the two calls to the service, one to get the list of Lighthouse objects and the other to get the collection of Rental objects together with their associated Lighthouse objects, result in the ReservationServlet having two different sets of Lighthouse objects in memory. If the lookup into the list continues to rely upon JVM identity, it fails.
The solution is to upgrade the Lighthouse class by having it override the equals method and implement the SupportsIdentityString interface. The rental Web application made these changes to the Rental and Customer classes for other reasons when porting from the rental Swing application, and now these changes are required for the Lighthouse class in porting to the rental enterprise application. Listings 10-5 and 10-7 in Chapter 10 show the implementations of the SupportsIdentityString interface and the equals method in the Rental class. The upgraded Lighthouse class uses similar code.
The changes to the enterprise application's ReservationServlet class are primarily driven by the public changes to the ReservationService. The enterprise version of the ReservationServlet, like its Web application counterpart, obtains a service at the start of the respond method. Unlike the Web application's ReservationServlet, the enterprise application's ReservationServlet obtains the reference to the ReservationService from the ReservationServiceLocator, shown in Figure 11-1, rather than by construction. The ReservationServlet must also catch the RemoteException as one of the exception types that may be thrown from the ReservationService.
At the same time, the use of a remote EJB service introduces small opportunities to increase the simplicity and efficiency in the ReservationServlet. Since the remote service returns only unmanaged objects, the ReservationServlet no longer needs to call the service to make all persistent objects in the model transient. Likewise, since page generation is working with unmanaged objects that do not benefit from transparent persistence, the ReservationServlet can release the remote service prior to page generation. By releasing the remote service before forwarding to the JSP presentation page, the enterprise application's ReservationServlet uses the remote service on each request for a slightly shorter period of time than it would if it held the service until page generation finished.
There are no other changes. The JSPs remain unchanged. Except for the Lighthouse class, the data model classes remain unchanged. The MVC pattern in the Web user interface remains unchanged. The ReservationModel and its helper classes remain unchanged.
Although the same low impact during the port can be achieved when using JDBC, the higher level of abstraction provided by JDO makes the ways to minimize the impact of the port more apparent. In fact, it is possible to completely encapsulate the differences between the local service and the EJB service, reducing them to differences between two implementations of a service locator interface and a data service interface. If you anticipate that you will be porting between these two deployment environments, creating this small amount of extra infrastructure would be a very sensible thing to do. It was not done in the examples in order to highlight the differences between the two application architectures.
The rental enterprise application is simple, but can you expect that its design will scale up to the complexity of a real-world application? It should. A significant advantage of JDO is the ability to work with the pervasive complexity of persistence at the right level of abstraction.
One important factor to consider when scaling up is the size of the serialization graph returned by the EJB's business methods. If you have a highly interrelated data model, then you could end up returning to the client far more information than you intend. When JDO is local to the client, a highly interrelated data model is not a concern because JDO's transparent persistence will fetch only what is required by the client. When persistent objects are returned to the client by serialization, the client receives a copy of everything that is reachable by serializable fields. Chapter 5 points out various approaches for handling the issues that arise when serializing application data objects.