Developing EJB clients

An enterprise bean can be accessed by all of the following types of EJB clients in both EJB server environments:

It is recommended that you avoid accessing EJB entity beans from client or servlet code. Instead, wrap and access EJB entity beans from EJB session beans. This improves performance in two ways:

Except for the basic programming tasks described in this chapter, creating a Java servlet, JSP, or Java application that is a client to an enterprise bean is not very different from designing standard versions of these types of Java programs. This chapter assumes that you understand the basics of writing a Java servlet, a Java application, or a JSP file.

Except where noted, all of the code described in this chapter is taken from the example Java application named TransferApplication. This Java application and the other EJB clients available with the documentation example code are explained in Information about the examples described in the documentation.

To access and manipulate an enterprise bean in any of the Java-based EJB client types listed previously, the EJB client must do the following:

In addition, an EJB client can participate in the transactions associated with enterprise beans used by the client. For more information, see Managing transactions in an EJB client.


Importing required Java packages

Although the Java packages required for any particular EJB client vary, the following packages are required by all EJB clients:

The Java client object request broker (ORB), which is automatically initialized in EJB clients, does not support dynamic download of implementation bytecode from the server to the client. As a result, all classes required by the EJB client at runtime must be available from the files and directories identified in the client's CLASSPATH environment variable. For information on the JAR files required by EJB clients, see Setting the CLASSPATH environment variable in the EJB server environment. You can install needed files on your client machine by doing a WebSphere Application Server installation on the machine. Select the Developer's Client Files option. You also need to make sure that the ioser and ioserx executable files are accessible on your client machine; these files are normally part of the Java install. If you are using a Windows System, make sure that EJB clients can locate the ioser.dll library file at run time. Figure 26 shows the import statements for the example Java application com.ibm.ejs.doc.client.TransferApplication. In addition to the required Java packages mentioned previously, the example application imports the com.ibm.ejs.doc.transfer package because the application communicates with a Transfer bean. The example application also imports the InsufficientFundsException class contained in the same package as the Account bean.

Figure 26. Code example: The import statements for the Java application TransferApplication

...
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.rmi.*
...
import javax.naming.*;
import javax.ejb.*;
import javax.rmi.PortableRemoteObject;
...
import com.ibm.ejs.doc.account.InsufficientFundsException;
import com.ibm.ejs.doc.transfer.*;
...
public class TransferApplication extends Frame implements 
     ActionListener, WindowListener {
     ...
}

Creating and getting a reference to a bean's EJB object

To invoke a bean's business methods, a client must create or find an EJB object for that bean. After the client has created or found this object, it can invoke methods on it in the standard way.

To create or find an instance of a bean's EJB object, the client must do the following:

  1. Locate and create an EJB home object for that bean. For more information, see Locating and creating an EJB home object.

  2. Use the EJB home object to create or (for entity beans only) find an instance of the bean's EJB object. For more information, see Creating an EJB object.

The TransferApplication client contains one reference to a Transfer EJB object, which the application uses to invoke all of the methods on the Transfer bean. When using session beans in Java applications, it is a good idea to make the reference to the EJB object a class-level variable rather than a variable that is local to a method. This allows your EJB client to repeatedly invoke methods on the same EJB object rather than having to create a new object each time the client invokes a session bean method. As discussed in Threading issues, this approach is not recommended for servlets, which must be designed to handle multiple threads.

Locating and creating an EJB home object
JNDI is used to find the name of an EJB home object. The properties that an EJB client uses to initialize JNDI and find an EJB home object vary across EJB server implementations. To make an enterprise bean more portable between EJB server implementations, it is recommended that you externalize these properties in environment variables, properties files, or resource bundles rather than hard code them into your enterprise bean or EJB client code.

The example Transfer bean uses environment variables as discussed in Implementing the ejbCreate methods. The TransferApplication uses a resource bundle contained in the com.ibm.ejs.doc.client.ClientResourceBundle.class file. To initialize a JNDI name service, an EJB client must set the appropriate values for the following JNDI properties:

javax.naming.Context.PROVIDER_URL
This property specifies the host name and port of the name server used by the EJB client. The property value must have the following format: iiop://hostname:port, where hostname is the IP address or hostname of the machine on which the name server runs and port is the port number on which the name server listens.

For example, the property value iiop://bankserver.mybank.com:9019 directs an EJB client to look for a name server on the host named bankserver.mybank.com listening on port 9019. The property value iiop://bankserver.mybank.com directs an EJB client to look for a name server on the host named bankserver.mybank.com at port number 900. The property value iiop:/// directs an EJB client to look for a name server on the local host listening on port 900. If not specified, this property defaults to the local host and port number 900, which is the same as specifying iiop:///. The port number used by the name service can be changed by using the administrative interface.

javax.naming.Context.INITIAL_CONTEXT_FACTORY
This property identifies the actual name service that the EJB client must use. This property must be set to com.ibm.ejs.ns.jndi.CNInitialContextFactory.
Locating an EJB home object is a two-step process:

  1. Create a javax.naming.InitialContext object. For more information, see Creating an InitialContext object.

  2. Use the InitialContext object to create the EJB home object. For more information, see Creating EJB home object.

Creating an InitialContext object
Figure 27 shows the code required to create the InitialContext object. To create this object, construct a java.util.Properties object, add values to the Properties object, and then pass the object as the argument to the InitialContext constructor. In the TransferApplication, the value of each property is obtained from the resource bundle class named com.ibm.ejs.doc.client.ClientResourceBundle, which stores all of the locale-specific variables required by the TransferApplication. (This class also stores the variables used by the other EJB clients contained in the documentation example, described in Information about the examples described in the documentation). The resource bundle class is instantiated by calling the ResourceBundle.getBundle method. The values of variables within the resource bundle class are extracted by calling the getString method on the bundle object.

The createTransfer method of the TransferApplication can be called multiple times as explained in Handling an invalid EJB object for a session bean. However, after the InitialContext object is created once, it remains good for the life of the client session. Therefore, the code required to create the InitialContext object is placed within an if statement that determines if the reference to the InitialContext object is null. If the reference is null, the InitialContext object is created; otherwise, the reference can be reused on subsequent creations of the EJB object.

Figure 27. Code example: Creating the InitialContext object

...
public class TransferApplication extends Frame implements ActionListener, 
     WindowListener {
     ...
     private InitialContext ivjInitContext = null;
     private Transfer ivjTransfer = null;
     private ResourceBundle bundle = ResourceBundle.getBundle(
               "com.ibm.ejs.doc.client.ClientResourceBundle");
     ...
     private String nameService = null;
     private String accountName = null;
     private String providerUrl = null;
     ...
     private Transfer createTransfer() {
          TransferHome transferHome = null;
          Transfer transfer = null;
     // Get the initial context
     if (ivjInitContext == null) {
          try {
               Properties properties = new Properties();
               // Get location of name service
               properties.put(javax.naming.Context.PROVIDER_URL, 
                         bundle.getString("providerUrl")); 
               // Get name of initial context factory
               properties.put(javax.naming.Context.INITIAL_CONTEXT_FACTORY, 
                         bundle.getString("nameService"));
               ...
               ivjInitContext = new InitialContext(properties);
           } catch (Exception e) { // Error getting the initial context
          ...
          }
     }
     ...
     // Look up the home interface using the JNDI name
     ...
     // Create a new Transfer object to return
     ...
     return transfer;
}

Creating EJB home object
After the InitialContext object (ivjInitContext) is created, the application uses it to create the EJB home object, as shown in Figure 28. This creation is accomplished by invoking the lookup method, which takes the JNDI name of the enterprise bean in String form and returns a java.lang.Object object. The JNDI name specified in the deployment descriptor is used.

The example TransferApplication gets the JNDI name of the Transfer bean from the ClientResourceBundle class. After an object is returned by the lookup method, the static method javax.rmi.PortableRemoteObject.narrow is used to obtain an EJB home object for the specified enterprise bean. The narrow method takes two parameters: the object to be narrowed and the class of the EJB home object to be returned by the narrow method. The object returned by the javax.rmi.PortableRemoteObject.narrow method is cast to the class associated with the home interface.

Figure 28. Code example: Creating the EJBHome object

private Transfer createTransfer() {
     TransferHome transferHome = null;
     Transfer transfer = null;
     // Get the initial context
     ...
     // Look up the home interface using the JNDI name
     try {
          java.lang.Object homeObject = ivjInitContext.lookup(
                    bundle.getString("transferName"));
          transferHome = (TransferHome)javax.rmi.PortableRemoteObject.narrow(
                                         homeObject, TransferHome.class);
     } catch (Exception e)  { // Error getting the home interface
          ...
     } 
     ...
     // Create a new Transfer object to return
     ...
     return transfer;
}

Creating an EJB object
After the EJB home object is created, it is used to create the EJB object. Figure 29 shows the code required to create the EJB object by using the EJB home object. A create method is invoked to create an EJB object or (for entity beans only) a finder method is invoked to find an existing EJB object. Because the Transfer bean is a stateless session bean, the only choice is the default create method.

Figure 29. Code example: Creating the EJB object

private Transfer createTransfer() {
     TransferHome transferHome = null;
     Transfer transfer = null;
     // Get the initial context
     ...
     // Look up the home interface using the JNDI name
     ...
     // Create a new Transfer object to return
     try {
          transfer = transferHome.create();
     } catch (Exception e) { // Error creating Transfer object
          ...
     }
     ...
     return transfer;
}

Handling an invalid EJB object for a session bean

Because session beans are ephemeral, the client cannot depend on a session bean's EJB object to remain valid. A reference to an EJB object for a session bean can become invalid if the EJB server fails or is restarted or if the session bean times out due to inactivity. (The reference to an entity bean's EJB object is always valid until that object is removed.) Therefore, the client of a session bean must contain code to handle a situation in which the EJB object becomes invalid.

An EJB client can determine if an EJB object is valid by placing all method invocations that use the reference inside of a try/catch block that specifically catches the java.rmi.NoSuchObjectException, in addition to any other exceptions that the method needs to handle. The EJB client can then invoke the code to handle this exception.

You determine how to handle an invalid EJB object. The example TransferApplication creates a new Transfer EJB object if the one it is currently using becomes invalid. The code to create a new EJB object when the old one becomes invalid is the same code used to create the original EJB object and is described in Creating and getting a reference to a bean's EJB object. For the example TransferApplication client, this code is contained in the createTransfer method.

Figure 30 shows the code used to create the new EJB object in the getBalance method of the example TransferApplication. The getBalance method contains the local boolean variable sessionGood, which is used to specify the validity of the EJB object referenced by the variable ivjTransfer. The sessionGood variable is also used to determine when to break out of the do-while loop. The sessionGood variable is initialized to false because the ivjTransfer can reference an invalid EJB object when the getBalance method is called. If the ivjTransfer reference is valid, the TransferApplication invokes the Transfer bean's getBalance method and returns the balance. If the ivjTransfer reference is invalid, the NoSuchObjectException is caught, the TransferApplication's createTransfer method is called to create a new Transfer EJB object reference, and the sessionGood variable is set to false so that the do-while loop is repeated with the new valid EJB object. To prevent an infinite loop, the sessionGood variable is set to true when any other exception is thrown.

Figure 30. Code example: Refreshing the EJB object reference for a session bean

private float getBalance(long acctId) throws NumberFormatException, RemoteException, 
     FinderException {
     // Assume that the reference to the Transfer session bean is no good
     ...
     boolean sessionGood = false;
     float balance = 0.0f;
     do {
          try { 
                // Attempt to get a balance for the specified account
               balance = ivjTransfer.getBalance(acctId);
               sessionGood = true;
               ...
          } catch(NoSuchObjectException ex) {
               createTransfer();
               sessionGood = false;
          } catch(RemoteException ex) {
               // Server or connection problem
               ...
          } catch(NumberFormatException ex) {
               // Invalid account number
               ...
          } catch(FinderException ex) {
               // Invalid account number
               ...
          }
     } while(!sessionGood);
     return balance;
}

Removing a bean's EJB object

When an EJB client no longer needs a stateful session EJB object, the EJB client should remove that object. Instances of stateful session beans have affinity to specific clients. They will remain in the container until they are explicitly removed by the client, or removed by the container when they time out. Meanwhile, the container might need to passivate inactive stateful session beans to disk. This requires overhead for the container and impacts performance of the application. If the passivated session bean is subsequently required by the application, the container activates it by restoring it from disk. By explicitly removing stateful session beans when finished with them, applications can decrease the need for passivation and minimize container overhead.

You remove entity EJB objects only when you want to remove the information in the data source with which the entity EJB object is associated.

To remove an EJB object, invoke the remove method on the object. As discussed in Creating and getting a reference to a bean's EJB object, the TransferApplication contains only one reference to a Transfer EJB object that is created when the application is initialized.

Figure 31 shows how the example Transfer EJB object is removed in the TransferApplication in the killApp method. To parallel the creation of the Transfer EJB object when the TransferApplication is initialized, the application removes the final EJB object associated with ivjTransfer reference right before closing the application's GUI window. The killApp method closes the window by invoking the dispose method on itself.

Figure 31. Code example: Removing a session EJB object

...
private void killApp() {
     try {
          ivjTransfer.remove();
          this.dispose();
          System.exit(0);     } catch (Throwable ivjExc) {
          ...
     }
}

Managing transactions in an EJB client

In general, it is practical to design your enterprise beans so that all transaction management is handled at the enterprise bean level. In a strict three-tier, distributed application, this is not always possible or even desirable. However, because the middle tier of an EJB application can include two subcomponents--session beans and entity beans--it is much easier to design the transactional management completely within the application server tier. Of course, the resource manager tier must also be designed to support transactions.
Note:
EJB clients that access entity beans with CMP that use Host On-Demand (HOD) or the External Call Interface (ECI) for CICS or IMS applications must begin a transaction before invoking a method on these entity beans. This restriction is required because these types of entity beans must use the Mandatory transaction attribute.
Nevertheless, it is still possible to program an EJB client (that is not an enterprise bean) to participate in transactions for those specialized situations that require it. To participate in a transaction, the EJB client must do the following:

  1. Obtain a reference to the javax.transaction.UserTransaction interface by using JNDI as defined in the Java Transaction Application Programming Interface (JTA).

  2. Use the object reference to invoke any of the following methods:
Figure 32 provides an example of an EJB client creating a reference to a UserTransaction object and then using that object to set the transaction timeout, begin a transaction, and attempt to commit the transaction. (The source code for this example is not available with the example code provided with this document.) Notice that the client does a simple type cast of the lookup result, rather than invoking a narrow method as required with other JNDI lookups. In both EJB server environments, the JNDI name of the UserTransaction interface is java:comp/UserTransaction.

Figure 32. Code example: Managing transactions in an EJB client

...
import javax.transaction.*;
...
// Use JNDI to locate the UserTransaction object
Context initialContext = new InitialContext();
UserTransaction tranContext = (
     UserTransaction)initialContext.lookup("java:comp/UserTransaction");
// Set the transaction timeout to 30 seconds
tranContext.setTransactionTimeout(30);
...
// Begin a transaction
tranContext.begin();
// Perform transaction work invoking methods on enterprise bean references
...
// Call for the transaction to commit
tranContext.commit();