Lookup services enable communications over a network. A client
program can use a lookup protocol to get information on remote
programs or machines and use that information to establish
a communication.
- One common lookup service you might already be familiar with is Directory Name Service (DNS).
It maps Internet Protocol (IP) addresses
to machine names. Programs use the DNS mapping to look up the IP address
associated with a machine name and use the IP address to establish
a communication.
- In the same way, the AuctionServlet
presented in Chapter 2 uses the naming service
built into the Enterprise JavaBeansTM architecture to look up
and reference Enterprise Beans registered with the Enterprise
JavaBeansTM server.
In addition to naming services, some lookup protocols provide directory
services. Directory services such as Lightweight Directory Access Protocol
(LDAP) and Sun's NIS+ provide other information and services beyond
what is available with simple naming services. For example, NIS+ associates
a workgroup
attribute with a user account. This attribute can
be
used to restrict access to a machine so only the users in the specified
workgroup
have access.
This chapter describes how the JavaTM Naming and Directory Interface
(JNDI)
is used in the auction application to look up Enterprise Beans. It also
explains how to use some of the many other lookup services that have
become available over time. The code to use these other services is not as
simple as the lookup code in the auction application in Chapter 2, but
the advantages to these other services can outweigh the need for more
complex code in some situations.
Java Naming and Directory Interface (JNDI)
The JNDI application programming interface (API) makes it easy to plug
lookup services from various providers into a program written in the
Java language. As long as the client and server both use the same lookup
service, the client can easily look up information registered with
the server and establish communication.
The auction application session Beans use JNDI and a special JNDI naming factory
from BEA Weblogic to look up entity Beans. JNDI services normally initialize the
naming factory as a property on the command line or as an initialization value.
First, the naming factory weblogic.jndi.TengahInitialContextFactory
is put into a java.util.Property
object, then the
Property
object is passed as a parameter to the
InitialContexT
constructor. Here is an example ejbCreate
method.
Context ctx; //JNDI context
public void ejbCreate()
throws CreateException, RemoteException {
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,
"weblogic.jndi.TengahInitialContextFactory");
try{
ctx = new InitialContext(env);
}catch(Exception e) {
System.out.println("create exception: "+e);
}
}
Once created, the JNDI context is used to look up Enterprise Bean home
interfaces. In this example, a reference to the Enterprise Bean bound
to the name registration
is retrieved and used for further
operations.
RegistrationHome rhome =
(RegistrationHome) ctx.lookup("registration");
RegistrationPK rpk=new RegistrationPK();
rpk.theuser=buyer;
Registration newbidder =
rhome.findByPrimaryKey(rpk);
On the server side, the deployment descriptor for the RegistrationBean
has its beanhomename
value set to registration
.
Enterprise JavaBeans tools generate the rest of the naming code for the
server.
The server calls ctx.bind
to bind the name registration
to the JNDI
context. The this
parameter references the _stub
class that represents the RegistrationBean
.
ctx.bind("registration", this);
JNDI is not the only way to look up remote objects. Lookup services are also available
in the RMI, JINI, and CORBA platforms. You can use these platform-specific lookup services
directly or from the JNDI API. JNDI allows the application to change the name service with
little effort. For example, here are the code changes to have the
BidderBean.ejbCreate
method use the org.omb.CORBA
lookup services instead of the default BEA Weblogic lookup services.
Hashtable env = new Hashtable();
env.put("java.naming.factory.initial",
"com.sun.jndi.cosnaming.CNCtxFactory");
Context ic = new InitialContext(env);
CORBA Naming Service
The Common Object Request Broker Architecture (CORBA) defines a
specification for objects in a distributed system to communicate
with each other. Objects that use the CORBA specification to
communicate are called CORBA objects, and consist of client and
server objects.
CORBA objects can be written
in any language with Interface Definition Language (IDL) mapping. These
languages include the Java programming language, C++, and many traditional
non-object-orientated languages.
The naming lookup service, like all other CORBA specifications, is
defined in terms of IDL. The IDL module for the CORBA lookup service
is called CosNaming
. Any platform with an IDL
mapping, such as the idltojava
tool, can use this service
to look up and discover CORBA objects. The IDL module for the CORBA lookup service is
available in the Java 2 platform in the org.omg.CosNaming
package.
The key interface in the CosNaming
module is NamingContext
.
The NamingContext
interface defines methods to bind
objects to a name, list those bidding, and retrieve bound object references.
In addition to these public interfaces are helper classes. The
NameComponent
helper class is used in CORBA client and server programs to build the full
name for
the object reference name. The full name is
an array of one or more NameComponents
that indicates
where to find the objects. The naming scheme
can be application specific.
For example in the auction application, the full name can be defined
to use auction
as the root naming context, and
RegistrationBean
and AuctionItemBean
as
children of the root context. This in effect employs a similar
naming scheme as that used for the application class packaging.
In this example, the auction application has adapted
SellerBean
to a CORBA naming service to look up the
CORBA RegistrationBean
.
The following code is extracted from the SellerBean
,
which acts as the CORBA client, the and RegistrationServer
CORBA server.
CORBA RegistrationServer
This code in the
RegistrationServer
program creates a NameComponent
object
that indicates where to locate the RegistrationBean
using auction
and RegistrationBean
as
the full name.
NameComponent[] fullname = new NameComponent[2];
fullname[0] = new NameComponent("auction", "");
fullname[1] = new NameComponent(
"RegistrationBean", "");
This next code binds the fullname
as a new context.
The first elements in the full name (auction
in this example)
are placeholders for building the context naming tree. The last element
of the full name (RegistrationBean
in this example)
is the name submitted as the binding to the object.
String[] orbargs = { "-ORBInitialPort 1050"};
ORB orb = ORB.init(orbargs, null) ;
RegistrationServer rs= new RegistrationServer();
orb.connect(rs);
try{
org.omg.CORBA.Object nameServiceObj =
orb.resolve_initial_references("NameService");
NamingContext nctx =
NamingContextHelper.narrow(nameServiceObj);
NameComponent[] fullname = new NameComponent[2];
fullname[0] = new NameComponent("auction", "");
fullname[1] = new NameComponent(
"RegistrationBean", "");
NameComponent[] tempComponent =
new NameComponent[1];
for(int i=0; i < fullname.length-1; i++ ) {
tempComponent[0]= fullname[i];
try{
nctx=nctx.bind_new_context(tempComponent);
}catch (Exception e){}
}
tempComponent[0]=fullname[fullname.length-1];
// finally bind the object to the full context path
nctx.bind(tempComponent, rs);
Once the RegistrationServer
object is bound, it can be looked up with a JNDI
lookup using a CosNaming
service provider as described at
the end of the section on JNDI, or using the CORBA name lookup service. Either way,
the CORBA name server must be started before any look ups can happen.
In the Java 2 platform, the CORBA nameserver
is started as follows:
tnameserv
This starts the CORBA RegistrationServer
on the default TCP
port 900.
If you need to use a different port, you can start the server
like this. On Unix systems only root can access port numbers lower
than 1025,
tnameserv -ORBInitialPort 1091
CORBA SellerBean
On the client side, the CORBA lookup uses the
NameComponent
object to construct the name.
Start the object server as follows:
java registration.RegistrationServer
The difference in the client is that this name is passed to
the resolve
method which returns the CORBA object. The
following code from the
SellerBean object
illustrates this point.
String[] args = { "-ORBInitialPort 1050"};
orb = ORB.init(args, null) ;
org.omg.CORBA.Object nameServiceObj =
orb.resolve_initial_references("NameService") ;
nctx= NamingContextHelper.narrow(nameServiceObj);
NameComponent[] fullname = new NameComponent[2];
fullname[0] = new NameComponent("auction", "");
fullname[1] = new NameComponent(
"RegistrationBean", "");
org.omg.CORBA.Object cobject= nctx.resolve(fullname);
The narrow
method, from the object Helper
method, is generated
by the IDL compiler, which provides a detailed mapping to translate each CORBA field into
its respective Java language field. For example, the
SellerBean.insertItem
method looks up a registration CORBA object
using the name RegistrationBean
, and returns a
RegistrationHome
object. With the RegistrationHome
object, you can
return a Registration record by calling its findByPrimaryKey
method.
org.omg.CORBA.Object cobject= nctx.resolve(fullname);
RegistrationHome regHome=
RegistrationHomeHelper.narrow(cobject);
RegistrationHome regRef =
RegistrationHomeHelper.narrow(
nctx.resolve(fullname));
RegistrationPKImpl rpk= new RegistrationPKImpl();
rpk.theuser(seller);
Registration newseller =
RegistrationHelper.narrow(
regRef.findByPrimaryKey(rpk));
if((newseller == null)||
(!newseller.verifyPassword(password))) {
return(Auction.INVALID_USER);
}
Interoperable Object References (IOR)
Using a CORBA name service works for most of CORBA applications especially
when the object request brokers (ORBs) are supplied by one vendor. However,
you might find the name service is not completely compatible among all
ORBs,
and you could get a frustrating COMM_FAILURE
message when the
CORBA client tries to connect to the CORBA server.
The solution is to use an Interoperable Object Reference (IOR) instead. An
IOR
is available in ORBs that support the Internet Inter-ORB protocol (IIOP).
It
contains the information that a naming service would keep for each object such as the
host and port where the object resides, a unique lookup key for the object
on that host, and what version of IIOP is supported.
IOR Server
To create an IOR all you do is call the object_to_string
method from the ORB
class and pass it an instance of the object.
For example, to convert
the RegistrationServer
object to an IOR, you need to
add the line String ref = orb.object_to_string(rs);
to the following code in the main
program:
String[] orbargs= {"-ORBInitialPort 1050"};
ORB orb = ORB.init(orbargs, null);
RegistrationServer rs = new RegistrationServer();
//Add this line
String ref = orb.object_to_string(rs);
So, instead of retrieving this object information from a naming service, there is
another way for the server to send information to the client.
You can register the returned String
with a substitute name
server, which can be a simple HTTP web server because the object
is already in a transmittable format.
IOR Client
This example uses an HTTP connection to convert the IOR string back into
an object. You call the string_to_object
method from the
ORB
class. This method requests the IOR from the RegistrationServer
and returns the IOR string. The String
is
passed to the ORB using the ORB.string_to_object
method,
and the ORB returns the remote object reference:
URL iorserver = new URL(
"http://server.com/servlet?object=registration");
URLConnection con = ioserver.openConnection();
BufferedReader br = new BufferReader(
new InputStreamReader(con.getInputStream));
String ref = br.readLine();
org.omg.CORBA.Object cobj = orb.string_to_object(ref);
RegistrationHome regHome =
RegistrationHomeHelper.narrow(cobj);
The substitute name server can keep persistent IOR records that
can survive a restart if needed.
Remote Method Invocation (RMI)
The Remote Method Invocation (RMI) API originally used its own communication
protocol called Java Remote Method Protocol (JRMP), which resulted in having its
own lookup service. Newer releases of RMI can now use the more ubiquitous IIOP protocol,
in addition to JRMP. RMI-IIOP is covered in the next section.
The JRMP RMI naming service is similar to other lookup and naming
services. The actual lookup is achieved by calling Naming.lookup
and passing a URL parameter to that method. The URL specifies the machine
name, an optional port where the RMI naming server, rmiregistry
,
that knows about that object is running, and the remote object you want
to reference and call methods on.
For example:
SellerHome shome =
(SellerHome)Naming.lookup(
"rmi://appserver:1090/seller");
This code returns the remote SellerHome
reference
_stub
from the object bound to the name seller
on the machine called
appserver
. The rmi
part of the URL is optional
and you may have seen RMI URLs without it, but if you are using JNDI or
RMI-IIOP, including rmi
in the URL will save confusion later on.
Once you have a reference to SellerHome
, you can call its methods.
In contrast to the JNDI lookup performed by
AuctionServlet.java
,
which requires a two-stage lookup to create a context and then the
actual lookup, RMI initializes the connection to the RMI name server,
rmiregistry
, and also gets the remote reference with one call.
This remote reference is leased to the client from the
rmiregistry
.
The lease means that unless the client informs the server it still
needs a reference to the object, the lease expires and the memory is
reclaimed.
This leasing operation is transparent to the user, but can be tuned by
setting the server property java.rmi.dgc.leaseValue
value
in milliseconds when starting the server as follows:
java -Djava.rmi.dgc.leaseValue=120000 myAppServer
RMI Over Internet Inter-ORB Protocol (IIOP)
The advent of RMI over Internet Inter-ORB Protocol (IIOP), means
existing RMI code can reference and look up an object with the CORBA
CosNaming
service. This gives you greater interoperability
between architectures with little change to your existing RMI code.
Note:
The rmic
compiler provides the -iiop
option to
generates the stub and tie classes necessary for RMI-IIOP.
IIOP Server
The RMI-IIOP protocol is implemented as a JNDI plug-in, so as before,
you need to create an InitialContext
:
Hashtable env = new Hashtable();
env.put("java.naming.factory.initial",
"com.sun.jndi.cosnaming.CNCtxFactory");
env.put("java.naming.provider.url",
"iiop://localhost:1091");
Context ic = new InitialContext(env);
The naming factory should look familiar as it is the same CORBA naming
service used in the CORBA section. The main difference is the addition of a
URL value specifing the naming service to which to connect. The naming
service used here is the tnameserv
program
started on port 1091.
tnameserv -ORBInitialPort 1091
The other main change to the server side is to replace calls
to Naming.rebind
to use the JNDI rebind
method in the InitialContext
instance. For example:
Old RMI code:
SellerHome shome=(SellerHome)Naming.lookup(
"rmi://appserver:1090/seller");
New RMI code:
Hashtable env = new Hashtable();
env.put("java.naming.factory.initial",
"com.sun.jndi.cosnaming.CNCtxFactory");
env.put("java.naming.provider.url",
"iiop://localhost:1091");
Context ic = new InitialContext(env);
SellerHome shome=
(SellerHome)PortableRemoteObject.narrow(
ic.lookup("seller"), SellerHome)
IIOP Client
On the client side, the RMI lookup is changed to use an instance of
the InitialContext
in the place of RMI
Naming.lookup
.
The return object is mapped to the requested object by using the
narrow
method of the
javax.rmi.PortableRemoteObject
class.
PortableRemoteObject
replaces
UnicastRemoteObject
that was previously available in the RMI
server code.
Old RMI lookup code:
SellerHome shome= new SellerHome("seller");
Naming.rebind("seller", shome);
New RMI code:
Hashtable env = new Hashtable();
env.put("java.naming.factory.initial",
"com.sun.jndi.cosnaming.CNCtxFactory");
env.put("java.naming.provider.url",
"iiop://localhost:1091");
Context ic = new InitialContext(env);
SellerHome shome= new SellerHome("seller");
ic.rebind("seller", shome);
The PortableRemoteObject
replaces
UnicastRemoteObject
previously available in the RMI server code. The RMI code would either
extend
UnicastRemoteObject
or call the exportObject
method
from the UnicastRemoteObject
class. The
PortableRemoteObject
also contains an equivalent exportObject
method. In the
current
implementation, is is best to explicitly remove unused objects by
calling PortableRemoteObject.unexportObject()
.
JINI lookup services
(To be done later)
Improving Lookup Performance
When you run your application, if you find it would be faster
to walk the object to the other computer on a floppy, you
have a network configuration problem. The source of the problem
is how host names and IP addresses are resolved, and there is
a workaround.
RMI and other naming services use the InetAddress
class to
obtain resolved host name and IP addresses. InetAddress
caches lookup results to improve subsequent calls, but when it is
passed a new IP address or host name, it performs a cross-reference
between the IP address and the host name to prevent address spoofing.
If you supply the host name as an IP address, InetAddress
still tries to verify the name of the host.
To workaround this problem, include the host name and IP address
in a hosts file on the client.
Unix Systems:
On Unix, the hosts file is usually /etc/hosts
.
Windows:
On Winddows 95 or 98, the hosts file is
c:\windows\hosts
, (the hosts.sam
file is a sample
file). On Windows NT, the hosts file is
c:\winnt\system32\drivers\etc\hosts
All you need to do is put these lines in your hosts file.
The myserver1
and myserver2
entries
are the hosts running the remote server and rmiregistry
127.0.0.1 localhost
129.1.1.1 myserver1
129.1.1.2 myserver2
[TOP]