IBM Skip to main content
Search for:   within 
      Search help  
     IBM home  |  Products & services  |  Support & downloads   |  My account

developerWorks > Java technology
developerWorks
Navigate the JNDI maze
82 KBe-mail it!
Contents:
EJB errors? Don't panic!
Get your terms straight
Properties provide a map
Finding your way to your bean
Safe at home
Resources
About the author
Rate this article
Related content:
IBM Developer Kits for the Java platform (downloads)
Getting started with Enterprise JavaBeans technology
Subscriptions:
dW newsletters
dW Subscription
(CDs and downloads)
Learn to find your bean's home interface without fuss

Level: Intermediate

Daniel Would (mailto:wouldd@uk.ibm.com?cc=&subject=Navigate the JNDI maze)
Software Engineer, IBM
18 November 2003

Java developers who move from single-machine programming to the wilder world of EJB technology and distributed computing often run into trouble: it can be difficult to write code that successfully navigates through the JNDI maze, and multiple machines and configurations just increase the chance of something going wrong. In this article, EJB developer Daniel Would explains how you can write client code that successfully finds its way to an EJB component published in a JNDI namespace. He shows you various programming options that make the process easier, and offers some code that you can use as a utility class in your own applications.

If you've never had any problem at all getting your client apps to see your EJB components, and really don't see any complexities in running your client and bean under different installs of the Java platforms, or on entirely different machines, then this article probably won't interest you. However, if you have just started out and have begun to see the many interesting and obscure error messages that seem to pop up the moment you try to do anything with a real configuration, then read on.

EJB errors? Don't panic!
So, you've read the chapter in your favorite Java book on Enterprise JavaBeans technology, and you've knocked out a quick HelloWorld bean, followed the recommended deployment procedure, and published it. Now you have to write a client that will invoke this masterpiece. So you come up with something that looks like Listing 1:

Listing 1. A very simple client for invoking a bean

InitialContext ic = new InitialContext();
Object or = ic.lookup("ejb/HelloWorldHome");
if (or != null) {
  // Narrow the return object to the Home class type
    HelloWorldHome home = 
      (HelloWorldHome)PortableRemoteObject.narrow(or, 
        HelloWorldHome.class);
  // Create an EJB object instance using the home interface.
    HelloWorld hw = home.create();
  // Invoke the method
    System.out.Println(hw.hello());
}

You run this client on the command line, using the nearest Java installation at hand -- namely, the one your app server is using. Everything works perfectly! Beaming with success, you move to run your client on a second machine. This time, you get a horrible error message. At first, you probably get java.lang.NoClassDefFoundError: javax/ejb/EJBObject, followed by a bunch of other NoClassDefFoundErrors, because you forgot to hand over a JAR file with the required stubs and ties, and various other EJB-related things were not present and accounted for. But eventually, you get your client as far as the first interesting line (InitialContext ic = new InitialContext();). The exception you see when you get to this line -- and you almost certainly will get an exception -- will vary depending on the particular context provider that you pick up.

Context? What's that? Get your terms straight
Before we proceed any further, it will be helpful to define a few terms. The computing world is littered with strange terms, buzzwords, and acronyms, and Java technology is no exception. (Or should that be JavaIsNoException?) If you're running into the problems discussed above, you're probably a little shaky on the terminology in this area. So let's discuss the terms that we will be throwing around in this article; it's a good idea to get them straight.

Namespace, context, initial context, and subcontext
These terms are all about locations -- the conceptual location where EJB components are available, from the point of view of your client. Think of a namespace as a town, with the shops in the town represented by EJB home interfaces (which we'll discuss in a moment). A context is a location within the town. An initial context is the location where you start -- the road into town, as it were. And a subcontext is a street name.

Home interface and remote interface
There are three parts to an Enterprise JavaBean component. First, there is the bean code itself. Then there is the home interface, which defines the methods that allow you to create an EJB bean of your type. The home interface is published in the namespace. When you have the home interface, you can call Create() to obtain the remote interface from the application server. Having obtained the remote interface, you can call the methods that make up your actual EJB code.

How do these terms fit into our town analogy? Well, having gotten to the right town and found the right address, you need to walk into the shop or ring the bell (call Create()). This process is the same for all the shops you might go to; however, the response you receive depends on who is waiting to provide a service -- a butcher, a baker, or a candlestick maker, for instance. This response represents the remote interface. Each person is different and can be asked to do different things. You must know that type of person (that is, the bean) that you are talking to in order to ask the right questions (that is, to call the right methods) -- it's no good asking a butcher for a loaf of bread.

CosNaming, LDAP, and JNDI
The Java Naming and Directory Interface (JNDI) provides a standard interface that dictates how you need to interact with a namespace. LDAP and CosNaming are, for our purposes, types of JNDI namespaces. To extend our metaphor: JNDI is a template for a town, while CosNaming and LDAP are specific towns. They operate in similar ways but there are differences in their layouts.

Properties provide a map
So, let's see how we use all these elements to successfully call methods on our EJB component from remote machines. In order for a client program to connect to your carefully crafted EJB component, it needs several things. First of all, it requires all the JAR files for the client code, generic EJB-related JAR files like J2EE.jar, and the stubs and ties that were generated during the deployment of your bean. Those files get your client as far as the first initial context.

The next bits of information your client needs are values for some properties. First, you'll need some value for java.naming.factory.initial. This property points to a class that provides an initial context factory. A typical value for this property, which we'll be using for these examples, would be com.sun.jndi.cosnaming.CNCtxFactory; this class exists in rt.jar and as such is part of the base JVM. This factory is for use by CosNaming nameservers, but the JVM includes an LDAP factory as well. Different application servers provide their own initial context factories, as we'll see later.

This class, along with details of nameserver URL and port number, is used to generate the InitialContext class that you use to interact with the namespace. However, without the provider URL it will simply connect to localhost at port 900 (or whatever the default port number for your context factory is). To connect to a remote server, you need a value for the property java.naming.provider.url.

The reason all this is so tricky for new programmers is that when you run everything local to your app server, this stuff generally just works. The environment takes care of everything, and when you ask for an InitialContext, the environment gives you the right one. But once you move your client to a different machine, you're on your own. You need to know which JAR files to copy, and what settings to make. I have known people to copy an app server's entire roster of JAR files to a second machine just to make their client work correctly!

By default, the InitialContext factory is defined in jndi.properties, and the factory class has defaults for a server URL and a port number. This file is found on the classpath (that generally means in the local directory) or inside any JAR on your classpath. Different app servers may provide their defaults in different JAR files; WebSphere Application Server stores a default copy in namingclient.jar. To specify your own defaults, you simply need to edit the copy of the file that will be first in the classpath. This is one way to configure the properties; in the absence of command-line or code-driven setup, the client will use values from jndi.properties. However, while this may be appropriate for a simple set-up, you may want to configure on a client-by-client basis if you are dealing with multiple servers and namespaces.

How do the values of these properties differ based on the namespace we want to use? As mentioned above, there are two flavors of JNDI namespace: CosNaming and LDAP. Each of these has a transport associated with it: IIOP and LDAP, respectively. An LDAP namespace uses a transport of LDAP (you would connect to it with a URL like ldap://myldapnameserver) and CosNaming uses a transport of IIOP (you would connect to it with a URL like iiop://mycosnamingserver). The default port number for CosNaming is 900, while the default for LDAP is 389. However, the default used by any given server implementation of the namespace may differ.

Configuring properties from the command line
Let's see how to configure the properties from the command line. If you'd like to play along at home, go to the bin folder within your JDK install. In this folder, you should find a program called tnameserv.exe (for Windows) or simply tnameserv (for UNIX-based systems). Executing this program will start up a sample CosNaming nameserver on port 900.

Now would be a good time to equip yourself with a utility capable of viewing a CosNaming namespace. I personally use Eclipse as a development environment, and I have provided a link to the JNDI Browser plug-in that I use in the Resources section below. In theory, you should be able to point a namespace browser at port 900 on your machine and see a very boring empty namespace (although some app servers will populate their namespace with many and varied things by default). To liven up our namespace, we'll now write a simple program to put something into it, illustrated in Listing 2:

Listing 2. A simple cosNaming namespace interaction

package example.publisher;

import javax.naming.InitialContext;

public class Publish {

    public static void main(String[] args) {
        //
        //This example creates a subcontext in a namespace
        //
        try{
            InitialContext ic = new InitialContext();
            ic.createSubcontext("Test");
        }catch(Exception e){
            System.out.println(e);
            e.printStackTrace();
            
        }
    }
}

This application will assume that all properties required to get the right initial context will simply be available. So now you can run it from the command line and supply those properties when you do so (adjusting the URL to your environment):


java -Djava.naming.factory.initial=com.sun.jndi.cosnaming.CNCtxFactory 
     -Djava.naming.provider.url=iiop://mymachine:900 
       example.publisher.Publish

All being well, our client will find the context for the example nameserver and create a subcontext called Test. You can confirm this by using the namespace browser.

Now try with the nameserver running on one machine and the application in Listing 2 running on another, using the same command line (though with the URL adjusted again, of course). It should work with no problem. (You might want to modify the sample to change what is bound, or even to delete the subcontext instead of creating it, so that you can be sure that it has worked the second time you run it.)

Configuring properties within the application
So, what if you don't want to set these options on the command line? There is an alternative. You can explicitly state these properties inside your program. This means you don't need to provide special options to the java command. When we update the code in Listing 2 to explicitly set the needed properties, it looks like the code in Listing 3:

Listing 3. Simple cosNaming namespace interaction, with properties set within the app code

package example.publisher;

import javax.naming.InitialContext;

public class Publish {

    public static void main(String[] args) {
        //
        //This example creates a subcontext in a namespace
        //
        try{
            Properties prop = new Properties();
            prop.setProperty("java.naming.factory.initial",
              "com.sun.jndi.cosnaming.CNCtxFactory");
            prop.setProperty("java.naming.provider.url",
              "iiop://mymachine:900");
            InitialContext ic = new InitialContext(prop);
            ic.createSubcontext("Test");
        }catch(Exception e){
            System.out.println(e);
            e.printStackTrace();
            
        }
    }
} 

Now the program requires no lengthy command-line configuration; keep in mind, however, that an application written this way is hardcoded to these settings.

Finding your way to your bean
So far, we've seen a few basic examples proving that we've connected to a remote namespace and done something, though that something was rather boring -- the creation of a subcontext. In reality, it is likely that your tooling will do all the creating and publishing of things for you; what you really want to do is look up an object. In this section, we will obtain the Home interface to a HelloWorld bean published in a CosNaming namespace. Then we'll see how to find one in an LDAP namespace as well.

For the sake of argument, let's assume that you have a HelloWorld bean deployed, and that its home interface, HelloWorldHome, is published at example/HelloWorldHome. (If you want to try this, but don't want to create a HelloWorld bean yourself, there is a link to download a prepackaged bean JAR file, as well as a file for a client to go with it, in the Resources.)

A context tip
In the namespace URL format, you can set your initial context to start further up the tree than the default. For instance, if you use iiop://mymachine:900/example for the provider URL for our example, then you would only need to look up HelloWorldHome, not example/HelloWorldHome; the initial context will be within example. This can be helpful if you are making several lookups under the same structure in a namespace; that way, if you change your setup, the only part of your code that will have to change is the provider URL.

In the last section, we did the hard work of connecting to the nameserver; now all we need to do is look up the EJB component. This requires us to pass a string to the lookup method, which represents the directions that get you from the InitialContext (where you start in the town) to the HomeInterface (the house or shop) you want. Sounds simple -- but this is where the specific context factory that you chose could start to affect matters. The factory classes that come with app servers like WebSphere do not always put you in the root of the namespace. So the string that we need in order to look up the HomeInterface changes depending on where in the town your InitialContext puts you. What's more, the context factory might put you in a different starting root for a local server than it would for a remote server.

For this reason, I recommend that the lookup string you use not be hardcoded as it was in Listing 3, but rather passed from a command line or properties file. This is particularly true in architectures with multiple steps. For instance, you might have a client that calls an EJB component; that bean might in turn need to call a second EJB component, which could potentially be on a different server! In such a situation, the properties should be passed through for each step. This provides a simple mechanism for trial-and-error lookups, and also leaves the end application flexible to relatively minor changes, such as deployment on a new nameserver. So let's look at a sample lookup application. In Listing 4, properties are set programmatically, but based off command-line values. This results in a slightly different looking command line than the one in our previous example, as we'll see.

Listing 4. Looking up a home interface

package example.lookup;
import java.util.Properties;
import javax.naming.InitialContext;
import javax.rmi.PortableRemoteObject;

import example.HelloWorld;
import example.HelloWorldBean;
import example.HelloWorldHome;
import javax.naming.InitialContext;

public class Lookup {

    public static void main(String[] args) {
        //
        //This example looks up the home interface of an EJB to a namespace
        //
        try{
            Properties prop = new Properties();
            prop.setProperty("java.naming.factory.initial",args[0]);
            prop.setProperty("java.naming.provider.url",args[1]);
            InitialContext ic = new InitialContext(prop);
            Object or = ic.lookup(args[2]);
            if (or != null) {
                // Narrow the return object to the Home class type
                HelloWorldHome home = 
                  (HelloWorldHome)PortableRemoteObject.narrow(or, 
                      HelloWorldHome.class);
                // Create an EJB object instance using the home interface.
                HelloWorld hw = home.create();
                // Invoke the method
                System.out.Println(hw.hello());
            }
        }catch(Exception e){
            System.out.println(e);
            e.printStackTrace();
            
        }
    }
}

This program is invoked with three parameters: the context factory to be used, the provider URL, and the string containing the name to look for. We already know what the first two are; what is the third?

Well, if you are still using tnameserv as your name server, then you will likely have published your bean directly to /example/HelloWorldHome. In that case, simply passing /example/HelloWorldHome as the third parameter should result in a successful lookup. However, if the nameserver you are using has a more complex namespace, you may have some additional levels imposed by the deployment tool used. WebSphere, for instance, deploys JavaBean components by default into ejb/, but this is not at the root of the namespace, and only if you use WebSphere's context factory will you be placed at the right place in the namespace by passing in the string /ejb/example/HelloWorldHome. The problem will be exacerbated if you use a context factory that is different from the one supplied by the application server (as you may need to do if you run a client from a machine that just has a standard Java installation). However, the nameserver documentation for your application server should explain where you will start in the namespace when you make a lookup for an EJB component. Look at the examples in the documentation and then look at the namespace with a browser to establish where their clients' InitialContext puts them. Namespaces tend to loop, so you can try to follow a branch to infinity. This does mean that from most starting contexts you can find a path that leads to the home you want.

At any rate, here's the command line that would pass the appropriate parameters to the application in Listing 4:


java Lookup com.sun.jndi.cosnaming.CNCtxFactory 
  iiop://mymachine:900 example/HelloWorldHome

With CosNaming, the namespace subcontexts are separated by the slash (/) character, like a standard URL. The syntax for LDAP is different, as we'll see.

Introducing LDAP
Now let's add LDAP into the picture. For our purposes here, LDAP is another JNDI namespace, but one whose structure is represented in a manner that's quite different from that for a CosNaming namespace. It also requires a different context factory -- but that's not a problem, because we'll be specifying the appropriate factory on the command line anyway. (In our example, we'll be using the one that is part of the base JVM, but again keep in mind that different application servers may have their own factories.) And it requires a pointer to a different nameserver -- and, lucky us, we're specifying that on the command line, too. Of course, the string that represents the location of the home interface is different, but guess what? That's right, we specify that one on the command line as well. You can see the benefits of using these command-line calls: all we need to do is change how we call our test program, and in theory we can get to any JNDI nameserver, even switching from CosNaming to LDAP without a hitch and without changing any code. Well, that's the theory, anyway; of course, the trick is getting the parameters right.

Some nameservers protect part of the namespace, meaning that you can publish only to those areas where you are allowed. Let's assume that you have an LDAP server running, and these are its details:

  • URL: ldap://mymachine:1389
  • BaseDN: c=myldap

The structure of the tree in the LDAP namespace will generally look something like this:


ibm-wsnName=MyServer,ibm-wsnName=HelloWorldHome

However, when we pass the string to our program, we need to reverse it (don't ask me why). So the string we use looks like this:


ibm-wsnName=HelloWorldHome,ibm-wsnName=MyServer,

The BaseDN represents the location where you want to start in the namespace. This could be many things for a given LDAP nameserver, depending on how it is structured. In this example, we go straight to a root of c=myldap. But if we wanted to jump to a tree in the namespace, we could specify ibm-wsnTree=myTree,c=myldap as a BaseDN instead jumping to that point.

So, the command-line parameters we'll pass to our program will look like this:


java Lookup com.sun.jndi.ldap.LdapCtxFactory ldap://mymachine:1389/c=myldap/ 
     ibm-wsnName=HelloWorldHome,ibm-wsnName=MyServer

Potential error messages
Despite everything, you will no doubt become familiar with various exception messages while deploying EJB components. Here are a few that you'll probably see a lot (I know that I have), along with some of the reasons you might have encountered them:

  • CORBA.OBJECT_NOT_EXIST: You've looked in the wrong place for your EJB.

  • CORBA.MARSHALL _ something: CORBA marshall exceptions occur often. They have different details but they all basically mean the same thing: you got some data, but not what your client was expecting. Maybe you looked up the wrong thing, maybe the versions of the EJB component classes your client knows about are different from the versions actually deployed. Or maybe there is a problem and the client ORB just doesn't understand what the server ORB sent.

  • javax.naming.NoInitialContextException: Whoops! You failed to specify the context factory or provider URL, or maybe the nameserver just isn't running.

Here we specify an LDAP context factory, then pass in the name of our LDAP server with the point at which we want to start. This is followed by the reversed path to the EJB component that we want to look up. And we can use this command line to invoke the same code (Listing 4) that we used in our CosNaming example.

Of course, there is no reason why the code used in this article could not form a method of a helper class -- something that takes in three parameters, and returns the Object or that is returned from the ic.lookup(args[2]) call before attempting to do any narrowing. Then, wherever you need to have a lookup done, you could simply use the helper class, pass it the appropriate parameters for your current situation, and get back the object reference you need, ready to narrow to its real class. (Note: I do not guarantee such a class's performance and am providing these code fragments "as is," without warranty of any kind by me or IBM.) Of course, a completely generic approach can be achieved using reflection, but that makes life a lot more complicated, and it's outside the scope of this article.

There's one final thing to consider before we conclude. You could write a client that combines the techniques we used in Listings 3 and 4. It would check to see if a value had been given at the command line; if so, it would then set those values; if not, it would use the hardcoded values. In that way, you could have sensible defaults in a program, but override them with command-line options if you wanted. Only small and fairly trivial adjustments to code would be required.

Safe at home
So to review: here are the top four things you should have or know to make any system work with multiple EJB lookups and multiple app servers:

  • At any given stage, all the stubs and ties of the next stage must be on the classpath. You can't narrow an object for use unless the local environment knows what the class looks like.

  • Each stage requires the generic EJB-related JAR files, such as J2EE.jar.

  • Pass the context factory type, nameserver name, and JNDI lookup string as parameters. This allows for easy accommodation of change.

  • Know your namespace. Remember that your JNDI lookup string needs to take you from the location where you start in the namespace to the location where your object is stored. But you won't always start in the same place! Get a tool to browse the namespace, and learn where you start for local and remote lookups.

Navigating through remote namespaces can be a baffling process for developers used to writing code that all executes on the same machine. Hopefully, the tips and code in this article will help you get your distributed EJB applications up and running. Once you've mastered the JNDI namespaces, go and check out developerWorks' EJB best practices series by Brett McLaughlin (see Resources) for great tips on optimizing your code.

Resources

About the author
Daniel Would joined IBM in July 2001. He worked as a CTG System Tester for one year before moving into his current role as a CICS System Tester. In his time at IBM, Daniel's work has focused on Java technology and EJB components. Contact Daniel at wouldd@uk.ibm.com.


82 KBe-mail it!

What do you think of this document?
Killer! (5) Good stuff (4) So-so; not bad (3) Needs work (2) Lame! (1)

Comments?



developerWorks > Java technology
developerWorks
  About IBM  |  Privacy  |  Terms of use  |  Contact