Implementing the EJBs


To implement our business logic with EJBs using WebSphere Studio, we will take the following steps:

  1. Create a new EJB Project

  2. Create the ShoppingCart stateful session bean

  3. Create the Order CMP entity bean

  4. Create the subordinate OrderItem CMP entity bean

  5. Create the utility OrderIdGenerator CMP entity bean

  6. Create a container-managed-relationship (CMR) between Order and OrderItem

  7. Create the Inventory CMP entity bean and define its customer finder methods

  8. Create the Customer CMP entity bean

  9. Create ancillary session beans required by future chapters

  10. Map the CMP entity beans to a relational database

  11. Specify J2EE references to EJBs and resources required by the EJBs

  12. Integrate EJBs with web module model objects

Create a New EJB Project

To add an EJB project to our existing Plants-By-WebSphere application, select menu option File | New | EJB Project. This will take you to the first dialog in a series that will step you through EJB project creation. The first dialog window enables you to choose the specification level of your EJB project:

click to expand

WebSphere Studio version 5.0 supports both the EJB 1.1 and 2.0 standards. We have to choose one now, so we'll choose to create a 2.0 EJB project, because we want to use the EJB 2.0 features such as CMR and EJB Query Language later on. Click Next to continue:

click to expand

We'll need to name our EJB project, so enter PlantsByWebSphereEJB for the project name.

EJB project are part of an enterprise application project, so we can either create a new enterprise application or add it to an existing one. Since we already have our PlantsbyWebSphereEAR enterprise application, we'll add our EJB project to that.

Click the Finish button to create the EJB project:

Create the ShoppingCart Bean

Now that we have our EJB project, we're ready to create our first EJB. Select menu option File | New | Enterprise Bean. This will take you to the first dialog in a series that will step you through enterprise bean creation. The first dialog window enables you to designate to which project this new enterprise bean will belong:

click to expand

Choose the PlantsByWebSphereEJB. Click the Next button to continue. Next choose the type of 2.0 enterprise bean that we want to create. We can choose any of the available EJB 2.0 EJB types:

click to expand

The message-driven Bean (MDB) type, introduced in EJB 2.0 is a new type of EJB that is assigned to a Java Message Service (JMS) message queue. When a message arrives on the queue, the MDB is dispatched to process the message. MDBs are useful for performing asynchronous processing. We won't use an MDB for this part of the Plants-By-WebSphere development. However, in Chapter 7, we will take you through the construction of an MDB.

Naturally, session and BMP entity beans are available choices. We will be using session beans in Plants-By-WebSphere. BMP beans, unlike CMP beans, manage their own persistence, which is necessary whenever the requirements for managing an entity's persistent state exceed the capabilities of available tooling. WebSphere Studio includes powerful relational mapping tools for building CMP entity beans, but it does not address every need. Non-relational data stores and complex mappings demand a BMP approach.

With a CMP entity bean, you have two choices: either CMP 1.1 or CMP 2.0 bean style. The component models of these two specification levels are dissimilar. The 1.1 style places the CMP fields directly in the bean itself and business logic may freely access them. The 2.0 style formalizes CMP field access by declaring the only access to them is through getters/setters, which are created through tooling. The generated code is a subclass of the bean itself. This pattern provides a more standardized structure for tools to add-in persistence support for CMP beans.

On the Create an Enterprise Bean dialog, select Session bean, specify the Bean name ShoppingCart and Default package com.ibm.pbw.ejb. Click Next to continue:

click to expand

On the preceding dialog, we have a choice of Stateful or Stateless session bean. We're building a business object to represent a shopping cart. The shopping cart will hold items selected by the shopper either until the shopper checks out, or until the shopper changes their mind and leaves our electronic storefront. Our shopping cart obviously has state, so must be declared as stateful.

WebSphere offers two models for stateful session beans. The first model is strictly in-memory, meaning the session bean's state is maintained in the memory of the application server. The second model stores the session bean's state to the local file system in between transactions. The first model offers the best performance; the second model offers potentially longer availability session bean state, since it can survive across restarts of the application server. The selector for this choice is referred to as Activation Policy. This is an assembly-time decision and is discussed in the Chapter 6.

This dialog also gives us the choice of local or remote interfaces for our session bean. We could have either or both. As you would expect, declaring a remote interface enables our session bean to be accessed from another process or machine. A local interface may be accessed only from within the application server. While offering more limited access, the local interface offers an important performance advantage. Methods on remote interfaces must support a costly pass-by-value semantic for parameters and return values. Obviously this is necessary if the caller is in another process or machine. However, the EJB specification requires this same behavior for remote interfaces even when the caller is co-located in the same process as the target remote interface. This was a significant barrier for EJB 1.1 beans and discouraged the creation of finer-grained objects due to performance concerns. Local interface methods use simple Java by-value parameter and return value semantics and provide much better performance.

We will specify only a local interface for our ShoppingCart session bean. When you choose only a local interface, you are making a design decision that affects the distribution capability of your components. In our case, since the Plants-By-WebSphere web components will interact with our EJBs through the local Java model objects, we are constraining our web and EJB components to be co-located in the same application server. That is acceptable for our purposes. Depending on your application architecture, that may or may not be acceptable. A natural way to remove that constraint is by providing a remote interface to your EJB.

OK, that was a bit of a side trip! The remainder of the work for us to do on this dialog is to specify the names of our EJB artifacts: bean class, its local interface, and the local home interface. You'll notice that WebSphere Studio suggests names that include the word "Local" as part of the artifact name, since we indicated we want a local client view. However, we'll modify these names to be ShoppingCartBean, ShoppingCart, and ShoppingCartHome, respectively, since we don't have a remote client view and therefore don't need the "Local" qualification for our names. Note that the default JNDI lookup name is formed from the fully qualified home interface name. Click the Finish button to move on.

At this point, let's switch our view from the J2EE Navigator to the J2EE Hierarchy. This view more clearly shows the organization of our components in their containing modules, which is consistent with packaging terminology described in the EJB specification. By expanding our ShoppingCart EJB, we can see its constituent parts: local home interface, local interface, and the bean itself. Double-click on the ShoppingCart bean to open the EJB Deployment Descriptor editor. We will do more work with the deployment descriptor further on, for now let's just modify the JNDI name for our ShoppingCart. In our application we will use a simple two-part naming convention: plantsby/<home interface name>. So enter plantsby/ShoppingCartHome:

click to expand

Implement the Business Methods

Now that we've created the basic parts that compose our ShoppingCart stateful session bean, let's add in our business methods. Business methods, as the name suggests, are those methods that provide client access to the business logic encapsulated in the bean. Business methods may provide varied degrees of functionality; for instance, methods that:

  • Enable a client to manipulate the state of the EJB; such as adding an item to a shopping cart.

  • Perform transformations against the state of the EJB; such as calculating a purchase total based on the items in the shopping cart.

  • Apply business rules to the EJB, possibly involving interaction with other EJBs. For example, although not a part of our design, implementing a business rule that requires maintaining a statistic of the 10 most frequently selected shopping items.

Methods declared on any of the remote home, local home, remote interface, or local interface constitute the public client interface to the EJB. Methods declared on any of these interfaces require an implementation in the EJB's bean class. While you probably already know this, it may be helpful to visualize these relationships before diving into the code. The following diagram depicts how the interfaces and bean class relate to one another:

click to expand

Each interface pair: remote and local home interface, and remote and local interface, may declare the same or different methods. As depicted in the preceding diagram, uniquely named interface methods, such as create(), and createLocal() on the remote and local home interfaces, respectively, map to correspondingly unique implementation methods in the bean class. However, if the same method is declared on both interfaces of an interface pair, such as addItem() on the remote and local interfaces, then both method instances map to the same named implementation method in the bean class.

In the case of our ShoppingCart, we have no methods on our local home, and only the addItem() and getItems() methods for our local interface. These were the ones introduced on the CartModel, as described earlier in this chapter. To add our methods, we'll need a code editor. We'll do the local interface first. From the J2EE Hierarchy view, double-click the ShoppingCart local interface to open a code window:

Next, add the method signatures for addItem() and getItem():

public interface ShoppingCart extends javax.ejb.EJBLocalObject {   public void addItem(StoreItem si);   public Vector getItems(); }

We'll also need the import statements necessary to resolve the types we reference. Add an import for java.util.Vector. We also need to resolve the reference to StoreItem, but this class presents a new challenge.

com.ibm.war.StoreItem and com.ibm.war.CustomerInfo

StoreItem is a simple helper object that represents a simple, non-persistent view of a single inventory item in our Plants-By-WebSphere store. CustomerInfo provides the same function for customers. They were introduced in the presentation logic back in Chapter 4 as part of the model layer so there would be insulation between the presentation and business logic, enabling the two to be developed independently of one another. These two helper classes end up being shared between J2EE modules – between our web and EJB modules.

Since we introduced these helper classes in our web module, we now have a visibility problem between our web and EJB modules. We expected our web module to reference our EJB module, but not the other way around. This would create a circular dependency, which detracts from our ability to use business logic in another application – an application with a different presentation, for instance. This dependency would force us to drag our web module along with our EJB module as its shadow. This is not something we want to have happen. We'll fix this now by moving StoreItem and CustomerInfo from the web module to our EJB module. We will also change the package name from com.ibm.pbw.war to com.ibm.pbw.ejb to be consistent with the packaging rules we've been applying thus far in our Plants-By-WebSphere development.

Relocating StoreItem and CustomerInfo from the PlantsbyWebSphereWAR module to the PlantsbyWebSphereEJB module will leave behind a number of unresolved references in the presentation logic's servlet and JSP pages that simply adding import statements alone will not satisfy. To establish visibility between these two modules, we must update PlantsbyWebSphereWAR project properties.

To update project properties, select the project in the J2EE Hierarchy, right-click, then select Properties from the pop-up menu. This will bring up a dialog that contains options for a number of project-related options. Among the more interesting ones are:

OK then, to fix our StoreItem and CustomerInfo visibility problem, we need to update the Java Build path. Select Java Build Path on the left, and the Projects tab on the right. A list of available projects is displayed. At this point, we only have two: the EJB project we're working on, and our web module project. Check the PlantsByWebSphereEJB checkbox to add that project to our build path. Click OK to save and continue:

click to expand

Then to move the two model classes, in the J2EE Navigator view simply select com.ibm.pbw.war.StoreItem and com.ibm.pbw.war.CustomerInfo from under the Java source folder and drag-and-drop them into the com.ibm.pbw.ejb folder of the PlantsByWebSphereEJB project. Studio can automatically correct all the references on your behalf:

click to expand

You will also need to update the JSP files manually to change any references to StoreItem or CustomerInfo to be fully qualified with their new package structure. For example, on product.jsp:

 com.ibm.pbw.ejb.StoreItem item = model.findById(id);

With our build problems solved, we can get back to our ShoppingCart EJB and implement the bean methods.

In the J2EE Hierarchy view, find and double-click ShoppingCartBean to open the bean class in a code window. A good way to get started with in the bean class is to cut and paste the imports and method signatures from the local interface.

Since the local interface methods operate on a vector – addItems() adds to it; getItems() returns it – let's define a local instance variable of type Vector to satisfy that need.

The items variable requires initialization. Nothing special, let's just assign a new Vector instance to the variable in the ShoppingCartBean ejbCreate() method, since that will receive control each time a new ShoppingCart bean is created:

package com.ibm.pbw.ejb; import java.util.Vector; public class ShoppingCartBean implements javax.ejb.SessionBean {   private Vector items = null;   public void ejbCreate() throws CreateException {      items = new Vector();     }

To finish out our work on this bean, we need to add the implementations for addItem() and getItem(). For addItem(), we'll add some simple logic to ensure that if the caller attempts to add the same item to the ShoppingCart more than once, that we detect the duplicate and merely update the quantity in case it is different. The getItem() method is trivial, as it merely returns the items vector:

   /**     * Add an item to the cart.    *    * @param new_item Item to add to the cart.    */   public void addItem(StoreItem new_item) {     boolean added = false;     StoreItem old_item;     // If the same item is already in the cart, just increase the quantity.     for (int i = 0; i < items.size(); i++) {       old_item = (StoreItem) items.elementAt(i);       if (new_item.equals(old_item)) {         old_item.setQuantity(old_item.getQuantity() + new_item.getQuantity());         added = true;         break;       }     }     // Add this item to shopping cart, if it is a brand new item.    if (! added)      items.addElement(new_item);   }   /**     * Get the items in the shopping cart.    *    * @return A Vector of StoreItems.    */   public Vector getItems() {     return items;    } 

Create the Order EJB

The OrderModel contains only a single method:

public class OrderModel {   public String addOrder(CustomerInfo info, Vector orderItems) {     return "123";   } }

Since an order is actually a collection of order items (the things being ordered) and some additional information about the order itself, such as name/address of customer and shipping name/address, we'll have to design a scheme for handling the list of items contained in the order. When we only had EJB 1.1, we might have done something like represent the item list as a serializable object and would have mapped it to a BLOB field in the database. While that approach would work, it makes it difficult to fully exploit the strengths of the database, with regard to queries over the order items.

The EJB 2.0 specification offers additional capabilities that are useful for modeling master-detail type business abstractions, such as order-orderItem. Container-managed-relationships (CMR) and EJB query language (EJB QL) are two EJB 2.0 features that are helpful in these cases. We will use both of these before we are finished with the Order EJB.

So while building our Order EJB, we'll create an additional CMP EJB, which we'll call OrderItem, to model the detail portion of the Order abstraction. Entity beans are key-referenced persistent objects. We'll need a way to generate key values for our Order EJB. Order numbers are not user-assigned; they are generated as part of the application. For our purposes, we'll define a simple OrderIdGenerator EJB, which will also be a CMP EJB. This will provide a simple way to manage a persistent "next order ID" value. The following diagram depicts the relationship of our Order EJB and the other EJBs that support it:

click to expand

We'll start by creating the Order EJB itself. Select menu option File | New | Enterprise Bean to bring up the Create an Enterprise Bean dialog again. This time we'll create a 2.0 CMP entity bean, and name it Order:

click to expand

Click Next to continue. For the same reason we outlined for ShoppingCart, we'll define only local interfaces for our Order CMP bean. We could always add a remote interface to our Order implementation if requirements demanded such a thing in the future. For now, we'll stick with local. Again, let's remove the term "Local" from the default names that WSAD generates for us automatically. In keeping with our Plants-By-WebSphere naming convention, let's also change the JNDI binding name from ejb/com/ibm/pbw/ejb/OrderHome to just plantsby/OrderHome:

click to expand

Since EJBs are keyed objects, we'll need a key class. WebSphere Studio suggests a default key class, OrderKey, which is fine, so we'll stick with it.

As per the EJB specification, entity beans have container-managed fields, and possibly container-managed relationships. These are defined in the entity bean's deployment descriptor. There are three ways we can define CMP fields:

  • Through the EJB creation dialog

  • Through WebSphere Studio's Deployment Descriptor editor

  • By directly editing the ejb-jar.xml deployment descriptor file

We'll add our CMP fields through the creation dialog. We can always go into the Deployment Descriptor editor later if we need to add, change, or delete anything. We'll leave the direct editing the ejb-jar.xml file to those of you who prefer wilderness survival and forging steel by hand.

Adding CMP fields through the dialog is easy, just click the Add button on the Create Enterprise Bean dialog and add the fields through the following dialog:

click to expand

Enter field name and field type. We must also consider the following options and check any that apply:

  • Array – check this box if the field is not a single item, but rather an array of items of the same type

  • Key Field – check this box if the field is the key for this EJB or a part of the key

  • Promote getter/setter methods to remote interface – check this box if a getter and setter should appear in the remote interface for this EJB

  • Promote getter/setter methods local interface – same as previous option, except it applies to the local interface.

The Order EJB comprises the following fields:

Field Name

Field Type

Additional Options

orderId

java.lang.String

key field

sellDate

java.lang.String

customerId

java.lang.String

billName

java.lang.String

billAddr1

java.lang.String

billAddr2

java.lang.String

billCity

java.lang.String

billState

java.lang.String

billZip

java.lang.String

billPhone

java.lang.String

shipName

java.lang.String

shipAddr1

java.lang.String

shipAddr2

java.lang.String

shipCity

java.lang.String

shipState

java.lang.String

shipZip

java.lang.String

shipPhone

java.lang.String

creditCard

java.lang.String

ccNum

java.lang.String

ccExpireMonth

java.lang.String

ccExpireYear

java.lang.String

cardholder

java.lang.String

shippingMethod

int

profit

float

We can enter each field in turn. Be sure to mark field orderId as the key field. After these fields have been defined, close the attribute dialog and click the Finish button on the EJB creation dialog.

Note

The pre-release version of WebSphere Studio we used to build this application failed to generate the OrderKey class that we specified when we initially created this EJB. Because we specified only a single key field, Studio generated the signature of the default create and finder methods on the local home interface implementation using only the same type as the key field:

public Order findByPrimaryKey(String orderID) throws javax.ejb.FinderException; public Order create(String orderID) throws javax.ejb.CreateException;

rather than:

public Order findByPrimaryKey(OrderKey pKey) throws javax.ejb.FinderException; public Order create(OrderKey pKey) throws javax.ejb.CreateException;

This small oversight should be addressed in WebSphere Studio by the time you are reading this book, since the general release version of Studio will be out by that time. However, if for any reason you find yourself using a level of Studio that exhibits this same key construction behavior, you can apply the following technique to get Studio to do what you want. In the Deployment Descriptor editor, you will find the following set of controls on the Bean tab:

click to expand

These controls allow you to configure the primary key. To force Studio to build the key class that we specified, simply add an additional field to the primary key, for example sellDate. Studio will then build our specified key class, OrderKey, and rebuild the default create and finder methods. Then simply remove sellDate from the primary key. Studio will then rebuild the OrderKey class – this time based only on our remaining key attribute, orderId. Voila! We have our key class.

While we're thinking about primary keys and home methods, we should add a custom create() method to OrderHome to enable us to create a new Order, passing all necessary values required to have a fully initialized order as input parameters. Let's add the following signature to OrderHome:

 public Order create(String customerID, String billName, String billAddr1,                      String billAddr2, String billCity, String billState,                      String billZip, String billPhone, String shipName,                      String shipAddr1, String shipAddr2, String shipCity,                      String shipState, String shipZip, String shipPhone,                     String creditCard, String ccNum, String ccExpireMonth,                      String ccExpireYear, String cardHolder, int shippingMethod,                      java.util.Vector items)      throws javax.ejb.CreateException; 

Create OrderItem and OrderIdGenerator EJBs

Following the same basic steps that we took to create the Order bean, create the OrderItem and OrderIdGenerator 2.0 CMP EJBs.

OrderItem has the following attributes:

General Attributes

Bean class

com.ibm.pbw.ejb.OrderItemBean

JNDI Name

plantsby/OrderItemHome

Local home interface

com.ibm.pbw.ejb.OrderItemHome

Local interface

com.ibm.pbw.ejb.OrderItem

Key class

com.ibm.pbw.ejb.OrderItemKey

CMP Attributes

indx (primary key)

int

inventoryId

java.lang.String

name

java.lang.String

pkgInfo

java.lang.String

price

float

cost

float

quantity

int

category

int

sellDate

java.lang.String

We'll also need the following custom create() method on the OrderItemHome interface:

 public OrderItem create(Order order, OrderKey orderKey, String inventoryID,                          String name, String pkgInfo, float price, float cost,                         int quantity, int category, String sellDate, int indx )     throws javax.ejb.CreateException; 

OrderIdGenerator has the following attributes:

General Attributes

Bean class

com.ibm.pbw.ejb.OrderIdGeneratorBean

JNDI Name

plantsby/OrderIdGeneratorHome

Local home interface

com.ibm.pbw.ejb.OrderIdGeneratorHome

Local interface

com.ibm.pbw.ejb.OrderIdGenerator

Key class

com.ibm.pbw.ejb.OrderItemKey

CMP Attributes

pKey (primary key)

int

orderId

int

Create Order-OrderItem CMR

The container-managed relationship, or CMR, support specified by the EJB 2.0 specification is a very useful capability for use in modeling and generating the implementation code for relationships between 2.0 CMP EJBs. The specification supports one-to-one, one-to-many, and many-to-many relationships. The way it works is that the related bean(s) is exposed as a CMR field – in the same spirit as a CMP field – in the source bean. WebSphere Studio automatically generates a getter and setter for the CMR field. A get or set on the CMR field triggers all necessary underlying events on the related bean(s) to either load it into the container or establish it as part of the relationship with the source bean. This saves us writing a bunch of code by hand.

To define a CMR, open the EJB Deployment Descriptor editor. Switch to Overview tab, if not already there. Scroll down until the area labeled Relationships 2.0 is in view. Click the Add button to open the Relationship definition dialog:

click to expand

Select Order on the left – this is the "from" side of the relationship. Choose OrderItem on the right – this is the "to" side of the relationship. Click the Next button to continue:

click to expand

Select Many as the multiplicity for OrderItem. Note that this makes the CMR field type in Order into a collection. Click Finish.

Our intention is to relate the underlying relational database tables through a foreign key relationship as depicted in the following diagram:

click to expand

This requires that we make the relationship part of the key. This is done in the EJB Deployment Descriptor editor:

click to expand

Select the Order relationship and click the Add to Key button. While this may not seem entirely intuitive, it will define the foreign key from OrderItem back to Order as part of the primary key of OrderItem. This is exactly what we want. This will become clearer once we do the data mapping for our CMP EJBs. We'll do this a little further on in this chapter.

Implement the Business Methods

At this level of our Plants-By-WebSphere design, there is no special business logic, no particular business rules, that we require from our Order EJB. We need only the business abstraction itself – a functioning order object. We already introduced the create method signatures for our Order and OrderItem EJBs. Now we just need to provide the implementations. As we have an EJB relationship (CMR) between these two EJBs, we need to add some additional code in their create methods in order to actually connect the Order to its related OrderItems.

The basic logic goes like this:

Order

  • Get an OrderId

  • Store create parameters in CMP fields

  • Create OrderItem collection from StoreItem vector

  • Store OrderItem collection in CMR field

OrderItem

  • Store Order in CMR field

Since our Order and OrderItem hold a reference to one another, and we have chosen to establish this linkage during creation time, we have a minor complexity on our hands to address. This complexity results from the fact that an EJB does not yet exist during the middle of its create() method processing. This is a problem for us because when we create an OrderItem EJB, we must pass it a reference to its associated Order EJB. The OrderItem EJB will update its CMR field with this Order EJB reference. When a CMR field is updated, there is some CMR maintenance logic that executes under the covers on behalf of the relationship. In this case, the CMR maintenance logic attempts to reference Order EJB. Since we're in the middle of Order's create() method, the Order EJB does not yet actually exist in the eyes of the EJB container. Since it does not yet exist, no reference to it can yet be made. Attempting to do so will cause an exception. Fortunately the EJB component model accounted for issues like these and includes a "post create" method in the bean-container contract. After ejbCreate() returns to the EJB container, the EJB now exists; the container then calls ejbPostCreate() to allow the EJB to do any additional initialization, including initialization that involves passing a valid reference to its local interface to other components.

Let's consider the following object interaction diagram, in order to get a better understanding of the logic flow:

click to expand

Order's ejbCreate() is driven by the EJB container in response to a call on OrderHome.create(…). In Order.ejbCreate(), we get a new orderId from the OrderIdGenerator. Order's ejbPostCreate() is similarly driven by the EJB container. In Order.ejbPostCreate(), we create the OrderItem collection. The EJB container calls ejbCreate() and ejbPostCreate() on each OrderItem we create. In OrderItem.ejbPostCreate(), we update the CMR field with our Order reference. Lastly, we update Order's CMR field with the fully-formed OrderItem collection.

OrderBean.ejbCreate() Code

This code is straightforward and predictable. It simply does three things:

  • Stores input parameters in CMP fields, using CMP field setter methods

  • Calls the OrderIdGenerator bean to get the next order ID

  • Builds and returns an OrderKey

     public OrderKey ejbCreate(String customerID, String billName, String billAddr1,                           String billAddr2, String billCity, String billState,                            String billZip, String billPhone, String shipName,                             String shipAddr1, String shipAddr2, String shipCity,                            String shipState, String shipZip, String shipPhone,                            String creditCard, String ccNum, String ccExpireMonth,                           String ccExpireYear, String cardHolder,                            int shippingMethod, java.util.Vector items)      throws CreateException  {    setCustomerID(customerId);   setBillName(billName);   setBillAddr1(billAddr1);   setBillAddr2(billAddr2);   setBillCity(billCity);   setBillState(billState);   setBillZip(billZip);   setBillPhone(billPhone);   setShipName(shipName);   setShipAddr1(shipAddr1);   setShipAddr2(shipAddr2);   setShipCity(shipCity);   setShipState(shipState);   setShipZip(shipZip);   setShipPhone(shipPhone);   setCreditCard(creditCard);   setCcNum(ccNum);   setCcExpireMonth(ccExpireMonth);   setCcExpireYear(ccExpireYear);   setCardholder(cardHolder);   setShippingMethod(shippingMethod);   String sellDate = Long.toString(System.currentTimeMillis());   //Pad it to 14 characters to sorting works properly   if (sellDate.length() < 14) {     StringBuffer sb = new StringBuffer(Util.ZERO_14);     sb.replace((14 – sellDate.length()), 14, sellDate);     sellDate = sb.toString();   }   setSellDate(sellDate);   int orderInt = OrderIdGeneratorHelper.nextId();   String orderID = Integer.toString(orderInt);   setOrderId(orderID);    OrderKey orderKey = new OrderKey(orderID);   return orderKey; } 

The OrderIdGenerator EJB is mapped to a relational database table consisting of only a single row. The primary key value of this single row is "1". The OrderIdGeneratorHelper class is a simple class that encapsulates the details of accessing the OrderIdGenerator EJB and getting the next order ID. This class handles the JNDI lookup on the OrderIdGeneratorHome, executes a findByPrimaryKey() method on that home to locate the one and only OrderIdGenerator EJB instance, then invokes the nextId() method on the OrderIdGenerator interface to acquire the next available order ID:

 package com.ibm.pbw.ejb; public class OrderIdGeneratorHelper {   static int nextId() {     int i;     try {       javax.naming.InitialContext ctx = new javax.naming.InitialContext();       OrderIdGeneratorHome orderGeneratorHome = (OrderIdGeneratorHome)          ctx.lookup("java:comp/env/ejb/OrderIdGeneratorHome");                 OrderIdGenerator orderIdGenerator =          orderGeneratorHome.findByPrimaryKey(new OrderIdGeneratorKey(1));       i = orderIdGenerator.nextId();       return i;     } catch(Exception e) {             e.printStackTrace();       throw new RuntimeException();     }   } } 

This requires us to add the nextId() method to the OrderIdGenerator EJB:

 public int nextId() {   int i = getOrderId();   i++;   setOrderId(i);   return i; } 

OrderBean.ejbPostCreate() Code

This method is where we create the OrderItem EJBs, aggregate them into a collection, then assign the collection to the Order EJB's CMR field. Naturally, we do a JNDI lookup to access the OrderItem EJB home and drive its create() method to create each OrderItem EJB:

 public void ejbPostCreate(String customerID, String billName,                            String billAddr1, String billAddr2, String billCity,                            String billState, String billZip, String billPhone,                            String shipName, String shipAddr1, String shipAddr2,                           String shipCity, String shipState, String shipZip,                            String shipPhone, String creditCard, String ccNum,                            String ccExpireMonth, String ccExpireYear,                            String cardHolder, int shippingMethod,                           Vector items)      throws CreateException  {    this.items = (Vector) items.clone();    Vector orderItems = new Vector();   OrderKey orderKey = (OrderKey) this.myEntityCtx.getPrimaryKey();   try {     javax.naming.InitialContext ctx= new  javax.naming.InitialContext();     OrderItemHome orderItemHome= (OrderItemHome)        ctx.lookup("java:comp/env/ejb/OrderItemHome");     for (int i = 0; i < items.size(); i++) {       StoreItem si;       si = (StoreItem) items.elementAt(i);       OrderItem item= orderItemHome.create(         (Order) this.myEntityCtx.getEJBLocalObject(), orderKey, si.getId(),                 si.getName(), si.getPkgInfo(), si.getPrice(), si.getCost()                 si.getQuantity(), si.getCategory(), si.getSellDate(), i);       orderItems.add(item);     }     setOrderitem(orderItems);   } catch (Exception e) {     throw new EJBException(e);    } } 

OrderItemBean.ejbCreate() Code

This method is straightforward; it simply uses the input parameters to set the OrderItem bean's CMP fields. Remember that the OrderItem key is composed of the CMP field indx and the Order-OrderItem CMR. This created an additional CMP field to hold the foreign key representing the relationship. We must store the orderId value from the orderKey parameter in order to persist the foreign key that makes the relationship actually work:

 public OrderItemKey ejbCreate(Order order, OrderKey orderKey,                                java.lang.String inventoryID, java.lang.String name,                               java.lang.String pkgInfo, float price, float cost,                               int quantity, int category,                                java.lang.String sellDate, int indx )    throws javax.ejb.CreateException {   setInventoryId(inventoryID);   setName(name);   setPkgInfo(pkgInfo);   setPrice(price);   setQuantity(quantity);   setCategory(category);   setSellDate(sellDate);   setIndx(indx);   this.setOrder_orderId(orderKey.orderId);   return null; } 

OrderItemBean.ejbPostCreate() Code

This method's purpose is only to store the input Order EJB in OrderItem's CMR field.

 public void ejbPostCreate(Order order, OrderKey orderKey,                            java.lang.String inventoryID, java.lang.String name,                           java.lang.String pkgInfo, float price, float cost,                           int quantity, int category, java.lang.String sellDate,                           int indx )     throws javax.ejb.CreateException {    this.setOrder(order); } 

Create the Inventory EJB

Where there are shopping carts and orders, inventory cannot be far behind. We will build an Inventory EJB as our next business abstraction. We can follow the same basic steps as we did for our other 2.0 CMP EJBs, earlier in this chapter. The following attributes describe the essential characteristics of the Inventory EJB:

General Attributes

Bean class

com.ibm.pbw.ejb.InventoryBean

JNDI Name

plantsby/InventoryHome

Local home interface

com.ibm.pbw.ejb.InventoryHome

Local interface

com.ibm.pbw.ejb.Inventory

Key class

com.ibm.pbw.ejb.InventoryKey

CMP Attributes

inventoryId (primary key)

java.lang.String

name

java.lang.String

description

java.lang.String

pkgInfo

java.lang.String

image

java.lang.String

imgBytes

byte Array field (dimension = 1)

price

float

cost

float

category

int

quantity

int

The InventoryHome interface will provide the following create() method signatures:

 public abstract Inventory create(InventoryKey key, String name, String desc,                                   String pkgInfo, String image, float price,                                   float cost, int quantity, int category)      throws CreateException; public Inventory create(StoreItem item) throws javax.ejb.CreateException; 

With complementary ejbCreate() and ejbPostCreate() methods like so (these are straightforward because there is no CMR relationship to manage):

 public InventoryKey ejbCreate(InventoryKey key, String name, String desc,                                String pkgInfo, String image, float price,                                float cost, int quantity, int category)      throws CreateException  {    setInventoryId(key.inventoryId);   setName(name);   setDescription(desc);   setPkgInfo(pkginfo);   setImage(image);   setPrice(price);   setCost(cost);   setQuantity(quantity);   setCategory(category);   return null; } public void ejbPostCreate(InventoryKey key, String name, String desc,                                   String pkgInfo, String image, float price,                                   float cost, int quantity, int category)      throws CreateException  { } public InventoryKey ejbCreate(StoreItem item) throws javax.ejb.CreateException {    setInventoryId(item.getId());   setName(item.getName());   setDescription(item.getDescr());   setPkgInfo(item.getPkgInfo());   setImage(item.getImageLocation());   setPrice(item.getPrice());   setCost(item.getCost());   setQuantity(item.getQuantity());   setCategory(item.getCategory());   return null; } public void ejbPostCreate(StoreItem item) throws CreateException {} 

Now that we have an actual inventory representation, we can wire up the StoreItem bean to get its data from an Inventory EJB through a new constructor:

   public StoreItem(Inventory inv) {     this.id = inv.getInventoryId();      this.name = inv.getName();     this.imageLocation = inv.getImage();     this.price = inv.getPrice();        this.pkgInfo = inv.getPkgInfo();     this.descr = inv.getDescription();     this.quantity = inv.getQuantity();          this.category = inv.getCategory();     this.cost = inv.getCost();   } 

The design requirements for the Inventory EJB allow us to get a closer look at another EJB 2.0 feature: the EJB Query Language. The EJB Query language, or EJB QL for short, is a SQL-like language for expressing selection criteria over the domain of 2.0 CMP EJBs. It provides a database-agnostic query language that can be mapped to virtually any relational backend. It is perfectly suited to the task of implementing EJB custom finders. It is also used for implementing ejbSelect() methods, which are EJB queries accessible to the bean implementation only.

Our Plants-By-WebSphere design requires the following custom finders:

 public abstract Collection findByCategory(int category)      throws FinderException; public abstract Collectin findByNameLikeness(java.lang.String name)     throws FinderException; 

To implement a custom finder an a 2.0 CMP, you must first add a query. Open to the Bean tab of the EJB Deployment Descriptor editor, scroll down to find Queries and click the Add button to open the query creation dialog:

click to expand

Specify this is a new local find method named findByCategory that takes an integer parameter, which will represent the category selection, and returns a collection. Click the Next button to continue.

In the next dialog window we can compose the EJB QL statement that will form the basis of this finder's implementation. Enter the appropriate EJB QL statement and click the Finish button to complete the custom finder:

click to expand

While we're defining custom finders, add this additional finder to Inventory:

click to expand

Finally, our Inventory bean contains some constants for determining the store category that the inventory item belongs to:

 public static int FLOWERS     = 0; public static int FRUIT       = 1; public static int TREES       = 2; public static int ACCESSORIES = 3; 

We'll see these in use later in the chapter when we connect our EJBs up to the web application.

Create the Customer EJB

The Customer EJB introduces no new concepts. We can simply build this EJB using the techniques we have already utilized in this chapter to build this additional CMP entity according to the following table of attributes:

General Attributes

Bean class

com.ibm.pbw.ejb.CustomerBean

JNDI Name

plantsby/CustomerHome

Local home interface

com.ibm.pbw.ejb.CustomerHome

Local interface

com.ibm.pbw.ejb.Customer

Key class

com.ibm.pbw.ejb.CustomerKey

CMP Attributes

customerId (primary key)

java.lang.String

password

java.lang.String

firstName

java.lang.String

lastName

java.lang.String

addr1

java.lang.String

addr2

java.lang.String

city

java.lang.String

state

java.lang.String

zip

java.lang.String

phone

java.lang.String

As with the Inventory bean, we'll create customer constructors for the create methods:

 public Customer create(CustomerKey key, String password, String firstName,                         String lastName, String addr1, String addr2,                        String addrCity, String addrState,                        String addrZip, String phone) throws CreateException; public CustomerKey ejbCreate(CustomerKey key, String password, String firstName,                               String lastName, String addr1, String addr2,                              String addrCity, String addrState,                              String addrZip, String phone)      throws CreateException {    setCustomerId(key.customerId);   setPassword(password);   setFirstName(firstName);   setLastName(lastName);   setAddr1(addr1);   setAddr2(addr2);   setCity(addrCity);   setState(addrState);   setZip(addrZip);   setPhone(phone);   return null; } public void ejbPostCreate(CustomerKey key, String password, String firstName,                            String lastName, String addr1, String addr2,                           String addrCity, String addrState,                           String addrZip, String phone) { } 

There's also an additional business method that will allow a customer's details to be updated:

 public void update(String firstName, String lastName,                    String addr1, String addr2,                    String addrCity, String addrState,                    String addrZip, String phone) {   setFirstName(firstName);   setLastName(lastName);   setAddr1(addr1);   setAddr2(addr2);   setCity(addrCity);   setState(addrState);   setZip(addrZip);   setPhone(phone); } 

We will also need to add a constructor to our CustomerInfo model to load our model with data from the Customer EJB:

 public CustomerInfo(Customer customer) {   email = customer.getCustomerID();   firsName = customer.getFirstName();   lastName = customer.getLastName();   addr1 = customer.getAddr1();   addr2 = customer.getAddr2();   city = customer.getCity();   state = customer.getState();   zipcode = customer.getZip();   phone = customer.getPhone(); } 

This completes the work on our Customer EJB, which was the last EJB we needed to develop in order to satisfy the model requirements of our Plants-By-WebSphere application MVC design, as described at the start of this chapter.

Create the Catalog EJB

While this chapter has focused primarily on the EJBs required to implement the models introduced in Chapter 4, there is an additional session bean, Catalog, which provides a remote, additional abstraction over the Inventory entity bean, providing a catalog metaphor. This bean is used in later chapters, such as the web services chapter.

As this bean is just a session abstraction we won't detail the coverage of it here. Its remote interface merely looks like this:

 package com.ibm.pbw.ejb; import java.rmi.RemoteException; import java.util.Vector; public interface Catalog extends javax.ejb.EJBObject {   public StoreItem[] getItems() throws RemoteException;   public StoreItem[] getItemsByCategory(int category) throws RemoteException;   public StoreItem[] getItemsLikeName(String name) throws RemoteException;   public StoreItem getItem(String inventoryID) throws RemoteException;   public boolean addItem(StoreItem item) throws RemoteException;   public boolean deleteItem(String inventoryID) throws RemoteException;   public void setItemName(String inventoryID, String name) throws RemoteException;   public void setItemDescription(String inventoryID, String desc)       throws RemoteException;   public void setItemPkginfo(String inventoryID, String pkginfo)       throws RemoteException;   public void setItemCategory(String inventoryID, int category)       throws RemoteException;   public void setItemImageFileName(String inventoryID, String imageName)       throws RemoteException;   public byte[] getItemImageBytes(String inventoryID) throws RemoteException;   public void setItemImageBytes(String inventoryID, byte[] imgbytes)       throws RemoteException;   public float getItemPrice(String inventoryID) throws RemoteException;   public void setItemPrice(String inventoryID, float price)       throws RemoteException;   public void setItemCost(String inventoryID, float cost) throws RemoteException;   public void setItemQuantity(String inventoryID, int amount)       throws RemoteException; } 

As you can see it has getters and setters that map to those on the Inventory model.

You can find the complete code for this bean in the source code bundle for this chapter on one of the accompanying CDs.

Map CMP Entities to a Relational Database

CMP entity beans, by their very nature, are designed as persistent objects. That means their state is backed in persistent storage, such as a relational database. WebSphere version 5.0 supports a number of JDBC relational backends. While it is possible to build entity beans that are backed in a variety of different persistent store types, such as a file system or another transaction server type, such as CICS or IMS, you'd have to switch to the bean-managed persistence model (BMP) to implement them. Future plans for WebSphere include direct support for backing CMP entity beans in CICS and IMS transaction systems.

For our application, we will use the bundled Cloudscape relational database. Cloudscape is a simple relational database that uses the file system as a backing store.

Generate a Schema Map

Relational schema mapping is all about specifying which EJB CMP fields map to which relational database table fields. The mapping process will result in generated code, containing SQL statements, which the EJB container will use to perform database operations on the EJB. There are three mapping models from which to choose:

  • Top-down
    A top-down mapping creates an EJB-to-table mapping that assumes the EJB name is the table name and that the CMP field names and types are the names and types of the table columns. This option is most useful for applications that will introduce new database tables.

  • Meet-in-middle
    A meet-in-middle mapping allows CMP fields to be mapped to existing database tables. The mapping tools allow individual CMP fields to be mapped to individual table columns. The names do not need to match, type conversions are possible through the use of special converter classes, and mapping an EJB to multiple database tables is possible.

  • Bottom-up
    A bottom-up mapping creates an EJB and its CMP field names and types based on the definition of an existing database table.

For Plants-By-WebSphere, we will do a top-down mapping, since our tables are new and our mapping is straightforward.

Right-click on the PlantsByWebSphereEJB project, then select Generate | EJB to RDB Mapping … from the menu:

click to expand

Since this is our first database mapping, our only choice is to create a new backend folder. Click the Next button to continue.

click to expand

Next choose Top Down as our mapping approach, as we just discussed. Click Next.

The next dialog window allows us to choose the type of database for which we are generating a mapping. We also specify the database and schema name. Schema name, in case you know it by another term, is the table qualifier that optionally appears in SQL statements – for example, SELECT ... FROM <schema name>.<table name> WHERE ...

You can create multiple mappings for your EJBs – one for each database you intend to use. For example, you might use Cloudscape for unit testing, and DB2 for production. WebSphere supports a number of relational databases, including:

  • DB2

  • Cloudscape

  • Informix

  • Sybase

  • Oracle

  • SQL Server

On this window you may also choose to have DDL generated, which you can use to create the target database tables for your EJBs.

click to expand

For our purposes, we will map to a Cloudscape database named PLANTSDB. Specify NULLID as the schema name. This is a reserved keyword in WebSphere Studio that means there is to be no schema name used in the table reference of the generated SQL statements. Click Finish to generate the mapping.

At this point, a confession is in order: we actually have an existing PLANTDB database (provided on the CD), which was created through an earlier Plants-By-WebSphere effort. It was created through a top-down mapping process and is ready and primed with inventory data for our use. We'll leverage that database here, so we don't have to repeat the table creation and priming exercise. The top-down mapping will almost perfectly map the fields to columns correctly – the only exception is the generated foreign key field in the ORDERITEM table. The only other rub is that some of the table names are not exactly right. But that's OK; we can explore another WebSphere Studio feature as we fix up these minor mapping inconsistencies. If in fact, our existing database was substantially different, we would have undertaken a "meet-in-middle" mapping.

In the J2EE Navigator view, you will find a back-end folder for our nearly created Cloudscape mapping. The folder includes the table definitions that were generated by the mapping process. Double-click a table name to open an editor:

click to expand

In the editor, change the table name from ORDER1 to ORDERINFO to match our existing PLANTSDB database. Also change the ORDERIDGENERATOR table to ORDERID and ORDERITEM to ORDERITEMS (note the "S" at the end).

Change the name of columns CITY, STATE, and ZIP in the CUSTOMER table to ADDRCITY, ADDRSTATE, and ADDRZIP respectively.

Lastly, change the name of the foreign key field in the ORDERITEMS table from ORDER_ORDERID to just ORDERID.

Declare EJB References

The next necessary step is to define the EJB references. As per the EJB specification, EJB references are the declarative mechanism by which an EJB developer makes it known that one EJB depends on another. Such a dependency requires the dependent EJB gain access to its provider EJB's local or remote home interface. This access is achieved through a JNDI lookup. The EJB reference serves two purposes then:

  • Informs the application assembler or deployer what other EJBs are required to deploy a particular EJB.

  • Provides a level of indirection by enabling the bean developer to specify and use a symbolic JNDI lookup name in their EJB, which is resolved lazily during application assembly or installation.

While building the business logic, we'll only declare the external dependencies that our components have. Resolving these dependencies is an application assembly and installation activity, which we will defer until the next chapter.

To declare an EJB reference, let's go to the References tab of the EJB Deployment Descriptor editor. Select the referencing EJB and click the Add button. Let's add the Order EJB's references first.

The Add Reference dialog first asks what kind of reference we want to declare. In the case of the Order EJB, it has references to both the OrderItem and OrderIdGenerator EJBs. As these two EJBs have only local interfaces, we clearly want to use a local EJB reference:

click to expand

In fact, we would probably always prefer local EJB interfaces – even if remote interfaces are available on the target EJB – because local interfaces always perform better than remote. Local interfaces are perfectly fine as long as our design requirements allow the referencing and referenced EJBs to be co-located in the same application server. If the design requires that these EJBs be distributable across separate application servers, then we would have to specify a remote interface reference. Click the Next button to continue.

The final step in declaring an EJB reference is to identity the EJB type to which we are making reference. The Order bean references OrderIdGenerator bean. We will complete the reference declaration on the following dialog window:

click to expand

Specify:

  • Name: ejb/OrderIdGeneratorHome
    This is the local name by which the Order bean will look up the OrderIdGenerator bean's home in JNDI.

  • Type: Entity
    This specifies the EJB type to which this reference refers. It is an assembly hint.

  • Local: com.ibm.pbw.ejb.OrderIdGenerator
    This specifies the bean type to which this reference refers. This is another assembly hint.

  • Local home: com.ibm.pbw.ejb.OrderIdGeneratorHome
    This specifies the bean home type to which this reference refers. This is the final assembly hint.

Click the Finish button to complete this EJB reference declaration.

Add additional local EJB references to our PlantsByWebSphereEJB module according to the summary table below:

Bean

Name

Local

LocalHome

Order

ejb/Order
ItemHome

com.ibm.pbw.ejb.OrderItem

com.ibm.pbw.ejb.OrderItemHome

OrderItem

ejb/Order

com.ibm.pbw.ejb.Order

com.ibm.pbw.ejb.OrderHome




Professional IBM WebSphere 5. 0 Applicationa Server
Professional IBM WebSphere 5. 0 Applicationa Server
ISBN: N/A
EAN: N/A
Year: 2001
Pages: 135

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net