Naming and Directory Operations

«« Previous
Next »»

Naming and Directory Operations


You can use the JNDI to perform naming operations, including read operations and operations for updating the namespace. The following operations are described in this lesson:

◈ Looking up an object
◈ Listing the contents of a context
◈ Adding, overwriting, and removing a binding
◈ Renaming an object
◈ Creating and destroying subcontexts

Configuration

Before performing any operation on a naming or directory service, you need to acquire an initial context--the starting point into the namespace. This is because all methods on naming and directory services are performed relative to some context. To get an initial context, you must follow these steps.

1. Select the service provider of the corresponding service you want to access.
2. Specify any configuration that the initial context needs.
3. Call the InitialContext constructor.

Step1: Select the Service Provider for the Initial Context

You can specify the service provider to use for the initial context by creating a set of environment properties (a Hashtable) and adding the name of the service provider class to it. Environment properties are described in detail in the JNDI Tutorial.

If you are using the LDAP service provider included in the JDK, then your code would look like the following.

Hashtable<String, Object> env = new Hashtable<String, Object>();
env.put(Context.INITIAL_CONTEXT_FACTORY,
        "com.sun.jndi.ldap.LdapCtxFactory");

To specify the file system service provider in the JDK, you would write code that looks like the following.

Hashtable<String, Object> env = new Hashtable>String, Object>();
env.put(Context.INITIAL_CONTEXT_FACTORY,
        "com.sun.jndi.fscontext.RefFSContextFactory");

You can also use system properties to specify the service provider to use.

Step2: Supply the Information Needed by the Initial Context

Clients of different directories might need various information for contacting the directory. For example, you might need to specify on which machine the server is running and what information is needed to identify the user to the directory. Such information is passed to the service provider via environment properties. The JNDI specifies some generic environment properties that service providers can use. Your service provider documentation will give details on the information required for these properties.

The LDAP provider requires that the program specify the location of the LDAP server, as well as user identity information. To provide this information, you would write code that looks as follows.

env.put(Context.PROVIDER_URL, "ldap://ldap.wiz.com:389");
env.put(Context.SECURITY_PRINCIPAL, "joeuser");
env.put(Context.SECURITY_CREDENTIALS, "joepassword");

This tutorial uses the LDAP service provider in the JDK. The examples assume that a server has been set up on the local machine at port 389 with the root-distinguished name of "o=JNDITutorial" and that no authentication is required for updating the directory. They include the following code for setting up the environment.

env.put(Context.PROVIDER_URL, "ldap://localhost:389/o=JNDITutorial");

If you are using a directory that is set up differently, then you will need to set up these environment properties accordingly. You will need to replace "localhost" with the name of that machine. You can run these examples against any public directory servers or your own server running on a different machine. You will need to replace "localhost" with the name of that machine and replace o=JNDITutorial with the naming context that is available.

Step3: Creating the Initial Context

You are now ready to create the initial context. To do that, you pass to the InitialContext constructor the environment properties that you created previously:

Context ctx = new InitialContext(env);

Now that you have a reference to a Context object, you can begin to access the naming service.

To perform directory operations, you need to use an InitialDirContext. To do that, use one of its constructors:

DirContext ctx = new InitialDirContext(env);

This statement returns a reference to a DirContext object for performing directory operations.

Naming Exceptions


Many methods in the JNDI packages throw a NamingException when they need to indicate that the operation requested cannot be performed. Commonly, you will see a try/catch wrapper around the methods that can throw a NamingException:

try {
    Context ctx = new InitialContext();
    Object obj = ctx.lookup("somename");
} catch (NamingException e) {
    // Handle the error
    System.err.println(e);
}

Exception Class Hierarchy

The JNDI has a rich exception hierarchy stemming from the NamingException class. The class names of the exceptions are self-explanatory and are listed here.

To handle a particular subclass of NamingException specially, you catch the subclass separately. For example, the following code specially treats the AuthenticationException and its subclasses.

try {
    Context ctx = new InitialContext();
    Object obj = ctx.lookup("somename");
} catch (AuthenticationException e) {
    // attempt to reacquire the authentication information
    ...
} catch (NamingException e) {
    // Handle the error
    System.err.println(e);
}

Enumerations

Operations such as Context.list() and DirContext.search() return a NamingEnumeration. In these cases, if an error occurs and no results are returned, then NamingException or one of its appropriate subclasses will be thrown at the time that the method is invoked. If an error occurs but there are some results to be returned, then a NamingEnumeration is returned so that you can get those results. When all of the results are exhausted, invoking NamingEnumeration.hasMore() will cause a NamingException (or one of its subclasses) to be thrown to indicate the error. At that point, the enumeration becomes invalid and no more methods should be invoked on it.

For example, if you perform a search() and specify a count limit (n) of how many answers to return, then the search() will return an enumeration consisting of at most n results. If the number of results exceeds n, then when NamingEnumeration.hasMore() is invoked for the n+1 time, a SizeLimitExceededException will be thrown.

Examples in This Tutorial

In the inline sample code that is embedded within the text of this tutorial, the try/catch clauses are usually omitted for the sake of readability. Typically, because only code fragments are shown here, only the lines that are directly useful in illustrating a concept are included. You will see appropriate placements of the try/catch clauses for NamingException if you look in the source files that accompany this tutorial.

Lookup an Object


To look up an object from the naming service, use Context.lookup() and pass it the name of the object that you want to retrieve. Suppose that there is an object in the naming service with the name cn=Rosanna Lee,ou=People. To retrieve the object, you would write

Object obj = ctx.lookup("cn=Rosanna Lee,ou=People");

The type of object that is returned by lookup() depends both on the underlying naming system and on the data associated with the object itself. A naming system can contain many different types of objects, and a lookup of an object in different parts of the system might yield objects of different types. In this example, "cn=Rosanna Lee,ou=People" happens to be bound to a context object (javax.naming.ldap.LdapContext). You can cast the result of lookup() to its target class.

For example, the following code looks up the object "cn=Rosanna Lee,ou=People" and casts it to LdapContext.

import javax.naming.ldap.LdapContext;
...
LdapContext ctx = (LdapContext) ctx.lookup("cn=Rosanna Lee,ou=People");

The complete example is in the file Lookup.java.

Oracle Java Tutorials and Materials, Oracle Java Guides

There are two new static methods available in Java SE 6 to lookup a name:

◈ InitialContext.doLookup(Name name)
◈ InitialContext.doLookup(String name)

These methods provide a shortcut way of looking up a name without instantiating an InitialContext.

List the Context


Instead of getting a single object at a time, as with Context.lookup() , you can list an entire context by using a single operation. There are two methods for listing a context: one that returns the bindings and one that returns only the name-to-object class name pairs.

The Context.List() Method

Context.list() returns an enumeration of NameClassPair. Each NameClassPair consists of the object's name and its class name. The following code fragment lists the contents of the "ou=People" directory (i.e., the files and directories found in "ou=People" directory).

NamingEnumeration list = ctx.list("ou=People");

while (list.hasMore()) {
    NameClassPair nc = (NameClassPair)list.next();
    System.out.println(nc);
}

Running this example yields the following output.

# java List
cn=Jon Ruiz: javax.naming.directory.DirContext
cn=Scott Seligman: javax.naming.directory.DirContext
cn=Samuel Clemens: javax.naming.directory.DirContext
cn=Rosanna Lee: javax.naming.directory.DirContext
cn=Maxine Erlund: javax.naming.directory.DirContext
cn=Niels Bohr: javax.naming.directory.DirContext
cn=Uri Geller: javax.naming.directory.DirContext
cn=Colleen Sullivan: javax.naming.directory.DirContext
cn=Vinnie Ryan: javax.naming.directory.DirContext
cn=Rod Serling: javax.naming.directory.DirContext
cn=Jonathan Wood: javax.naming.directory.DirContext
cn=Aravindan Ranganathan: javax.naming.directory.DirContext
cn=Ian Anderson: javax.naming.directory.DirContext
cn=Lao Tzu: javax.naming.directory.DirContext
cn=Don Knuth: javax.naming.directory.DirContext
cn=Roger Waters: javax.naming.directory.DirContext
cn=Ben Dubin: javax.naming.directory.DirContext
cn=Spuds Mackenzie: javax.naming.directory.DirContext
cn=John Fowler: javax.naming.directory.DirContext
cn=Londo Mollari: javax.naming.directory.DirContext
cn=Ted Geisel: javax.naming.directory.DirContext

The Context.listBindings() Method

Context.listBindings() returns an enumeration of Binding. Binding is a subclass of NameClassPair. A binding contains not only the object's name and class name, but also the object. The following code enumerates the "ou=People" context, printing out each binding's name and object.

NamingEnumeration bindings = ctx.listBindings("ou=People");

while (bindings.hasMore()) {
    Binding bd = (Binding)bindings.next();
    System.out.println(bd.getName() + ": " + bd.getObject());
}

Running this example yields the following output.

# java ListBindings
cn=Jon Ruiz: com.sun.jndi.ldap.LdapCtx@1d4c61c
cn=Scott Seligman: com.sun.jndi.ldap.LdapCtx@1a626f
cn=Samuel Clemens: com.sun.jndi.ldap.LdapCtx@34a1fc
cn=Rosanna Lee: com.sun.jndi.ldap.LdapCtx@176c74b
cn=Maxine Erlund: com.sun.jndi.ldap.LdapCtx@11b9fb1
cn=Niels Bohr: com.sun.jndi.ldap.LdapCtx@913fe2
cn=Uri Geller: com.sun.jndi.ldap.LdapCtx@12558d6
cn=Colleen Sullivan: com.sun.jndi.ldap.LdapCtx@eb7859
cn=Vinnie Ryan: com.sun.jndi.ldap.LdapCtx@12a54f9
cn=Rod Serling: com.sun.jndi.ldap.LdapCtx@30e280
cn=Jonathan Wood: com.sun.jndi.ldap.LdapCtx@16672d6
cn=Aravindan Ranganathan: com.sun.jndi.ldap.LdapCtx@fd54d6
cn=Ian Anderson: com.sun.jndi.ldap.LdapCtx@1415de6
cn=Lao Tzu: com.sun.jndi.ldap.LdapCtx@7bd9f2
cn=Don Knuth: com.sun.jndi.ldap.LdapCtx@121cc40
cn=Roger Waters: com.sun.jndi.ldap.LdapCtx@443226
cn=Ben Dubin: com.sun.jndi.ldap.LdapCtx@1386000
cn=Spuds Mackenzie: com.sun.jndi.ldap.LdapCtx@26d4f1
cn=John Fowler: com.sun.jndi.ldap.LdapCtx@1662dc8
cn=Londo Mollari: com.sun.jndi.ldap.LdapCtx@147c5fc
cn=Ted Geisel: com.sun.jndi.ldap.LdapCtx@3eca90

Terminating a NamingEnumeration

A NamingEnumeration can be terminated in one of three ways: naturally, explicitly, or unexpectedly.

◈ When NamingEnumeration.hasMore() returns false, the enumeration is complete and effectively terminated.

◈ You can terminate an enumeration explicitly before it has completed by invoking NamingEnumeration.close(). Doing this provides a hint to the underlying implementation to free up any resources associated with the enumeration.

◈ If either hasMore() or next() throws a NamingException, then the enumeration is effectively terminated.

Regardless of how an enumeration has been terminated, once terminated it can no longer be used. Invoking a method on a terminated enumeration yields an undefined result.

Why Two Different List Methods?

list() is intended for browser-style applications that just want to display the names of objects in a context. For example, a browser might list the names in a context and wait for the user to select one or a few of the names displayed to perform further operations. Such applications typically do not need access to all of the objects in a context.

listBindings() is intended for applications that need to perform operations on the objects in a context en masse. For example, a backup application might need to perform "file stats" operations on all of the objects in a file directory. Or a printer administration program might want to restart all of the printers in a building. To perform such operations, these applications need to obtain all of the objects bound in a context. Thus it is more expedient to have the objects returned as part of the enumeration.

The application can use either list() or the potentially more expensive listBindings(), depending on the type of information it needs.

Add, Replace or Remove a Binding


The Context interface contains methods for adding, replacing, and removing a binding in a context.

Adding a Binding

Context.bind() is used to add a binding to a context. It accepts as arguments the name of the object and the object to be bound.

Before you go on: The examples in this lesson require that you make additions to the schema. You must either turn off schema-checking in the LDAP server or add the schema that accompanies this tutorial to the server. Both of these tasks are typically performed by the directory server's administrator. See the LDAP Setuplesson.

// Create the object to be bound
Fruit fruit = new Fruit("orange");

// Perform the bind
ctx.bind("cn=Favorite Fruit", fruit);

This example creates an object of class Fruit and binds it to the name "cn=Favorite Fruit" in the context ctx. If you subsequently looked up the name "cn=Favorite Fruit" in ctx, then you would get the fruit object. Note that to compile the Fruit class, you need the FruitFactory class.

If you were to run this example twice, then the second attempt would fail with a NameAlreadyBoundException. This is because the name "cn=Favorite Fruit" is already bound. For the second attempt to succeed, you would have to use rebind().

Adding or Replacing a Binding

rebind() is used to add or replace a binding. It accepts the same arguments as bind(), but the semantics are such that if the name is already bound, then it will be unbound and the newly given object will be bound.

// Create the object to be bound
Fruit fruit = new Fruit("lemon");

// Perform the bind
ctx.rebind("cn=Favorite Fruit", fruit);

When you run this example, it will replace the binding created by the bind() example.

Oracle Java Tutorials and Materials, Oracle Java Guides

Removing a Binding

To remove a binding, you use unbind().

// Remove the binding
ctx.unbind("cn=Favorite Fruit");

This example, when run, removes the binding that was created by the bind() or rebind() example.

Rename


You can rename an object in a context by using Context.rename().

// Rename to Scott S
ctx.rename("cn=Scott Seligman", "cn=Scott S");

Oracle Java Tutorials and Materials, Oracle Java Guides
This example renames the object that was bound to "cn=Scott Seligman" to "cn=Scott S". After verifying that the object got renamed, the program renames it to its original name ("cn=Scott Seligman"), as follows.

// Rename back to Scott Seligman
ctx.rename("cn=Scott S", "cn=Scott Seligman");

Create and Destroy Subcontexts


The Context interface contains methods for creating and destroying a subcontext, a context that is bound in another context of the same type.

The example described here use an object that has attributes and create a subcontext in the directory. You can use these DirContext methods to associate attributes with the object at the time that the binding or subcontext is added to the namespace. For example, you might create a Person object and bind it to the namespace and at the same time associate attributes about that Person object. The naming equivalent will have no attributes.

The createSubcontext() differs from bind() in that it creates a new Object i.e a new Context to be bound to the directory while as bind() binds the given Object in the directory.

Creating a Context

To create a naming context, you supply to createSubcontext() the name of the context that you want to create. To create a context that has attributes, you supply to DirContext.createSubcontext() the name of the context that you want to create and its attributes.

Before you go on: The examples in this lesson require that you make additions to the schema. You must either turn off schema-checking in the LDAP server or add the schema that accompanies this tutorial to the server. Both of these tasks are typically performed by the directory server's administrator. See the LDAP Setup lesson.

// Create attributes to be associated with the new context
Attributes attrs = new BasicAttributes(true); // case-ignore
Attribute objclass = new BasicAttribute("objectclass");
objclass.add("top");
objclass.add("organizationalUnit");
attrs.put(objclass);

// Create the context
Context result = ctx.createSubcontext("NewOu", attrs);

This example creates a new context called "ou=NewOu" that has an attribute "objectclass" with two values, "top" and "organizationalUnit", in the context ctx.

# java Create
ou=Groups: javax.naming.directory.DirContext
ou=People: javax.naming.directory.DirContext
ou=NewOu: javax.naming.directory.DirContext

This example creates a new context, called "NewOu", that is a child of ctx.

Oracle Java Tutorials and Materials, Oracle Java Guides

Destroying a Context

To destroy a context, you supply to destroySubcontext() the name of the context to destroy.

// Destroy the context
ctx.destroySubcontext("NewOu");

This example destroys the context "NewOu" in the context ctx.

Attribute Names


An attribute consists of an attribute identifier and a set of attribute values. The attribute identifier, also called attribute name, is a string that identifies an attribute. An attribute value is the content of the attribute and its type is not restricted to that of string. You use an attribute name when you want to specify a particular attribute for either retrieval, searches, or modification. Names are also returned by operations that return attributes (such as when you perform reads or searches in the directory).

When using attribute names, you need to be aware of certain directory server features so that you won't be surprised by the result. These features are described in the next subsections.

Attribute Type

In directories such as the LDAP, the attribute's name identifies the attribute's type and is often called the attribute type name. For example, the attribute name "cn" is also called the attribute type name. An attribute's type definition specifies the syntax that the attribute's value is to have, whether it can have multiple values, and equality and ordering rules to use when performing comparison and ordering operations on the attribute's values.

Attribute Subclassing

Some directory implementations support attribute subclassing, in which the server allows attribute types to be defined in terms of other attribute types. For example, a "name" attribute might be the superclass of all name-related attributes: "commonName" might be a subclass of "name". For directory implementations that support this, asking for the "name" attribute might return the "commonName" attribute.

When accessing directories that support attribute subclassing, you have to be aware that the server might return attributes that have names different from those that you requested. To minimize the chance of this, use the most derived subclass.

Attribute Name Synonyms

Some directory implementations support synonyms for attribute names. For example, "cn" might be a synonym for "commonName". Thus a request for the "cn" attribute might return the "commonName" attribute.

When accessing directories that support synonyms for attribute names, you must be aware that the server might return attributes that have names different from those you requested. To help prevent this from happening, use the canonical attribute name instead of one of its synonyms. The canonical attribute name is the name used in the attribute's definition; a synonym is the name that refers to the canonical attribute name in its definition.

Language Preferences

An extension to the LDAP v3 ( RFC 2596) allows you to specify a language code along with an attribute name. This resembles attribute subclassing in that one attribute name can represent several different attributes. An example is a "description" attribute that has two language variations:

description: software
description;lang-en: software products
description;lang-de: Softwareprodukte

A request for the "description" attribute would return all three attributes.

When accessing directories that support this feature, you must be aware that the server might return attributes that have names different from those that you requested.

Read Attributes


To read the attributes of an object from the directory, use DirContext.getAttributes() and pass it the name of the object for which you want the attributes. Suppose that an object in the naming service has the name "cn=Ted Geisel, ou=People". To retrieve this object's attributes, you'll need code that looks like this:

Attributes answer = ctx.getAttributes("cn=Ted Geisel, ou=People");

You can then print the contents of this answer as follows.

for (NamingEnumeration ae = answer.getAll(); ae.hasMore();) {
    Attribute attr = (Attribute)ae.next();
    System.out.println("attribute: " + attr.getID());
    /* Print each value */
    for (NamingEnumeration e = attr.getAll(); e.hasMore();
         System.out.println("value: " + e.next()))
        ;
}

This produces the following output.

# java GetattrsAll
attribute: sn
value: Geisel
attribute: objectclass
value: top
value: person
value: organizationalPerson
value: inetOrgPerson
attribute: jpegphoto
value: [B@1dacd78b
attribute: mail
value: Ted.Geisel@JNDITutorial.example.com
attribute: facsimiletelephonenumber
value: +1 408 555 2329
attribute: telephonenumber
value: +1 408 555 5252
attribute: cn
value: Ted Geisel

Returning Selected Attributes

To read a selective subset of attributes, you supply an array of strings that are attribute identifiers of the attributes that you want to retrieve.

// Specify the ids of the attributes to return
String[] attrIDs = {"sn", "telephonenumber", "golfhandicap", "mail"};

// Get the attributes requested
Attributes answer = ctx.getAttributes("cn=Ted Geisel, ou=People", attrIDs);

This example asks for the "sn", "telephonenumber", "golfhandicap" and "mail" attributes of the object "cn=Ted Geisel, ou=People". This object has all but the "golfhandicap" attribute, and so three attributes are returned in the answer. Following is the output of the example.

# java Getattrs
attribute: sn
value: Geisel
attribute: mail
value: Ted.Geisel@JNDITutorial.example.com
attribute: telephonenumber
value: +1 408 555 5252

Modify Attributes


The DirContext interface contains methods for modifying the attributes and attribute values of objects in the directory.

Using a List of Modifications

One way to modify the attributes of an object is to supply a list of modification requests ( ModificationItem). Each ModificationItem consists of a numeric constant indicating the type of modification to make and an Attribute describing the modification to make. Following are the three types of modifications:

◈ ADD_ATTRIBUTE
◈ REPLACE_ATTRIBUTE
◈ REMOVE_ATTRIBUTE

Modifications are applied in the order in which they appear in the list. Either all of the modifications are executed, or none are.

The following code creates a list of modifications. It replaces the "mail" attribute's value with a value of "geisel@wizards.com", adds an additional value to the "telephonenumber" attribute, and removes the "jpegphoto" attribute.

// Specify the changes to make
ModificationItem[] mods = new ModificationItem[3];

// Replace the "mail" attribute with a new value
mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE,
    new BasicAttribute("mail", "geisel@wizards.com"));

// Add an additional value to "telephonenumber"
mods[1] = new ModificationItem(DirContext.ADD_ATTRIBUTE,
    new BasicAttribute("telephonenumber", "+1 555 555 5555"));

// Remove the "jpegphoto" attribute
mods[2] = new ModificationItem(DirContext.REMOVE_ATTRIBUTE,
    new BasicAttribute("jpegphoto"));

Windows Active Directory: Active Directory defines "telephonenumber" to be a single-valued attribute, contrary to RFC 2256. To get this example to work against Active Directory, you must either use an attribute other than "telephonenumber", or change the DirContext.ADD_ATTRIBUTE to DirContext.REPLACE_ATTRIBUTE.

After creating this list of modifications, you can supply it to modifyAttributes() as follows.

// Perform the requested modifications on the named object
ctx.modifyAttributes(name, mods);

Using Attributes


Alternatively, you can perform modifications by specifying the type of modification and the attributes to which to apply the modification.

For example, the following line replaces the attributes (identified in orig) associated with name with those in orig:

ctx.modifyAttributes(name, DirContext.REPLACE_ATTRIBUTE, orig);

Any other attributes of name remain unchanged.

Both of these uses of modifyAttributes() are demonstrated in the sample program . This program modifies the attributes by using a modification list and then uses the second form of modifyAttributes() to restore the original attributes.

Add, Replace Bindings with Attributes


The naming examples discussed how you can use bind(), rebind(). The DirContext interface contains overloaded versions of these methods that accept attributes. You can use these DirContext methods to associate attributes with the object at the time that the binding or subcontext is added to the namespace. For example, you might create a Person object and bind it to the namespace and at the same time associate attributes about that Person object.

Adding a Binding That Has Attributes

DirContext.bind() is used to add a binding that has attributes to a context. It accepts as arguments the name of the object, the object to be bound, and a set of attributes.

// Create the object to be bound
Fruit fruit = new Fruit("orange");

// Create attributes to be associated with the object
Attributes attrs = new BasicAttributes(true); // case-ignore
Attribute objclass = new BasicAttribute("objectclass");
objclass.add("top");
objclass.add("organizationalUnit");
attrs.put(objclass);

// Perform bind
ctx.bind("ou=favorite, ou=Fruits", fruit, attrs);

This example creates an object of class Fruit and binds it to the name "ou=favorite" into the context named "ou=Fruits", relative to ctx. This binding has the "objectclass" attribute. If you subsequently looked up the name "ou=favorite, ou=Fruits" in ctx, then you would get the fruit object. If you then got the attributes of "ou=favorite, ou=Fruits", you would get those attributes with which the object was created. Following is this example's output.

# java Bind
orange
attribute: objectclass
value: top
value: organizationalUnit
value: javaObject
value: javaNamingReference
attribute: javaclassname
value: Fruit
attribute: javafactory
value: FruitFactory
attribute: javareferenceaddress
value: #0#fruit#orange
attribute: ou
value: favorite

The extra attributes and attribute values shown are used to store information about the object (fruit). These extra attributes are discussed in more detail in the trail.

If you were to run this example twice, then the second attempt would fail with a NameAlreadyBoundException. This is because the name "ou=favorite" is already bound in the "ou=Fruits" context. For the second attempt to succeed, you would have to use rebind().

Replacing a Binding That Has Attributes

DirContext.rebind() is used to add or replace a binding and its attributes. It accepts the same arguments as bind(). However, rebind()'s semantics require that if the name is already bound, then it will be unbound and the newly given object and attributes will be bound.

// Create the object to be bound
Fruit fruit = new Fruit("lemon");

// Create attributes to be associated with the object
Attributes attrs = new BasicAttributes(true); // case-ignore
Attribute objclass = new BasicAttribute("objectclass");
objclass.add("top");
objclass.add("organizationalUnit");
attrs.put(objclass);

// Perform bind
ctx.rebind("ou=favorite, ou=Fruits", fruit, attrs);

When you run this example , it replaces the binding that the bind() example created.

# java Rebind
lemon
attribute: objectclass
value: top
value: organizationalUnit
value: javaObject
value: javaNamingReference
attribute: javaclassname
value: Fruit
attribute: javafactory
value: FruitFactory
attribute: javareferenceaddress
value: #0#fruit#lemon
attribute: ou
value: favorite

Search


One of the most useful features that a directory offers is its yellow pages, or search, service. You can compose a query consisting of attributes of entries that you are seeking and submit that query to the directory. The directory then returns a list of entries that satisfy the query. For example, you could ask the directory for all entries with a bowling average greater than 200 or all entries that represent a person with a surname beginning with "Sch."

The DirContext interface provides several methods for searching the directory, with progressive degrees of complexity and power. The various aspects of searching the directory are covered in the following sections:

1. basic search

The simplest form of search requires that you specify the set of attributes that an entry must have and the name of the target context in which to perform the search.

The following code creates an attribute set matchAttrs, which has two attributes "sn" and "mail". It specifies that the qualifying entries must have a surname ("sn") attribute with a value of "Geisel" and a "mail" attribute with any value. It then invokes DirContext.search() to search the context "ou=People" for entries that have the attributes specified by matchAttrs.

// Specify the attributes to match
// Ask for objects that has a surname ("sn") attribute with 
// the value "Geisel" and the "mail" attribute

// ignore attribute name case
Attributes matchAttrs = new BasicAttributes(true); 
matchAttrs.put(new BasicAttribute("sn", "Geisel"));
matchAttrs.put(new BasicAttribute("mail"));

// Search for objects that have those matching attributes
NamingEnumeration answer = ctx.search("ou=People", matchAttrs);

You can then print the results as follows.

while (answer.hasMore()) {
    SearchResult sr = (SearchResult)answer.next();
    System.out.println(">>>" + sr.getName());
    printAttrs(sr.getAttributes());
}

printAttrs()is similar to the code in the getAttributes() example that prints an attribute set.

Running this example produces the following result.

# java SearchRetAll
>>>cn=Ted Geisel
attribute: sn
value: Geisel
attribute: objectclass
value: top
value: person
value: organizationalPerson
value: inetOrgPerson
attribute: jpegphoto
value: [B@1dacd78b
attribute: mail
value: Ted.Geisel@JNDITutorial.example.com
attribute: facsimiletelephonenumber
value: +1 408 555 2329
attribute: cn
value: Ted Geisel
attribute: telephonenumber
value: +1 408 555 5252

Returning Selected Attributes

The previous example returned all attributes associated with the entries that satisfy the specified query. You can select the attributes to return by passing search() an array of attribute identifiers that you want to include in the result. After creating the matchAttrs as shown previously, you also need to create the array of attribute identifiers, as shown next.

// Specify the ids of the attributes to return
String[] attrIDs = {"sn", "telephonenumber", "golfhandicap", "mail"};

// Search for objects that have those matching attributes
NamingEnumeration answer = ctx.search("ou=People", matchAttrs, attrIDs);

This example returns the attributes "sn", "telephonenumber", "golfhandicap", and "mail" of entries that have an attribute "mail" and have a "sn" attribute with the value "Geisel". This example produces the following result. (The entry does not have a "golfhandicap" attribute, so it is not returned.)

# java Search 
>>>cn=Ted Geisel
attribute: sn
value: Geisel
attribute: mail
value: Ted.Geisel@JNDITutorial.example.com
attribute: telephonenumber
value: +1 408 555 5252

2. Search Filters

In addition to specifying a search using a set of attributes, you can specify a search in the form of a search filter. A search filter is a search query expressed in the form of a logical expression. The syntax of search filters accepted by DirContext.search() is described in RFC 2254.

The following search filter specifies that the qualifying entries must have an "sn" attribute with a value of "Geisel" and a "mail" attribute with any value:

(&(sn=Geisel)(mail=*))

The following code creates a filter and default SearchControls, and uses them to perform a search. The search is equivalent to the one presented in the basic search example.

// Create the default search controls
SearchControls ctls = new SearchControls();

// Specify the search filter to match
// Ask for objects that have the attribute "sn" == "Geisel"
// and the "mail" attribute
String filter = "(&(sn=Geisel)(mail=*))";

// Search for objects using the filter
NamingEnumeration answer = ctx.search("ou=People", filter, ctls);

Running this example produces the following result.

# java SearchWithFilterRetAll
>>>cn=Ted Geisel
attribute: sn
value: Geisel
attribute: objectclass
value: top
value: person
value: organizationalPerson
value: inetOrgPerson
attribute: jpegphoto
value: [B@1dacd75e
attribute: mail
value: Ted.Geisel@JNDITutorial.example.com
attribute: facsimiletelephonenumber
value: +1 408 555 2329
attribute: cn
value: Ted Geisel
attribute: telephonenumber
value: +1 408 555 5252

Quick Overview of Search Filter Syntax

The search filter syntax is basically a logical expression in prefix notation (that is, the logical operator appears before its arguments). The following table lists the symbols used for creating filters.

Symbol Description 
& conjunction (i.e., and -- all in list must be true) 
disjunction (i.e., or -- one or more alternatives must be true) 
negation (i.e., not -- the item being negated must not be true) 
equality (according to the matching rule of the attribute) 
~=  approximate equality (according to the matching rule of the attribute) 
>=  greater than (according to the matching rule of the attribute) 
<= less than (according to the matching rule of the attribute) 
=* presence (i.e., the entry must have the attribute but its value is irrelevant) 
wildcard (indicates zero or more characters can occur in that position); used when specifying attribute values to match 
escape (for escaping '*', '(', or ')' when they occur inside an attribute value) 

Each item in the filter is composed using an attribute identifier and either an attribute value or symbols denoting the attribute value. For example, the item "sn=Geisel" means that the "sn" attribute must have the attribute value "Geisel" and the item "mail=*" indicates that the "mail" attribute must be present.

Each item must be enclosed within a set of parentheses, as in "(sn=Geisel)". These items are composed using logical operators such as "&" (conjunction) to create logical expressions, as in "(& (sn=Geisel) (mail=*))".

Each logical expression can be further composed of other items that themselves are logical expressions, as in "(| (& (sn=Geisel) (mail=*)) (sn=L*))". This last example is requesting either entries that have both a "sn" attribute of "Geisel" and the "mail" attribute or entries whose "sn" attribute begins with the letter "L."

Returning Selected Attributes

The previous example returned all attributes associated with the entries that satisfy the specified filter. You can select the attributes to return by setting the search controls argument. You create an array of attribute identifiers that you want to include in the result and pass it to SearchControls.setReturningAttributes(). Here's an example.

// Specify the ids of the attributes to return
String[] attrIDs = {"sn", "telephonenumber", "golfhandicap", "mail"};
SearchControls ctls = new SearchControls();
ctls.setReturningAttributes(attrIDs);

This example is equivalent to the Returning Selected Attributes example in the Basic Search section. Running this example produces the following results. (The entry does not have a "golfhandicap" attribute, so it is not returned.)

# java SearchWithFilter
>>>cn=Ted Geisel
attribute: sn
value: Geisel
attribute: mail
value: Ted.Geisel@JNDITutorial.example.com
attribute: telephonenumber
value: +1 408 555 5252

3. Search Controls


The default SearchControls specifies that the search is to be performed in the named context ( SearchControls.ONELEVEL_SCOPE). This default is used in the examples in the Search Filters section.

In addition to this default, you can specify that the search be performed in the entire subtree or only in the named object.

Search the Subtree

A search of the entire subtree searches the named object and all of its descendants. To make the search behave in this way, pass SearchControls.SUBTREE_SCOPE to SearchControls.setSearchScope() as follows.

// Specify the ids of the attributes to return
String[] attrIDs = {"sn", "telephonenumber", "golfhandicap", "mail"};
SearchControls ctls = new SearchControls();
ctls.setReturningAttributes(attrIDs);
ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);

// Specify the search filter to match
// Ask for objects that have the attribute "sn" == "Geisel"
// and the "mail" attribute
String filter = "(&(sn=Geisel)(mail=*))";

// Search the subtree for objects by using the filter
NamingEnumeration answer = ctx.search("", filter, ctls);

This example searches the context ctx's subtree for entries that satisfy the specified filter. It finds the entry "cn= Ted Geisel, ou=People" in this subtree that satisfies the filter.

# java SearchSubtree
>>>cn=Ted Geisel, ou=People
attribute: sn
value: Geisel
attribute: mail
value: Ted.Geisel@JNDITutorial.example.com
attribute: telephonenumber
value: +1 408 555 5252

Search the Named Object

You can also search the named object. This is useful, for example, to test whether the named object satisfies a search filter. To search the named object, pass SearchControls.OBJECT_SCOPE to setSearchScope().

// Specify the ids of the attributes to return
String[] attrIDs = {"sn", "telephonenumber", "golfhandicap", "mail"};
SearchControls ctls = new SearchControls();
ctls.setReturningAttributes(attrIDs);
ctls.setSearchScope(SearchControls.OBJECT_SCOPE);

// Specify the search filter to match
// Ask for objects that have the attribute "sn" == "Geisel"
// and the "mail" attribute
String filter = "(&(sn=Geisel)(mail=*))";

// Search the subtree for objects by using the filter
NamingEnumeration answer = 
    ctx.search("cn=Ted Geisel, ou=People", filter, ctls);

This example tests whether the object "cn=Ted Geisel, ou=People" satisfies the given filter.

# java SearchObject
>>>
attribute: sn
value: Geisel
attribute: mail
value: Ted.Geisel@JNDITutorial.example.com
attribute: telephonenumber
value: +1 408 555 5252

The example found one answer and printed it. Notice that the name of the result is the empty string. This is because the name of the object is always named relative to the context of the search (in this case, "cn=Ted Geisel, ou=People").

Result Count

Sometimes, a query might produce too many answers and you want to limit the number of answers returned. You can do this by using the count limit search control. By default, a search does not have a count limit--it will return all answers that it finds. To set the count limit of a search, pass the number to SearchControls.setCountLimit().

The following example sets the count limit to 1.

// Set the search controls to limit the count to 1
SearchControls ctls = new SearchControls();
ctls.setCountLimit(1);

If the program attempts to get more than the count limit number of results, then a SizeLimitExceededException will be thrown. So if a program has set a count limit, then it should either differentiate this exception from other NamingExceptions or keep track of the count limit and not request more than that number of results.

Specifying a count limit for a search is one way of controlling the resources (such as memory and network bandwidth) that your application consumes. Other ways to control the resources consumed are to narrow your search filter (be more specific about what you seek), start your search in the appropriate context, and use the appropriate scope.

Time Limit

A time limit on a search places an upper bound on the amount of time that the search operation will block waiting for the answers. This is useful when you don't want to wait too long for an answer. If the time limit specified is exceeded before the search operation can be completed, then a TimeLimitExceededException will be thrown.

To set the time limit of a search, pass the number of milliseconds to SearchControls.setTimeLimit(). The following example sets the time limit to 1 second.

// Set the search controls to limit the time to 1 second (1000 ms)
SearchControls ctls = new SearchControls();
ctls.setTimeLimit(1000);

To get this particular example to exceed its time limit, you need to reconfigure it to use either a slow server, or a server that has lots of entries. Alternatively, you can use other tactics to make the search take longer than 1 second.

A time limit of zero means that no time limit has been set and that calls to the directory will wait indefinitely for an answer.

Trouble Shooting Tips

Here are the most common problems that you might encounter when you try to run a successfully compiled program that uses the JNDI classes.

1. You get a NoInitialContextException.

Cause: You did not specify which implementation to use for the initial context. Specifically, the Context.INITIAL_CONTEXT_FACTORY environment property was not set to the class name of the factory that will create the initial context. Or, you did not make available to the program the classes of the service provider named by Context.INITIAL_CONTEXT_FACTORY.

Solution: Set the Context.INITIAL_CONTEXT_FACTORY environment property to the class name of the initial context implementation that you are using. See Configuration section for details.

If the property was set, then make sure that the class name was not mistyped, and that the class named is available to your program (either in its classpath or installed in the jre/lib/ext directory of the JRE). The Java Platform includes service providers for LDAP, COS naming, DNS, and the RMI registry. All other service providers must be installed and added to the execution environment.

2. You get a CommunicationException, indicating "connection refused."

Cause: The server and port identified by the Context.PROVIDER_URL environment property is not being served by the server. Perhaps someone has disabled or turned off the machine on which the server is running. Or, maybe you mistyped the server's name or port number.

Solution: Check that there is indeed a server running on that port, and restart the server if necessary. The way that you perform this check depends on the LDAP server that you are using. Usually, an administrative console or tool is available that you can use to administer the server. You may use that tool to verify the server's status.

3. The LDAP server responds to other utilities (such as its administration console) but does not seem to respond to your program's requests.

Cause: The server does not respond to LDAP v3 connection requests. Some servers (especially public servers) do not respond correctly to the LDAP v3, ignoring the requests instead of rejecting them. Also, some LDAP v3 servers have problems handling a control that Oracle's LDAP service provider automatically sends and often return a server-specific failure code.

Solution. Try setting the environment property "java.naming.ldap.version" to "2". The LDAP service provider by default attempts to connect to an LDAP server using the LDAP v3; if that fails, then it uses the LDAP v2. If the server silently ignores the v3 request, then the provider will assume that the request worked. To work around such servers, you must explicitly set the protocol version to ensure proper behavior by the server.

If the server is a v3 server, then try setting the following environment property before creating the initial context:

env.put(Context.REFERRAL, "throw");

This will turn off the control that the LDAP provider sends automatically. (Check out the JNDI Tutorial for details.)

4. The program hangs.

Causes: Some servers (especially public ones) won't respond (not even with a negative answer) if you attempt to perform a search that would generate too many results or that would require the server to examine too many entries in order to generate the answer. Such servers are trying to limit the amount of resources that they expend on a per-request basis.

Or, you tried to use Secure Socket Layer (SSL) against a server/port that does not support it, and vice versa (that is, you tried to use a plain socket to talk to an SSL port).

And lastly, the server is either responding very slowly due to heavy load or is not responding at all for some reason.

Solution: If your program is hanging because the server is trying to restrict the use of its resources, then retry your request using a query that will return a single result or only a few results. This will help you to determine whether the server is alive. If it is, then you can broaden your initial query and resubmit it.

If your program is hanging because of SSL problems, then you need to find out whether the port is an SSL port and then set the Context.SECURITY_PROTOCOL environment property appropriately. If the port is an SSL port, then this property should be set to "ssl". If it is not an SSL port, then this property should not be set.

If you program is hanging for none of the above reasons the property com.sun.jndi.ldap.read.timeout comes in handy to specify the read timeout. The value of this property is the string representation of an integer representing the read timeout in milliseconds for LDAP operations. If the LDAP provider cannot get a LDAP response within that period, it aborts the read attempt. The integer should be greater than zero. An integer less than or equal to zero means no read timeout is specified which is equivalent to waiting for the response infinitely until it is received.

If this property is not specified, the default is to wait for the response until it is received.

For example,

env.put("com.sun.jndi.ldap.read.timeout", "5000"); causes the LDAP service provider to abort the read attempt if the server does not respond with a reply within 5 seconds.

5. You get a NameNotFoundException.

Causes: When you initialized the initial context for the LDAP, you supply a root-distinguished name. For example, if you set the Context.PROVIDER_URL environment property for the initial context to "ldap://ldapserver:389/o=JNDITutorial" and subsequently supplied a name such as "cn=Joe,c=us", then the full name that you passed to the LDAP service was "cn=Joe,c=us,o=JNDITutorial". If this was really the name that you intended, then you should check your server to make sure that it contains such an entry.

Also, the Oracle Directory Server returns this error if you supply an incorrect distinguished name for authentication purposes. For example, the LDAP provider will throw a NameNotFoundException if you set the Context.SECURITY_PRINCIPAL environment property to "cn=Admin, o=Tutorial", and "cn=Admin, o=Tutorial" is not an entry on the LDAP server. The correct error for the Oracle Directory Server to return actually should be something related to authentication, rather than "name not found."

Solution: Verify the name that you supplied is that of an entry existing on the server. You can do this by listing the entry's parent context or using some other tool such as the directory server's administration console.

Here are some problems that you might encounter when trying to deploy an applet that uses the JNDI classes.

6. You get an AppletSecurityException when your applet attempts to communicate with a directory server that is running on a machine different from the one from which the applet was loaded

Cause: Your applet was not signed, so it can connect only to the machine from which it was loaded. Or, if the applet was signed, the browser has not granted the applet permission to connect to the directory server machine.

Solution: If you want to allow the applet to connect to directory servers running on arbitrary machines, then you need to sign both your applet and all of the JNDI JARs that your applet will be using. For information on signing jars, see Signing and Verifying JAR files .

7. You get an AppletSecurityException when your applet attempts to set up the environment properties using system properties.

Cause: Web browsers limit access to system properties and throw a SecurityException if you attempt to read them.

Solution: If you need to obtain input for your applet, then try using applet params instead.

8. You get an AppletSecurityException when an applet running inside Firefox attempts to authenticate using CRAM-MD5 to the LDAP server.

Cause: Firefox disables access to the java.security packages. The LDAP provider used the message digest functionality provided by java.security.MessageDigest for implementing CRAM-MD5.

Solution: Use the Java Plug-in.

«« Previous
Next »»