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.
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 { ... } |
To create or find an instance of a bean's EJB object, the client must do the following:
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.
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:
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.
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; } |
If the system administrator binds the EJB home in the host name tree of a specific bootstrap host, then the JNDI name prefix will be host/resources/factories/EJBHomes. If the system administrator binds the EJB home in a workgroup name tree, then the JNDI name prefix will be workgroup/resources/factories/EJBHomes, and the EJB client must belong to the same preferred workgroup. If the system administrator binds the EJB home in the cell name tree, then the JNDI name prefix is cell/resources/factories/EJBHomes.
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; } |
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; } |
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; } |
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) { ... } } |
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(); |
For more information on developing these types of clients, see the IBM Redbook entitled IBM Component Broker Connector Overview, form number SG24-2022-02.
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.
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.