- Naming and Directory Services
- Using JNDI with J2EE RI
- Using JNDI Outside J2EE
- Using JNDI Functionality
- Directory Services
- JNDI Security
- Summary
- Q&A
- Exercise
Using JNDI Functionality
You have already seen the most common use of JNDI within the J2EE environment and that is the lookup of objects, usually J2EE components. JNDI can also be used to:
Bind objects to the name service
List objects in a context
Create and delete contexts
These features are briefly discussed in the following sections.
CAUTION
One word of warning about the security features discussed: Some Name Services (such as LDAP) may use security features to ensure that only authorized programs can change the object bindings or contexts. On a live system, the program must supply valid user credentials when obtaining the initial context (see the "Security" section later in this lesson). If you attempt to modify an existing name or bind a new name without the requisite permissions, a javax.naming.NoPermissionException is thrown. The "Security" section of today's lesson covers this in more detail.
Binding and Renaming Objects
Binding an object means adding a name to the Name Service and associating that name with a Java object. The name and object are bound to a context using the Context.bind() method. The object to be bound must implement the Serializable interface so that the name server can store a copy of the object.
NOTE
There is a way of binding objects that does not require the class to implement the Serializable interface. A class can implement the javax.naming.Referenceable interface instead; this is quite specialized and outside the scope of this chapter (this interface is described in the JNDI Tutorial from Sun Microsystems, see http://java.sun.com/products/jndi/tutorial/).
The following code fragment taken from Day 19 shows how to bind an RMI-IIOP server (HelloUserImpl) against the name HelloUser:
HelloUserImpl hui = new HelloUserImpl(); Context ctx = new InitialContext(); ctx.rbind("HelloUser",hui);
If an object is already bound to that name, the Context.bind() method will fail with a NameAlreadyBoundException (which extends NamingException). The method Context.rebind() will unbind any currently bound object before binding the new object.
A Service Provider may not support binding of all types of objects. If the service cannot bind a particular object, it will throw an exception. An exception is also thrown if an invalid name is used. Remember that different Service Providers may have different naming conventions.
An object can be unbound using Context.unbind(), for example:
Context ctx = new InitialContext(); ctx.unbind("HelloUser",hui);
You can rename objects using Context.rename() by specifying the old name and then the new name as parameters.
ic.rename("HelloUser","SayHello");
The new name must specify a name in the same context as the old name. An object must be bound to the old name, and the new name must not have a bound object or else a NamingException is thrown.
Changing Contexts
So far you have used the default context returned by the InitalContext constructor. Often you can simplify name look up or improve performance by changing contexts.
J2EE components may find it convenient to change contexts to the Java Component Environment before looking up DataSource objects or EJB references.
Use the Context.lookup() method to look up a sub-context and then use the return Context object for future lookups. For example:
Context ic = new InitialContext(); Context ctx = (Context)ic.lookup("java:comp/env"); DataSource ds = (DataSource)ctx.lookup("jdbc/Agency");
This is particularly useful if you are doing multiple lookups within one context. The idea is much the same as changing directories in a file system when working within the same directory structure.
Listing Contexts
The namespace represents contexts as names, and you can look these up just like any other name. You can obtain a listing of the names in a context by using Context.list(). This method provides a list of name and class bindings as a javax.naming.NamingEnumeration, where each element in the enumeration is a javax.naming.NameClassPair object. Listing 3.2 shows a simple program to list the names and classes for the example sams sub context.
Listing 3.2 Full Text of JNDIList.java
import javax.naming.*; public class JNDIList { public static void main(String[] args) { try { Context ctx = new InitialContext(); if (args.length == 0) { listContext (ctx,""); } else { for (int i=0; i<args.length; i++) listContext (ctx,args[i]); } } catch (NamingException ex) { ex.printStackTrace(); System.exit(1); } } public static void listContext (Context ctx, String subctx) throws NamingException { System.out.println("Listing Context: "+subctx); NamingEnumeration list = ctx.list(subctx); while (list.hasMore()) { NameClassPair item = (NameClassPair)list.next(); String cl = item.getClassName(); String name = item.getName(); System.out.println(cl+" - "+name); } } }
The parameter to the list() method defines the name of the context to list. If this is the empty string, the method lists the current context.
Use the command
asant JNDList
to run this example program and enter a context, or list of contexts, when prompted. Initially just press return to get a listing of the default context, then try the jdbc context to see the entry for the jdbc/Agency DataSource you used earlier.
The Context.list() method returns the name and the bound object's classname, but not the object itself. It is a lightweight interface designed for browsing the namespace.
A second method, called Context.listBindings(), retrieves the object name, class name, as well as the object itself. This method invokes the extra run time cost of retrieving the object from the name. The listBindings() method also returns a NamingEnumeration, but this time each element is of type javax.naming.Binding. Access methods in the Binding class support retrieval of the information of the bound object.
Listing 3.3 shows a simple recursive tree-walking program that is a useful diagnostic tool for examining JNDI namespaces.
Listing 3.3 Full Text of JNDITree.java
import javax.naming.*; public class JNDITree { public static void main(String[] args) { Context ctx=null; try { ctx = new InitialContext(); listContext (ctx,""); } catch (NamingException ex) { System.err.println (ex); System.exit(1); } } private static void listContext (Context ctx, String indent) { try { NamingEnumeration list = ctx.listBindings(""); while (list.hasMore()) { Binding item = (Binding)list.next(); String className = item.getClassName(); String name = item.getName(); System.out.println(indent+className+" "+name); Object o = item.getObject(); if (o instanceof javax.naming.Context) listContext ((Context)o,indent+" "); } } catch (NamingException ex) { System.err.println ("List error: "+ex); } } }
Creating and Destroying Contexts
Binding a composite name will automatically create any intermediate sub-contexts required to bind the name. Binding the name such as
Day03/examples/jdbc/Agency
creates the following sub-contexts if they don't already exist:
Day03 Day03/examples Day03/examples/jdbc
you can explicitly create contexts with the Context.createSubcontext() method. The single method parameter is the name of the context. If this is a composite name, all intermediate contexts must already exist. The createSubContext() method will throw a NameAlreadyBoundException if the name already exists.
The Context.destroySubcontext() method can destroy contexts. Again, the single method parameter is the name of the context. The context does not have to be empty, because the method will remove from the namespace any bound names and sub-contexts with the destroyed context.
The destroyContext() method can throw a NameNotFoundException if the name doesn't exist and a NotContextException if the bound name is not a context.
Working with Multiple Name Services
You can specify a URL as a parameter to the Context lookup() and bind() methods. For example,
Context ic = new InitialContext(); Object obj = ic.lookup("ldap://localhost:389/cn=Winston,dc=my-domain,dc=com");
This overrides the default context and forces JNDI to perform the lookup against the specified server. You need to take care with this approach, because the CLASSPATH must contain the necessary Service Provider classes, and these must be able to process the request bind or lookup operation. In practice, this means that the URL must use the same Service Provider classes as the initial context.
JNDI Events
JNDI supports an event model similar to the event listeners in the Java AWT and Swing classes. However, the underlying JNDI Service Provider must also provide support for the event model for a client to register event handlers.
The javax.naming.event package supports two types of JNDI event listener (both are sub-classes of NamingListener):
NamespaceChangeListener reports on changes to the namespace objects that are added, removed, or renamed.
ObjectChangeListener reports on changes to an object when its binding is replaced (the object has been updated) or attributes are added, removed, or replaced.
Both interfaces define appropriate methods that are called when changes occur in the JNDI namespace. A javax.naming.event.NamingEvent object is passed to the listener method to define:
The type of event (for example, name added or object changed)
The name binding before the event occurred
The name binding after the event occurred
JNDI event handling provides an effective means for monitoring changes to a namespace to maintain up-to-date information about the registered objects.