Developing Your First Enterprise Beans, Part 1
Pages: 1, 2, 3, 4
cabin.jar: The JAR File
TheJAR file is a platform-independent file format for compressing, packaging, and delivering several files together. Based on the ZIP file format and the ZLIB compression standards, the JAR (Java archive) tool and packages were originally developed to make downloads of Java applets more efficient. As a packaging mechanism, however, the JAR file format is a very convenient way to "shrink-wrap" components and other software for delivery to third parties. In EJB development, a JAR file packages all the classes and interfaces associated with a bean, including the deployment descriptor, into one file.
Creating the JAR file for deployment is easy. Position yourself in the dev directory that is just above the com/titan/cabin directory tree, and execute the following command:
\dev % jar cf cabin.jar com/titan/cabin/*.class META-INF/ejb-jar.xml
F:\..\dev>jar cf cabin.jar com\titan\cabin\*.class META-INF\ejb-jar.xml
You might have to create the META-INF
directory first and copy ejb-jar.xml into that
directory. The c option tells the
jar utility to create a new JAR file that
contains the files indicated in subsequent parameters. It also tells
the jar utility to stream the resulting JAR file
to standard output. The f option tells
jar to redirect the standard output to a new
file named in the second parameter (cabin.jar).
It's important to get the order of the option
letters and the command-line parameters to match. You can learn more
about the jar utility and the
java.util.zip package in Java in a Nutshell by David Flanagan, or Learning
Java by Pat Niemeyer and Jonathan Knudsen (both published by O'Reilly).
The jar utility creates the file cabin.jar in the dev directory. If you're interested in looking at the contents of the JAR file, you can use any standard ZIP application (WinZip, PKZIP, etc.), or you can use the command jar tvf cabin.jar.
Creating a CABIN Table in the Database
One of the primary jobs of a deployment tool is mapping entity beans to
databases. In the case of the Cabin EJB, we must map its
id, name,
deckLevel, shipId, and
bedCount container-managed fields to some data
source. Before proceeding with deployment, you need to set up a
database and create a CABIN table. You can use the
following standard SQL statement to create a CABIN
table that will be consistent with the examples provided in this
chapter:
create table CABIN
(
ID int primary key NOT NULL,
SHIP_ID int,
BED_COUNT int,
NAME char(30),
DECK_LEVEL int
)
This statement creates a CABIN table that has five
columns corresponding to the container-managed fields in the
CabinBean class. Once the table is created and
connectivity to the database is confirmed, you can proceed with the
deployment process.
Deploying the Cabin EJB
Deployment is the process of reading the bean's JAR file, changing or adding properties to the deployment descriptor, mapping the bean to the database, defining access control in the security domain, and generating any vendor-specific classes needed to support the bean in the EJB environment. Every EJB server product has its own deployment tools, which may provide a graphical user interface, a set of command-line programs, or both. Graphical deploymentwizards are the easiest deployment tools to use.
A deployment tool reads the JAR file and looks for ejb-jar.xml. In a graphical deployment wizard, the deployment descriptor elements are presented using a set of property sheets similar to those used in environments such as VisualBasic.NET, PowerBuilder, and JBuilder. Figure 4-3 shows the deployment wizard for the J2EE 1.3 SDK (Reference Implementation) server.

Figure 4-3. J2EE 1.3 SDK Reference Implementation's deployment wizard
The J2EE Reference Implementation's deployment
wizard has fields and panels that match the XML deployment
descriptor. You can map security roles to user groups, set the JNDI
lookup name, map the container-managed fields to the database, etc.
EJB deployment tools provide varying degrees of support for mapping
container-managed fields to a data source. Some provide sophisticated
graphical user interfaces, while others are simpler and less
flexible. Fortunately, mapping the
CabinBean's container-managed
fields to the CABIN table is a fairly
straightforward process. The documentation for your
vendor's deployment tool will show you how to create
this mapping. Once you have finished the mapping, you can complete
the deployment of the Cabin EJB and prepare to access it from the EJB
server.
Creating a Client Application
Now that the Cabin EJB has been deployed, we want to access it from a
remote client. In this section, we create a remote client that
connects to the EJB server, locates the EJB remote home for the Cabin
EJB, and creates and interacts with several Cabin EJBs. The following
code shows a Java application that creates a new Cabin EJB, sets its
name, deckLevel,
shipId, and bedCount
properties, and then locates it again using its primary key:
package com.titan.cabin;
import com.titan.cabin.CabinHomeRemote;
import com.titan.cabin.CabinRemote;
import javax.naming.InitialContext;
import javax.naming.Context;
import javax.naming.NamingException;
import java.rmi.RemoteException;
import java.util.Properties;
import javax.rmi.PortableRemoteObject;
public class Client_1 {
public static void main(String [] args) {
try {
Context jndiContext = getInitialContext( );
Object ref = jndiContext.lookup("CabinHomeRemote");
CabinHomeRemote home = (CabinHomeRemote)
PortableRemoteObject.narrow(ref,CabinHomeRemote.class);
CabinRemote cabin_1 = home.create(new Integer(1));
cabin_1.setName("Master Suite");
cabin_1.setDeckLevel(1);
cabin_1.setShipId(1);
cabin_1.setBedCount(3);
Integer pk = new Integer(1);
CabinRemote cabin_2 = home.findByPrimaryKey(pk);
System.out.println(cabin_2.getName( ));
System.out.println(cabin_2.getDeckLevel( ));
System.out.println(cabin_2.getShipId( ));
System.out.println(cabin_2.getBedCount( ));
} catch (java.rmi.RemoteException re){re.printStackTrace( );}
catch (javax.naming.NamingException ne){ne.printStackTrace( );}
catch (javax.ejb.CreateException ce){ce.printStackTrace( );}
catch (javax.ejb.FinderException fe){fe.printStackTrace( );}
}
public static Context getInitialContext( )
throws javax.naming.NamingException {
Properties p = new Properties( );
// ... Specify the JNDI properties specific to the vendor.
return new javax.naming.InitialContext(p);
}
}
To access an enterprise bean, a
client starts by using JNDI to obtain a directory connection to a
bean's container. JNDI is an
implementation-independent API for directory and naming systems.
Every EJB vendor must provide a directory service that is
JNDI-compliant. This means that they must provide a JNDI service
provider, which is a piece of software analogous to a driver in JDBC.
Different service providers connect to different directory
services - not unlike JDBC, where different drivers connect to
different relational databases. The
getInitialContext( )
method uses JNDI to obtain a network connection to the EJB server.
The code used to obtain the JNDI Context depends
on which EJB vendor you use. Consult your vendor's
documentation to find out how to obtain a JNDI
Context appropriate to your product. For example,
the code used to obtain a JNDI Context in
WebSphere might look something like the following:
public static Context getInitialContext( )
throws javax.naming.NamingException {
java.util.Properties properties = new java.util.Properties( );
properties.put(javax.naming.Context.PROVIDER_URL, "iiop:///");
properties.put(javax.naming.Context.INITIAL_CONTEXT_FACTORY,
"com.ibm.ejs.ns.jndi.CNInitialContextFactory");
return new InitialContext(properties);
}
The same method developed for BEA's WebLogic Server would be different:
public static Context getInitialContext( )
throws javax.naming.NamingException {
Properties p = new Properties( );
p.put(Context.INITIAL_CONTEXT_FACTORY,
"weblogic.jndi.WLInitialContextFactory");
p.put(Context.PROVIDER_URL, "t3://localhost:7001");
return new javax.naming.InitialContext(p);
}
Once a JNDI connection is established and a context is obtained from
the getInitialContext( ) method, the context can
be used to look up the EJB home of the Cabin EJB.
Object ref = jndiContext.lookup("CabinHomeRemote");
Throughout this book, we'll use lookup names like "CabinHomeRemote" for remote client applications. The actual name you use to do a lookup may be different, depending on the requirements of your vendor. You will need to bind a lookup name to the EJB server's naming service, and some vendors may require a special directory path.
If you are using a standard J2EE component (Servlet, JSP, EJB,
or J2EE Application Client), you will not need to set the properties
explicitly when creating a JNDI InitialContext, no
matter which EJB vendor you are using. That's
because the
JNDI
properties can be configured at deployment time and are applied
automatically. A J2EE component would obtain its
InitialContext as follows:
public static Context getInitialContext( )
throws javax.naming.NamingException {
return new javax.naming.InitialContext( );
}
This is simpler and more portable than configuring JNDI properties
for simple Java clients. All J2EE components use the same JNDI naming
system that enterprise beans use to lookup any service. Specifically,
they require that EJB references be bound to the
java:comp/env/ejb/ namespace. For example, for a
J2EE component, here's all we need to look up the
Cabin EJB:
Object ref = jndiContext.lookup("java:comp/env/ejb/CabinHomeRemote");
At deployment time you would use the vendor's deployment tools to map that JNDI name to the Cabin EJB's home. In this book, Java client applictions will need to use explicit parameters for JNDI lookups. As an alternative you could use a special J2EE component called a J2EE Application Client, but this type of component is outside the scope of this book. For more information about J2EE Application Client components consult the J2EE 1.3 (for EJB 2.0) or the J2EE 1.4 specifications.
The Client_1 application uses the
PortableRemoteObject.narrow( ) method to narrow
the Object ref to a
CabinHomeRemote reference:
Object ref = jndiContext.lookup("CabinHomeRemote");
CabinHomeRemote home = (CabinHomeRemote)
PortableRemoteObject.narrow(ref,CabinHomeRemote.class);
The PortableRemoteObject.narrow( )
method was first introduced in EJB 1.1 and continues to be used on
remote clients in EJB 2.1 and 2.0. It is needed to support the
requirements of RMI over IIOP. Because CORBA supports many
different languages, casting is not native to CORBA (some languages
don't have casting). Therefore, to get a remote
reference to CabinHomeRemote, we must explicitly
narrow the object returned from lookup( ). This
has the same effect as casting and is explained in more detail in
Chapter 5.
The name used to find the Cabin EJB's EJB home is set by the deployer using a deployment wizard like the one pictured earlier. The JNDI name is entirely up to the person deploying the bean; it can be the same as the bean name set in the XML deployment descriptor, or something completely different.