5.3 Value List Iterator Pattern
The client programs in the previous chapter read in all the recordings from the Music Collection database by invoking the getMusicList() method in the Music EJB. These clients use Swing components and HTML Optiontags to display the recording list on the user's display screen. This works just fine if we have a short list, say something under 25 items. What happens if our database contains 100 items, 1,000 items, or even 10,000 items?
For a client application to effectively deal with a list that big presents logistic problems. No user wants to page through a 1,000-item list. In fact, a user machine with a browser and a large list may not be able to run the application at all, due to limited resources and memory constraints. Furthermore, a user may not be interested in seeing the entire list of items, resulting in wasted resources and time required to transmit all the list items.
The Value List Iterator Pattern can help with large lists of data. This pattern allows clients to request data a page at a time. A Value List Iterator applies to many common situations. For example, it applies when a user wants to access only portions of a list, or if the list doesn't fit in memory or on the user's display. A Value List Iterator can also be handy if transmitting the entire list takes too much time or if a client application has no idea how large the list is.
There are several consequences to implementing a Value List Iterator. First of all, the client controls how much data to transmit. By breaking up a list into multiple remote calls for retrieval, the client makes more requests on the server, thereby increasing network traffic. Also, the Value List Iterator should be applied to read-only lists, since concurrent updates to mutable lists could possibly invalidate the data.
There are several strategies for implementing the Value List Iterator Pattern.
Stateful session bean. The bean implementation keeps track of the client's current page and desired page size. This presents a clean and simple interface to the client, because a client can request the next page, grab the previous page, or ask if there are more elements to read (either forward or in reverse). The downside is that a stateful session bean must be tied to a single client. This can be a strain on the server's resources when many clients make simultaneous requests. Most alarmingly, the server must cache multiple copies of the data.
Stateless session bean. The bean implementation does not keep track of client-specific data. This means more bookkeeping tasks for the client, who is now responsible for the current page, page size, and any other variables needed to effectively manage the list. However, the EJB container can reuse a stateless session bean easily, requiring far fewer copies of the data in the server's memory.
Stateless session bean/stateful session bean combination. This strategy uses a stateless session bean to access the data and a stateful session bean to manage the iteration of the list. A stateful session bean presents a clean interface to a client and keeps track of all the client-specific data. The stateful session bean also translates client-specific parameters into calls to the stateless bean, which holds the data. This approach provides the best features of both of the previous implementation strategies.
Design Guideline
The combination approach unburdens clients by encapsulating client-specific data into a stateful bean. Moreover, the stateless bean requires less server-side resources because the EJB container can easily switch instances among clients. This is the strategy we use to implement our Value List Iterator Pattern.
Now we are ready to show you the implementations for the MusicIterator EJB and the MusicPage EJB. Figure 5-3 shows a class diagram of the Value List Iterator Pattern participants. You can see how these EJB components interact to implement this pattern. The MusicIterator EJB is a stateful session bean that keeps track of client-specific data and provides methods to page through the recording data forward and backward. The MusicPage EJB is a stateless session bean that uses the MusicDAO to read and hold the recording data from the Music Collection database. (Note that the box labeled MusicDAO is an implementation of the MusicDAO interface.) Although the MusicPage EJB does not track any client-specific data, it provides a method to get a page of recording data for the MusicIterator EJB.
Figure 5-3 Class Diagram Showing the Participants of the Value List Iterator Pattern
Figure 5-4 is a sequence diagram showing the method invocations (messages) sent between client, MusicIterator EJB, MusicPage EJB, and the MusicDAO implementation object. This interaction diagram shows the messages required to create the data (the ArrayListof RecordingVOs) and read a page (using MusicIterator method nextpage()).
Figure 5-4 Sequence Diagram Showing the Interactions for Reading a Page of Data from the MusicDAO Object
Let's look at the MusicPage stateless session bean first.
MusicPage Stateless Session Bean
The MusicPage EJB is similar to the Music EJB from Chapter 4. Listing 5.6 shows the home interface for the MusicPage EJB. Since this is a stateless session bean, it has only one create() method.
Listing 5.6 MusicPageHome.java
// MusicPageHome.java import java.rmi.RemoteException; import javax.ejb.CreateException; import javax.ejb.EJBHome; public interface MusicPageHome extends EJBHome { MusicPage create() throws CreateException, RemoteException; }
Listing 5.7 shows the remote interface for the MusicPage EJB, consisting of three business methods. The getSize()method returns the number of recordings in the Music Collection database and getPage()returns a single page of data. Method getTrackList() reads the track list from a specific recording.
Listing 5.7 MusicPage.java
// MusicPage.java import java.rmi.RemoteException; import javax.ejb.EJBObject; import java.util.*; public interface MusicPage extends EJBObject { public ArrayList getTrackList(RecordingVO rec) throws NoTrackListException, RemoteException; public int getSize() throws RemoteException; public ArrayList getPage(int currentIndex, int pageSize) throws RemoteException; }
The MusicPage EJB implementation class is shown in Listing 5.8. To access the Music Collection database, we use a MusicDAO object to encapsulate the vendor-dependent details. This is the same DAO pattern technique we used with the Music EJB in Chapter 4 (see "DAO Pattern Implementation" on page 121).
The getPage() method is responsible for returning a page of recordings from the recording list. To do this, the method builds an ArrayList with the page of data. As you will soon see, the MusicIterator EJB calls this method with a page index (currentIndex) and a page size (pageSize). Inside the getpage() method, we do some sanity checks on the arguments before creating the page as an ArrayList.
Since MusicPage EJB is a stateless session bean, the EJB container is free to assign the same instance to a new client as soon as the current client returns from a method call. This allows one instance to be shared among many clients.
Listing 5.8 MusicPageBean.java
// MusicPageBean.java import java.util.*; import javax.ejb.*; import javax.naming.*; public class MusicPageBean implements SessionBean { // Instance variable MusicDAO object // to access database. // MusicDAO object is instantiated in ejbCreate() // and provides implementation of the MusicDAO interface // for the particular database we're using. private MusicDAO dao; // Instance variable ArrayList that holds all recordings private ArrayList musicList; // Business methods public int getSize() { return musicList.size(); } public ArrayList getPage(int currentIndex, int pageSize) { // perform a sanity check on the arguments if (currentIndex > musicList.size()) currentIndex = musicList.size(); else if (currentIndex < 0) currentIndex = 0; if (pageSize <= 0) pageSize = 1; else if (pageSize > musicList.size()) pageSize = musicList.size(); // create the sublist ArrayList page = new ArrayList(); // initialize an iterator to point to // the requested element ListIterator current = musicList.listIterator(currentIndex); // grab the elements for the desired page size for (int i = 0; current.hasNext() && i < pageSize; i++) page.add(current.next()); return page; } public ArrayList getTrackList(RecordingVO rec) throws NoTrackListException { ArrayList trackList; try { // Encapsulate database calls in MusicDAO trackList = dao.dbLoadTrackList(rec); } catch (MusicDAOSysException ex) { throw new EJBException("getTrackList: " + ex.getMessage()); } if (trackList.size() == 0) { throw new NoTrackListException( "No Track List found for RecordingID " + rec.getRecordID()); } return trackList; } // EJB methods public void ejbCreate() { try { // The MusicDAOFactory class returns an // implementation of the MusicDAO interface dao = MusicDAOFactory.getDAO(); // initialize the shared music ArrayList musicList = dao.dbLoadMusicList(); System.out.println("MusicPageBean:ejbCreate():" + "initialized musicList from DAO object"); } catch (MusicDAOSysException ex) { throw new EJBException(ex.getMessage()); } } public MusicPageBean() {} public void ejbRemove() {} public void ejbActivate() {} public void ejbPassivate() {} public void setSessionContext(SessionContext sc) {} } // MusicPageBean
Deployment Descriptor
Listing 5.9 contains the deployment descriptor for the MusicPage EJB. It is similar to the one for the Music EJB, shown previously in Listing 4.7 on page 105. We'll include it here, since this descriptor also includes the environment entry for specifying the DAO class. Note that MusicPageBean is stateless.
Listing 5.9 MusicPage EJB Deployment Descriptor
<ejb-jar> <display-name>MusicPageJAR</display-name> <enterprise-beans> <session> <display-name>MusicPageBean</display-name> <ejb-name>MusicPageBean</ejb-name> <home>MusicPageHome</home> <remote>MusicPage</remote> <ejb-class>MusicPageBean</ejb-class> <session-type>Stateless</session-type> <transaction-type>Bean</transaction-type> <env-entry> <env-entry-name>MusicDAOClass</env-entry-name> <env-entry-type>java.lang.String</env-entry-type> <env-entry-value>MusicDAOCloudscape</env-entry-value> </env-entry> <security-identity> <description></description> <use-caller-identity></use-caller-identity> </security-identity> <resource-ref> <res-ref-name>jdbc/MusicDB</res-ref-name> <res-type>javax.sql.DataSource</res-type> <res-auth>Container</res-auth> <res-sharing-scope>Shareable</res-sharing-scope> </resource-ref> </session> </enterprise-beans> </ejb-jar>
MusicIterator Stateful Session Bean
We're now ready to examine the MusicIterator EJB. This stateful session bean keeps track of the client's page size and current position in the recording list. Here is the home interface, found in Listing 5.10. Note that even though Music-Iterator EJB is a stateful session bean, its home interface contains only a single create() method.
Listing 5.10 MusicIteratorHome.java
// MusicIteratorHome.java import java.io.Serializable; import java.rmi.RemoteException; import javax.ejb.CreateException; import javax.ejb.EJBHome; public interface MusicIteratorHome extends EJBHome { MusicIterator create() throws RemoteException, CreateException; }
We'll look at the remote interface for the MusicIterator EJB in a moment. Before we do, let's define a set of useful operations that apply to all Value List Iterator objects. To do this formally, we'll create an interface called Value-ListIteratorand define methods for the operations. Then we can extend our MusicIteratorremote interface from ValueListIteratorand implement the methods in the MusicIterator bean class. This has two advantages. One, it separates the general operations of the Value List Iterator Pattern from the application-specific operations we require in the MusicIterator EJB. Second, the ValueListIterator interface is reusable with other EJB applications that require paging.
Listing 5.11 contains the source code for the ValueListIterator interface. This interface defines methods common to all value lists, such as setting page size, fetching the next or previous page, and determining if there are more elements to read. Method resetIndex()resets the index for the data and method setPageSize() returns the old page size.
Listing 5.11 ValueListIterator.java
// ValueListIterator.java import java.rmi.RemoteException; import java.util.*; public interface ValueListIterator { public static final int defaultPageSize = 10; public int getSize() throws RemoteException; public int setPageSize(int numberOfElements) throws RemoteException; public int getPageSize() throws RemoteException; public ArrayList previousPage() throws RemoteException; public ArrayList nextPage() throws RemoteException; public void resetIndex() throws RemoteException; public boolean hasMoreElements() throws RemoteException; public boolean hasPreviousElements() throws RemoteException; } // ValueListIterator
Design Guideline
When an enterprise bean implements a reusable set of operations, isolate the reusable parts in an interface. Then, extend this "reusable" specification in the enterprise bean's remote interface. Make sure all the methods in this reusable interface have RemoteException in their throws clauses.
Listing 5.12 is the remote interface for the MusicIterator EJB. Note that this interface extends ValueListIterator (in addition to EJBObject). This means we inherit all the method definitions of ValueListIteratorimplicitly. All we have to do is add getTrackList() to complete the remote interface for our MusicIterator EJB.
Listing 5.12 MusicIterator.java
// MusicIterator.java import javax.ejb.EJBObject; import java.rmi.RemoteException; import java.util.*; public interface MusicIterator extends EJBObject, ValueListIterator { public ArrayList getTrackList(RecordingVO rec) throws NoTrackListException, RemoteException; } // MusicIterator
The only thing left to show you is the MusicIteratorBeanimplementation class in Listing 5.13. This class contains the code for all methods in the remote interface, including those from ValueListIterator as well as the ejbCre-ate()method (corresponding to create()in the home interface). Since this is a stateful session bean, the class also includes the declaration and management of client-specific instance variables. These fields keep track of the client's request for reading the recordings from the Music Collection. Recording data can be accessed forwards or backwards with a page size that the client may customize.
As you peruse the code, note that MusicIterator EJB uses instance variable pageBeanto forward calls to the MusicPage EJB. This instance variable is initialized in ejbCreate().
Listing 5.13 MusicIteratorBean.java
// MusicIteratorBean.java import java.rmi.RemoteException; import javax.rmi.PortableRemoteObject; import javax.ejb.*; import javax.naming.*; import java.util.*; public class MusicIteratorBean implements SessionBean { // Instance variables private MusicPage pageBean = null; private int currentIndex; private int previousStart; private int pageSize; private int arraySize; // Business methods public ArrayList getTrackList(RecordingVO rec) throws NoTrackListException { try { return pageBean.getTrackList(rec); } catch (Exception ex) { throw new EJBException(ex.getMessage()); } } public int getSize() { return arraySize; } public int setPageSize(int numberOfElements) { int oldSize = pageSize; if (numberOfElements < 1 ) { pageSize = 1; } else if (numberOfElements > arraySize) { pageSize = arraySize; } else { pageSize = numberOfElements; } return oldSize; } public int getPageSize() { return pageSize; } public ArrayList previousPage() { // Return the previous page to the client // Perform some basic sanity checks // on the parameters try { currentIndex = previousStart - pageSize; if (currentIndex < 0) currentIndex = 0; int newIndex = currentIndex; previousStart = currentIndex; if (previousStart == 0) previousStart = -1; currentIndex += pageSize; if (currentIndex > arraySize) currentIndex = arraySize; return pageBean.getPage(newIndex, pageSize); } catch (Exception ex) { throw new EJBException(ex.getMessage()); } } public ArrayList nextPage() { // Return the next page to the client // Perform some basic sanity checks // on the parameters try { int newIndex = currentIndex; previousStart = currentIndex; currentIndex += pageSize; if (currentIndex > arraySize) currentIndex = arraySize; return pageBean.getPage(newIndex, pageSize); } catch (Exception ex) { throw new EJBException(ex.getMessage()); } } public void resetIndex() { currentIndex = 0; previousStart = 0; } public boolean hasMoreElements() { return (currentIndex < arraySize); } public boolean hasPreviousElements() { return (previousStart >=0); } // EJB Methods public MusicIteratorBean() {} // We must initialize all instance variables here public void ejbCreate() { try { Context initial = new InitialContext(); Object objref = initial.lookup("java:comp/env/ejb/MusicPage"); MusicPageHome pageHome = (MusicPageHome) PortableRemoteObject.narrow(objref, MusicPageHome.class); pageBean = pageHome.create(); arraySize = pageBean.getSize(); } catch (Exception ex) { throw new EJBException(ex.getMessage()); } // set page size from ValueListIterator default pageSize = ValueListIterator.defaultPageSize; currentIndex = 0; previousStart = 0; System.out.println("MusicIteratorBean:ejbCreate()"); } public void ejbRemove() {} public void ejbActivate() {} public void ejbPassivate() {} public void setSessionContext(SessionContext sc) {} } // MusicIteratorBean
Deployment Descriptor
Listing 5.14 contains the deployment descriptor for MusicIterator EJB. It indicates that MusicIterator EJB is stateful, and it includes the EJB reference for MusicPage EJB.
Listing 5.14 MusicIterator EJB Deployment Descriptor
<ejb-jar> <display-name>MIteratorJAR</display-name> <enterprise-beans> <session> <display-name>MusicIteratorBean</display-name> <ejb-name>MusicIteratorBean</ejb-name> <home>MusicIteratorHome</home> <remote>MusicIterator</remote> <ejb-class>MusicIteratorBean</ejb-class> <session-type>Stateful</session-type> <transaction-type>Bean</transaction-type> <ejb-ref> <ejb-ref-name>ejb/MusicPage</ejb-ref-name> <ejb-ref-type>Session</ejb-ref-type> <home>MusicPageHome</home> <remote>MusicPage</remote> </ejb-ref> <security-identity> <description></description> <use-caller-identity></use-caller-identity> </security-identity> </session> </enterprise-beans> </ejb-jar>
Invocation Patterns
Suppose that our application has more than one client active at the same time. How does the EJB container handle multiple client requests? When does the EJB container instantiate the MusicIterator EJB and when does it instantiate the MusicPage EJB?
Figure 5-5 shows three clients, each with their own instance of the stateful MusicIterator EJB. (We show this ownership with a solid arrow from the client to the MusicIterator EJB instance.) The EJB container will instantiate the stateless MusicPage EJB as needed. Depending on the timing of the requests, it is possible that a single instance could service all requests. How significant is this?
Figure 5-5 Stateful vs. Stateless Session Bean Objects
In the MusicIterator EJB implementation code, the following statement makes a request to instantiate the MusicPage EJB.
pageBean = pageHome.create();
Recall that when a client invokes the MusicPage EJB's create() method, the EJB container maps this call to ejbCreate()unless an instance of the bean already exists. If an instance exists, the container does not execute the ejbCreate() method. This is significant in our example, since the MusicPage EJB uses the MusicDAO to read in the entire database! Fortunately, we can avoid this expensive step because the EJB container is free to assign the same instance of the MusicPage EJB to another client request. We show this "dynamic-assignable" relationship in Figure 5-5 with a dotted arrow from each MusicIterator EJB to the single instance of the MusicPage EJB.
Because the MusicPage EJB is a stateless bean, we can economize on the expensive resources of reading the database and caching the data. The EJB container must assign one stateful MusicIterator EJB to each client for the duration of the session. However, since the MusicIterator EJB does not consume a lot of resources, we don't pay a high price to make it stateful.
Design Guideline
The MusicPage EJB uses the MusicDAO to read the entire Music Collection database into memory. In the real world, a large database may not make this approach feasible. In this situation, the MusicDAO could implement its own caching scheme to deliver data for paging. Note that this capability does not affect the overall architecture of our application, since the details would be confined to the MusicPage EJB and its helper class, MusicDAO.