Java Naming and Directory Interface (JNDI)

In Part 3 of this series of articles, the java.rmi.Naming class was used to bind and locate objects in the remote server of the RMI application. This class is part of the Java Application Programming Interface (API) for the provision of a simple naming service.
As time goes on, more and more naming and directory service products were made available by different vendors. Each product is different from the others. Over the years, many different naming directory standards and protocols for accessing directories have become available. The more well-known, commercially available directory services available today include: Novell eDirectory, Sun Java System Directory Server, Microsoft Active Directory, Fedora Directory Server, and OpenLDAP (an open source implementation of LDAP).
Problems with Different Directory Standards
The problem with different directory standards is that the client code for connecting to the directory has to be rewritten each time it accesses a different directory service. As each naming and directory service has its own API library (or commonly known as driver), you need to download it, connect it to your client code and test it each time you use a different directory service. This is tedious, time-consuming and error prone.
Java Solution
To resolve the problem arising from different directory standards, Java provides a layer of indirectness in the form of Java Naming and Directory Interface (JNDI). As the name implies, JNDI provides a common interface for Java-based clients to access any naming and directory services. Instead of writing client code to connect to the different directory services, developers write a unified JNDI code that serves as a bridge over disparate naming and directory services. Whether you are connecting to a LDAP (Lightweight Directory Access Protocol), an NIS (Network Information System), or Novell eDirectory (formerly Network Directory System (NDS)) type of directories, you use the same API to connect to them. There is thus a common framework for users to connect to different naming and directory services, without them having to download drivers, write different client code and test them. This approach greatly reduces the burden on developers in writing drivers to connect to different types of directory services.
Client API and Service Provider Interface
There are two parts in JNDI:
-
The Client Application Programming Interface (API)
-
The Service Provider Interface (SPI)
The API is used by developers to connect to any directory services and the SPI is the converse of the API. SPI is implemented by different directory service providers with their proprietary protocols into the JNDI system.
The JNDI architecture of API and SPI allows clients to leverage on the various naming and directory services while maintaining a high level of portability of their code. Figure 4.1 shows how the two parts of the JNDI architecture connect with one another.
FIGURE 4.1: Client API and SPI
JNDI Service Providers
The Java SE comes bundled with a set of JNDI service providers. They include: RMI-IIOP, LDAP, CORBA Naming Service and DNS (Domain Name System). Other service providers can be added separately; they include: Novell eDirectory (or NDS), NIS, SLP (Service Location Protocol available from Novell), File System and many others.
For a full list of service providers, check out https://java.sun.com/products/jndi/serviceproviders.html
Binding, Contexts, Subcontexts
When an object is included into a naming or directory service, it has to be bound to a name which identifies the object. The association of an object with a name is known as binding. For example, this article is stored in a Microsoft Word document file called 04.doc. The physical document file is said to be bound to the filename 04.doc. The file has been included in a folder D:\Series\contents\articles. While the name of the file is 04.doc, its compound name is given as D:\Series\contents\ articles\04.doc.
A compound name consists of multiple bindings. For this case, there are four bindings: one to Series, one to contents, one to articles, and one to 04.doc. These four bindings form a context (a set of zero or more bindings) and a context may have one or more sub-contexts. For example, D:\Series has two subcontexts: contents and articles. A subcontext is also a context with its own set of name-object bindings, referring to subfolders or files.
Naming System and Namespace
A naming system is a collection of contexts with the same name syntax. The set of names in a naming system constitutes the namespace. For example, the set of folders organized in a form of folder tree in a file system is a naming system and the name of the files and directory folders in the file system is the namespace. Similarly, an LDAP tree of contexts and subcontexts constitute another naming system. However, the ways name-object bindings are referred to in naming systems are different.
Initial Context and Initial Context Factory
Earlier we specified D:\Series as the context and its subfolders as subcontexts in a file naming system. Alternatively, we could have named D:\ as the context. In this case, D:\Series is the subcontext of D:\.
Whether D:\Series or D:\ is the context is not important. What is important is knowing which context to use as the initial context, the starting point for exploring a namespace. It is through the initial context that all naming and directory operations are performed.
How do we get a handle to the initial context in JNDI? The answer lies in the initial context factory. The latter is implemented by the JNDI driver and is used to churn out initial contexts.
There is an initial context factory for each naming system. Hence, there is an LDAP initial context factory, a file system initial context factory, a DNS initial context factory, and an NDS initial context factory. Each initial context factory knows the directory structure of the naming system it represents. It also knows how to navigate through the directory structure.
To enable JNDI to acquire an initial context, you need to supply the following necessary information of the server in which the JNDI is implemented:
-
the IP address of the server
-
the port number of the server accepting the requests on
-
the starting point in the JNDI tree
-
any other attributes such as user name and password necessary to use the server.
A JNDI Application Example
We will show you how to create a client application (see Code 4.1) that makes use of JNDI to list name-object bindings, and to lookup or resolve a name-object binding in the context of the server application. We will be using the file system as the server application since contexts, subcontexts and name-object bindings are already available in a file system.
The JNDI driver for the file system is not bundled with the JavaSE kit. You will need to download it from https://java.sun.com/products/jndi/downloads/index.html and place it in the Java run-time library extension folder of your JavaSE installation i.e.
\jre\lib\ext\
where is the directory where you installed your JavaSE. You should have the following jar files in your Java run-time library extension folder:
-
fscontext.jar
-
providerutil.jar
Code 4.1: Client application FileSystem
1. package fileSystem;
2. import javax.naming.Context;
3. import javax.naming.InitialContext;
4. import javax.naming.NamingException;
5. import java.util.Properties;
6. import javax.naming.NamingEnumeration;
7. class FileSystem {
8.
-
public static void main(String[] args) {
-
// Check for arguments
-
if (args.length != 1) {
-
System.err.println(“usage: java FileSystem ”);
-
System.exit(-1);
-
}
-
String name = args[0];
-
// Initialise service provider to File System
17. Properties properties = new Properties();
18. properties.setProperty(Context.INITIAL_CONTEXT_FACTORY,
19. “com.sun.jndi.fscontext.RefFSContextFactory”);
20.
-
try {
-
// Create the initial context
23. Context ic = new InitialContext(properties);
- // Look up a name-object binding
25. Object object = ic.lookup(name);
- // Print out binding
27. System.out.println(name + " is bound to: " + object);
- System.out.println();
29.
- // List bindings of subcontexts and files
31. if (!name.equals(object.toString())) {
32. for (NamingEnumeration ne = ic.list(name);
33. ne.hasMoreElements();) {
34. System.out.println(ne.nextElement());
35. }
36. }
- // Close the initial context
38. ic.close();
39. } catch (NamingException ne) {
40. System.err.println(“Problem looking up " + name + “: " + ne);
41. }
- }
43. }
We set the initial context factory to com.sun.jndi.fscontext.RefFSContextFactory in Lines 17 to 19.
Create an initial context using the initial context factory (Line 23). We use the initial context to lookup or resolve the name-object binding in Line 25. A NamingException is thrown if there is problem looking up the file object (Lines 39 to 41).
If the name entered refers to a subcontext, the name-object bindings in the subcontext are listed (Lines 31 to 36), otherwise, the name is bound to the file in the file system (Line 27). Finally, we close the initial context in Line 38.
In our next article “Client/Server Computing Part 5: An RMI-IIOP Application”, we will explain the steps involved in writing a distributed application using Java RMI-IIOP protocol. This protocol allows for Java objects to communicate with non-Java remote objects over the network. The RMI-IIOP application makes use of JNDI to bind and locate remote objects.
This post is part of the series: Client/Server Computing
Programs have often been written to run on one computer. What if there are more than one computers available? Can we have a software program to run on more than one computers? How do we maximize the performance of these machines? How does Client/Server Computing address this need?