The Remote Client API

Enterprise bean developers are required to provide a bean class, component interfaces, and, for entity beans, a primary key. The component interfaces and primary key class are visible to the client; the bean class itself is not. The component interfaces and primary key contribute to the client-side API in EJB.

Any client, whether it is in the same container system or not, may use the Remote Client API, which means that it may use the remote interface, the remote home interface, and Java RMI to access entity and session beans. Enterprise beans that are located in the same EJB container have the option of using the Local Client API. The Local Client API provides local component interfaces and avoids the restrictions and overhead of the Remote Client API. This section examines the remote component interfaces and the primary key, as well as other Java types that make up EJB’s remote client-side API.

Java RMI-IIOP

Enterprise JavaBeans defines an enterprise bean’s remote client API in terms of Java RMI-IIOP, which enforces compliance with CORBA. This means that the underlying protocol used by remote clients to access enterprise beans can be anything the vendor wants as long as it supports the types of interfaces and arguments that are compatible with Java RMI-IIOP. However, in addition to any proprietary protocols, vendors must also support the CORBA IIOP 1.2 protocol as defined in the CORBA 2.3.1 specification.

To use the Remote Client API, define your component interfaces and argument types so that they comply with Java RMI-IIOP types. It’s not all that difficult to comply with this restriction. The next few sections discuss the Java RMI-IIOP programming model for EJB.

Java RMI return types, parameters, and exceptions

As an implementation of Java RMI, Java RMI-IIOP must first comply with the basic restrictions of Java RMI. We’ll first take a look at Java RMI restrictions and then proceed to examine addition restrictions imposed by Java RMI-IIOP.

The supertypes of the remote home interface and remote interface, javax.ejb.EJBHome and javax.ejb.EJBObject, both extend java.rmi.Remote. As Remote interface subtypes, they are expected to adhere to the Java RMI specification for Remote interfaces.

Return types and parameters

The remote component interfaces must follow several guidelines, some of which apply to the return types and parameters that are allowed. There are two kinds of return and parameter types: declared types, which are checked by the compiler, and actual types, which are checked by the runtime. Java RMI requires the use of actual types. The actual types used in the java.rmi.Remote interfaces must be primitives, java.rmi.Remote types, or serializable types (including the String type). java.rmi.Remote types and serializable types do not have to implement java.rmi.Remote and java.io.Serializable explicitly. For example, the java.util.Collection type, which does not explicitly extend java.io.Serializable, is a perfectly valid return type for a remote finder method, provided that the concrete class implementing Collection, the actual type, does implement java.io.Serializable.

Java RMI has no special rules regarding declared return types or parameter types. At runtime, a type that is not a java.rmi.Remote type is assumed to be serializable; if it is not, an exception is thrown. The actual type that is passed cannot be checked by the compiler; it must be checked at runtime.

Here is a list of the types that can be passed as parameters or returned in Java RMI:

Primitives

These include byte, boolean, char, short, int, long, double, and float.

Java serializable types

Any class that implements or any interface that extends java.io.Serializable.

Java RMI remote types

Any class that implements or any interface that extends java.rmi.Remote.

Serializable objects are passed by copy (a.k.a. passed by value), not by reference, which means that changes in a serialized object on one tier are not automatically reflected on the others. Objects that implement Remote, like TravelAgentRemote or CabinRemote, are passed as remote references, which are a little different. A remote reference is a Remote interface implemented by a distributed object stub. When a remote reference is passed as a parameter or returned from a method, the stub is serialized and passed by value, not the object referenced by the stub. In Chapter 11, the home interface for the TravelAgent EJB is modified so that the create( ) method takes a reference to a Customer EJB as its only argument:

public interface TravelAgentHomeRemote extends javax.ejb.EJBHome {
    public TravelAgentRemote create(CustomerRemote customer)
        throws RemoteException, CreateException;
}

The customer argument is a remote reference to a Customer EJB that is passed into the create( ) method. When a remote reference is passed or returned in Enterprise JavaBeans, the EJB object stub is passed by copy. The copy of the EJB object stub points to the same EJB object as the original stub. Therefore, both the enterprise bean instance and the client have remote references to the same EJB object. Changes made on the client using the remote reference will be reflected when the enterprise bean instance uses the same remote reference. Figures Figure 5-1 and Figure 5-2 show the difference between a serializable object and a remote reference argument.

Serializable arguments

Figure 5-1. Serializable arguments

Remote reference arguments in RMIExceptions

Figure 5-2. Remote reference arguments in RMIExceptions

The RMI specification states that every method defined in a Remote interface must throw the java.rmi.RemoteException . The RemoteException is used when problems occur with distributed object communications, such as a network failure or inability to locate the object server. Remote interfaces can also throw application-specific exceptions (exceptions defined by the application developer). The following code shows the remote interface to the TravelAgent EJB discussed in Chapter 2. The bookPassage( ) method in the TravelAgentRemote interface throws the RemoteException (as required) in addition to an application exception, IncompleteConversationalState:

public interface TravelAgentRemote extends javax.ejb.EJBObject {

    public void setCruiseID(int cruise) 
        throws RemoteException, FinderException;
    public int getCruiseID( ) throws RemoteException;
    
    public void setCabinID(int cabin) 
        throws RemoteException, FinderException;
    public int getCabinID( ) throws RemoteException;
    
    public int getCustomerID( ) throws RemoteException;
    
    public Ticket bookPassage(CreditCardRemote card, double price)
        throws RemoteException,IncompleteConversationalState; 
               
    public String [] listAvailableCabins(int bedCount)
        throws RemoteException;
    
}

Java RMI-IIOP type restrictions

Along with the Java RMI programming model, Java RMI-IIOP imposes restrictions on the remote interfaces and value types used in the Remote Client API. The restrictions are born of limitations in the Interface Definition Language (IDL) upon which CORBA IIOP 1.2 is based. The exact nature of these limitations is outside the scope of this book. Here are two; the others, like IDL name collisions, are rarely encountered:[15]

  • Method overloading is restricted; a remote interface may not directly extend two or more interfaces that have methods with the same name (even if their arguments are different). A remote interface may, however, overload its own methods and extend a remote interface with overloaded method names. Overloading is viewed, here, as including overriding. Figure 5-3 illustrates both of these situations.

  • Serializable types must not directly or indirectly implement the java.rmi.Remote interface.

Overloading rules for remote interface inheritance

Figure 5-3. Overloading rules for remote interface inheritance

Explicit narrowing using PortableRemoteObject

In Java RMI-IIOP, remote references must be explicitly narrowed using the javax.rmi.PortableRemoteObject.narrow( ) method. The typical practice in Java is to cast the reference to the more specific type:

javax.naming.Context jndiContext;
...
CabinHomeRemote home = 
    (CabinHomeRemote)jndiContext.lookup("CabinHomeRemote");

The javax.naming.Context.lookup( ) method returns an Object. In EJB’s Local Client API, we can assume that it is legal to cast the return argument. However, the Remote Client API must be compatible with Java RMI-IIOP, which means that clients must adhere to limitations imposed by the IIOP 1.2 protocol. To accommodate all languages, many of which have no concept of casting, IIOP 1.2 does not support stubs that implement multiple interfaces. The stub returned in IIOP implements only the interface specified by the return type of the remote method that was invoked. If the return type is Object, as is the remote reference returned by the lookup( ) method, the stub will implement only methods specific to the Object type.

Of course, some means for converting a remote reference from a more general type to a more specific type is essential in an object-oriented environment. In Java RMI-IIOP, the mechanism is javax.rmi.PortableRemoteObject.narrow( ). Remember that while the Remote Client API requires that we use Java RMI-IIOP reference and argument types, the wire protocol need not be IIOP 1.2. Other protocols besides IIOP may also require explicit narrowing.

To narrow the return value of the Context.lookup( ) method to the appropriate type, we must explicitly ask for a remote reference that implements the interface we want:

import javax.rmi.PortableRemoteObject;
...
javax.naming.Context jndiContext;
...
Object ref = jndiContext.lookup("CabinHomeRemote");
CabinHomeRemote home = (CabinHomeRemote)
    PortableRemoteObject.narrow(ref, CabinHomeRemote.class);

The narrow( ) method takes two arguments: the remote reference that is to be narrowed and the type to which it should be narrowed. When it has executed, it returns a stub that implements the specified Remote interface. Because the stub is known to implement the correct type, we can then use Java’s native casting to narrow the stub to the correct Remote interface.

The narrow( ) method is used only when a remote reference to an EJB home or EJB object is returned without a specific Remote interface type. This occurs in six circumstances:

  1. When a remote EJB home reference is obtained using the javax.naming.Context.lookup( ) method:

    Object ref = jndiContext.lookup("CabinHomeRemote");
    CabinHomeRemote home = (CabinHomeRemote) 
        PortableRemoteObject.narrow(ref, CabinHomeRemote.class);
  2. When a remote EJB object reference is obtained from a Collection or Enumeration returned by a remote home interface finder method:

    ShipHomeRemote shipHome = ... // get ship home
    Enumeration enum = shipHome.findByCapacity(2000);
    while(enum.hasMoreElements( )){
        Object ref = enum.nextElement( );
        ShipRemote ship = (ShipRemote) 
            PortableRemoteObject.narrow(ref, ShipRemote.class);
        // do something with Ship reference
    }
  3. When a remote EJB object reference is obtained using the javax.ejb.Handle.getEJBObject( ) method:

    Handle handle = .... // get Handle
    Object ref = handle.getEJBObject( );
    CabinRemote cabin = (CabinRemote) 
    PortableRemoteObject.narrow(ref,CabinRemote.class);
  4. When a remote EJB home reference is obtained using the javax.ejb.HomeHandle.getEJBHome( ) method:

    HomeHandle homeHdle = ... // get home Handle
    EJBHome ref = homeHdle.getEJBHome( ); 
    CabinHomeRemote home = (CabinHomeRemote)
        PortableRemoteObject.narrow(ref, CabinHomeRemote.class);
  5. When a remote EJB home reference is obtained using the javax.ejb.EJBMetaData.getEJBHome( ) method:

    EJBMetaData metaData = homeHdle.getEJBMetaData( );
    EJBHome ref = metaData.getEJBHome( );
    CabinHomeRemote home = (CabinHomeRemote) 
        PortableRemoteObject.narrow(ref, CabinHomeRemote.class);
  6. When a wide remote EJB object type is returned from any business method; here is a hypothetical example:

    // Officer extends Crewman
    ShipRemote ship = // get Ship remote reference
    CrewmanRemote crew = ship.getCrewman("Burns", "John", "1st Lieutenant");
    OfficerRemote burns = (OfficerRemote) 
        PortableRemoteObject.narrow(crew, OfficerRemote.class);

PortableRemoteObject.narrow( ) is not required when the remote type is specified in the method signature. This is true of the create and find methods (see Creating and finding beans later in this chapter) in remote home interfaces that return a single bean. For example, the create( ) and findByPrimaryKey( ) methods defined in the CabinHomeRemote interface (Chapter 4) do not require the use of the narrow( ) method because these methods already return the correct EJB object type. Business methods that return the correct type do not need to use the narrow( ) method either, as the following code illustrates:

/* The CabinHomeRemote.create( ) method specifies 
 * the CabinRemote interface as the return type, 
 * so explicit narrowing is not needed.*/
CabinRemote cabin = cabinHome.create(new Integer(12345));

/* The CabinHomeRemote.findByPrimaryKey( ) method specifies 
 * the CabinRemote interface as the return type, 
 * so explicit narrowing is not needed.*/
CabinRemote cabin = cabinHome.findByPrimaryKey(new Integer(12345));

/* The ShipRemote.getCrewman( ) business method specifies 
 * the CrewmanRemote interface as the return type, 
 * so explicit narrowing is not needed.*/
CrewmanRemote crew = ship.getCrewman("Burns", "John", 
    "1st Lieutenant");

The Remote Home Interface

The remote home interface provides life-cycle operations and metadata. When we use JNDI to access a bean, we obtain a remote reference, or stub, to the bean’s EJB home, which implements the remote home interface. Every bean type may have one home interface, which extends the javax.ejb.EJBHome interface:

public interface javax.ejb.EJBHome extends java.rmi.Remote {
    public abstract EJBMetaData getEJBMetaData( )
        throws RemoteException;
    public HomeHandle getHomeHandle( )
        throws RemoteException;
    public abstract void remove(Handle handle)
        throws RemoteException, RemoveException;
    public abstract void remove(Object primaryKey)
        throws RemoteException, RemoveException;
}

Removing beans

The EJBHome.remove( ) methods are responsible for deleting an enterprise bean. The argument is either the javax.ejb.Handle of the enterprise bean or, if it’s an entity bean, its primary key. The Handle is discussed in more detail later, but it is essentially a serializable pointer to a specific enterprise bean. When either of the EJBHome.remove( ) methods is invoked, the remote reference to the enterprise bean on the client becomes invalid: the stub to the enterprise bean that was removed no longer works. If for some reason the enterprise bean can’t be removed, a RemoveException is thrown.

The impact of the EJBHome.remove( ) on the enterprise bean itself depends on the type of bean. For session beans, the EJBHome.remove( ) methods end the session’s service to the client. When EJBHome.remove( ) is invoked, the remote reference to the session bean becomes invalid, and any conversational state maintained by the session bean is lost. The TravelAgent EJB you created in Chapter 4 is stateless, so no conversational state exists.

When a remove( ) method is invoked on an entity bean, the remote reference becomes invalid, and any data it represents is deleted from the database. This operation is destructive because once an entity bean has been removed, the data it represents no longer exists. The difference between using a remove( ) method on a session bean and using remove( ) on an entity bean is similar to the difference between hanging up on a telephone conversation and actually killing the caller on the other end.

The following code fragment is taken from the main( ) method of a client application similar to the clients we created to exercise the Cabin and TravelAgent EJBs. It shows that we can remove enterprise beans using a primary key (for entity beans only) or a Handle. Removing an entity bean deletes the entity from the database; removing a session bean results in the remote reference becoming invalid. Here’s the code:

Context jndiContext = getInitialContext( );

// Obtain a list of all the cabins for ship 1 with bed count of 3.

Object ref = jndiContext.lookup("TravelAgentHomeRemote");
TravelAgentHomeRemote agentHome = (TravelAgentHomeRemote)
    PortableRemoteObject.narrow(ref,TravelAgentHomeRemote.class);

TravelAgentRemote agent = agentHome.create( );
String list [] = agent.listCabins(1,3);  
System.out.println("1st List: Before deleting cabin number 30");
for(int i = 0; i < list.length; i++){
    System.out.println(list[i]);
}

// Obtain the home and remove cabin 30. Rerun the same cabin list.

ref = jndiContext.lookup("CabinHomeRemote");
CabinHomeRemote c_home = (CabinHomeRemote)
    PortableRemoteObject.narrow(ref, CabinHomeRemote.class);

Integer pk = new Integer(30);
c_home.remove(pk);
list = agent.listCabins(1,3);  
System.out.println("2nd List: After deleting cabin number 30");
for (int i = 0; i < list.length; i++) {
    System.out.println(list[i]);
}

First, the application creates a list of cabins, including the cabin with the primary key 30. Then it removes the Cabin EJB with this primary key and creates the list again. The second time the iteration is performed, cabin 30 is not listed; the listCabin( ) method will be unable to find a cabin with a primary key equal to 30 because the bean and its data are no longer in the database. The output should look something like this:

1st List: Before deleting cabin number 30
1,Master Suite                  ,1
3,Suite 101                     ,1
5,Suite 103                     ,1
7,Suite 105                     ,1
9,Suite 107                     ,1
12,Suite 201                     ,2
14,Suite 203                     ,2
16,Suite 205                     ,2
18,Suite 207                     ,2
20,Suite 209                     ,2
22,Suite 301                     ,3
24,Suite 303                     ,3
26,Suite 305                     ,3
28,Suite 307                     ,3
29,Suite 309                     ,3
30,Suite 309                     ,3
2nd List: After deleting cabin number 30
1,Master Suite                  ,1
3,Suite 101                     ,1
5,Suite 103                     ,1
7,Suite 105                     ,1
9,Suite 107                     ,1
12,Suite 201                     ,2
14,Suite 203                     ,2
16,Suite 205                     ,2
18,Suite 207                     ,2
20,Suite 209                     ,2
22,Suite 301                     ,3
24,Suite 303                     ,3
26,Suite 305                     ,3
28,Suite 307                     ,3
29,Suite 308                     ,3

Bean metadata

EJBHome.getEJBMetaData( ) returns an instance of javax.ejb.EJBMetaData that describes the remote home interface, remote interface, and primary key classes and indicates whether the enterprise bean is a session or entity bean.[16] This type of metadata is valuable to Java tools such as IDEs that have wizards or other mechanisms for interacting with an enterprise bean from a client’s perspective. A tool could, for example, use the class definitions provided by the EJBMetaData with Java reflection to create an environment in which deployed enterprise beans can be “wired” together by developers. Of course, information such as the JNDI names and URLs of the enterprise beans is also needed.

Most application developers rarely use the EJBMetaData. Knowing that it’s there, however, is valuable when we need to create code generators or some other automatic facility. In those cases, familiarity with the Reflection API is necessary.[17] The following code shows the interface definition for EJBMetaData. Any class that implements the EJBMetaData interface must be serializable; it cannot be a stub to a distributed object. This allows IDEs and other tools to save the EJBMetaData for later use:

public interface javax.ejb.EJBMetaData {
    public abstract EJBHome getEJBHome( );
    public abstract Class getHomeInterfaceClass( );
    public abstract Class getPrimaryKeyClass( );
    public abstract Class getRemoteInterfaceClass( );
    public abstract boolean isSession( );
    public abstract boolean isStatelessSession( )
}

The following code shows how the EJBMetaData for the Cabin EJB could be used to get more information about the enterprise bean. Notice that there is no way to get the bean class using the EJBMetaData; the bean class is not part of the client API and therefore doesn’t belong to the metadata. Here’s the code:

Context jndiContext = getInitialContext( );

Object ref = jndiContext.lookup("CabinHomeRemote");
CabinHomeRemote c_home = (CabinHomeRemote)
    PortableRemoteObject.narrow(ref, CabinHomeRemote.class);

EJBMetaData meta = c_home.getEJBMetaData( );

System.out.println(meta.getHomeInterfaceClass( ).getName( ));
System.out.println(meta.getRemoteInterfaceClass( ).getName( ));
System.out.println(meta.getPrimaryKeyClass( ).getName( ));
System.out.println(meta.isSession( ));

This application creates output like the following:

com.titan.cabin.CabinHomeRemote
com.titan.cabin.CabinRemote
java.lang.Integer
false

In addition to providing the class types of the enterprise bean, the EJBMetaData makes the remote EJB home available for the bean. Once we get the remote EJB home from the EJBMetaData, we can obtain references to the remote EJB object and perform other functions. In the following code, we use the EJBMetaData to get the primary key class, create a key instance, obtain the remote EJB home, and get a remote reference to the EJB object for a specific cabin entity from the EJB home:

Object primKeyType = meta.getPrimaryKeyClass( );
if(primKeyType instanceof java.lang.Integer){
    Integer pk = new Integer(1);

    Object ref = meta.getEJBHome( );
    CabinHomeRemote c_home2 = (CabinHomeRemote)
        PortableRemoteObject.narrow(ref,CabinHomeRemote.class);

    CabinRemote cabin = c_home2.findByPrimaryKey(pk);
    System.out.println(cabin.getName( ));
}

The HomeHandle

The HomeHandle is accessed by calling EJBHome.getHomeHandle( ) . This method returns a javax.ejb.HomeHandle object that provides a serializable reference to an enterprise bean’s remote home. The HomeHandle allows a remote home reference to be stored and used in the future. It is similar to the javax.ejb.Handle and is discussed in more detail a little later.

Creating and finding beans

In addition to the standard javax.ejb.EJBHome methods that all remote home interfaces inherit, the remote home interfaces also include special create and find methods—find methods are used with entity beans only. The following code shows the remote home interface defined for the Cabin EJB:

public interface CabinHomeRemote extends javax.ejb.EJBHome {
    public CabinRemote create(Integer id)
        throws CreateException, RemoteException;

    public CabinRemote findByPrimaryKey(Integer pk)
        throws FinderException, RemoteException;
}

Create methods throw a CreateException if something goes wrong during the creation process; find methods throw a FinderException if there is an error. Since these methods are defined in an interface that subclasses Remote, they must also declare that they throw the RemoteException.

It is up to the bean developer to define the appropriate create and find methods in the remote home interface. CabinHomeRemote currently has only one create method, which creates a cabin with a specified ID, and one find method, which looks up an enterprise bean, given its primary key. However, it is easy to imagine methods that would create and find a cabin with particular properties—for example, a cabin with three beds, or a deluxe cabin with blue wallpaper.

Beginning with EJB 2.0, the create method names can have suffixes . In other words, all create methods can take the form create< SUFFIX >( ). For example, the Customer EJB might define a remote home interface with several create methods, each of which takes a different Integer type parameter and has a different method name:

public interface CustomerHome extends javax.ejb.EJBHome {
    
    public CustomerRemote createWithSSN(Integer id, String socialSecurityNumber)
        throws CreateException, RemoteException;

    public CustomerRemote createWithPIN(Integer personalIdNumber)
        throws CreateException, RemoteException;

    public CustomerRemote createWithBLN(Integer id, String businessLicenseNumber)
        throws CreateException, RemoteException;
    
    public Customer findByPrimaryKey(Integer id) 
        throws FinderException, RemoteException;
}

While the use of a suffix in the create method names is allowed, it is not required. You can name all your create methods create(...) and differentiate them by their parameters (method overloading).

The create and find methods defined in the remote home interfaces are straightforward and easy for the client to use. The create methods must match the ejbCreate( ) and ejbPostCreate( ) methods of the bean class. The create( ), ejbCreate( ), and ejbPostCreate( ) methods match when they have the same parameters, when the arguments are of the same type and in the same order, and when their method names are the same. This way, when a client calls the create method on the home interface, the call can be delegated to the corresponding ejbCreate( ) and ejbPostCreate( ) methods on the bean instance.

For bean-managed entities, every find< SUFFIX >( ) method in the home interface must correspond to an ejbFind< SUFFIX >( ) method in the bean itself. Container-managed entities do not implement ejbFind( ) methods in the bean class; the EJB container supports find methods automatically. You will discover more about how to implement the ejbCreate( ), ejbPostCreate( ), and ejbFind( ) methods in the bean in Chapter 6 through Chapter 10.

Home methods

In addition to find and create methods, the home interface of entity beans may also define home methods . A home method is a business method that can be invoked on the home interface (local or remote) and is not specific to one bean instance. For example, the Cabin EJB could define a home method, getDeckCount( ), which returns the number of cabins on a specific deck level:

public interface CabinHomeRemote extends javax.ejb.EJBHome {
    public CabinRemote create(Integer id)
        throws CreateException, RemoteException;

    public CabinRemote findByPrimaryKey(Integer pk)
        throws FinderException, RemoteException;

    public int getDeckCount(int level) throws RemoteException;
}

Any method in the home interface that is not a create or find method is assumed to be a home method and should have a corresponding ejbHome( ) method in the bean class, as shown here:

public class CabinBean implements javax.ejb.EntityBean{
    public int ejbHomeGetDeckCount(int level){
        // implement logic to determine deck count
    }
    ...
}

Clients can use home methods from the enterprise bean’s home interface. The client does not need a reference to a specific EJB object:

Object ref = jndiContext.lookup("CabinHome");
CabinHomeRemote home = (CabinHomeRemote)
    PortableRemoteObject.narrow(ref, CabinHomeRemote.class);

int count = home.getDeckCount(2);

Home methods are only available to entity beans. They can be used for generic business logic that applies changes across a group of entity beans or obtains information that is not specific to any single entity bean. Home methods are discussed in more detail in Chapter 10.

The Remote Interface

The business methods of an enterprise bean can be defined by the bean’s remote interface. The javax.ejb.EJBObject interface, which extends the java.rmi.Remote interface, is the base class for all remote interfaces. Here is the remote interface for the TravelAgent bean we developed in Chapter 4:

public interface TravelAgentRemote extends javax.ejb.EJBObject {

    public String [] listCabins(int shipID, int bedCount)
        throws RemoteException;
}

Figure 5-4 shows the TravelAgentRemote interface’s inheritance hierarchy.

Enterprise bean interface inheritance hierarchy

Figure 5-4. Enterprise bean interface inheritance hierarchy

Remote interfaces are focused on the business problem and do not include methods for system-level operations such as persistence, security, concurrency, or transactions. System-level operations are handled by the EJB server, which relieves the client developer of many responsibilities. All remote interface methods for beans must throw a java.rmi.RemoteException, which identifies problems with distributed communications. In addition, methods in the remote interface can throw custom exceptions to indicate abnormal business-related conditions or errors in executing the business method. You will learn more about defining custom exceptions in Chapter 11 and Chapter 15. To deploy the example discussed in this section, see Exercise 5.1 in the Workbook.

EJBObject, Handle, and Primary Key

All remote interfaces extend the javax.ejb.EJBObject interface, which provides a set of utility methods and return types. These methods and return types are valuable in managing the client’s interactions with beans. Here is the definition of EJBObject:

public interface javax.ejb.EJBObject extends java.rmi.Remote {
    public abstract EJBHome getEJBHome( )
        throws RemoteException;
    public abstract Handle getHandle( ) 
        throws RemoteException;
    public abstract Object getPrimaryKey( ) 
        throws RemoteException;
    public abstract boolean isIdentical(EJBObject obj) 
        throws RemoteException;
    public abstract void remove( ) 
        throws RemoteException, RemoveException;
}

When the client obtains a reference to the remote interface, it is actually obtaining a remote reference to an EJB object. The EJB object implements the remote interface by delegating business method calls to the bean class; it provides its own implementations for the EJBObject methods, which return information about the corresponding bean instance on the server. The server automatically generates the EJB object, so the bean developer doesn’t need to write an EJBObject implementation.

Getting the EJBHome

The EJBObject.getEJBHome( ) method returns a remote reference to the bean’s EJB home. The remote reference is returned as a javax.ejb.EJBHome object, which can be narrowed to the specific enterprise bean’s remote home interface. This method is useful when an EJB object has left the scope of the remote EJB home that manufactured it. Because remote references can be passed as references and returned from methods, like any other Java object, a remote reference can quickly find itself in a completely different part of the application from its remote home. The following code is contrived, but it illustrates how a remote reference can move out of the scope of its home, and how getEJBHome( ) can be used to get a new reference to the EJB home at any time:

public static void main(String [] args) {
    try {
        Context jndiContext = getInitialContext( );  
        Object ref = jndiContext.lookup("TravelAgentHomeRemote");
        TravelAgentHomeRemote home = (TravelAgentHomeRemote)
            PortableRemoteObject.narrow(ref,TravelAgentHomeRemote.class);

        // Get a remote reference to the bean (EJB object).
        TravelAgentRemote agent = home.create( );
        // Pass the remote reference to some method.
        getTheEJBHome(agent);

    } catch (java.rmi.RemoteException re){re.printStackTrace( );}
      catch (Throwable t){t.printStackTrace( );}
}

public static void getTheEJBHome(TravelAgentRemote agent)
    throws RemoteException {

    // The home interface is out of scope in this method,
    // so it must be obtained from the EJB object.
    Object ref = agent.getEJBHome( );
    TravelAgentHomeRemote home = (TravelAgentHomeRemote)
        PortableRemoteObject.narrow(ref,TravelAgentHomeRemote.class); 
    // Do something useful with the home interface.
}

Primary key

EJBObject.getPrimaryKey( ) returns the primary key for an entity bean, and isn’t supported by EJB objects that represent other types of beans. To better understand the nature of a primary key, we need to look beyond the boundaries of the client’s view into the EJB container’s layer.

The EJB container is responsible for the persistence of entity beans, but the exact mechanism for persistence is up to the vendor. To locate an instance of a bean in a persistent store, the data that makes up the entity must be mapped to some kind of unique key. In relational databases, data is uniquely identified by one or more column values that can be combined to form a primary key. In an object-oriented database, the key wraps an object ID (OID) or some kind of database pointer. Regardless of the mechanism—which isn’t really relevant from the client’s perspective—the unique key for an entity bean’s data is represented by the primary key, which is returned by the EJBObject.getPrimaryKey( ) method.

The primary key can be used to obtain remote references to entity beans using the findByPrimaryKey( ) method:

Context jndiContext = getInitialContext( );
 
Object ref = jndiContext.lookup("CabinHomeRemote");
CabinHomeRemote home = (CabinHomeRemote)
    PortableRemoteObject.narrow(ref,CabinHomeRemote.class);

CabinRemote cabin_1 = home.create(new Integer(101));
Integer pk = (Integer)cabin_1.getPrimaryKey( );
CabinRemote cabin_2 = home.findByPrimaryKey(pk);

In this code, the client creates a Cabin EJB, retrieves its primary key, and then uses the key to get a new reference to the same Cabin EJB. Thus, we have two variables, cabin_1 and cabin_2, that are remote references to EJB objects. The variables both reference the same Cabin bean, with the same underlying data, because they have the same primary key.

A primary key is only valid for the correct bean in the correct container. For example, imagine that a third-party vendor sells the Cabin EJB as a product. The vendor sells the Cabin EJB to both Titan and a competitor. Both companies deploy the entity bean using their own relational databases with their own data. As you would expect, both cruise companies have a Cabin bean with a primary key equal to 20, but they represent different cabins for different ships. The Cabin EJBs come from different EJB containers, so their primary keys are not equivalent.[18] Every entity EJB object has a unique identity within its EJB home. If two EJB objects have the same home and same primary key, they are considered identical.

A primary key must implement the java.io.Serializable interface. This means that a primary key can always be obtained from an EJB object, stored on the client using the Java serialization mechanism, and deserialized when needed. When a primary key is deserialized, it can be used to obtain a remote reference to the same entity bean using findByPrimaryKey( ), provided that the key is used on the correct remote home interface and container. Preserving the primary key using serialization might be useful if the client application needs to access specific entity beans at a later date.

The following code shows a primary key that is serialized and then deserialized:

// Obtain cabin 101 and set its name.
Context jndiContext = getInitialContext( ); 

Object ref = jndiContext.lookup("CabinHomeRemote");
CabinHomeRemote home = (CabinHomeRemote)
    PortableRemoteObject.narrow(ref, CabinHomeRemote.class);

Integer pk_1 = new Integer(101);
CabinRemote cabin_1 = home.findByPrimaryKey(pk_1);
cabin_1.setName("Presidential Suite");

// Serialize the primary key for cabin 101 to a file.
FileOutputStream fos = new FileOutputStream("pk101.ser");
ObjectOutputStream outStream = new ObjectOutputStream(fos);
outStream.writeObject(pk_1);
outStream.flush( );
outStream.close( );
pk_1 = null;

// Deserialize the primary key for cabin 101.
FileInputStream fis = new FileInputStream("pk101.ser");
ObjectInputStream inStream = new ObjectInputStream(fis);
Integer pk_2 = (Integer)inStream.readObject( );
inStream.close( );

// Reobtain a remote reference to cabin 101 and read its name.
CabinRemote cabin_2 = home.findByPrimaryKey(pk_2);
System.out.println(cabin_2.getName( ));

Comparing beans for identity

The EJBObject.isIdentical( ) method compares two EJB object remote references. It’s worth considering why Object.equals( ) isn’t sufficient for comparing EJB objects. An EJB object is a distributed object stub and therefore contains a lot of networking and other state. As a result, references to two EJB objects may be unequal, even if they both represent the same unique bean. The EJBObject.isIdentical( ) method returns true if two EJB object references represent the same bean, even if the EJB object stubs are different object instances.

The following code starts by creating two remote references to the TravelAgent EJB. These remote EJB objects both refer to the same type of enterprise bean; comparing them with isIdentical( ) returns true. The two TravelAgent EJBs were created separately, but because they are stateless, they are equivalent. If TravelAgent EJB had been a stateful bean, the outcome would have been different. Comparing two stateful beans results in false because stateful beans have conversational state, which makes them unique. When we use CabinHomeRemote.findByPrimaryKey( ) to locate two EJB objects that refer to the same Cabin entity bean, we know the entity beans are identical, because we used the same primary key. In this case, isIdentical( ) also returns true:

Context ctx  = getInitialContext( );

Object ref = ctx.lookup("TravelAgentHomeRemote");
TravelAgentHomeRemote agentHome =(TravelAgentHomeRemote) 
    PortableRemoteObject.narrow(ref, TravelAgentHomeRemote.class);

TravelAgentRemote agent_1 = agentHome.create( );
TravelAgentRemote agent_2 = agentHome.create( );
boolean x = agent_1.isIdentical(agent_2);
// x will equal true; the two EJB objects are equal.

ref = ctx.lookup("CabinHomeRemote");
CabinHomeRemote c_home = (CabinHomeRemote) 
    PortableRemoteObject.narrow(ref, CabinHomeRemote.class);

Integer pk_1 = new Integer(101);
Integer pk_2 = new Integer(101);
CabinRemote cabin_1 = c_home.findByPrimaryKey(pk_1);
CabinRemote cabin_2 = c_home.findByPrimaryKey(pk_2);
x = cabin_1.isIdentical(cabin_2); 
// x will equal true; the two EJB objects are equal.

The Integer primary key used in the Cabin bean is simple. More complex, custom-defined primary keys require us to override Object.equals( ) and Object.hashCode( ) for the EJBObject.isIdentical( ) method to work. Chapter 10 discusses the development of more complex custom primary keys, which are called compound primary keys.

Removing beans

The EJBObject.remove( ) method removes session and entity beans. The impact of this method is the same as the EJBHome.remove( ) method. For session beans, remove( ) releases the session and invalidates the remote EJB object reference. For entity beans, the data that the bean represents is deleted from the database and the remote reference becomes invalid. The following code shows the EJBObject.remove( ) method in use:

Context jndiContext = getInitialContext( );

Object ref = jndiContext.lookup("CabinHomeRemote");
CabinHomeRemote c_home = (CabinHomeRemote)
    PortableRemoteObject.narrow(ref,CabinHomeRemote.class);

Integer pk = new Integer(101);
CabinRemote cabin = c_home.findByPrimaryKey(pk);
cabin.remove( );

The remove( ) method throws a RemoveException if for some reason the reference can’t be deleted.

The enterprise bean Handle

The EJBObject.getHandle( ) method returns a javax.ejb.Handle object. The Handle is a serializable reference to the remote EJB object. A Handle allows us to recreate a remote EJB object reference that points to the same type of session bean or the same unique entity bean from which the Handle originated. The client can save the Handle using Java serialization and then deserialize it to obtain a reference to the original EJB object.

Here is the interface definition of the Handle:

public interface javax.ejb.Handle {
    public abstract EJBObject getEJBObject( )
      throws RemoteException;
}

The Handle interface specifies only one method, getEJBObject( ). Calling this method returns the remote EJB object from which the Handle was created. Once we’ve gotten the object back, we can narrow it to the appropriate remote interface type. The following code shows how to serialize and deserialize an EJB Handle on a client:

// Obtain cabin 100.
Context jndiContext = getInitialContext( );

Object ref = jndiContext.lookup("CabinHomeRemote");
CabinHomeRemote home = (CabinHomeRemote)
    PortableRemoteObject.narrow(ref,CabinHomeRemote.class);

Integer pk_1 = new Integer(100);
CabinRemote cabin_1 = home.findByPrimaryKey(pk_1);

// Serialize the Handle for cabin 100 to a file.
Handle handle = cabin_1.getHandle( );
FileOutputStream fos = new FileOutputStream("handle100.ser");
ObjectOutputStream outStream = new ObjectOutputStream(fos);
outStream.writeObject(handle);
outStream.flush( );
fos.close( );
handle = null;

// Deserialize the Handle for cabin 100.
FileInputStream fis = new FileInputStream("handle100.ser");
ObjectInputStream inStream = new ObjectInputStream(fis);
handle = (Handle)inStream.readObject( );
fis.close( );

// Reobtain a remote reference to cabin 100 and read its name.

ref = handle.getEJBObject( );
CabinRemote cabin_2 = (CabinRemote)
    PortableRemoteObject.narrow(ref, CabinRemote.class);

if(cabin_1.isIdentical(cabin_2))
    // This will always be true.

At first glance, the Handle and the primary key appear to do the same thing, but in truth they are very different. Using the primary key requires us to have the correct remote EJB home—if we no longer have a reference to the EJB remote home, we must look up the container using JNDI and get a new home. Only then can we call findByPrimaryKey( ) to locate the actual enterprise bean. Here’s how this might work:

// Obtain the primary key from an input stream.
Integer primaryKey = (Integer)inStream.readObject( );

// The JNDI API is used to get a root directory or initial context.
javax.naming.Context ctx = new getInitialContext( );

// Using the initial context, obtain the EJBHome for the Cabin bean.

Object ref = ctx.lookup("CabinHomeRemote");
CabinHomeRemote home = (CabinHomeRemote)
    PortableRemoteObject.narrow(ref,CabinHomeRemote.class);

// Obtain a reference to an EJB object that represents the entity instance.
CabinRemote cabin_2 = home.findByPrimaryKey(primaryKey);

The Handle object is easier to use because it encapsulates the details of doing a JNDI lookup on the container. With a Handle, the correct EJB object can be obtained in one method call, Handle.getEJBObject( ), rather than the three method calls needed to look up the context, get the home, and find the actual bean. Furthermore, while the primary key can obtain remote references to unique entity beans, it is not available for session beans; Handle, on the other hand, can be used with either type of enterprise bean. This makes using a Handle more consistent across bean types.

Consistency is good in its own right, but it isn’t the whole story. Normally, we think of session beans as not having identifiable instances because they exist for only the life of the client session, but this is not exactly true. We have mentioned (but not yet shown) stateful session beans, which retain state information between method invocations. Two instances of a stateful session beans are not equivalent. A Handle allows us to work with a stateful session bean, deactivate the bean, and then reactivate it at a later time. A client could, for example, be using a stateful session bean to process an order when the process is interrupted for some reason. Instead of losing all the work performed in the session, a Handle can be obtained from the EJB object and the client application can be closed down. When the user is ready to continue the order, the Handle can be used to obtain a reference to the stateful session EJB object. Note that this process is not necessarily fault-tolerant. If the EJB server goes down or crashes, the stateful session bean is lost and the Handle is useless. It’s also possible for the session bean to time out, which would cause the container to remove it from service. If this happens, the session bean is no longer available to the client.

HomeHandle

The javax.ejb.HomeHandle is similar to javax.ejb.Handle. Just as the Handle is used to store and retrieve references to remote EJB objects, the HomeHandle is used to store and retrieve references to remote EJB homes. In other words, the HomeHandle can be stored and later used to access an EJB home’s remote reference the same way that a Handle can be serialized and later used to access an EJB object’s remote reference. Here’s how the HomeHandle can be obtained, serialized, and used:

// Obtain cabin 100.
Context jndiContext = getInitialContext( );

Object ref = jndiContext.lookup("CabinHomeRemote");
CabinHomeRemote home = (CabinHomeRemote)
    PortableRemoteObject.narrow(ref,CabinHomeRemote.class);

// Serialize the HomeHandle for the Cabin bean.
HomeHandle homeHandle = home.getHomeHandle( );
FileOutputStream fos = new FileOutputStream("handle.ser");
ObjectOutputStream outStream = new ObjectOutputStream(fos);
outStream.writeObject(homeHandle);
outStream.flush( );
fos.close( );
homeHandle = null;

// Deserialize the HomeHandle for the Cabin bean.
FileInputStream fis = new FileInputStream("handle.ser");
ObjectInputStream inStream = new ObjectInputStream(fis);
homeHandle = (HomeHandle)inStream.readObject( );
fis.close( );

EJBHome homeRef = homeHandle.getEJBHome( );
CabinHomeRemote home2 = (CabinHomeRemote)
    PortableRemoteObject.narrow(homeRef,CabinHomeRemote.class);

Inside the Handle

Thinking about how Handles might be implemented gives us a better understanding of how they work. (Just remember that each vendor has its own implementation, which may be completely different from the implemenation we’ll discuss.) Here’s an implementation of a Handle for an entity bean:

package com.titan.cabin;

import javax.naming.InitialContext;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.ejb.EJBObject;
import javax.ejb.Handle;
import java.rmi.RemoteException;
import java.util.Properties;
import javax.rmi.PortableRemoteObject;

public class VendorX_CabinHandle
    implements javax.ejb.Handle, java.io.Serializable {

    private Integer primary_key;
    private String home_name;
    private Properties jndi_properties;

    public VendorX_CabinHandle(Integer pk, String hn, Properties p) {
        primary_key = pk;
        home_name = hn;
        jndi_properties = p;
    }

    public EJBObject getEJBObject( ) throws RemoteException {
        try {
            Context ctx = new InitialContext(jndi_properties);
   
            Object ref = ctx.lookup(home_name);
            CabinHomeRemote home =(CabinHomeRemote)
                PortableRemoteObject.narrow(ref,CabinHomeRemote.class);

            return home.findByPrimaryKey(primary_key);
        } catch (javax.ejb.FinderException fe) {
            throw new RemoteException("Cannot locate EJB object",fe);
        } catch (javax.naming.NamingException ne) {
            throw new RemoteException("Cannot locate EJB object",ne);
        }
    }
}

Our implementation encapsulates the JNDI lookup and the use of the home’s findByPrimaryKey( ) method, so any change that invalidates the key also invalidates preserved Handle objects that depend on that key. Additionally, the Handle assumes that the networking configuration and naming—the IP address of the EJB server and the JNDI name of the bean’s home—remain stable. If the EJB server’s network address changes or the name used to identify the home changes, the Handle becomes useless.

In addition, some vendors choose to implement a security mechanism in the Handle that prevents its use outside the scope of the client application that originally requested it. How this mechanism would work is unclear, but the security limitation it implies should be considered before attempting to use a Handle outside the client’s scope. To deploy the example in this section, see Exercise 5.2 in the Workbook.



[15] To learn more about CORBA IDL and its mapping to the Java language, consult “The Common Object Request Broker: Architecture and Specification” and “The Java Language to IDL Mapping,” both available at the OMG web site (http://www.omg.org).

[16] Message-driven beans don’t have component interfaces and can’t be accessed by Java RMI-IIOP.

[17] The Reflection API is outside the scope of this book, but it is covered in Java in a Nutshell, by David Flanagan (O’Reilly).

[18] This is, of course, not true if both Cabin EJBs use the same database, which is common in a clustered scenario.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.140.197.136