Incremental J2EE

J2EE is Sun's application server framework that dramatically simplifies and accelerates the development of distributed applications. It subsumes all the Java APIs targetted at multi-tier, distributed, enterprise information systems. "Historically, Java APIs have been delivered in dribs and drabs with no real coordination between them. This was fine when the APIs were evolving in isolation. With J2EE, however, the constituent APIs have 'grown up' to become integrated and dependent on each other for key functionality."   [ 1 ]

This article takes a simple-minded stand-alone application, and incrementally evolves it to gently introduce the most interesting APIs of J2EE. The coverage includes:

All the applications presented here were implemented using Sun's J2EE SDK v1.2.1 reference implementation. The discussion does not include any deployment practices associated with this "vendor implementation".

Starting point

The application we'll be using for this step-wise discussion is a very simple book store. It presents a list of titles and the user makes choices. When the order is complete, the list of selections is sent to the store, and the store returns a confirmation statement. A fundamental OO design principle is: never "name" a concrete class, always use an interface type when declaring an argument or return type.   [ 2 ]   [ 3 ]   An additional step of decoupling commonly practiced is the use of factory methods for instantiating objects.   [ 4 ]   Whenever the new operator is used to create an object, the exact implementation class must be hard-wired in the code. Factory methods provide the indirection necessary to break this coupling.

"All constructors should be private or protected. The only way to create instances of a class should be through factory methods. It's nobody's business but the class' whether a class manufactures a new object or recycles an old one."   [ 5 ]

The use of factory methods becomes even more desirable in distributed systems. Factory methods can encapsulate the logic for identifying the desirable concrete class to instantiate, and where that object should be hosted on the network. They can also be used to hide the use of back-end persistence mechanisms.   [ 6 ]

Given the design heuristics of interfaces and factory methods, and anticipating the desirable demonstration points of future increments, the implementation in below produces the previous output.

The implementation of the IStore interface above is spelled out below. The Item class doesn't do much yet, but when we get to Entity Beans, it will take on some fairly interesting responsibilities. The placeOrder() method indexes into an array of Item objects and implicitly calls toString() on each one as it formats a confirmation String. [The complete application can be found in listing 1.]

RMI

In designing a distributed application, classes to be accessed or communicated across the network must be identified. Any object that is going to be physically passed from one virtual machine to another (passed by value) must implement the interface java.io.Serializable. Objects that stay anchored on a remote host, and get accessed via "remote procedure call" (passed by reference), must implement java.rmi.Remote. In our example, Item objects will be transported to, and live in, the client's virtual machine, and the StoreImpl object will service all remote requests from its home on the server's virtual machine.

Additionally, the methods defined in a "remote" class must all be amended to throw java.rmi.RemoteException. This is a proactive convention that anticipates the inhospitable environment presented by the network.

The ability to project method invocations across the network requires a significant amount of effort. This includes: marshalling arguments and return values, segmenting each request into packets, guaranteeing the delivery of these packets, reconstructing packets back into a "virtual function call" on the receiving end, and going through the inverse processing to return the results to the caller. That functionality has been implemented in the class java.rmi.server.UnicastRemoteObject, and is simply reused by inheriting from this framework class.

In order for servers and clients to find each other, a third party is needed called a "naming service". Sun provides a standard Naming class front-end and the free "rmiregistry" executable back-end. A physical server object is registered in the naming service under a logical string using the static method rebind(). Clients can then request a remote proxy to the server object by invoking the static method lookup() with the same logical string. Because the remote server object may exist on a different machine, Sun has chosen to use the form of a standard URL for communicating the location of the distributed object to the naming service.

Because of the magic of client-side "stub" code and server-side "skeleton" code, the remainder of the StoreImpl and StoreClient implementations remain the same. This "stub" and "skeleton" code is boiler-plate in form, and can be mechanically generated. That is the role of the Sun-supplied "rmic" utility.

The output below demonstrates the requisite build and execute steps. Notice that three different Command Prompt windows are required: the "rmiregistry" window, the server window, and the client window.

[The complete application can be found in listing 2.]

JNDI

Java Naming and Directory Interface (JNDI) is Sun's new industrial-strength naming service for the enterprise. It provides a common interface for obtaining "metadata" information about where objects and services may be found. The source of that information may be a heterogeneous collection of: directory services (e.g. LDAP), naming services (e.g. DNS), databases, file systems, etc.

Sun's original RMI naming service does not support: a hierarchical naming space, federation of existing name spaces, or integration of legacy naming and directory services. JNDI handles all of these.

To use JNDI, the remote class replaces its inheritance relationship to the class UnicastRemoteObject with the class PortableRemoteObject. Additionally, it replaces its invocation of the static method rebind(), with a call to the instance method rebind() defined on the InitialContext class. This class implements the Context interface and provides the starting point for resolution of names. A "context" object is like a directory - it consists of a set of name-to-object bindings.

You will notice that RMI's use of a URL "name" argument has been retired. Instead, a tree-like naming hierarchy can be built up by using simple Strings, and binding many child "context" objects to a parent "context" object.

The "stub" and "skeleton" remote proxy code is still generated by the "rmic" utility. But the argument "-iiop" is required, because JNDI uses CORBA's Internet Inter-ORB Protocol as its transport mechanism.

The transformation of the client application is very similar. One additional wrinkle is replacing the previous "brute force" downcast, with a much safer static method invocation. The narrow() method will ensure that the object being passed can indeed be cast to the desired type. One final detail is the new naming server. With RMI, the naming server is "rmiregistry". With JNDI, the server is the J2EE reference implementation server.

[The complete application can be found in listing 3.]

Servlet

Now that we have a working distributed application, let's supplement its current "command line" user interface, by adding a Web user interface. We could choose to use an applet, but they have proven to be less than: portable, scalable, and desirable. We could choose HTML pages and CGI scripts, but they are slow, ugly, and do not scale well. Instead, we'll use the server-side equivalent of an applet called a servlet.

The servlet runs as a single instance hosted by a Web "container" that is running on the server machine. Each client that wants to use the services of a servlet is vectored to a servlet instance by the Web container, and it shares the services of this servlet object with possibly many other clients. This architectural introduction is necessary because servlets exhibit the same default "stateless" behavior as the HTTP protocol.

In our application, we would like "stateful" behavior. As the user selects books, we would like to keep a per-user record of these selections, and then when '0' is entered, the list of accumulated selections is passed in the placeOrder() invocation. Fortunately, servlets provide a per-user "session" object that programmers can use as a general purpose "state" repository. We'll return to this issue in just a bit.

Like applets, servlets have a base class that performs all their "heavy lifting". Each servlet simply inherits from this base class. The servlet state that can be "shared" by all the multiplexed clients can be implemented as attributes of the servlet class. In this application, that consists of the reference to the remote Store, and the list of current titles (listing 1). Servlets are not initialized with constructors, they are initialized with an init() method that is invoked automatically by the Web container. All of our JNDI code, and our getItems() request can go in this method.

HTML pages and forms generate two kinds of requests: get, and post. Servlets have an entry point (i.e. method) for each: doGet(), and doPost(). For most purposes, they can be treated interchangeably; so we'll have one call the other, and we won't have to duplicate any functionality (listing 2).

A servlet needs to access the input coming from the user, and generate output destined for the user. That is the purpose of the two arguments specified in the doGet() and doPost() methods. The "request" object encapsulates the input parameters and the "session" capability. The "response" object encapsulates the output stream for all the dynamic HTML the servlet chooses to send back to the user.

To communicate with the user, the servlet must generate HTML. The conduit for getting that HTML to the right socket on the Internet is the PrintWriter object encapsulated in the "response" object. All the Strings that are output to the PrintWriter object will be interpreted in the user's browser as HTML. A discussion of HTML is beyond the scope of this article. The code in listing 3 will produce the screen dump in figure 1. How a URL maps to a particular servlet is vendor specific, and is not discussed here. The previous section implemented the initial data entry page. The next section demonstrates how to discriminate between the first invocation of this page, and all subsequent invocations. This is done using the mechanism for communicating "state" information between doGet() invocations - the HttpSession object. "Attributes" can be written to, and read from, this object very much like a HashMap or Properties object. If the requested attribute is null, then this is the first invocation. When this is the case, we want to create the attribute object and store it in the "session" object (listing 4).

When this is not the case, we know there is user input waiting to be retrieved in the HTML text input component called "choice". The contents of HTML input components can be retrieved by invoking getParameter() on the "request" object, and supplying the "name" assigned to the input component when it was created in HTML. If the user entered a non-zero value, then the designated selection needs to be remembered in the ArrayList object that is stored in the HttpSession object.

If the user entered a zero value, then it is time to send the ArrayList of choices to the remote Store object by invoking the placeOrder() method (listing 5). The formatted confirmation string that is returned will have its white-space "mangled" if it is sent directly to a Web browser. For this reason, the private method formatOrderConfirmation() has been written. It converts: embedded carriage returns into "<br>" tags, and indentation into non-collapsible blank characters (figure 2).

When the user is done with this page, selecting the hyperlink called "Top" will cause flow-of-control to return to the very first page. At this point, we would like a new session to be instantiated for this user - so that the previous choices will be discarded. We can make this happen by "invalidating" the current session.

Since the browser window is entirely re-written everytime a new Web page is referenced, the "history" of previous selections disappears. To keep this "history" information visible, an HTML "unordered list" can be appended to each new rendering of the user input page. Listing 6 will add that additional list to the bottom of the page (figure 3). [The complete application can be found in listing 4.]

JSP

All the previous pw.println("...") code is fairly tedious. Servlets require writing Java code to produce HTML. Java Server Pages are a way of embedding Java code directly in HTML files. A different, and more thoughtful, characterization of JSPs is that they allow presentation to be focused in HTML files and control and domain logic to be encapsulated in Java code.

The HTML file in listing 7 demonstrates the basics of JSPs. It is standard HTML with Java code placed inside "<%" and "%>" delimiters. An additional feature demonstrated is the JSP expression. These are prefaced with a "<%=" delimiter, and allow the result of a Java expression to be "inlined" with HTML text. This file produces the screen dump in figure 4. [How a URL maps to a particular JSP is vendor specific, and is not discussed here.]

Let's return to our distributed Store application, and convert the pieces from the servlet discussion to JSP. Any significant algorithmic logic should not be placed in HTML files. This kind of processing should be placed in a Java Bean (a Java class that: implements java.io.Serializable, has a default constructor, and follows a few naming conventions for attribute accessor methods).

The first two lines in listing 8 are the beginning of our Store JSP file (the remainder of the code belongs to a .java source file). The second line demonstrates the syntax for creating an instance of a Java Bean. The name of the object will be "store", the class that will be instantiated is "article.jsp.StoreBean", and the scope of the object will be the same as in the previous servlet. The JSP engine (that is supplied with, or bolted on to, the Web server) will generate all the servlet code that developers used to have to write themselves.

The code for the first screen dump in the servlet discussion is shown in listing 9. The top nine lines appear in the JSP file. The "jsp:getProperty" tag is new. This invokes a getItemList() method [note the "get" prefix and the use of camel-case capitalization] on the "store" object. The signature of a "get" method should accept no arguments, and return a type that will be inserted into the HTML stream.

The bottom seven lines implement getItemList() in the StoreBean class. Normally, presentation code is confined to an HTML file, and application processing functionality is partitioned in a Java Bean. This convention was bent in listing 9 - the "<li>" tag was put in the Java Bean getItemList() method to simplify the HTML file.

The next block in the previous servlet discussion deals with handling "choice" input from the user. The refactoring to a JSP implementation is given in listing 10. Because we chose to define a Java Bean in the previous step, we no longer need an HttpSession object to support retained state.

The "jsp:setProperty" tag is new. It is another point of leverage offered by JSP. We no longer need to invoke getParameter() methods on a servlet "request" object to retrieve URL and form parameters. We specify an HTML "widget" that should be mapped to a "set" method in our Java Bean [note the "set" prefix and the use of camel-case capitalization]. The signature of the "set" method should accept one String argument and return void. The top line in listing 10 goes in the JSP file, and causes the form parameter called "choice" to be mapped to the "str" argument of the setSelection() method of the "store" object.

Continuing to march through the previous servlet discussion, the next chunk of functionality is placing the order with the remote Store object and displaying the confirmation string returned. In order to simplify the code that would otherwise be required in the JSP file, an isDone() abstraction was added to the Java Bean (see listing 11). If isDone() returns true, the next five lines in the JSP file are interpreted.

The first of those lines is another "jsp:getProperty" tag. This causes the getResponse() method to be invoked on the "store" Java Bean, and the method's return value to be inserted into the HTML stream. The final two instructions in the JSP file are embedded Java code. The JSP framework automatically creates an HttpSession object with the variable name "session". This is where the Java Bean object that was created with the "jsp:useBean" tag was stored. [You will recall it was declared to have "session scope" above.] The invalidate() request will cause the Bean to be released, so that its retained state is jettisoned.

The last remaining piece of the servlet to be converted is appending the "history" of previous selections. This was implemented with a JSP expression ("<%=" delimiter) strictly for demonstration purposes (listing 12). It could have just as well been implemented with a "jsp:getProperty" tag. The Java code that appears after the "<%=" is expected to return a value that can be inserted into the HTML stream. Again, HTML was embedded in the Java Bean to simplify the JSP file. [The complete application can be found in listing 5.]

EJB - Stateless Session Bean

We now have three "clients" for our Store server: the original stand-alone application, the servlet, and the JSP. Let's return to our JNDI demonstration, and refactor it to use Enterprise JavaBeans.

EJB is the "component model" for the J2EE framework. Its goal is to offload Enterprise implementation issues like transaction processing, security, persistence, resource pooling, and scalability from the EJB component developer. The EJB app server (with its "container" abstraction) will manage these details for all its Enterprise Beans. But in order to do that, the container totally decouples each bean from the clients requesting its services. This decoupling (or "insulation") is the primary factor in all the architectural elements discussed in this section; and, significantly affects what EJBs will be allowed to do for themselves. For example, EJBs cannot:

There are two types of EJBs: session, and entity. A Session Bean is intended to model a "workflow" controller, or an "event" mediator. Session Beans are designed to be one of two flavors: stateless (like the HTTP protocol), or stateful (like a shopping cart application). An Entity Bean is intended to model a persistence business object.

The current implementation of our Store server maps nicely to a stateless Session Bean (SLSB) - the client maintains knowledge of previously selected Items, and the Store is only responsible for responding to getItems() and placeOrder() requests. In subsequent increments, we will: transform the Store into a stateful Session Bean (SFSB), and refactor the Item class into an Entity Bean.

Reworking the JNDI Store server as a SLSB will require remarkably little massaging. Every EJB has three architectural elements: a remote (or "business") interface, a home (or "factory") interface, and a bean implementation. The first of these is exactly what we defined for our original RMI demonstration. The only difference is the requirement to inherit from the javax.ejb.EJBObject interface, instead of the java.rmi.Remote interface.

The "home" interface encapsulates what could be described as "life cycle" services: create, and find methods. Because the EJB framework will be responsible for generically creating, initializing, caching, and reusing any and all bean instances; an architecture had to be set up that the framework could use at deploy time (in conjunction with the Java reflection mechanism) to discover and characterize all "create" and "find" entry points.

"The home interface of all SLSBs contains one create() method with no arguments. This is a requirement of the EJB specification. It is illegal to define create() methods with arguments, because SLSBs don't maintain conversational state that needs to be initialized. There are no find() methods in Session Beans because Session Beans ... do not represent data in a database."   [ 7 ]   The following interface was added to our Store component.

The Store server no longer needs to inherit implementation from javax.rmi.PortableRemoteObject. That class' role was to provide the "remote procedure call" framework for distributed servers. With EJB, each bean is not the distributed server. Each bean is the incarnation of the business functionality the client is seeking, but the client never actually references the bean (or a bean proxy) directly. The app server's container implementation totally insulates the bean and the client from one another. The inheritance relationship in the JNDI implementation has been replaced with an aggregation-delegation relationship in the EJB architecture.

In order to realize this "collaboration at arm's length" between a client and the insulated bean, an interface is required - SessionBean in this case. This interface specifies the "technical contract" that the container requires of the bean, in order for the container to accomplish all its scalability and decoupling magic.

The Store server does not actually implement the remote interface anymore. This is because the previous "is a" relationship has now been replaced with a "has a" relationship inside the EJB app server's container. The programmer implementing the bean (not the compiler) is responsible for ensuring that all business methods are defined. As a final step in the development process, a vendor-specific "deployment tool" will ensure that all contracts and conventions have been satisfied.

Because the app server's container needs to be able to create, share, and reuse bean objects at will (in order to achieve its scalability goals), initialization of a bean instance is now the responsibility of the ejbCreate() method. This method is called by the create() method declared in the home interface above. The EJB framework is able to map the one to the other through the aggregation-delegation relationship introduced earlier. Note that the return type for create() is the remote interface, but the return type for ejbCreate() is void. This is the convention for Session Beans. All that remains on the server side is to provide implementations of the four methods mandated by the SessionBean contract. The first three are associated with "swapping" a potentially large number of virtual beans in and out of some scalable number of real objects. The last provides an access point for referencing "environment" information. These methods provide the mechanism for handling "state" information, so we'll revisit them when we discuss Entity Beans. We are now ready to discuss what changes are necessary in the client application. With RMI and JNDI, a server object either had to be explicitly started on the server, or "activated" as the result of a client request. For scalability reasons, we would rather hide these kinds of implementation details behind a "factory" interface. The factory abstraction is solely responsible for choosing when and where Session Bean objects are created, and how these objects are shared, swapped, and retired.

For this reason, the client of a Session Bean does not "look up" the server object. Instead, the client looks up the factory object. This object is generated by the EJB framework, but the JNDI name that the object is bound under is specified during the deployment process. After finding the factory object, the client is required to explicitly ask for a server object to be "created". This notion of "create" is entirely abstract. What the factory object does in response to this request is "nobody's business" but its own.   [ 5 ]

No other changes are required in the client application - the "remote store" abstraction functions just as it did previously.

[The complete application can be found in listing 6.]

EJB - Stateful Session Bean

In our previous increments, the client has been responsible for maintaining the list of all Items chosen to date. It would be a reasonable act of grace on the part of the server to maintain this list for the client. To this end, let's transform our current SLSB into an SFSB (stateful Session Bean).

The "business" interface will need to change. This is generally discouraged because of the impact it will have on the client community. But, considering the remarkable reduction in effort that will be afforded those clients, they will be only too happy to accept that relatively minor inconvenience. The changes to the remote interface include: add a selectItem() method, and remove the parameter to the placeOrder() method.

Because the Session Bean now holds "state" information, there are several differences from the previous section that need to be highlighted. The server will now be responsible for maintaining the ArrayList object called "theChoices". It has migrated from being a local variable in the client's main(), to being a private attribute in class StoreImpl. The implementation of the placeOrder() method no longer requires a parameter. Instead, it will reference the attribute "theChoices". The method selectItem() has been added to update that attribute. In the client application, instead of calling add() on the ArrayList when a selection is made; selectItem() will be called on the remote Store server. Additionally, the call to placeOrder() no longer requires an argument. There are quite a few subjects that have not been discussed here. These include: passivation, activation, transactions, concurrency, reentrancy. For the sake of limiting the length of this article, that information has not been included.

[The complete application can be found in listing 7.]

EJB - CMP Entity Bean

It is now time to make the Item class more interesting. We will add price and totalOrdered attributes to the class. The first will be fixed when each Item object is created; the second will accumulate the number of copies of this Item ordered by all clients. A sample client interaction is demonstrated in listing 13. When the current client connected, we see the total number of Items sold to date was three. Two copies of the first Item and one of the second are ordered. When the next client visit occurs, the total sold now reflects six. The purpose of Entity Beans is to manage concurrent access to shared persistent data. There are two ways of handling the persistence: container managed persistence (CMP), and bean managed persistence (BMP). With the former, the app server's "container" capability writes all the SQL, and does all the work to store and retrieve Bean state from a database. With the latter, the Bean developer is responsible for all the SQL code. We will discuss CMP first.

An Entity Bean requires the same three architectural elements as a Session Bean: a remote (or "business") interface, a home (or "factory") interface, and a bean implementation. In listing 14, the remote interface declares a getSummary() method that will replace the previous toString(). This is to emphasize that the class will now be reporting the name, price, and totalOrdered for each object. An order() method has been added that will increment the totalOrdered attribute, and, will also return the price of the Item object.

The home interface's purpose is to provide a level of indirection capable of providing the same quality of decoupling that a factory affords (see the discussion in the "Starting point" section at the top of the article). Because Entity Beans represent persistent data, the client is expected to first search for the desired object, and if the object does not exist, then ask for the object to be created.

Each Entity Bean home interface must declare at least one "find" method - findByPrimaryKey(). This method shall take one argument - the type of the primary key that has been designed to uniquely identify each instance of the Entity Bean. At a minimum, the primary key must be a class that implements java.io.Serializable. If the type of the primary key is a primitive type (as is the case here), then that type must be "promoted" to its java.lang wrapper type equivalent. The method shall return the remote interface type.

Any number of "find" methods may be declared. The only restrictions are: the name of the method must provide the EJB framework sufficient insight so that it can infer a mapping from the find method's name to its corresponding implementation class attribute, and, each method must return either the remote interface type or the java.util.Collection type. An example would be - Collection findByPrice( Integer price ). The findAllInstances() method declared in listing 14 is a common (but optional) convention.

As with Session Beans, the bean implementation class does not directly implement the remote and home interfaces, it implements the EntityBean interface (listing 15). For container managed persistence, the attributes to be persisted must be public. This is because the EJB framework is doing the work of writing the attributes to persistent storage, and it does not enjoy any priviledged visibility into the bean implementation class. The SLSB section introduced the requirement that there must be one ejbCreate() method in the bean implementation class for each create() method in the home interface. By convention: the return type of ejbCreate() is the type of the primary key, the argument list must match that of its corresponding create(), and the value returned is always null (listing 16).

When ejbCreate() is invoked by the container, the Entity Bean does not have a fully formed identity. If the Bean needed to reference that identity, or pass it to some other component, that should not be done in ejbCreate(). For these situations, an ejbPostCreate() method has been specified. Its argument list should match that of the corresponding ejbCreate(). Its return type is void. Its implementation is routinely empty, unless, the developer needs to do one the things just described.

The EntityBean interface (that the bean implementation class affiliates itself with) mandates the same four methods that were briefly discussed at the end of the SLSB section. In addition, it introduces three more methods. The first two, ejbLoad() and ejbStore() (listing 17) are associated with keeping each bean instance "synchronized" with its corresponding record in the underlying database table. Normally, the CMP implementation will automatically map and maintain the entire persistence mechanism. Only in cases where the complexity of the bean's attributes outstrip the CMP capability of your app server vendor, will the developer be obliged to define implementations for these methods.

The final "new" method is unsetEntityContext(). This is called by the EJB framework as the final response to an invocation of the remove() method. The developer is encouraged to assign null to the context attribute, for reasons similar to the practice of assigning null to a deallocated pointer in C. Returning to the methods shared with the SessionBean interface, ejbActivate() and ejbPassivate() are used to support "swapping" logical bean instances in and out of physical bean hosts (not unlike the symbiont-host relationship of the Trill race in Star Trek). These entry points should be used to close (and subsequently reopen) resources (e.g. a JDBC connection) that should not remain active while the bean is in "hibernation". Additionally, ejbActivate() should always initialize the bean class' primary key attribute.

The SLSB discussion briefly mentioned the SessionContext object. Entity Beans have a comparable EntityContext object. This object is created by the EJB framework before ejbCreate() is invoked. It provides access to elements of each bean's "environment" like: the associated EJBObject wrapper object, the associated EJBHome factory object, the primary key, transaction elements, and security information.

We are through implementing the "server" side of Item Entity Beans. It is now time to consider the "client" side. Entity Beans are fairly "fine grain" components. Allowing clients to access them directly across the network can quickly bring application and network performance to its knees. For this reason, it is highly recommended that clients should only interact with Session Beans, which in turn manipulate Entity Beans as appropriate.

The "client" of Item Entity Beans is the Store Session Bean. Its first order of business is to retrieve all current Item beans. This will involve acquiring a reference to an implementation of the bean's home interface, and requesting findAllInstances() (listing 18). If the Collection that is returned is empty, then no instances exist in the persistent store yet. The only option available at this point is to create a set of default instances.

Store's itemList attribute no longer contains lightweight, serializable objects. Instead, it contains proxies to remote Entity Beans. We don't want the getItems() method returning this ArrayList object directly to its client, because that would entail the client making remote method invocations across the network every time it needed a price or total check.

We can address this issue by having getItems() return a Collection of "summary objects" - an abstraction that provides just enough state and behavior for the client to accomplish its responsibilities (without having to make round-trip requests across the network). [See listing 19.] In this simple design, the summary object is nothing more than a String. As a result, the client of the Store Session Bean doesn't change at all. Before the Item class became an Entity Bean, the collection of objects returned from getItems() each responded to an implicit invocation of toString() when it was passed to System.out.println(). Now, the collection of String objects returned are output directly to the client's standard out.

The placeOrder() method in the Store Session Bean has changed slightly to accomodate the expanded functionality of Item objects (listing 20). Each Item Entity Bean maintains a persistent record of the total orders placed against it. This attribute is incremented by calling the order() method. Strictly for demonstration purposes, order() was also designed to simultaneously provide a "getPrice" capability. [The complete application can be found in listing 8.]

EJB - BMP Entity Bean

Converting an Entity Bean from CMP to BMP is basically an exercise in trudging through a lot of JDBC code. For this reason, only the highlights will be presented here. A complete implementation can be found in listing 9 at www.javareport.com.

Each of the entry points mandated and called by the EJB framework generally map to a single SQL statement -

Because the bean itself is now responsible for its own persistence, all the attributes of the bean implementation class migrate from public to private. ejbFindByPrimaryKey() should return the primary key object, or throw a FinderException. ejbFindAllInstances() should return an object of type Collection, or throw a FinderException. ejbCreate() should return the primary key object, or throw a CreateException. The implementation of ejbLoad() is a superset of the code required for ejbFindByPrimaryKey(). ejbActivate() and ejbPassivate() are the same as CMP.

[The complete application can be found in listing 9.]

Summary

In this article, we started with a simple "store" capability (encapsulated behind an interface and a factory method), and incrementally embellished it to demonstrate the major pieces of the J2EE framework. We made Store a "distributed object" with RMI. The new JNDI naming service was swapped in. Two new Web-based user interfaces were added with a servlet and a JSP [see myweb.onramp.net/~huston/j2ee/incremental_j2ee.html]. Store was "componentized" for the enterprise using EJB - first with a Session Bean, and then, with an Entity Bean.

All these architectural elements contribute to the scalability and maintainability of enterprise information systems. They provide a "component model" capable of supporting deploy-time specification of transactions, security, and collaborations. Additionally, they promote flexible multi- tier, distributed designs that encourage the segmentation of responsibilities into: model, view, and controller layers.

J2EE will continue to grow. The discussion offered here was intended to introduce a significant subset of its current state.

References

  1. Peter Fischer, "J2EE earns Java a station in the enterprise", Application Development Trends, July 2000, p29
  2. Dirk Riehle, Erika Dubach, "Working with Java interfaces and classes", Java Report, July 1999, p35
  3. Jim Waldo, "Dynamic lingo", Performance Computing, July 1998, p25
  4. Gamma, Helm, Johnson, Vlissides, Design Patterns, Addison-Wesley, 1995, pp107-116
  5. Mark Davis, "Abstraction", Java Report, June 1999, p86
  6. Kathy Bohrer, "Why not just create components with new", Distributed Computing, Jan/Feb 1999, p60
  7. Richard Monson-Haefel, Enterprise JavaBeans, 2nd edition, O'Reilly, 2000, p182