Directory Services
As discussed at the start of today's work a Directory Service is a Name Service with the ability to categorize names to support complex searches of the name space. The JNDI support for a Directory Service is differentiated from a Name Service by storing attributes as well as an object against a bound name. Typically you will probably use LDAP as your Directory Service, but NDS (Novell Directory Services) is also a Directory Service. The simple name services provided with J2SE (CORBA) and J2EE are not directory services.
An attribute is additional information stored with a name. Storing full name, address, phone number, and email with a person's name is a common use of a directory service. NDS uses attributes to control access to shared network drives and to configure a user's login environment.
A directory service stores attributes as values against a keyword (LDAP calls them IDs). Directory services usually support searching for names (objects) that have certain attributes defined (or not defined). Searching often supports looking for names with attributes that have a specific value (often wildcard pattern matching is supported). A simple search of a personnel database under an LDAP server might, for example, find all entries with the surname Washington.
LDAP uses a schema system to control which attributes an object must define and those that it may optionally define. Any attributes that you add or delete must not break the schema's requirements. LDAP servers may be able to disable schema checking, but disabling schema checking is usually a bad idea because the schema was created for a purpose.
If you want to see the capabilities of attributes, you must have access to a directory server. The rest of this section shows how to use an LDAP Directory Server.
Using LDAP
Using an LDAP Directory Service requires you to set the JNDI properties to specify the JNDI Service provider from Sun Microsystems and, of course, you must have an LDAP server running.
The J2EE RI does not include an LDAP server, so if you wish to work through this section, and you do not already have an LDAP server, you will have to obtain one from elsewhere. Only certain operating systems provide LDAP servers. Windows NT, 2000, and XP users will have to purchase the enterprise (or server) editions of these operating systems, which are typically significantly more expensive than the usual desktop or professional editions. Sun Microsystems' Solaris 8 Operating Environment includes an LDAP server.
Linux (and Solaris) users can download and install the OpenLDAP implementation, which is an open source server available free of charge for personal use. The Open LDAP server can be downloaded from http://www.openldap.org/software/download/.
Users of Microsoft Windows will have to make other arrangements as OpenLDAP is not available for this platform. If an Active Directory server (which supports an LDAP interface) is accessible on the network or you use the Enterprise (or Server) edition of the Operating System, you are ok. Otherwise, your best solution is to install Linux and OpenLDAP on a spare PC.
To use an LDAP server simply create a jndi.properties file with the following entries:
java.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory java.naming.provider.url=ldap://localhost:389
If the LDAP server is not running on the current machine, replace the name localhost with the name or IP address of the actual LDAP server. Port number 389 is the default LDAP port number, and you can omit it if LDAP is running on the default port (or replace it by the actual port number if a non-standard port is being used).
LDAP names conform to the X.500 standard that requires a hierarchical namespace. A Distinguished Name (DN) unambiguously identifies each entry in the directory. The DN consists of the concatenation of the names from the root of the directory tree down to the specific entry.
A sample LDAP DN looks like the following:
cn=Martin Bond, ou=Authors, o=SAMS, c=us
This will be a familiar structure if you have worked with digital certificates or Active Directory.
Using a Directory Service
Directory Services cannot be accessed through the ordinary Context object. Instead, you must use a javax.naming.directory.DirContext class. The DirContext is a sub-class of Context, and you can use it in place of a Context when dealing with a Directory Service where you require directory functionality (such as attributes). For example,
DirContext ic = new InitialDirContext();
The DirContext class supports the same lookup(), bind(), rebind(), list() and other operations of the Context class. Additionally the DirContext provides support for attributes.
Attributes are read from the context just like you would look up a name from the context. The DirContext.getAttributes() method returns a NamingEnumeration that contains a collection of Attribute objects. Each Attribute has an ID (or key) and a list of values (an attribute can have more than one value for the same key). The following example prints all the attributes for a name specified by args[0]:
DirContext ctx = new InitialDirContext(); Attributes attrs = ctx.getAttributes(args[0]); NamingEnumeration ae = attrs.getAll(); while (ae.hasMore()) { Attribute attr = (Attribute)ae.next(); System.out.println(" attribute: " + attr.getID()); NamingEnumeration e = attr.getAll(); while (e.hasMore()) System.out.println(" value: " + e.next()); }
A second form of the getAttributes() method allows you to provide an array of attribute names, and it only returns the values for those attributes. It is not an error to query an attribute that isn't defined; a value is simply not returned for that attribute. The following fragment shows how to find the email and cellphone attributes for a name:
String[] IDs = {"email", "cellphone"}; Attributes attrs = ctx.getAttributes("cn=Martin Bond, ou=Authors, o=SAMS, c=us", IDs);
Overloaded versions of the bind() and rebind() methods in the DirContext class take a third Attributes parameter for binding a name. The following example could bind a new entry LDAP entry for cn=Martin Bond (the details of the Person object have been omitted for clarity and the email address has not been defined for privacy):
Person martin = ...; Attributes attrs = new BasicAttributes(); attrs.put(new BasicAttribute("email","...")); attrs.put(new BasicAttribute("description","author")); ctx.bind("cn=Martin Bond, ou=Authors, o=SAMS, c=us", martin, attrs);
As a final point, the DirContext.ModifyAttributes() method supports the addition, modification, and deletion of attributes for a name.
Searching a Directory Service
A powerful and useful feature of attributes is the ability to search for names that have specific attributes or names that have attributes of a particular value.
You use the DirContext.search() method to search for names. There are several overloaded forms of this method, all of which require a DN to define the context in the name tree where the search should begin. The simplest form of search() takes a second parameter that is an Attributes object that contains a list of attributes to find. Each attribute can be just the name, or the name and a value for that attribute.
The following code excerpt shows how to find all names that have an email attribute and a description attribute with the value author in the o=SAMS name space.
DirContext ctx = new InitialDirContext(); Attributes match = new BasicAttributes(true); match.put(new BasicAttribute("email")); match.put(new BasicAttribute("description","author")); NamingEnumeration enum = ctx.search("o=SAMS", match); while (enum.hasMore()) { SearchResult res = (SearchResult)enum.next(); System.out.println(res.getName()+", o=SAMS"); }
The search() method returns a NamingEnumeration containing objects of class SearchResult (a sub-class of NameClassPair discussed earlier). The SearchResult encapsulates information about the names found. The example code excerpt simply prints out the names (the names in the SearchResult object are relative to the context that was searched).
The SearchResult class also has a getAttributes() method that returns all the attributes for the found name. A second form of the search() method takes a third parameter that is an array of String objects specifying the attributes for the method to return. The following code fragment shows how to search and return just the email and cellphone name attributes:
Attributes match = new BasicAttributes(true); match.put(new BasicAttribute("email")); match.put(new BasicAttribute("description","author")); String[] getAttrs = {"email","cellphone"} NamingEnumeration enum = ctx.search("o=SAMS", match, getAttrs);
Yet another form of the search() method takes a String parameter and a SearchControls parameter to define a search filter.
The filter String uses a simple prefix notation for combining attributes and values.
You can use the javax.naming.directory.SearchControls argument required by search() to
Specify which attributes the method returns (the default is all attributes)
Define the scope of the search, such as the depth of tree to search down
Limit the results to a maximum number of names
Limit the amount of time for the search
The following example searches for a description of author and an email address ending with sams.com with no search controls:
String filter ="(|(description=author)(email=*sams.com))"; NamingEnumeration enum = ctx.search("o=SAMS", filter, null);
The JNDI API documentation and the JNDI Tutorial from Sun Microsystems provide full details of the search filter syntax.