Sei sulla pagina 1di 12

JNDI—Java Naming and Directory Interface

By Mark Wutka <http://www.informit.com/articles/article.aspx?p=26231> Date: Apr 5, 2002

Enterprise-level applications use a lot of different directory services-lookup services that locate re-
sources associated with a particular name. When you use RMI, for example, you locate objects with
a directory service called the RMI Registry. When you use CORBA, you use the COS Naming fa-
cility (CORBA's naming service) to locate objects. When you convert a hostname to an IP address,
you usually use a directory service called DNS (Domain Name Service). There are also general dir-
ectory services that use protocols, such as X.500 (the CCITT directory standard) and LDAP (Light-
weight Directory Access Protocol). These directory services can hold many kinds of data.
Although most people tend to use the terms "naming service" and "directory service" interchange-
ably, there is a difference. A naming service associates a single name with a specific resource. A dir-
ectory service associates a name with a set of attributes and resources. When you search a naming
service, you can only search for a specific name. When you search a directory, you can search for
items matching a specific set of attributes.
One of the interesting things about all these types of naming and directory services is that they gen-
erally perform the same task-mapping a name to some set of attributes or objects. Of course, not all
directory services are created equally. Some of them have a flat namespace, whereas others offer a
tree structure for the names. Some of them allow you to store specific types of objects, whereas oth-
ers allow you to store almost any kind of object.
The Java Naming and Directory Interface (JNDI) draws a distinction between naming services and
directory services. A naming service maps a name to an object. The RMI Registry and the CORBA
Naming Service are both examples of naming services. You can only store an RMI object in the
RMI Registry and you can only store a CORBA object in the CORBA Naming Service. A directory
service also stores objects, but these objects can have associated attributes that the directory service
recognizes. You can search a directory using the item attributes. For example, you can search an
LDAP directory for everyone in a specific department or everyone named Smith.
JNDI provides a uniform way to access naming and directory services. It supports flat namespaces
as well as tree namespaces, and it allows you to store many different types of objects. The beauty of
JNDI lies it its simplicity and uniformity. After you know the basic JNDI API calls, you can read
data out of any kind of directory as long as there is a JNDI service provider for that directory.
You have already encountered JNDI in several earlier chapters. You use JNDI to locate Enterprise
JavaBeans and JDBC connection pools from within your EJB container. You might have implemen-
ted simple lookup schemes before in your applications; that is, you create a class with static lookup
methods or store a Hashtable in a static field somewhere. You might choose to use JNDI to replace
these kinds of local storage mechanisms, although you might need to write your own service pro-
vider.
JNDI is also extremely useful in the area of configuration. If many applications use common con-
figuration data, you might consider storing the data in a directory service, such as LDAP, instead of
in a file or database. LDAP is especially good if the configuration information is hierarchical-that is,
if it is more like a tree structure than a flat list of values.
One of the hidden benefits of directory services is the fact that there are a lot of directory service
browsers and editors-especially for LDAP. You can view the contents of the directory and edit them
using an off-the-shelf tool. That saves you from having to write a custom configuration editor.

JNDI—Java Naming and Directory Interface 1


JNDI Basics
The Context class is the core of the JNDI API. You use it to perform any lookup and to add any new
name-value associations. When you use JNDI, you typically create an InitialContext object first:

Context ctx = new InitialContext();


The InitialContext constructor looks for a system property called java.naming.factory. initial that
contains the name of the class that creates the InitialContext. Sometimes, you must supply this
value yourself. Some EJB containers, like the one that comes with Sun's J2EE SDK, already have
this property set.
JDK 1.3 comes with three built-in service providers: RMI, CORBA, and LDAP. The classnames for
the different initial context factories are
com.sun.jndi.rmi.registry.RegistryContextFactory
com.sun.jndi.cosnaming.CNCtxFactory
com.sun.jndi.ldap.LdapCtxFactory

Note
Don't worry about setting defining the class for the initial context factory unless you get an error
telling you there's no initial context factory.

When you run your program, you can specify the initial context factory on the command-line using
the -D option:

java -Djava.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory

usingj2ee.naming.JNDIDemo

You can also specify the initial context factory in a Hashtable that you can pass to the InitialContext
constructor:

Hashtable props = new Hashtable ();


props.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.ldap.LdapCtxFactory");
Context ctx = new InitialContext(props);
Bear in mind that if you specify the initial context factory using a Hashtable object, you might be
limiting the portability of your classes. For example, most WebLogic examples tell you to create the
InitialContext this way:

Hashtable props = new Hashtable();


props.put(Context.INITIAL_CONTEXT_FACTORY,
"weblogic.jndi.WLInitialContextFactory");
props.put(Context.PROVIDER_URL,
"t3://localhost:7001");
Context = new InitialContext(props);
The problem here is that if you want to run your code with another application server, you'll have to
recompile your code with a new set of properties. It's better to set these items on the command line:

java Djava.naming.factory.initial=weblogic.jndi.WLInitialContextFact-
ory

-Djava.naming.provider.url=t3://localhost:7001 MyTestClient

JNDI—Java Naming and Directory Interface 2


Tip
Rather than specifying the initial factory on the command line, you can put these associations in a
file called jndi.properties, which can be located somewhere in your classpath.

When you develop Enterprise Java Beans, you can usually count on the environment being set up
properly ahead of time, so you normally don't need to initialize any properties or set any system
properties. When you run your client programs to test the EJBs, however, you usually need to spe-
cify an initial context factory.
Although most people use the InitialContext object as their first entry point into JNDI, there is an
alternative. You can use the javax.naming.spi.NamingManager class to create a service-specific
context for you based on a URL prefix. A fully qualified JNDI name is of the form service://item-
name, where service is a name such as iiop, rmi, ldap, and so on, and itemname is the name of the
item in that service. The NamingManager class lets you create a Context object based on the service
name. For example, to create an LDAP Context object, you can call:

Context ctx = NamingManager.getURLContext("ldap", null);


One thing to keep in mind when you use this technique is that the Context you get back usually
doesn't understand names for other services. For example, if you create an initial context that is a
CORBA naming service, you can still do an LDAP lookup like this:

Object ob = context.lookup("ldap://localhost/dc=wutka,dc=com");
The InitialContext object knows how to resolve references that use other kinds of services. If you
try this with a context returned by getURLContext, however, you'll get an error telling you that the
name isn't valid for the context you are using.
Okay, now that you have a Context object, you can use the lookup method to locate an object. For
example, when you locate an EJB, you usually make a call like this:

Object personHomeRef = context.lookup(


"java:comp/env/ejb/Person");
Tip
Don't forget, if you need to cast the result from context.lookup to a specific Remote or Home inter-
face type, you must use PortableRemoteObject.narrow.
The java service is available only within an EJB container, and it acts as a local directory service for
other objects within the same EJB environment.
To create a new name-value association, use the bind method:

ctx.bind("rmi://localhost/MyRemoteObject", remoteObject);
If the object already exists in the directory, bind throws a NameAlreadyBoundException. The re-
bind method does the same thing as bind except that it doesn't care whether the object already ex-
ists:
ctx.rebind("rmi://localhost/MyRemoteObject", remoteObject);
rebind doesn't throw an exception if the object doesn't exist; that is, you can use rebind to create a
new association as well as to overwrite an old one.

JNDI—Java Naming and Directory Interface 3


To remove an association, call unbind:

ctx.unbind("rmi://localhost/MyRemoteObject");

To rename an association, call rename:


ctx.rename("rmi://localhost/MyRemoteObject",
"rmi://localhost/MyNewRemoteObject");

You can close the InitialContext by calling the close method:


ctx.close();
Because the context uses resources in the naming or directory service, you should close the context
when you are done with it.
Note

Make sure each EJB client creates its own InitialContext, especially if you are using EJB security
credentials. The credentials are tied to the InitialContext, and if you aren't careful, one client may be
using another client's credentials. Normally this isn't a problem if the clients are running as separate
processes. If you're writing a Web application, however, on a server that acts as multiple clients, you
must be careful to keep the contexts separated.
Directory Operations
JNDI has directory-specific extensions for performing directory operations as opposed to the simple
name-value operations in most naming services. The DirContext interface and InitialDirContext
classes provide additional methods for dealing with directories. The directory-specific classes are
all contained within the javax.naming.directory package.
If you need to perform directory operations, create an InitialDirContext instead of an InitialContext.
For example

DirContext dirCtx = new InitialDirContext();


All the same rules apply to InitialDirContext as far as the property names for choosing an initial
context factory.
The Attribute interface and the BasicAttribute class represent an attribute of an object stored in a
directory. An attribute might have more than one value, but it only has a single name. For example,
a directory entry representing a person might have an attribute called children that could contain
any number of names. A person might also have an age attribute containing a single number.
Most of the methods in the Attribute interface are for dealing with multi-valued attributes. There are
add methods and remove methods, as well as get and set methods:

public void add(int index, Object value)


public boolean add(Object value)
public Object remove(int index)
public boolean remove(Object value)
public Object get()
public Object get(int index)
public Object set(index, Object value)
The Attributes interface and the BasicAttributes class encapsulate all the attributes. It's easier to
manage single-valued attributes from the Attributes class than it is to first get an Attribute and then
perform the manipulation. The main methods you use are get, put, and remove:

JNDI—Java Naming and Directory Interface 4


public Attribute get(String attrName)
public Attribute put(Attribute attr)
public Attribute put(String attrName, Object attrValue)
public Attribute remove(String attrName)
The Attribute returned by put is the attribute being replaced by the new attribute-that is, the attribute
previously stored under the same name as the new attribute. If there was no attribute with that
name, put returns null. The remove method returns the Attribute that is being removed, or null if no
such attribute exists.
The bind and rebind methods in the DirContext interface let you bind an object with a specific set
of attributes:

public void bind(String name, Object ob, Attributes attrs)


public void bind(Name name, Object ob, Attributes attrs)
public void rebind(String name, Object ob, Attributesattrs)
public void rebind(Name name, Object ob, Attributes attrs)
The DirContext interface also provides several variations of a search method. One of the things that
distinguishes a directory service from a naming service is that you can search for items based on a
set of attributes and not a specific name. For instance, find all people with age greater than 18. The
various search methods are

public NamingEnumeration search(Name name, Attributes[] matchAttrib-


utes)
public NamingEnumeration search(Name name, Attributes[] matchAttrib-
utes,
String[] attributesToReturn)
public NamingEnumeration search(Name name, String searchFilter,
SearchControls controls)
public NamingEnumeration search(Name name, String searchFilter,
Object[] filterArgs, SearchControls controls)
public NamingEnumeration search(String name, Attributes[] matchAttrib-
utes)
public NamingEnumeration search(String name, Attributes[] matchAttrib-
utes,
String[] attributesToReturn)
public NamingEnumeration search(String name, String searchFilter,
SearchControls controls)
public NamingEnumeration search(String name, String searchFilter,
Object[] filterArgs, SearchControls controls)

Using LDAP with JNDI


Of the directory services supported by JDK 1.3, LDAP is by far the most flexible. You can store a
wide variety of items in an LDAP directory and you can get LDAP servers for a wide variety of op-
erating systems. A good place to get a free LDAP server for Linux and Unix is http://www.open-
ldap.org. They are also working on a version for Windows NT.
LDAP stores data in a hierarchical (tree) structure. You refer to an entry in the tree by listing the
names of the nodes in the tree, starting at the one you want, working backward to the top of the tree.
LDAP paths look confusing at first, but after you understand the notation, it's not so bad. Figure
18.1 shows an example LDAP tree.

JNDI—Java Naming and Directory Interface 5


Figure 18.1. LDAP stores its entries in a tree structure.

Each node in the tree has a unique name of the form


nodetype=value. That is, the name includes the type of the node,
at least to some extent. For example, the top part of the tree in
Figure 18.1 has nodes that represent the LDAP server's domain.
These topmost nodes are domain components. For a domain of
http://wutka.com, you have two domain components: wutka and
com. Node type for a domain component is dc, so the topmost
nodes are named dc=wutka and dc=com. Underneath the wutka
domain component is an organization called Wutka Consulting.
An organization has a node type of o, so the Wutka Consulting
node has a name of o=Wutka Consulting.
Now, if you're using JNDI to access the wutkaconsulting node,
you must list the node names starting from the one you want and
working backward to the top. In other words, the name you want
is o=Wutka Consulting,dc=wutka,dc=com.
Listing 18.1 shows a program that reads the Wutka Consulting object and prints out its attributes.

Listing 18.1 Source Code for ShowWC.java


package usingj2ee.naming;

import javax.naming.*;
import javax.naming.directory.*;

public class ShowWC


{
public static void main(String[] args)
{
try
{
// Get the initial context
InitialDirContext ctx = new InitialDirContext();

// Locate the Wutka Consulting object on the server running


// at ldap.wutka.com
Attributes attrs = ctx.getAttributes(
"ldap://ldap.wutka.com/o=Wutka Consulting, dc=wutka,
dc=com");

// Get the attributes for the object


NamingEnumeration e = attrs.getAll();

while (e.hasMoreElements())
{
// Get the next attribute
Attribute attr = (Attribute) e.nextElement();

// Print out the attribute's value(s)


System.out.print(attr.getID()+" = ");
for (int i=0; i < attr.size(); i++)
{
if (i > 0) System.out.print(", ");
System.out.print(attr.get(i));
}
JNDI—Java Naming and Directory Interface 6
System.out.println();
}
}
catch (Exception exc)
{
exc.printStackTrace();
}
}
}
Figure 18.2 shows the output from the ShowWC program.
Figure 18.2. It's easy to print the attributes in an LDAP object.
Note

Due to possible network changes, you may not be able to access http://ldap.wutka.com in the future.
You might need to set up your own LDAP server to run the example.

LDAP Classes and Attributes


Although LDAP entries are really just a collection of attributes, LDAP has the concept of classes.
Every LDAP entry has an attribute called objectClass that lists the class hierarchy for an object. Not
only does objectClass contain the object's class, it must contain the entire list of superclasses all the
way back to the top class. Fortunately, the classes aren't nested too deeply, so the objectClass list is
usually fairly small.
One other thing to keep in mind: The class hierarchy doesn't dictate the structure of the directory
tree. A node in the directory tree can contain one of its superclasses as a child.
Table 18.1 lists some of the common LDAP classes. The complete set of classes is defined in the
standard RFC2256, which you can view at http://www.ietf.org/rfc/rfc2256.txt.

Table Some Common LDAP Classes


Classname Parent Class Required Attribute(s)
top None ObjectClass

country top c

locality top none

organization top o

organizationalUnit top ou

person top sn, cn

organizationalPerson top none

The LDAP specification also lists some common attribute names. These attribute names tend to
look confusing at first glance because many of them are only one or two characters long. You see
these attributes in other places too, such as in X.509 certificates (for digital signatures and encryp-
tion). One of the reasons for the similarity is that LDAP uses many of the items defined in the
X.500 series of recommendations (standards), which includes X.509.
Table 18.2 lists some of the common attributes and their meanings.

JNDI—Java Naming and Directory Interface 7


Table Some Common LDAP Attributes
Attribute Name Meaning
objectClass The classname of the object and its superclasses

dc A domain context-a part of a domain name

cn Common name, usually the name of the object

sn Surname-a person's family name (the last name in most


Western cultures)

c The standard two-letter country code

l Locality (city, county, or other region)

st State or province

o Organization

ou Organizational unit

title A person's job title

personalTitle A person's personal (not job-related) title

description A description of the object

mail A person's email address

One other concept you should be aware of is that a context is really a set of names. You can create a
context that is a subset of names by calling createSubcontext in the DirContext object. Essentially, a
subcontext is just the set of names starting at a particular node in the directory tree.
The interesting thing is, you create a new node in the tree by creating a new subcontext. Listing
18.2 shows a program that adds two entries to the LDAP directory. Notice that the program must
supply a username in the form of a SECURITY_PRINCIPAL and a password in the form of SE-
CURITY_CREDENTIALS to make changes to the LDAP directory. Most servers let you read the
directory anonymously but require a username and password to make changes.

Listing 18.2 Source Code for AddPerson.java


package usingj2ee.naming;

import java.util.*;
import javax.naming.*;
import javax.naming.directory.*;

public class AddPerson


{
public static void main(String[] args)
{
try
{
// Pass the security information to the directory context
// The LDAP server requires a username (SECURITY_PRINCIPAL)
// and password (SECURITY_CREDENTIALS) to add/remove
// items.
JNDI—Java Naming and Directory Interface 8
Hashtable props = new Hashtable();
props.put(Context.SECURITY_PRINCIPAL,
"cn=Manager,dc=wutka,dc=com");
props.put(Context.SECURITY_CREDENTIALS,
"secret");

// Get the initial context


InitialDirContext ctx = new InitialDirContext(props);

// Create a new set of attributes


BasicAttributes attrs = new BasicAttributes();

// The item is an organizationalPerson, which is a subclass of person.


// Person is a subclass of top. Store the class hierarchy in the
// objectClass attribute
Attribute classes = new BasicAttribute("objectclass");
classes.add("top");
classes.add("person");
classes.add("organizationalPerson");

// Add the objectClass attribute to the attribute set


attrs.put(classes);

// Store the other attributes in the attribute set


attrs.put("sn", "Tippin");
attrs.put("title", "Computer Expert");
attrs.put("mail", "samantha@wutka.com");

// Add the new entry to the directory server


ctx.createSubcontext("ldap://ldap.wutka.com/cn=Samantha
Tippin,"+
"o=Wutka Consulting,dc=wutka,dc=com", attrs);

// Create another set of attributes


attrs = new BasicAttributes();

// Use the same objectClass attribute as before


attrs.put(classes);

// Set the other attributes


attrs.put("sn", "Tippin");
attrs.put("title", "Computer Expert");
attrs.put("mail", "kaitlynn@wutka.com");

// Add another entry to the directory server


ctx.createSubcontext("ldap://ldap.wutka.com/cn=Kaitlynn
Tippin,"+
"o=Wutka Consulting,dc=wutka,dc=com", attrs);
}
catch (Exception exc)
{
exc.printStackTrace();
}
}
}

JNDI—Java Naming and Directory Interface 9


It's fairly easy to search through an LDAP directory using JNDI. You just call the search method in
the DirContext. There are two main ways to search: by specifying either a set of attributes to match
or an LDAP filter string.
Attribute matching is very straightforward, as you can see in Listing 18.3.

Listing 18.3 Source Code for Name Search.java


package usingj2ee.naming;

import javax.naming.*;
import javax.naming.directory.*;

public class NameSearch


{
public static void main(String[] args)
{
try
{
// Get the initial context
InitialDirContext ctx = new InitialDirContext();

// Create the search attributes - look for a surname of Tippin


BasicAttributes searchAttrs = new BasicAttributes();

searchAttrs.put("sn", "Tippin");

// Search for items with the specified attribute starting


// at the top of the search tree
NamingEnumeration objs = ctx.search(
"ldap://ldap.wutka.com/o=Wutka Consulting, dc=wutka,
dc=com",
searchAttrs);

// Loop through the objects returned in the search


while (objs.hasMoreElements())
{
// Each item is a SearchResult object
SearchResult match = (SearchResult)objs.nextElement();

// Print out the node name


System.out.println("Found "+match.getName()+":");

// Get the node's attributes


Attributes attrs = match.getAttributes();
NamingEnumeration e = attrs.getAll();

// Loop through the attributes


while (e.hasMoreElements())
{
// Get the next attribute
Attribute attr = (Attribute) e.nextElement();

// Print out the attribute's value(s)


System.out.print(attr.getID()+" = ");
for (int i=0; i < attr.size(); i++)
{
if (i > 0) System.out.print(", ");
System.out.print(attr.get(i));
}
System.out.println();
JNDI—Java Naming and Directory Interface 10
}
System.out.println("---------------------------------------");
}
}
catch (Exception exc)
{
exc.printStackTrace();
}
}
}
Searching by filter is a little more complicated. Any LDAP filter string must be surrounded by par-
entheses. To match all objects in the directory, you can use a filter string, such as (objectClass=*).
You can do comparisons using =, >=, <=, and ~= (approximately), like (age>=18).
The syntax for and, or, and not is a little strange. If you want to test age>=18 and sn=Smith, the ex-
pression is (&(age>=18)(sn=Smith)). Use & for and, | for or, and ! for not. For and and or, you can
list as many expressions as you want to after the & or | characters. For not, you can only have a
single expression.
For example, because you can only do a greater-than-or-equal-to comparison (>=), you do a greater-
than by doing not-less-than-or-equal-to. For example, if age must be strictly greater than 18, use
(!(age<=18)). If you need to combine the and and or operators, you must use parentheses to separate
the expressions.
For example, you might want to search for age>=18 or (age >=13 and parentalPermission=true).
The expression would be (|(age>=18)(&(age>=13)(parentalPermission=true))). The two expressions
being ored together are (age>=18) and (&(age>=13)(parentalPermission=true)).
You can find a full definition of the LDAP search filter syntax in RFC1558
(http://www.ietf.org/rfc/rfc1558.txt).
Listing 18.4 shows a program that performs a simple filter search to dump out the entire contents of
the directory.

Listing 18.4 Source Code for AllSearch.java


package usingj2ee.naming;

import javax.naming.*;
import javax.naming.directory.*;

public class AllSearch


{
public static void main(String[] args)
{
try
{
// Get the initial context
InitialDirContext ctx = new InitialDirContext();

SearchControls searchControls = new SearchControls();


searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE
);

// Search for items with the specified attribute starting


// at the top of the search tree
NamingEnumeration objs = ctx.search(
"ldap://ldap.wutka.com/o=Wutka Consulting, dc=wutka,
dc=com",
JNDI—Java Naming and Directory Interface 11
"(objectClass=*)", searchControls);

// Loop through the objects returned in the search


while (objs.hasMoreElements())
{
// Each item is a SearchResult object
SearchResult match = (SearchResult)
objs.nextElement();

// Print out the node name


System.out.println("Found "+match.getName()+":");

// Get the node's attributes


Attributes attrs = match.getAttributes();

NamingEnumeration e = attrs.getAll();

// Loop through the attributes


while (e.hasMoreElements())
{
// Get the next attribute
Attribute attr = (Attribute) e.nextElement();

// Print out the attribute's value(s)


System.out.print(attr.getID()+" = ");
for (int i=0; i < attr.size(); i++)
{
if (i > 0) System.out.print(", ");
System.out.print(attr.get(i));
}
System.out.println();
}
System.out.println("----------------------------------
-----");
}
}
catch (Exception exc)
{
exc.printStackTrace();
}
}
}
Troubleshooting
Initial Context
Q: Why do I get an error when I create the initial context?
A: You might need to specify an initial context factory property. You may also need to specify a
PROVIDER_URL or other properties. Consult the documentation for your JNDI implementation to
see what properties it requires.
General Errors
Q: Why can't I store my object in the naming service?
A: Some services only allow certain types of objects. The RMI Registry only accepts RMI object
and the CORBA Naming service only accepts CORBA objects. Other services might require that
your object is serializable.

JNDI—Java Naming and Directory Interface 12

Potrebbero piacerti anche