This chapter has been something of a mixed bag. We first discussed the general idea of a naming service. This led to a discussion of the idea of federating, and from there we implemented
our own naming service. Implementing our own naming service served two purposes: it helped to make the idea that there are many different possible naming services, each with their own
advantages and disadvantages, more concrete. It also served as a fully worked example of a highly threaded server; our naming service is significantly more complex that any version of
Account_Impl that will appear in this book.
The final topic for this chapter is the Java Naming and Directory Interface JNDI. JNDI consists of three things:
• A set of fairly generic interfaces that encapsulate the common operations performed on
naming services. These interfaces are defined in the javax.naming
and javax.naming.directory
packages and include such interfaces as Context
, Attribute
, and Name
. •
A generic way for client code to initialize a connection to a naming service that implements the JNDI interfaces. JNDI provides the
InitialContext class, defined in
the javax.naming
package, to enable code to connect to a naming service. InitialContext
works by using various system properties to create an instance of Context
for client code. •
A device-driver model for naming service providers. Client code uses the interfaces and connects to a context using
InitialContext . The last remaining piece involves
connecting a naming service to JNDI. This requires two things: a set of classes that implement the JNDI interfaces and code that enables
InitialContext and its
supporting classes to create and configure the naming service.
15.8.1 The Context Interface
From the point of view of an application developer, the most important part of JNDI is the Context
interface. At first glance, Context
is intimidating: it defines 29 methods as opposed to the RMI registrys 5 methods or the 15 methods we defined in our naming service. However,
when you take a closer look at them, Context
becomes a much friendlier interface. Here are the methods defined in
Context :
public NamingEnumeration listName name throws NamingException public NamingEnumeration listString name throws NamingException
public NamingEnumeration listBindingsName name throws Naming Exception public NamingEnumeration listBindingsString name throws
NamingException public Object lookupName name throws NamingException
public Object lookupString name throws NamingException public Object lookupLinkName name throws Naming Exception
public Object lookupLinkString name throws NamingException public void bindName name, Object obj throws NamingException
public void bindString name, Object obj throws NamingException public void rebindName name, Object obj thr ows NamingException
public void rebindString name, Object obj throws NamingException public void unbindName name throws NamingException
public void unbindString name throws NamingException public void renameName oldName, Name newName throws NamingE xception
public void renameString oldName, String newName throws NamingException
public Context createSubcontextName name throws NamingException public Context createSubcontextString name throws NamingException
public void destroySubcontextName na me throws NamingException public void destroySubcontextString name throws NamingException
public void close throws NamingException public String getNameInNamespace throws NamingException
public Object addToEnvironmentString propName, Object p ropVal throws NamingException
public Hashtable getEnvironment throws NamingException public Object removeFromEnvironmentString propName throws
NamingException public Name composeNameName name, Name prefix throws NamingException
public String composeNameString name, String prefix throws NamingException
public NameParser getNameParserName name throws NamingException public NameParser getNameParserString name throws NamingException
Twenty-one of the 29 methods defined in Context
are variations on methods weve already seen. There are minor changes, of course. For example,
list returns an instance of
NamingEnumeration rather than an array of objects. And the process of unbinding a server and
rebinding it under another name has been promoted to a full-fledged method called rename
. However, all in all, most of this is fairly straightforward.
NamingEnumeration
NamingEnumeration is an interface that extends the
Enumeration interface defined in the
java.util package.
Enumeration defines the
following two methods: public boolean hasMoreElements
public Object nextElement And
NamingEnumeration adds three additional methods:
public void close throws NamingException public boolean hasMore throws NamingException
public Object next throws NamingException
NamingEnumeration extends
Enumeration for convenience™one
piece of code can get NamingEnumeration
and then pass it around to other objects as an instance of
Enumeration which is a more familiar
interface. The implementations of hasMore
and hasMoreElements
are functionally equivalent™its just that one throws a more detailed type of exception.
You may wonder why NamingEnumeration
doesnt extend Remote
. As it is defined, the instances of
NamingEnumeration have to be
serialized and passed by value over the wire. Why not instead use the following interface:
public interface NamingEnumeration extends Remote { public void close throws NamingException,
RemoteException public boolean hasMore throws NamingException,
RemoteException public Object next throws NamingException,
RemoteException }
This lets us use NamingEnumeration
as a remote iterator one of our design guidelines from
Chapt er 7 . But it also leads to two significant
problems:
•
It makes JNDI RMI-centric. Other naming and directory services might not use RMI. Making the
NamingEnumeration interface
extend Remote
, and declaring the methods to throw RemoteException
, partially defeats the goal of a generic interface.
•
It forces instances of NamingEnumeration
to be servers. If there are only five return values for a
list... query, the
query probably shouldnt cause the creation of a short -lived remote iterator.
Instead, if we want to use a remote iterator for a particular response, we can create a subclass of
NamingEnumeration that uses a stub on the
remote iterator and forwards method calls. In this case, the instance of the subclass of
NamingEnumeration becomes a client-side proxy for
the remote iterator.
The genuinely new functionality is contained in seven methods. The first three methods enable Context
to store environment variables in an instance of Hashtable
for easy retrieval: public Object addToEnvironmentString prop Name, Object propVal throws
NamingException public Hashtable getEnvironment throws NamingException
public Object removeFromEnvironmentString propName throws NamingException
There are two main differences between a name-value pair bound into a context and a name- value pair that defines an environment variable:
• When you use
bind , theres an implicit understanding that what youre binding is a
complex structure of some type. It doesnt necessarily have to be a server,
[ 8]
but its usually an object with methods and structure.
[ 8]
In the case of RMI, the object being bound has to be a server. But with other JNDI contexts, the LDAP JNDI implementation for example, there are no restrictions at all.
• When you use
bind , youre explicitly allowing for paths, complex names, and more
general queries. For example, if the context you bind into is a subcontext of another context, the object that has been bound may be accessible via a path-based lookup.
Environment variables are explicitly local and can only be retrieved by name.
Its not clear when environment variables are a good idea. Theyre often convenient theyre the conceptual equivalent of attaching a sticky note to the context, but they dont really add any
functionality. And using such a weakly typed interface to store important information can only cause headaches.
The final four methods are: public Name composeNameName name, Name prefix throws NamingException
public String composeNameString name, String prefix throws NamingException
public NameParser getNameParserName name throws NamingException public NameParser getNameParserString name throws NamingException
These are an attempt to get around the fact that different naming services use different formatting conventions for their names. For example, the RMI registry uses strings with the following format:
rmi: host:port
humanReadableName The naming service we implemented, on the other hand, doesnt use a URL format. Instead, it
just uses a path-based notation to choose between contexts. The
NamingParser interface lets client code parse, and deal with components of names,
without having to know the exact details of the format.
JNDI Service Providers
A JNDI Service Provider Implementation is an implementation of the JNDI interfaces, including the code that enables
Initial Context to
find the naming service so specified. As of this writing, JDK 1.3 ships with two JNDI Service Provider implementations: one that wraps the RMI
registry, and one that wraps the CORBA naming service.
In addition, you can download several additional service providers from Javasofts web site. Currently, the Javasoft web site contains JNDI
wrappers for DNS, LDAP, and the filesystem. Of course, third-party software companies such as Novell provide JNDI wrappers for their
directory-based products.
15.8.2 Using JNDI with the Bank Example