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 2
Page 2 of 3
public Object getObjectInstance(Object obj,Name name,
            Context ctx,Hashtable env)throws Exception{
Reference ref = (Reference)obj;
RefAddr addr = ref.get("Apartment details");
String  temp = (String)addr.getContent();
int offset= temp.indexOf(":");
String  size =  temp.substring(0,offset);
String  location = temp.substring(offset+1);
return (new Apartment(size,location));
}

The getObjectInstance() method parses the components and tries to reconstruct a reference that describes an Apartment object. The factory interprets the values of the reference, and it's free to ignore nonrelevant parts and create a new object instead. The objects and properties produced after ADDReference.java runs can be seen in screen shots in Figures 3 and 3a.

Figure 3. Screen shot showing that objects were added to the server
Figure 3a. Screen shot of the object properties, highlighting the fact that it is actually a reference

Store information as attributes

The last technique to store nonserializable and nonreferenceable objects involves storing attributes rather than the object or a reference to the object. If the bound object implements the DirContext interface, the LDAP ADD operation extracts and stores the object's attributes. This technique doesn't store the actual object, but rather stores the attributes inside that object. Consider the User object:

public class User implements DirContext{
   String id;
   String dn;
   Attributes myAttrs = new BasicAttributes(true);
   Attribute oc = new BasicAttribute("objectclass");
   Attribute ouSet = new BasicAttribute("ou");
public User(String dn,String uid, String givenname,String sn,String ou,
              String mail,String tel,String fax){
      this.dn=dn;
      id = uid;
      oc.add("inetOrgPerson");
      oc.add("organizationalPerson");
      oc.add("person");
      oc.add("top");
      ouSet.add("People");
      ouSet.add(ou);
      String cn = givenname+" "+sn;
      myAttrs.put(oc);
      myAttrs.put(ouSet);
      myAttrs.put("uid",uid);
      myAttrs.put("cn",cn);
      myAttrs.put("sn",sn);
      myAttrs.put("givenname",givenname);
      myAttrs.put("mail",mail);
      myAttrs.put("telephonenumber",tel);
      myAttrs.put("facsimilietelephonenumber",fax")
   }
public Attributes getAttributes(String name) throws NamingException {
      if (! name.equals("")){
         throw new NameNotFoundException();
      }
      return myAttrs;
   }
public Attributes getAttributes(Name name) throws NamingException {
      return getAttributes(name.toString());
   }
public Attributes getAttributes(String name, String[] ids)
                                            throws NamingException {
      if(! name.equals(""))
         throw new NameNotFoundException();
      Attributes answer = new BasicAttributes(true);
      Attribute target;
      for (int i = 0; i < ids.length; i++){
         target = myAttrs.get(ids[i]);
         if (target != null){
            answer.put(target);
         }
      }
      return answer;
   }
public Attributes getAttributes(Name name, String[] ids)
                                              throws NamingException {
      return getAttributes(name.toString(), ids);
   }
// other methods of DirContext with blank implementations.

The User object can be added simply by:

User usr = new User("styagi","Sameer","Tyagi","ou=Accounting",
   "sameertyagi@usa.net");
ctx.bind("uid=styagi,ou=People,o=myserver.com", usr);

Let me emphasize again that this technique does not store the User object, but rather stores its attributes. AddAtributes.java shows how you can add 10 Person objects using attributes. Figures 4 and 4a show screen shots of the server and property sheet for the Moe Joe entry.

Figure 4. Screen shot showing that entries were added
Figure 4a. Screen shot showing attributes of an entry

Modify stored objects

Once we've stored an object, we can add attributes to its entry -- a simple and strong combination that allows us to return Java objects with a search for the objects' attributes. The DirContext class's modifyAttributes() method can perform ADD, REPLACE, or REMOVE modifications. In the following example, we store an account object and add an attribute lastlogin with values of date, last deposit, last withdrawal, and balance; we'll use the simpler, overloaded version of the modifyAttributes() method.

Attributes myAttrs = new BasicAttributes(true);
  Attribute oc = new BasicAttribute("lastlogin");
      oc.add("Monday January 12 1999"); //date
      oc.add("200"); //last deposit
      oc.add("43"); //last withdrawal
      oc.add("7652"); balance
  myAttrs.put(oc);
  Account  act = new Account("styagi");
  String cn="cn=test,ou=JavaObjects,o=myserver.com";
  ctx.bind(cn, act);
  ctx.modifyAttributes(cn,DirContext.ADD_ATTRIBUTE,myAttrs);

When an object attribute has multiple values, if the extra values are not sent with a replacement value, they will be removed. This version of modifyAttributes() allows only a single atomic operation on the object on one or more attributes. If several modifications to a stored object entry are to be made, the ModificationItem that takes an Attribute object and a modification type is used. Like all other operations, an authenticated user with sufficient rights can perform these operations. Modifications are applied in the order in which they appear in the array, and either all of the modifications execute or none do. The example below shows how to replace, add, or remove multiple attributes:

DirContext ctx = new InitialDirContext(env);ModificationItem[] mods = new ModificationItem[3];Attribute mod0 = new BasicAttribute("telephonenumber","860-888-8888");
Attribute mod1 = new BasicAttribute("l", "Hartford");
mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE,mod0);
mods[1] = new ModificationItem(DirContext.ADD_ATTRIBUTE,mod1);
mods[2] = new ModificationItem(DirContext.REMOVE_ATTRIBUTE,mod1);
ctx.modifyAttributes("uid=styagi,ou=People,o=myserver.com", mods);

MODObject.java shows an example. However if modification involves renaming only the DN, then the Context.rename() method will suffice, as seen below:

Context.rename("uid=styagi,ou=People,o=myserver.com",
                                   "uid=sameer,ou=People,o=myserver.com");

Figure 5 demonstrates the server's output from the MODObject.java example.

Figure 5. Property sheet for the vectorid-1 entry added in the ADDSerialize example and modified by executing MODObject.java

Delete stored objects

A context consists of a set of name-to-object bindings. The subcontext represents the context under the context in the LDAP tree. We can delete the User object by destroying the subcontext. The converse, however, is not true -- that is, creating a subcontext with the DirContext.createSubcontext() method doesn't add any objects. The Context.unbind() method produces the same output, but also works under a clustered naming system in which a context from one naming system may be bound to a name in another. The unbind() method succeeds in removing this foreign context. (A foreign context is by definition not a subcontext of the context in which it is bound). Deleting objects works only for leaves in the tree; if tried on a node, JNDI will throw a ContextNotEmptyException.

To delete an object:

DirContext ctx = new InitialDirContext(env);
ctx.destroySubcontext("uid=styagi, ou=People, o=myserver.com");

DELObject.java shows an example of deleting objects.

Search the server for an entry

LDAP boasts feature-rich search facilities. The simplest level of search can use the JNDI naming service to perform the following types of searches:

  • Context.lookup(), which returns a stored object corresponding to that DN in the server
  • Context.list(), which returns the names bound and the class names of the objects bound in the context
  • Context.listBindings(), which returns the names bound and the objects bound to them in the context

The example DifferentSearches.java looks up a DN and checks if the object returned is a Vector, as seen in Figure 6.

Figure 6. Output from DifferentSearches.java for a lookup operation

LDAP stores information in the tree and everything is specific to the node. Therefore, to search for an object in the LDAP server, you need to specify the node or the base from which to start -- the scope of the search. The scope defines exactly how much of the tree should be searched. There are three levels of scope, as outlined in Table 3 and Figures 7, 7a, and 7b below.

SUBTREE_SCOPEStarts at the base entry; searches the base entry and everything below it
ONELEVEL_SCOPESearches only the entries below the base entry
OBJECT_SCOPESearches only the base entry; useful if you need to get attributes/value pair of just one entry
Table 3. Different levels of scope
Figure 7. Searchbase: o=myserver.com Scope=LDAP_SCOPE_SUBTREE
Figure 7a. Searchbase: ou=people, o=myserver.com Scope=LDAP_SCOPE_ONE_LEVEL
Figure 7b. Searchbase :uid=styagi,ou=people,o=myserver.com Scope= LDAP_SCOPE_ONE_LEVEL

To perform the physical search, we invoke the search() method on the object that implements the DirContext interface (InitialDirContext). The search result is NamingEnumeration, an enumeration of SearchResult objects. At minimum, you need the search base and the filter in order to perform a search, but other parameters can be specified to manage the results. The SearchContols object holds most additional parameters for the search, including scope, as seen below:

SearchControls ctls = new SearchControls();
setSearchScope(SearchControls.SUBTREE_SCOPE)

Search on attributes

The other type of search involves searching the attributes of objects and entries for particular values. The simplest form of search requires the set of attributes and the name of the target context in which the search is to be performed. The following code shows how to search the server for anything that has specified values for uid and email. The result is a NamingEnumeration:

Attributes matchAttrs = new BasicAttributes(true);
matchAttrs.put(new BasicAttribute("uid", "kevink"));
matchAttrs.put(new BasicAttribute("mail","sameertyagi@usa.net"));
// Search for objects with these matching attributes
NamingEnumeration answer = ctx.search("ou=People,o=myserver.com",matchAttrs);

When the above code is executed, you get the result shown in Figure 8.

Figure 8. Output of differentSearches.java for a basic search.

Search with filters

LDAP works with search filters. A search filter is a search query expressed in the form of a logical expression. RFC 2254 describes the syntax of search filters that the DirContext.search() methods will accept (see Resources), with the exception that Unicode characters are also allowed. Keep in mind that the use of Unicode characters is preferable to the use of encoded UTF-8 octets. The search filter syntax is a logical expression with a prefix notation (the logical operator appears before its arguments). Table 4 summarizes the different filter symbols and their meanings.

SymbolFilterExampleExample matches
~Approximate(sn~=Tyagi)Tyagi or variations in spelling
=Equality(sn=Tyagi)Surname of Tyagi only
>Greater than(sn=Tyagi)Any surname that alphabetically follows Tyagi
>=Greater than or equal to(sn>=Tyagi)Any surname that includes or alphabetically follows Tyagi
<Less than(sn<Tyagi)Any surname that alphabetically precedes Tyagi
<=Less than or equal to(sn<=Tyagi)Any surname that includes or alphabetically precedes Tyagi
=*Presence(sn=*)All surnames (all entries with the sn attribute)
 Substring(sn=Tya*), (sn=*yag*), (sn=Ty*g*)Any matching string, substring, or superstring that matches Tyagi
&And(&(sn=Tyagi) (cn=Sameer Tyagi))Any entry that matches both surname of Tyagi and a common name of Sameer Tyagi
|Or(|(sn=Tyagi) (cn=Sameer Tyagi))Any entry that matches either surname of Tyagi or a common name of Sameer Tyagi
!Not(!(sn=Tyagi))Any entry other than that with a surname of Tyagi
Table 4: Symbols and examples used for creating filters

Each item in the filter is composed of an attribute identifier and either an attribute value or symbols denoting the attribute value. The following search filter specifies that the qualifying entries must have a userid attribute with a value of styagi and a jpeg photograph with bytes: 82 12 38 4e 23 e3 (binary data):

(&(uid=styagi)( jpegphoto=\82\12\38\4e\23\e3))

The following code demonstrates how to search using a filter and search controls:

SearchControls ctls = new SearchControls();
String filter = "(&(cn=myappuser-1)( lastlogin=*))";
NamingEnumeration answer = ctx.search("ou=People", filter, ctls);

The output of the above code can be seen in Figure 9.

Figure 9. DifferentSearches for a filtered search

Control search results

Let's now turn our attention to controlling the results of the search.

Control attributes

By default, a search will return all attributes associated with the result that satisfy the specified query. To specify the desired attributes, create an array of attribute identifiers, then invoke setReturningAttributes on the search controls:

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

The output of the above code can be seen in Figure 10.

Figure 10. Output of DifferentSearches for controlled attribute search

Control the number of results

By default, a search does not have a limit on the number of results returned. If the search produces too many results or consumes too many resources, the setCountLimit() method can specify the number of results, although proper use of scope and filters should be the preferred means to refine searches:

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

If the count limit is set and there are more results, the search may throw a SizeLimitExceededException.

1 2 3 Page 2
Page 2 of 3