Java Technology Home Page
A-Z Index

Java Developer Connection(SM)
Online Training

Downloads, APIs, Documentation
Java Developer Connection
Tutorials, Tech Articles, Training
Online Support
Community Discussion
News & Events from Everywhere
Products from Everywhere
How Java Technology is Used Worldwide
 
Training Index

Writing Advanced Applications
Chapter 4: Lookup Services

[<<BACK] [CONTENTS] [NEXT>>]

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]


[ This page was updated: 13-Oct-99 ]

Products & APIs | Developer Connection | Docs & Training | Online Support
Community Discussion | Industry News | Solutions Marketplace | Case Studies
Glossary - Applets - Tutorial - Employment - Business & Licensing - Java Store - Java in the Real World
FAQ | Feedback | Map | A-Z Index
For more information on Java technology
and other software from Sun Microsystems, call:
(800) 786-7638
Outside the U.S. and Canada, dial your country's AT&T Direct Access Number first.
Sun Microsystems, Inc.
Copyright © 1995-99 Sun Microsystems, Inc.
All Rights Reserved. Legal Terms. Privacy Policy.