JNDI overview, Part 1: An introduction to naming services

Use JNDI to better manage your distributed applications

Those of you who've been to a library and can still remember the experience may recall the process of locating a library book. If you aren't in touch with your antiquarian side, this situation will seem unfamiliar; but every once in a while I wander off to a local library to look for a genuine, offline book. Libraries are filled with thousands of the things -- they're dusty and made of wood pulp and cow hide, but they're fascinating in their own way. In any event, when the compulsion to find a certain one strikes, I avoid the naive course of walking up and down the library aisles searching for it and turn instead to the card catalog.

TEXTBOX: TEXTBOX_HEAD: JNDI overview: Read the whole series!

A card catalog, for the uninitiated, maps the names of books to their location in the library. By going to the card catalog first and looking up the book's location, I save myself a significant amount of walking. (Incidentally, I've heard that some libraries actually allow patrons to use computers instead of the card catalog. They've got it half right -- now if they'll just put the information in the books into the computer where it belongs...)

As surprising as it may seem, the notion of a card catalog is quite handy in the world of computing, as well. In computing, we call it a naming service, which associates names with the locations of services and with information. It provides computer programs with a single location where they can find the resources they need. In the way, programs don't waste time by performing the electronic equivalent of walking up and down the aisles, and don't require that the locations be hardcoded into their logic, either.

Finding resources is of particular importance in large-scale enterprise environments, where the applications you build may depend on services provided by applications written by other groups in other departments. A well-designed naming infrastructure makes such projects possible -- and the lack of one makes them impossible. In fact, many business-process reengineering efforts begin with the design and implementation of a robust, enterprise-wide naming and directory infrastructure.

This month, I introduce the Java Naming and Directory Interface (JNDI). JNDI provides a common-denominator interface to many existing naming services. As such, JNDI was not designed to replace existing technology; instead, it provides a common interface to existing naming services. Let's begin by taking a look at some of these services.

An introduction to naming services

The figure below depicts the organization of a generic naming service.

A generic naming service

A naming service maintains a set of bindings. Bindings relate names to objects. All objects in a naming system are named in the same way (that is, they subscribe to the same naming convention). Clients use the naming service to locate objects by name.

There are a number of existing naming services, a few of which I'll describe below. They each follow the pattern above, but differ in the details.

  • COS (Common Object Services) Naming: The naming service for CORBA applications; allows applications to store and access references to CORBA objects.

  • DNS (Domain Name System): The Internet's naming service; maps people-friendly names (such as www.etcee.com) into computer-friendly IP (Internet Protocol) addresses in dotted-quad notation (207.69.175.36). Interestingly, DNS is a distributed naming service, meaning that the service and its underlying database is spread across many hosts on the Internet.

  • LDAP (Lightweight Directory Access Protocol): Developed by the University of Michigan; as its name implies, it is a lightweight version of DAP (Directory Access Protocol), which in turn is part of X.500, a standard for network directory services. Currently, over 40 companies endorse LDAP.

  • NIS (Network Information System) and NIS+: Network naming services developed by Sun Microsystems. Both allow users to access files and applications on any host with a single ID and password.

Common features

As I mentioned earlier, the primary function of a naming system is to bind names to objects (or, in some cases, to references to objects -- more on which in a moment). In order to be a naming service, a service must at the very least provide the ability to bind names to objects and to look up objects by name.

Many naming systems don't store objects directly. Instead, they store references to objects. As an illustration, consider DNS. The address 207.69.175.36 is a reference to a computer's location on the Internet, not the computer itself.

JNDI provides an interface that supports all this common functionality. I will present this interface later in this article.

Their differences

It's also important to understand how existing naming services differ, since JNDI must provide a workable abstraction that gets around those differences.

Aside from functional differences, the most noticeable difference is the way each naming service requires names to be specified -- its naming convention. A few examples should illustrate the problem.

In DNS, names are built from components that are separated by dots ("."). They read from right to left. The name "www.etcee.com" names a machine called "www" in the "etcee.com" domain. Likewise, the name "etcee.com" names the domain "etcee" in the top-level domain "com."

In LDAP, the situation is slightly more complicated. Names are built from components that are separated by commas (","). Like DNS names, they read from right to left. However, components in an LDAP name must be specified as name/value pairs. The name "cn=Todd Sundsted, o=ComFrame, c=US" names the person "cn=Todd Sundsted" in the organization "o=ComFrame, c=US." Likewise, the name "o=ComFrame, c=US" names the organization "o=ComFrame" in the country "c=US."

As the examples above illustrate, a naming service's naming convention alone has the potential to introduce a significant amount of the flavor of the underlying naming service into JNDI. This is not a feature an implementation-independent interface should have.

JNDI solves this problem with the Name class and its subclasses and helper classes. The Name class represents a name composed of an ordered sequences of subnames, and provides methods for working with names independent of the underlying naming service.

A look at JNDI naming

As I mentioned above, it's important to remember that JNDI is an interface rather than an implementation. This fact has some disadvantages -- you need access to an existing naming service (such as an LDAP service) and you need to understand something about how it works in order to play with JNDI. On the other hand, it does allow JNDI to integrate seamlessly into an existing computing environment where an established naming service holds sway.

JNDI naming revolves around a small set of classes and a handful of operations. Let's take a look at them.

Context and InitialContext

The Context interface plays a central role in JNDI. A context represents a set of bindings within a naming service that all share the same naming convention. A Context object provides the methods for binding names to objects and unbinding names from objects, for renaming objects, and for listing the bindings.

Some naming services also provide subcontext functionality. Much like a directory in a filesystem, a subcontext is a context within a context. This hierarchical structure permits better organization of information. For naming services that support subcontexts, the Context class also provides methods for creating and destroying subcontexts.

JNDI performs all naming operations relative to a context. To assist in finding a place to start, the JNDI specification defines an InitialContext class. This class is instantiated with properties that define the type of naming service in use and, for naming services that provide security, the ID and password to use when connecting.

For those of you familiar with the RMI Naming class, many of the methods provided by the Context interface outlined below will look familiar. Let's take a look at Context's methods:

  • void bind(String stringName, Object object): Binds a name to an object. The name must not be bound to another object. All intermediate contexts must already exist.

  • void rebind(String stringName, Object object): Binds a name to an object. All intermediate contexts must already exist.

  • Object lookup(String stringName): Returns the specified object.

  • void unbind(String stringName): Unbinds the specified object.

The Context interface also provides methods for renaming and listing bindings.

  • void rename(String stringOldName, String stringNewName): Changes the name to which an object is bound.
  • NamingEnumeration listBindings(String stringName): Returns an enumeration containing the names bound to the specified context, along with the objects and the class names of the objects bound to them.

  • NamingEnumeration list(String stringName): Returns an enumeration containing the names bound to the specified context, along with the class names of the objects bound to them.

Each of these methods has a sibling that takes a Name object instead of a String object. A Name object represents a generic name. The Name class allows a program to manipulate names without having to know as much about the specific naming service in use.

The example

The example below illustrates how to connect to a naming service, list all of the bindings, or list a specific binding. It uses the filesystem service provider, which is one of the reference JNDI service-provider implementations provided by Sun. The filesystem service provider makes the filesystem look like a naming service (which it is, in many ways -- filenames like /foo/bar/baz are names and are bound to objects like files and directories). I selected it because everyone has access to a filesystem (as opposed to, say, an LDAP server).

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.Binding;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import java.util.Hashtable;
public
class Main {
  public
  static
  void
  main(String [] rgstring) {
    try {
      // Create the initial context.  The environment
      // information specifies the JNDI provider to use
      // and the initial URL to use (in our case, a
      // directory in URL form -- file:///...).
      Hashtable hashtableEnvironment = new Hashtable();
      hashtableEnvironment.put(
        Context.INITIAL_CONTEXT_FACTORY, 
        "com.sun.jndi.fscontext.RefFSContextFactory"
      );
      hashtableEnvironment.put(
        Context.PROVIDER_URL, 
        rgstring[0]
      );
      Context context = new InitialContext(hashtableEnvironment);
      // If you provide no other command line arguments,
      // list all of the names in the specified context and
      // the objects they are bound to.
      if (rgstring.length == 1) {
        NamingEnumeration namingenumeration = context.listBindings("");
        while (namingenumeration.hasMore()) {
          Binding binding = (Binding)namingenumeration.next();
          System.out.println(
            binding.getName() + " " +
            binding.getObject()
          );
        }
      }
      // Otherwise, list the names and bindings for the
      // specified arguments.
      else {
        for (int i = 1; i < rgstring.length; i++) {
          Object object = context.lookup(rgstring[i]);
          System.out.println(
            rgstring[i] + " " +
            object
          );
        }
      }
      context.close();
    }
    catch (NamingException namingexception) {
      namingexception.printStackTrace();
    }
  }
}

The program in the listing above first creates an initial context from the specified JNDI provider (in this case, Sun's filesystem provider) and a URL specifying a local directory. If no additional command-line arguments are specified, the program lists the objects and names of every entity in the specified directory. Otherwise, it lists the objects and names of only those items specified on the command line.

Conclusion

You should now have both an understanding of and an appreciation for naming services in general and JNDI in particular. In distributed environments, they are valuable tools for locating information and resources. JNDI makes it possible to work with a variety of naming services without having to master a multitude of APIs. Next month, we'll take a look at the other half of JNDI -- its directory functions.

Todd Sundsted has been writing programs since computers became available in convenient desktop models. Though originally interested in building distributed applications in C++, Todd moved on to the Java programming language when it became the obvious choice for that sort of thing. In addition to writing, Todd also works as a Java architect with ComFrame Software.

Learn more about this topic

Recommended
Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more