Enterprise bean developers are required to provide a bean class, component interfaces, and, for entity beans, a primary key. Only the component interfaces and primary key class are visible to the client; the bean class is not. The component interfaces and primary key contribute to the client-side API in EJB.
In EJB 1.1, all clients, whether they are in the same container system or not, must use the Remote Client API, which means they must use the remote interface, the remote home interface, and Java RMI in all their interactions. In EJB 2.0, remote clients still must use the Remote Client API, but enterprise beans that are located in the same EJB container system 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 in more detail the remote component interfaces and the primary key, as well as other Java types that make up EJB’s remote client-side API, which is used in both EJB 2.0 and EJB 1.1. This will provide you with a better understanding of how the remote client-side API is used and its relationship with the bean class on the EJB server. At the end of this chapter, in Section 5.3, we will examine the use of local component interfaces for EJB 2.0 readers.
Enterprise JavaBeans 2.0 and 1.1 define an enterprise bean’s remote interfaces 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. EJB 1.1 required only that the wire protocol used by vendors utilize types that were compatible with Java RMI-IIOP. In other words, the interface types and values used in remote references had to be compliant with the types allowed for Java RMI-IIOP. This ensured that early Java RMI-IIOP adopters were supported and made for a seamless transition for other vendors who wanted to use real Java RMI-IIOP in EJB 2.0. In EJB 2.0, vendors can still offer other Java RMI-IIOP-compatible protocols, but in addition to any proprietary protocols they support, they must also support the CORBA IIOP 1.2 protocol as defined in the CORBA 2.3.1 specification.
To comply with Java RMI-IIOP types, EJB vendors have to restrict the definition of interfaces and arguments to types that map nicely to IIOP 1.2. These restrictions are really not all that bad, and you probably won’t even notice them while developing your beans, but it’s important to know what they are. The next few sections discuss the Java RMI-IIOP programming model for both EJB 2.0 and EJB 1.1. Note that EJB 2.0’s local component interfaces are not Java RMI interfaces and do not have to support IIOP 1.2 or use types compliant with the Java RMI-IIOP protocol.
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.
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.
The types that may be used in Java RMI are actual types. To be
compatible with Java RMI, the actual types used in the
java.rmi.Remote
interfaces must be primitives,
String
types,
java.rmi.Remote
types, or serializable types.
java.rmi.Remote
types and serializable types do
not have to explicitly implement java.rmi.Remote
and java.io.Serializable
. 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
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:
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, it is the stub that is
serialized and passed by value, not the object server remotely
referenced by the stub. In Chapter 12, 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.
This results in both the enterprise bean instance and the client
having remote references to the same EJB object. Thus, changes made
on the client using the remote reference will be reflected when the
enterprise bean instance uses the same remote reference. Figures 5-1
and 5-2 show the difference between a serializable object and a
remote reference argument in Java RMI.
The Java 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. In addition,
Remote
interface types can throw any
application-specific exceptions (exceptions defined by the
application developer) that are necessary. The following code shows
the remote interface to the TravelAgent EJB discussed in Chapter 2. This remote interface is similar to the one
defined in Chapter 4.
TravelAgentRemote
has several remote methods,
including bookPassage()
. The
bookPassage()
method can throw a
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; }
In addition to the Java RMI programming model discussed earlier, Java RMI-IIOP imposes additional restrictions on the remote interfaces and value types used in the Remote Client API. These restrictions are born of limitations inherit 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 of the restrictions; the others, like IDL name collisions, are so rarely encountered that it wouldn’t be constructive to mention them:[21]
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.
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 2.0’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, so Java RMI-IIOP provides a mechanism
for explicitly narrowing references to a specific type. The
javax.rmi.PortableRemoteObject.narrow()
method
abstracts this narrowing to provide narrowing in IIOP as well as
other protocols. Remember that while the Remote Client API requires
that you 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. The
PortableRemoteObject
abstracts the narrowing
process so that any protocol can be used.
To narrow the return argument 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);
When the narrow()
method has successfully
executed, it returns a stub that implements the
Remote
interface specified. Because the stub is
known to implement the correct type, you can then use Java’s
native casting to narrow the stub to the correct
Remote
interface. The narrow()
method takes two arguments: the remote reference that is to be
narrowed and the type to which it should be narrowed. The definition
of the narrow()
method is:[22]
package javax.rmi; public class PortableRemoteObject extends java.lang.Object { public static java.lang.Object narrow(java.lang.Object narrowFrom, java.lang.Class narrowTo) throws java.lang.ClassCastException; ... }
The narrow()
method needs to be 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:
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);
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 }
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);
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);
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);
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);
The PortableRemoteObject.narrow()
method is not
required when the remote type is specified in the method signature.
This is true of the create methods and find methods 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(12345); /* The CabinHomeRemote.findByPrimaryKey() method specifies * the CabinRemote interface as the return type, * so explicit narrowing is not needed.*/ CabinRemote cabin = cabinHome.findByPrimaryKey(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
provides life-cycle operations and metadata for the bean. When you
use JNDI to access a bean, you 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.
Here is the
EJBHome
interface:
public interface javax.ejb.EJBHome extends java.rmi.Remote { public abstract EJBMetaData getEJBMetaData() throws RemoteException; public HomeHandle getHomeHandle() // new in 1.1 throws RemoteException; public abstract void remove(Handle handle) throws RemoteException, RemoveException; public abstract void remove(Object primaryKey) throws RemoteException, RemoveException; }
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
will be discussed in more detail later, but
it is essentially a serializable pointer to a specific enterprise
bean. When either of the EJBHome.remove()
methods
are 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 (you’ll read more about this in
Chapter 12).
When a remove()
method is invoked on an entity
bean, the remote reference becomes invalid, and any data it
represents is actually deleted from the database. This is a far more
destructive activity, because once an entity bean is 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. Both end the
conversation, but the methods are a little different.
The following code fragment is taken from the
main()
method of a client
application that is similar to the clients we created to exercise the
Cabin and TravelAgent EJBs. It shows that you 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 will not be 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. Your 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
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.[23] 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 you need to create automatic code
generators or some other automatic facility. In those cases,
familiarity with the Reflection API is necessary.[24] 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(); }
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 available the remote EJB home
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
is accessed
by calling the EJBHome.getHomeHandle()
method.
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 later. It is similar to the
javax.ejb.Handle
and is discussed in more detail a
little later.
In addition to the standard
javax.ejb.EJBHome
methods that all remote home
interfaces inherit, remote home interfaces also include special
create and find methods for
the
bean. We
have already talked about create and find methods, but a little
review will solidify your understanding of the remote home
interface’s role in the Remote Client API. 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
.
The create and find methods are specific to each enterprise bean, so
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.
Only entity beans have find methods; session beans do not. Entity beans represent unique identifiable data within a database and therefore can be found. Session beans, on the other hand, do not represent data: they are created to serve a client application and are not persistent, so there is nothing to find. A find method for a session bean would be meaningless.
In EJB 2.0, the create methods were expanded so that a method name
could be used as
suffix. In other words, all create
methods may 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
String
type parameter but has a different method
names:
public interface CustomerHome extends javax.ejb.EJBHome { public CustomerRemotecreateWithSSN
(Integer id, String socialSecurityNumber) throws CreateException, RemoteException; public CustomerRemotecreateWithPIN
(Integer personalIdNumber) throws CreateException, RemoteException; public CustomerRemotecreateWithBLN
(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 in EJB 2.0 is allowed, it is not required. EJB 1.1 doesn’t support the use of suffixes in create method names.
The create and find methods defined in the remote home interfaces are
straightforward and can be easily employed by the client. The create
methods on the home interface have to match the
ejbCreate()
and ejbPostCreate()
methods on 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.
The find methods in the home interface work similarly for
bean-managed entities in EJB 2.0 and 1.1. 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 ebjCreate()
,
ejbPostCreate()
, and ejbFind()
methods in the bean in Chapter 6 through
11.
In addition to find and create methods, the home interface of EJB 2.0 entity beans may also define home methods . The 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 that 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 defined 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 have to get 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 and not session 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 one entity bean. Home methods are discussed in more detail in Chapter 11.
The business methods of an enterprise bean can be defined by the
remote
interface provided by the enterprise bean developer. The
javax.ejb.EJBObject
interface, which extends the
java.rmi.Remote
interface, is the base class for
all remote interfaces.
The following code 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.
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, at the very least, a
java.rmi.RemoteException
,
which identifies problems with distributed communications. In
addition, methods in the remote interface can throw as many custom
exceptions as needed to indicate abnormal business-related conditions
or errors in executing the business method. You will learn more about
defining custom exceptions in Chapter 12
and Chapter 14.
Please refer to Workbook Exercise 5.1, The Remote Component Interfaces. This workbook is available free, in PDF format, at http://www.oreilly.com/catalog/entjbeans3/workbooks.
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 for the
EJBObject
interface:
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. These methods return
information about the corresponding bean instance on the server. As
discussed in Chapter 2, the EJB object is
automatically generated when deploying the bean in the EJB server, so
the bean developer doesn’t need to write an
EJBObject
implementation.
The
EJBObject.getEJBHome()
method returns a remote reference to the EJB
home for the bean. 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. }
EJBObject.getPrimaryKey()
returns the primary key for an entity bean. This method is supported
only by EJB objects that represent entity beans. Entity beans
represent specific data that can be identified using this primary
key. Session beans represent tasks or processes, not data, so primary
keys are meaningless for this type of bean. 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, which was
introduced in Chapter 2 and Chapter 3.
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
encapsulated 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. From
the client’s perspective, the primary key object can be used to
identify a unique entity bean. Understanding the context of a primary
key’s uniqueness is important, as the following code shows:
Context jndiContext = getInitialContext(); Object ref = jndiContext.lookup("CabinHomeRemote"); CabinHomeRemote home = (CabinHomeRemote) PortableRemoteObject.narrow(ref,CabinHomeRemote.class); CabinRemote cabin_1 = home.create(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
, which are remote references to EJB
objects. These both reference the same Cabin bean, with the same
underlying data, because they have the same primary key.
The primary key must be used for the correct bean in the correct
container. While this seems fairly obvious, the primary key’s
relationship to a specific container and home interface is important.
The primary key can be guaranteed to return the same entity only if
it is used within the container that produced the key. As an 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. An Integer
primary key with
value of 20 in Titan’s EJB system will not map to the same
Cabin entity in the competitor’s EJB system. 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.[25] 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 the primary
key, regardless of its form, can 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 right 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 to reobtain a remote reference to the same bean:
// 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());
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 shows how this works. It 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 considered to be equivalent. If TravelAgent EJB
had been a stateful bean (which it becomes in Chapter 12) the outcome would have been different.
Comparing two stateful beans in this manner will result 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
because both remote EJB object references
point to the same entity. Here’s the code:
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 11 discusses the development of more complex
custom primary keys, which are called compound primary keys.
The EJBObject.
remove()
method is used
to
remove session and entity beans. The
impact of this method is the same as the
EJBHome.remove()
method discussed previously. For
session beans, remove()
causes the session to be
released and the remote EJB object reference to become invalid. For
entity beans, the actual entity data 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
EJBObject.getHandle()
method returns a javax.ejb.Handle
object. The
Handle
is a serializable reference to the remote
EJB object. This means that the client can save the
Handle
object using Java serialization and then
deserialize it to reobtain a reference to the same remote EJB object.
This is similar to serializing and reusing the primary key. The
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.
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 you’ve gotten the
object back, you can narrow it to the appropriate remote interface
type. The following code shows how to serialize and deserialize the
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(101); 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 you to have the correct remote EJB home—if you no
longer have a reference to the EJB remote home, you must look up the
container using JNDI and get a new home. Only then can you call
findByPrimaryKey()
to locate the actual enterprise
bean. The following code shows 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 = 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 required 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, of course, 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.
With stateful session beans, two instances are not equivalent. A
Handle
allows you to work with a stateful session
bean, deactivate the bean, and then reactivate it at a later time
using the Handle
.
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 as fault tolerant as using the
Handle
or primary key of an entity object. If the
EJB server goes down or crashes, the stateful session bean will be
lost and the Handle
will be useless. It’s
also possible for the session bean to time out, which would cause the
container to remove it from service so that it is no longer available
to the client.
Changes to the
container technology can invalidate both
Handles
and primary keys. If you think your
container technology might change, be careful to take this limitation
into account. Primary keys obtain EJB objects by providing unique
identification of instances in persistent data stores. A change in
the persistence mechanism, however, can impact the integrity of the
key.
The
javax.ejb.HomeHandle
is similar in purpose 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. The following code shows 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);
Different vendors define their concrete
implementations of the EJB
Handle
objects differently. However, thinking
about hypothetical implementations of Handles
will
give you a better understanding of how they work. In this example, we
define the implementation of a Handle
for an
entity bean. Our implementation encapsulates the JNDI lookup and the
use of the home’s findByPrimaryKey()
method
so that any change that invalidates the key invalidates preserved
Handle
objects that depend on that key.
Here’s the code for our hypothetical implementation:
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); } } }
The Handle
is less stable than the primary key
because it relies on the networking configuration and
naming—the IP address of the EJB server and the JNDI name of
the bean’s home—to 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.
Please refer to Workbook Exercise 5.2, The EJBObject, Handle, and Primary Key. This workbook is available free, in PDF format, at http://www.oreilly.com/catalog/entjbeans3/workbooks.
[21] 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).
[22] Other
methods included in the PortableRemoteObject
class
are not important to EJB application developers. They are intended
for Java RMI developers.
[23] Message-driven beans in EJB 2.0 don’t have component interfaces and can’t be accessed by Java RMI-IIOP.
[24] The Reflection API is outside the scope of this book, but it is covered in Java™ in a Nutshell, by David Flanagan (O’Reilly).
[25] This is, of course, not true if both Cabin EJBs use the same database, which is common in a clustered scenario.