Naming and directory services provide critical user support in large enterprise networks. They allow users to access information about the network's services, applications, computers, shared devices, and users. Without naming and directory services, a user's knowledge of network capabilities is severely limited, often confined to whatever he can learn from user manuals and word of mouth.
As an enterprise software development platform, Java provides comprehensive support for naming and directory services. The Java Naming and Directory Interface (JNDI) is a standard extension API that allows naming and directory services to be integrated with Java applications and applets. JNDI is independent of any specific naming and directory services, such as X.500 or the NetWare Directory Service (NDS), and allows a wide variety of these services to be used in a common way.
In this chapter, you'll learn about directory services and how they simplify enterprise networking. You'll be introduced to JNDI and learn how it can be used to interface Java programs with these directory services. You'll also learn how to use specific directory service providers with JNDI. When you finish this chapter, you'll be able to develop directory service-enabled applications.
Naming services map names to network objects and are essential to network communication. Imagine using the phone without a phone book or a directory information service. Or worse, imagine using the Internet without the Domain Name Service (DNS). The phone book is an example of a naming service. It maps people's names and addresses to their phone numbers. DNS is another name service. It maps computer names to their IP addresses. You'll learn about other naming services later in this book. When you study Java remote method invocation (RMI) in Part IX, you'll learn about the naming service that RMI uses to map object names (URLs) to distributed objects. When you study CORBA in Chapter 41, "Java IDL and ORBs," you'll learn how the CORBA naming service maps object names to their implementation.
Naming services are said to bind an object name with the object being named. A context is a set of name-to-object bindings. This terminology is commonly used in the DNS, RMI, and CORBA naming services, but not in naming services such as the phone book. A naming service lookup results in the named object being retrieved based on its name. The process of using a name to look up a named object is name resolution.
Names may be atomic, compound, or composite. Atomic names are indivisible names that identify an object. For example, the name index.htm names a file on my Web server. Compound names are names that consist of one or more atomic names. For example, the relative path /java/examples/index.htm names a file on my Web server. Composite names are names that are composed of multiple naming services. For example, the URL http://www.jaworski.com/java/examples/index.htm consists of a protocol identifier (http://), a DNS name (www.jaworski.com), and a file system path (/java/examples/ index.htm).
Directory services build upon and extend naming services. Directory services usually organize name spaces in a hierarchical fashion and include attributes that provide additional information about named objects. For example, consider the DOS and UNIX file systems. File and directory names are mapped to file and directory objects. These objects are organized in a hierarchical fashion, with subdirectories and files extending the directories in which they are contained. Size, date, and access attributes provide additional information about the files and directories of the file system.
Network directory services provide information about the enterprise network, computers, devices (such as printers), network services and applications, users, security information, and other objects. Examples of directory services include the International Standards Organization's X.500 directory service, the Novell NetWare Directory Service (NDS), and Sun's Network Information Service Plus (NIS+). NDS and NIS+ are both proprietary protocols.
The Lightweight Directory Access Protocol (LDAP) is a popular protocol for accessing directory service. It was developed at the University of Michigan as a front-end for the X.500 directory service but has grown to replace X.500 servers with its own directory servers. Its principal advantages are that it is non-proprietary, runs over TCP/IP networks, and is a manageable subset of the X.500 international standard. LDAP version 3 is the current version and is described in RFC 2251.
LDAP's popularity stems from X.500's shortcomings. X.500 provides the basis for large directory services that are distributed over wide area networks; LDAP scales X.500 services to the needs of large enterprises. X.500 requires the use of the higher-level OSI protocol layers; LDAP works over TCP/IP. LDAP simplifies the management of X.500 directories and makes these directories globally accessible via the Internet.
LDAP directories consist of individual entries that contain information about an object, such as a person. Each entry consists of a set of attributes. Each attribute consists of a type and a value. Figure 35.1 provides an example of an LDAP entry.
FIGURE 35.1. An LDAP entry describes an object.
LDAP entries are organized in a hierarchical fashion. For example, an LDAP server could contain information about the Federal Government, one part of which is the Department of Defense (DOD). DOD directories can be organized into the Army, Navy, and Air Force, as shown in Figure 35.2. These directories are refined into smaller organizational units until all Federal Government employees are included in the directory name space.
FIGURE 35.2. LDAP directories are organized in a hierarchical structure.
The JNDI API consists of three packages: javax.naming, javax.naming.directory, and javax.naming.spi. The javax.naming package supports naming operations, and javax.naming.directory supports directory operations. The javax.naming.spi package provides support for service provider interfaces, such as LDAP, NDS, and NIS+. These packages are covered in the following subsections.
NOTE: The JNDI is a standard extension API. It can be downloaded from the JavaSoft Web site at http://www.javasoft.com/jndi/index.html.
The javax.naming package provides five interfaces and 10 classes that support basic naming operations. These interfaces and classes are used as follows:
The javax.naming package provides the basic classes and interfaces for managing name spaces. These classes and interfaces are used in conjunction with those of the javax.naming.directory and javax.naming.spi packages.
The javax.naming.directory package consists of three interfaces and six classes that provide directory services to Java programs. These interfaces and classes are as follows:
SearchResult--Returns a search result as a binding.
Since DirContext and InitialDirContext extend Context and InitialContext, from an API perspective, you can view a directory service as an extension of a naming service with additional support provided for Attribute objects.
The javax.naming.spi package provides the interface between the JNDI and provider-specific naming and directory service implementations, such as LDAP and NDS. This package consists of the following five interfaces and three classes:
The next section will show you how to use JNDI with the specific service providers.
NOTE: A service provider is an implementation of a naming or directory service, such as LDAP or NDS, that is accessible via JNDI.
JNDI can be viewed as an API for developing naming and directory service clients that work with multiple naming and directory services. These services are implemented by specific service providers, such as LDAP or NIS+. Figure 35.3 shows how the JNDI API fits into this architecture. A Java application uses the classes and interfaces of the JNDI API to access local naming and directory service capabilities, which are provided through a Naming Manager (see the NamingManager class of the javax.naming.spi package). The Naming Manager provides access to locally installed service provider implementations through the JNDI Service Provider Interface (refer to the javax. naming.spi package). These service provider implementations, such as LDAP and NDS, provide access to enterprise naming services.
FIGURE 35.3. The JNDI architecture.
Figure 35.1 shows that using JNDI requires the use of specific service providers and naming/directory services. Service providers for LDAP, NIS+, NDS, and the File System Service Provider are available from the JNDI Web site at http://www.javasoft.com/products/jndi/index.html. With the exception of the File System Service Provider, using these service providers requires the availability of naming and directory servers. If you have access to an enterprise naming and directory server, you should download a service provider that is appropriate for your enterprise server. The File System Service Provider is a directory service that operates on your local file system.
To get you started with JNDI, JavaSoft includes in the JNDI 1.1 distribution a test service provider that operates out of a flat, local name space. We'll use this service provider to illustrate JNDI programming. However, you can easily substitute other service providers that are compatible with your enterprise network. Each service provider will provide its own unique installation instructions.
Before going on, download the JNDI 1.1 distribution from JavaSoft's Web site. It consists of a single jndi11.zip file that unzips into the following:
Make sure that you compile the flat service provider before going to the next section. You can run the TestFlat program included in the examples\spi\flat directory to verify that the flat service provider is set up correctly. It will generate the following output:
original b:binding_b a:binding_a c:binding_c after changes aa:binding_a d:binding_d c:new_binding
The flat service provider implements a simple flat (non-hierarchical) naming service. However, it illustrates the basics of using the JNDI API to access any naming service. The JNDITest program, shown in Listing 35.1, shows how to create a simple client to use the flat name service.
When you run JNDITest from a console window, it displays the following output:
Commands: add name objectString delete name change oldName newName lookup name names bindings exit >
Use the add command to add a name-to-object binding to the name space. For example:
add Jamie jamie@jaworski.com
This binds the name Jamie to the email address jamie@jaworski.com. Use the add command to add this binding. Also add the following bindings:
add Z kenz@cts.com add Tim timdel@ix.netcom.com
You can then enter the bindings command to obtain a list of the bindings that you entered:
> bindings Jamie -> jamie@jaworski.com Tim -> timdel@ix.netcom.com Z -> kenz@cts.com
The names command simply lists all names in the name space. You can use it as follows:
> names Jamie Tim Z
The change command can be used to rename a name to a new name. Change the name Z to Ken using the following command:
change Z Ken
You can verify the change with another bindings command:
> bindings Ken -> kenz@cts.com Jamie -> jamie@jaworski.com Tim -> timdel@ix.netcom.com
The lookup command returns the object to which a name is bound. When you look up Tim it returns Tim's email address, as follows:
> lookup Tim timdel@ix.netcom.com
The delete command deletes a name and its binding from the name space. Let's use it to delete Tim, as follows:
delete Tim
Then run a bindings command to verify the contents of the name space:
> bindings Ken -> kenz@cts.com Jamie -> jamie@jaworski.com
Play around with the program to become familiar with its operation. When you finish, enter exit to exit the program.
The JNDITest program uses the javax.naming and examples.spi.flat packages in addition to java.util and java.io. These packages must be in your CLASSPATH for the program to compile and execute.
The JNDITest class declares the environment variable as a Hashtable to hold the java.naming.factory.initial property. This property identifies the naming factory used to create the service provider. Its value is set to examples.spi.flat. FlatInitCtxFactory in the JNDITest() constructor so that the flat service provider is configured for use. You may set this property to that of another installed service provider. Other configuration parameters may also need to be supplied, as described in the service provider's documentation.
The context variable is used to reference a naming context. The in variable is used to access an input reader. The command, parm1, and parm2 variables reference the command and parameters entered by the user.
After setting up the java.naming.factory.initial property, the JNDITest() constructor creates a new initial naming context. This context is created with respect to the flat service provider, using the FlatInitCtxFactory class.
The run() method displays the list of commands to the user and then loops forever, getting the next command and then processing it. An exit command causes the loop to terminate.
The getCommand() method prompts the user to enter a command and then reads the line entered by the user. It invokes the parseCommand() method to parse the line into its command and parameters. The parseCommand() method uses a StringTokenizer to parse the input line.
The processCommand() method is the heart of this example and shows how the methods of the Context interface are used to implement naming service operations. These methods are as follows:
The hasMore() and next() methods are used to iterate through a NamingEnumeration and retrieve individual Binding objects. The getName() and getObject() methods of Binding return the name and object to which a name is bound.
import java.io.*; import javax.naming.*; import examples.spi.flat.*; public class JNDITest { Hashtable environment = new Hashtable(); Context context; BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); String command; String parm1; String parm2; public static void main(String[] args) { JNDITest app = new JNDITest(); app.run(); } public JNDITest() { // Setup SPI environment.put("java.naming.factory.initial", "examples.spi.flat.FlatInitCtxFactory"); try { context = new InitialContext(environment); } catch (NamingException e) { e.printStackTrace(); System.exit(0); } } void run() { displayHelp(); for(;;) { getCommand(); if(command.equals("exit")) break; processCommand(); } } void getCommand() { try { System.out.print("> "); System.out.flush(); String newLine = in.readLine(); if(newLine == null) command = "exit"; else parseCommand(newLine); } catch(Exception e) { e.printStackTrace(); System.exit(0); } } void parseCommand(String line) { StringTokenizer tokenizer = new StringTokenizer(line); if(tokenizer.hasMoreElements()) command = ((String) tokenizer.nextElement()).toLowerCase(); else command = ""; if(tokenizer.hasMoreElements()) parm1 = (String) tokenizer.nextElement(); else parm1 = ""; if(tokenizer.hasMoreElements()) parm2 = (String) tokenizer.nextElement(); else parm2 = ""; } void processCommand() { try { if(command.equals("add")) { if(!parm1.equals("") && !parm2.equals("")) { context.bind(parm1,parm2); }else System.out.println("*** missing parameter"); }else if(command.equals("delete")) { if(!parm1.equals("")) { context.unbind(parm1); }else System.out.println("*** missing parameter"); }else if(command.equals("change")) { if(!parm1.equals("") && !parm2.equals("")) { context.rename(parm1,parm2); }else System.out.println("*** missing parameter"); }else if(command.equals("lookup")) { if(!parm1.equals("")) { System.out.println(context.lookup(parm1)); }else System.out.println("*** missing parameter"); }else if(command.equals("names")) { NamingEnumeration bindings = context.listBindings(""); if(bindings != null) { while(bindings.hasMore()) { Binding b = (Binding) bindings.next(); System.out.println(b.getName()); } } }else if(command.equals("bindings")) { NamingEnumeration bindings = context.listBindings(""); if(bindings != null) { while(bindings.hasMore()) { Binding b = (Binding) bindings.next(); System.out.println(b.getName()+" -> "+b.getObject()); } } }else System.out.println("*** unrecognized command"); }catch(Exception e) { e.printStackTrace(); System.exit(0); } } void displayHelp() { System.out.println("Commands:"); System.out.println(" add name objectString"); System.out.println(" delete name"); System.out.println(" change oldName newName"); System.out.println(" lookup name"); System.out.println(" names"); System.out.println(" bindings"); System.out.println(" exit"); } }
In this chapter, you learned about directory services and how they simplify enterprise networking. You were introduced to JNDI, and you learned how it can be used to interface Java programs with directory services. You also learned how to use specific directory service providers with JNDI. In the next chapter, you'll learn how the Java Management API supports the management of enterprise networks.
© Copyright, Macmillan Computer Publishing. All rights reserved.