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.

Note:
In the EJB server (CB) environment, an enterprise bean can also be accessed by a Java applet, an ActiveX client, a CORBA-based Java client, and to a limited degree, by a C++ CORBA client. The Travel example briefly described in Information about the examples described in the documentation illustrates some of these types of clients. More information on EJB clients specific to the EJB server (CB) provides additional information about EJB clients that use ActiveX and CORBA-based Java and C++.

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 (AE) environment or Setting the CLASSPATH environment variable in the EJB server (CB) environment. You can install needed files on your client machine by doing a WebSphere Application Server installation on the machine. If you are using the Advanced Application Server, select the Developer's Client Files option; if you are using Component Broker, select the Java client 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 Windows NT, make sure that EJB clients can locate the ioser.dll library file at run time. Figure 36 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 36. 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:///. In the EJB server (AE), 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.
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 37 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 37. 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 38. 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 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 38. 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(
                    (org.omg.CORBA.Object) 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 39 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 39. 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 40 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 40. 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 41 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 41. 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 TX_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 42 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 42. 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();

More information on EJB clients specific to the EJB server (CB)

When developing EJB clients for the EJB server (CB) environment, you can develop the following types of clients:

For more information on developing these types of clients, see the IBM Redbook entitled IBM Component Broker Connector Overview, form number SG24-2022-02.

EJB clients that use ActiveX
If you write your EJB client as a component that adheres to the JavaBeans(TM) Specification, you can use the JavaBeans bridge to run the EJB client as an ActiveX control. An EJB client of this type must provide a no-argument constructor, it must implement the java.io.Serializable interface, and it must have a readObject and a writeObject method, if applicable.

If your EJB client is also an applet, you must not perform your JNDI initialization as part of object construction. Rather, perform JNDI initialization in the applet's start method. The JavaBeans bridge must create an instance of your EJB client so that it can introspect it and make the necessary stubs to create the ActiveX proxy for it. You must delay the JNDI connections until the user can specify the necessary properties by way of the ActiveX property sheet.

Clients using the Component Broker Session Service
In addition to the Transaction Service, Component Broker also provides a Session Service for the Procedural Application Adaptor (PAA) that enables the use of backend systems such as CICS and IMS. Since the JTA does not have a Session Service, it is not possible to use JNDI to look up a handle to the service in an EJB client. In this case, the EJB client must act as an ordinary CB Java client.

The normal lookup procedure for a CB Java client is to use the CORBA resolve_initial_references method. In this case, the CORBA object to look up is named SessionCurrent.

Before you can call the resolve_initial_references method, the ORB needs to be properly initialized for the CB runtime environment. The initialization method depends on whether or not you are using VisualAge for Java access beans in the CB environment. If you are using access beans, then the ORB must be manually initialized. ORB initialization in access beans is done in a "lazy" fashion. That is, initialization is not done until the first remote method is invoked. However, because a session must be started before that method is called, the ORB initialization must be done manually. The example code in Figure 43 shows this initialization.

Figure 43. Code example: Initializing the ORB (if using access beans)


String[] CBargs = null;
CBargs = new String[6];
CBargs[0] = "-ORBBootstrapHost";  
// substitute your bootstrap host name
CBargs[1] = "cbs3.rchland.ibm.com"; 
CBargs[2] = "-ORBBootstrapPort";
CBargs[3] = "900";
CBargs[4] = "-ORBClass";
CBargs[5] = "com.ibm.CORBA.iiop.ORB";
com.ibm.CBCUtil.CBSeriesGlobal.Initialize(CBargs);

If you are not using access beans, initialization code is not necessary. The ORB is properly initialized during the creation of the InitialContext object with the appropriate properties. For example, your client code should already contain lines similar to those in Figure 44. This code is used to find the service, look up the home object, narrow the home object, and create the proxy object (tasks automatically done if an access bean is being used).

Figure 44. Code example: Creating the InitialContext object (if not using access beans)


Properties properties = new Properties();
properties.put(javax.naming.Context.PROVIDER_URL, "iiop:///");
// CB Factory Name
properties.put(javax.naming.Context.INITIAL_CONTEXT_FACTORY,
"com.ibm.ejb.cb.runtime.CBCtxFactory"); 
Context ctx = new InitialContext(properties);

After the ORB is initialized (either automatically or manually), you must use CB-specific APIs for creating and using the sessionCurrent object. You must include code similar to the example code in Figure 45.

Figure 45. Code example: Creating and using the sessionCurrent object


org.omg.CORBA.Object orbCurrent = null;
com.ibm.ISessions.Current sessionCurrent = null;
...
orbCurrent = com.ibm.CBCUtil.CBSeriesGlobal.orb().resolve_initial_references(
					"ISessions::Current");
sessionCurrent = com.ibm.ISessions.CurrentHelper.narrow(orbCurrent);
sessionCurrent.beginSession("myApp");
...
// commit
sessionCurrent.endSession(com.ibm.ISessions.EndMode.EndModeCheckPoint, true);

For more information on using the resolve_initial_references method, see the Component Broker Programming Guide.