LDAP and JNDI: Together forever

Learn how LDAP and JNDI combine to form a powerful directory and Java object storage facility

1 2 3 Page 3
Page 3 of 3

Control time

If needed, the setTimeLimit() method can set the maximum search completion time for the answers. This is a useful technique when you don't want your program to block for an extended period of time:

// Set search controls to limit time to 3 seconds
SearchControls ctls = new SearchControls();
ctls.setTimeLimit(3000);

If the search exceeds its specified time limit before the search operation can be completed, a javax.naming.TimeLimitExceededException is thrown. A time limit of 0 will make the program wait indefinitely until the search is completed.

Return the Java objects in the result

It is important to invoke the setReturningObjFlag(boolean on) method, as it determines whether the search returns the attributes stored in the server or the object itself. It should be set to true in order to get Java objects in the NamingEnumeration. If set to false, the search is faster, but it returns only the object name and class, not the object data:

SearchControls ctls = new SearchControls();
ctls. setReturningObjFlag (true);

Control links

A link is an entry in a context that references another entry, similar to a symbolic link in a filesystem. Searches can either traverse links, looking for matches to a search, or ignore them. The setDerefLinkFlag(boolean on) method determines if this happens or not:

SearchControls ctls = new SearchControls();
ctls.setDerefLinkFlag (true);

DifferentSearches.java shows an example of all the searches described above.

The COMPARE operation

In LDAP, a COMPARE operation allows a client to specify a DN and an attribute/value pair (an assertion provided with an entry) in the directory. The server determines if the named entry has those name/value pairs as attributes, returning a boolean answer indicating this. The COMPARE operation allows the server to keep certain attribute/value pairs secure by not exposing them to applications. The server can compare the DN uid=styagi,ou=People,o=myserver.com for the attribute password=hotjava, but it can't search for all users who have hotjava as the password. The comparison can be performed in JNDI using a search with certain constraints:

  • The filter string should have no wildcards (i.e., it should be a pure name=values set)
  • The search scope should be OBJECT_SCOPE
  • The SetReturningAttributes() method should not be invoked, or should be invoked with setReturningAttributes(new String[0])

As an example, DifferentSearches.java examines the attribute's last login to see if it has a value of 200 for a DN of cn=vectorid-i,ou=JavaObjects,o=myserver.com:

SearchControls ctls = new SearchControls();
ctls. setReturningObjFlag (true);
ctls.setReturningAttributes(new String[0]);
ctls.setSearchScope(SearchControls.OBJECT_SCOPE);
String filter = "(lastlogin=200)";
NamingEnumeration answer =  ctx.search("cn=vectorid-1,ou=JavaObjects,o=myserver.com", filter, ctls);

Since a search always returns enumeration, and there is no method to determine the number of results (except setting a counter) if the compare succeeds, the resulting enumeration will contain a single result with an empty name and no attributes.

Event handling

JNDI supports AWT 1.1-type event handling in the javax.naming.event package. However, you need to add the listeners to the objects after the objects are registered in the JNDI tree if you want to make the tree event aware. The LDAP server must support persistent search control for events to be propagated.

Table 5 shows the trapped NamingEvent types.

NamingEvent.OBJECT_CHANGEDAn object's contents (e.g., its attributes) have changed or its binding have been replaced
NamingEvent.OBJECT_ADDEDA new object has been added to the namespace
NamingEvent.OBJECT_REMOVEDAn existing object has been removed from the namespace
NamingEvent.OBJECT_RENAMEDAn existing object has been given another name
Table 5. Object events

For an object to be a listener, it must implement the javax.naming.event.NamingListener interface or either of its two specialized subinterfaces: javax.naming.NamespaceChangeListner or javax.naming.event.ObjectChangeListener. The NamespaceChangeListener registers interest in the DN and invokes when objects are added, removed, or renamed to that DN. In contrast, an ObjectChangeListener, which is more single-object specific, invokes when its attributes are modified or the object/object binding is replaced. MyNamingListener.java and MyObjectListener.java show how to implement a simple NamespaceChangeListener and ObjectChangeListener, respectively. Since we add the object in the JNDI tree to add a listener, that object first has to be located. The EventContext locates the object when the DN or name of the object is known, whereas we employ EventDirContext when we have to search for the object. The code from DifferentListeners.java below shows how to add a NamespaceChange listener to an EventContext:

Context inictx =getContext();
EventContext ctx=(EventContext)(inictx.lookup("o=myserver.com"));
NamingListener objlsnr =new MyNamingListener();
ctx.addNamingListener("ou=JavaObjects",EventContext.SUBTREE_SCOPE, objlsnr);
ctx.bind("cn=eventtest,ou=JavaObjects",new Vector());
ctx.rename("cn=eventtest,ou=JavaObjects","cn=eventtestrenamed,ou=JavaObjects");

If the object's DN is not known, EventDirContext can search for the object starting from the ou=JavaObjects in the DIT:

Context inictx =getDirContext();
EventDirContext ctx=(EventDirContext)(inictx.lookup("o=myserver.com"));
NamingListener naminglsnr= new MyNamingListener();
SearchControls ctls = new SearchControls();
ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
ctx.addNamingListener("ou=JavaObjects","(cn=*)",ctls, naminglsnr);
// Test the listener
ctx.unbind("cn=vectorid-9,ou=JavaObjects");

Just as in the case of a search, listeners have a scope (e.g., EventContext.ONELEVEL_SCOPE) defining the part of the tree to which they should listen. DifferentListeners.java shows how to use a NamespaceChangeListener. A screen shot of the output can be seen in figure 11.

Figure 11. Screen shot of the console, showing how an event is trapped

Remove listeners

There are three ways to remove a registered listener:

  1. When the Context.close() method invokes on the event source and all registered listeners are removed

  2. When a listener receives an error notification as a result of the NamingExceptionThrown()

  3. When EventContext.removeNamingListener() explicitly removes a listener

Conclusion

The Lightweight Directory Access Protocol defines the ways in which clients should access data from a server. As we've seen, LDAP is a powerful directory server, especially when used in conjunction with Java's JNDI. In this article, we've defined several important LDAP-related terms, looked at how to connect to an LDAP server, examined how to store Java objects on the server, and detailed LDAP's search features.

If the information presented here seems overwhelming, let me assure you that it's not. After you build a couple of applications, you'll appreciate LDAP's simplicity and usefulness.

Sameer Tyagi has been working on Java since the 1.02 days. Besides developing Internet/intranet applications with server-side Java, he has also run training sessions, workshops, and written articles. In other words, when he's not doing Java, its because he's doing more Java.

Learn more about this topic

  • Example code
  • Resources necessary to run the examples
  • Todd Sundsted's How To Java LDAP series
  • SunWorld's LDAP series
  • Other important LDAP resources

1 2 3 Page 3
Page 3 of 3