One of the most useful features a directory service can offer is the ability to search its entries for ones that have attribute values that meet certain criteria. JNDI supports this kind of searching in directory systems, which means you can implement search functionality in your JNDI applications. DirContext provides a number of different search() methods that allow you to specify what you are searching for and let you control how the search operates.
There are two ways to specify what you are searching for. The simpler technique is to create a set of attributes that serve as the search criteria. In this case, you can either set an attribute value, meaning that an entry must have that attribute value to match or leave the value empty, so that all entries that have the attribute match, no matter what the value.
The more flexible way to specify search criteria is with a search filter string. A search filter allows you to express search criteria using LDAP search syntax, specified in RFC-2254. Note that this syntax works with all JNDI providers, not just LDAP; it's the JNDI standard for searching all kinds of directories. The search filter is a String that takes the following general form:
(attribute operator value)
You can use an asterisk (*) to represent a wildcard. For example, here's how to search for all entries in an LDAP directory:
(objectclass=*)
A search for all users takes the form of:
(objectclass=user)
You can also use the wildcard character to represent completion, just like in a Unix shell or a DOS prompt. For example, here's a filter for searching for all users whose first names start with "k":
(cn=k*)
You can use operators other than equals (=), as in:
(revision<24)
You can also combine search filters with operators such as AND (&) and OR (|). The way to do this is to wrap the entire expression in parentheses:
(&(objectclass=computer)(cn=Billy))
Finally, you can nest search expressions:
(&(|(objectclass=computer)(objectclass=user))(cn=Billy)))
Obviously, the attributes you specify in a search depend on the directory service you are searching.
Regardless of how you specify the search criteria, the search() method you call returns a NamingEnumeration of SearchResult objects. There is a SearchResult for each directory entry that matches the search criteria. SearchResult is a direct subclass of Binding that stores a set of Attributes along with the usual name, class name, and object. (As we'll see shortly, the object in a SearchResult may be null, depending on the SearchControls you set.) Since a search operation returns a NamingEnumeration, you must cast the object that the enumeration returns from the next() method to a SearchResult object. Once you have done that, you can retrieve attributes with the getAttributes() method and use methods inherited from Binding (and NameClassPair) to get other information about the matching entry.
The search() methods that take a SearchControls object allow you to control how a search operates. You can set the scope of a search, whether the search should return objects, and the maximum amount of time the search should take, among other things. The easiest way to create a SearchControls object is to use the default constructor and then call various set() methods to set particular search properties.
For example, the setSearchScope() method controls where the search should look for matching directory entries. Most of the time, you set the scope of a SearchControls object to search an entire subtree, but you can also limit the search to an object or its children. Table 6-3 lists the available search scopes.
Scope | Meaning |
---|---|
OBJECT_SCOPE |
Searches only the object itself |
ONELEVEL_SCOPE |
Searches only the children of the search target |
SUBTREE_SCOPE |
Searches the entire subtree |
The setReturningObjFlag() method determines whether the results of a search contain references to the actual directory entries or only the names and class names of the entries. The default behavior is not to return the actual entries, meaning that calling getObject() on a SearchResult returns null.
The SearchControls object also allows you to specify other aspects of the behavior of a search:
The number of milliseconds to wait for the directory to return the search results (by default, a search can take as long as it takes)
The number of entries that can be returned from the search (by default, as many as are present)
Whether to follow links to finish the search (no by default)
What attributes if any to return (all by default)
In general, the default behavior is typically what you want for these parameters.
Now that we've discussed how the various search() methods work, let's look at a real example. Example 6-11 shows the implementation of a search command for NamingShell. This example uses the search() method that takes the name of the context to be searched, a search filter that describes the search criteria, and a SearchControls object.
import java.util.Vector; import javax.naming.*; import javax.naming.directory.*; class search implements Command { public void execute(Context c, Vector v) throws CommandException { if (NamingShell.getCurrentContext() == null) throw new CommandException(new Exception(), "No current context"); else if (v.isEmpty()) throw new CommandException(new Exception(), "No filter specified"); String filter = (String)v.firstElement(); try { SearchControls cons = new SearchControls(); cons.setSearchScope(SearchControls.SUBTREE_SCOPE); NamingEnumeration results = ((DirContext)c).search("", filter, cons); while (results.hasMore()) { SearchResult result = (SearchResult)results.next(); System.out.println(result.getName()); } } catch (InvalidSearchFilterException isfe) { throw new CommandException(isfe, "The filter [" + filter + "] is invalid"); } catch (NamingException e) { throw new CommandException(e, "The search for " + filter + " failed"); } catch (ClassCastException cce) { throw new CommandException(cce, "Not a directory context"); } } public void help() { System.out.println("Usage: search filter"); } }
The search command always starts searching in the current context, so you need to move to the appropriate location in the directory service using cd before you use search. search requires you to specify a search filter as its first argument. Note that you cannot use any spaces in the filter, or the filter will be parsed as multiple arguments and therefore not work. Here's how we might use the search command:
o=Novell% search (&(objectclass=person)(cn=a*)) cn=admin cn=admin,ou=cook1,ou=user cn=admin,ou=fj,ou=user cn=admin,ou=Stanford,ou=user cn=admin,ou=Ed Reed,ou=user cn=admin,ou=antimony,ou=user cn=admin,ou=keaves,ou=user cn=admin,ou=acme,ou=user cn=admin,ou=nld,ou=user cn=admin,ou=wibble,ou=user cn=admin,ou=xxx,ou=user cn=admin,ou=piet,ou=user cn=admin,ou=adamtest1,ou=user cn=admin,ou=novell,ou=user ...
Copyright © 2001 O'Reilly & Associates. All rights reserved.