DSSeries LDAP V3  Client for Java  Programming Guide

Introduction

This document describes how to install and program the DSSeries LDAP V3 classes. It is not intended to be an introduction to JNDI or LDAP, it assumes you've read Sun's JNDI document  included with the release and understand how LDAP operates.

At this time the following LDAP V3 functions are implemented:

This document is organized as follows: The classes require JDK 1.x or higher.

Installation

Using DSSeries LDAP V3 Client for Java

To use the DSSeries LDAP V3 classes, two properties must be set in the environment properties passed to the IntialDirContext constructor:
  1. java.naming.factory.initial - this property MUST be set to "com.ibm.jndi.LDAPCtxFactory".
  2. java.naming.factory.url.pkgs - this property MUST be set to "com.ibm.jndi".
Example:
 
    Properties env = new Properties();
    env.put("java.naming.factory.initial","com.ibm.jndi.LDAPCtxFactory");
    env.put("java.naming.factory.url.pkgs","com.ibm.jndi");
    DirContext ctx = new InitialDirContext(env);

Alternatively you can specify the properties from the command line:

java "-Djava.naming.factory.initial=com.ibm.jndi.LDAPCtxFactory,-Dndi.urlfactory.pkgs=com.ibm.jndi"
 

LDAP V3 URL Overview

One of the most powerful features of LDAP V3 is its URL.  LDAP V3 defines URLs that can be used  to search and modify directory entries or base DSSeries LDAP  V3 operations  in a directory hierarchy. The format of the url is:

     ldap://[hostname]:[port]/[dn]?[attributes]?[scope]?[filter]?[extensions]

Below is an explanation of the various parts of the LDAP V3 URL:
 
     ldap:// - this is the scheme id of the URL.The first seven characters of the URL must be set to
                        these characters for the classes to parse the URL correctly.
     hostname - the host to connect to, if it is missing JNDI defaults to localhost.
     port - TCP/IP port to use, if it is missing JNDI defaults to 389.
     dn - the LDAP dn (distinguished name) to base the operation in the hierarchy at
     attributes - the attributes to return from the search
    scope - the scope of the search, if it is missing, a scope of base is used.
     filter - the filter to use, if it is missing, a filter of "(objectclass=*)" is used.
     extensions - these are special fields to carry authentication information, they are unused
                                at this time.

In addition, since LDAP URLs conform to RFC 1738,  unsafe characters occurring inside the URL elements must be escaped. These characters are:  ?&/@:{}\^~[]`"<>#% and a space character.

URL examples:

The last example shows how the character '&'  and space character need to be escaped (26 is the hex value for '&' ,  20 for the space).  In a more  readable form, the filter element would look: but must be passed into JNDI escaped as above.
 

Searching  and Getting Attributes

DSSeries LDAP V3 provides great flexibility in searching  and retrieving attributes from LDAP directories.  Probably two of the most used methods in the API are the search and getAttribute methods.   Below,  are several examples of  Java code that search a directory and retrieve entry attributes. The parameters for these operations are directory entries with a surname of  "smith" and a base  of "o=IBM, c=US".  Also demonstrated,  is the technique to set the properties to use the DSSeries LDAP V3 classes  described  in the section above "Using DSSeries LDAP V3".
  1. This example uses LDAP V3 URL to do a subtree search. Note how the hostname and port of the ldapserver are determined by the URL string passed into the search method:
  2. Properties env = new Properties();
    env.put("java.naming.factory.initial","com.ibm.jndi.LDAPCtxFactory"); env.put("java.naming.factory.url.pkgs","com.ibm.jndi");
    String url="ldap://ldapserver:711/o=IBM,c=US??sub?(sn=smith)";
    try {
        DirContext ctx = new InitialDirContext(defProp);
        NamingEnumeration results = ctx.search(url);
    } catch (NamingException e) {
        System.err.println("Search example failed "+e.getMessage());
    }

    To  return  phone number attributes only,  change the search url to:
        ldap://ldapserver:711/o=IBM,c=US?telephonenumber?sub?(sn=smith)
     

  3. This example does the same search as above; but doesn't use aURL to do the search. Instead the search filter, base and contraints are built explicitly and passed into the method:
  4. Properties env = new Properties();
    env.put("java.naming.factory.initial","com.ibm.jndi.LDAPCtxFactory");
    env.put("java.naming.factory.url.pkgs","com.ibm.jndi");
    env.put("java.naming.provider.url","ldap://ldapserver");
    String base="ou=IBM,c=US";
    String filter="(sn=smith");
    SearchControls constraints = new SearchControls();
    constraints.setSearchScope(SearchControls.SUBTREE_SCOPE);
    try {

      DirContext ctx = new InitialDirContext(defProp);
      NamingEnumeration results = ctx.search(base,filter,constraints);
    } catch (NamingException e) {
      System.err.println("Search example failed "+e.getMessage());
    }

    Note how the special JNDI property "java.naming.provider.url" must be set to tell
    DSSeries LDAP V3  what server and port  to use. To return only the telephone
    numbers attributes, add the following code to #2 example:
     

      String attr[]=new String[1];
      attr[0]="telephonenumber";
      constraints.setReturningAttributes(attr);
       
  5. The next example,  uses  a LDAP URL to return all the attributes for the LDAP distinguished name

  6. "cn=bill smith, o=IBM, c=US":

    Properties env = new Properties();
    env.put("java.naming.factory.initial","com.ibm.jndi.LDAPCtxFactory"); env.put("java.naming.factory.url.pkgs","com.ibm.jndi");
    String url="ldap://ldapserver/cn=bill smith,o=IBM,c=US";
    try {

      DirContext ctx = new InitialDirContext(defProp);
      Attributes results = ctx.getAttributes(url);
    } catch (NamingException e) {
        System.err.println("Search example failed "+e.getMessage());
    }

    To return the entries  telephone number and roomnumber, change the URL to:
        ldap://ldapserver/cn=bill smith,o=IBM,c=US?roomnumber,telephonenumber

 Adding and Deleting

JNDI makes it easy to add and delete entries in a directory. Since most LDAP servers should
require authentication for changes, this section also illustrates passing authentication information into DSSeries LDAP V3. We assume that the LDAP system administrator has password protected write
access to the data.
  1. This example adds an entry with the attributes  roomnumber and telephonenumber to the directory

  2.  ldapserver listening at port 711:

    Properties env = new Properties();
    env.put("java.naming.factory.initial","com.ibm.jndi.LDAPCtxFactory");
    env.put("java.naming.factory.url.pkgs","com.ibm.jndi");
    env.put("java.naming.provider.url","ldap://ldapserver:711");
    env.put("java.naming.security.principal","cn=Directory Manager, o=IBM, c=US");
    env.put("java.naming.security.credentials","dirpassword");
    Attributes atSet = new Attributes();
    Attribute objClasses = new Attribute("objectclass");
    objClasses.add("person");
    objClasses.add("organizationalPerson");
    objClasses.add("inetOrgPerson");
    Attribute roomNum = new Attribute("roomnumber", "2000");
    Attribute teleNum = new Attribute("telephonenumber", "1-800-use-LDAP");
    atSet.add(roomNum);
    atSet.add(teleNum);
    String name="cn=mary smith,o=IBM,c=US";
    try {

      DirContext ctx = new InitialDirContext(defProp);
      ctx.createSubcontext(name, atSet);
    } catch (NameAlreadyBoundException nabe) {
      System.out.println("createSubContext: " + name + " "+  nabe.getMessage());
    } catch (NamingException e) {
      System.err.println("createSubContext: " + name + " "+  e.getMessage());
    }
     
  3. This example deletes the above entry:
  4. Properties env = new Properties();
    env.put("java.naming.factory.initial","com.ibm.jndi.LDAPCtxFactory");
    env.put("java.naming.factory.url.pkgs","com.ibm.jndi");
    env.put("java.naming.provider.url","ldap://ldapserver:711");
    env.put("java.naming.security.principal","cn=Directory Manager, o=IBM, c=US");
    env.put("java.naming.security.credentials","dirpassword");
    String name="cn=mary smith,o=IBM,c=US";
    try {

      DirContext ctx = new InitialDirContext(defProp);
      ctx.destroySubcontext(name);
    } catch (NameNotFoundException nnfe) {
      System.out.println( "destroySubcontext: " + name + nnfe.getMessage());
    } catch (NamingException ne) {
      System.err.println("destroySubcontext: "+name+" " + ne.getMessage());
    }

     

  5.  This example deletes the same entry; but uses a URL passed into the destroyContext method to define the server, port and entry to delete:
  6. Properties env = new Properties();
    env.put("java.naming.factory.initial","com.ibm.jndi.LDAPCtxFactory");
    env.put("java.naming.factory.url.pkgs","com.ibm.jndi");
    env.put("java.naming.security.principal","cn=Directory Manager, o=IBM, c=US");
    env.put("java.naming.security.credentials","dirpassword");
    String url="ldap://ldapserver:711/cn=mary smith,o=IBM,c=US";
    try {

      DirContext ctx = new InitialDirContext(defProp);
      ctx.destroySubcontext(url);
    } catch (NameNotFoundException nnfe) {
      System.out.println( "destroySubcontext: " + name + nnfe.getMessage());
    } catch (NamingException ne) {
      System.err.println("destroySubcontext: "+name+" " + ne.getMessage());
    }
     

Modifying  Attributes

DSSeries LDAP V3 can modify, create or remove attributes from directory entries.
  1. This example will replace will smith's surname,  add his room number, and delete his

  2.  phone number:

    Properties env = new Properties();
    env.put("java.naming.factory.initial","com.ibm.jndi.LDAPCtxFactory");
    env.put("java.naming.factory.url.pkgs","com.ibm.jndi");
    env.put("java.naming.security.principal","cn=Directory Manager, o=IBM, c=US");
    env.put("java.naming.security.credentials","dirpassword");
    String url="ldap://ldapserver:711/cn=will smith,o=IBM,c=US";
    ModificationItem[] mods

      = new ModificationItem[]();
    Attribute modAdd = new Attribute("roomnumber", "5000");
    mods.append(DirContext.ADD_ATTRIBUTE, modAdd);
    Attribute modReplace = new Attribute("sn", "smith");
    mods.append(DirContext.REPLACE_ATTRIBUTE, modReplace);
    Attribute modDelete = new Attribute("telephonenumber", "456-7777");
    mods.append(DirContext.REMOVE_ATTRIBUTE, modDelete);
    try {
      DirContext ctx = new InitialDirContext(defProp);
      ctx.modifyAttributes(url, mods);
    } catch (NamingException ne) {
      System.err.println("modify failed" + ne.getMessage());
    }
     

Renaming a Directory Entry

LDAP V3 allows directory entries to rename the leftmost, or least significant component (relative distinguished name) of a directory entry. For example, you could rename "cn=bill smith,o=IBM,c=US" to "cn=william smith,o=IBM,c=US".
  1.  This example renames a directory entry using a URL to define host, port and entry to modify:
             Properties env = new Properties();
             env.put("java.naming.factory.initial","com.ibm.jndi.LDAPCtxFactory");
             env.put("java.naming.factory.url.pkgs","com.ibm.jndi");
             env.put("java.naming.security.principal","cn=Directory Manager, o=IBM, c=US");
             env.put("java.naming.security.credentials","dirpassword");
             String oldname="ldap://ldapserver:711/cn=bill smith,o=IBM,c=US";
             String newname="cn=william smith";

            try {
                  DirContext ctx = new InitialDirContext(defProp);
                  ctx.rename(oldname, newname);
            }   catch (NamingException ne) {
                  System.err.println("rename("rename: " + ne.getMessage());
            }
 

Browsing a Directory Hierarchy

Dsseries JNDI can be used to browse and  maneuver around a directory hierarchy.  Suppose you wanted to browse the entries of the human resources department at IBM. The following example will:
  1. gets all of the relative distinguished names under "ou=hr,o=IBM,c=US" by using the list method
  2. enumerate through them looking for "cn=mary smith",
  3. get a context for  "cn=mary smith"
  4. returns her room and telephone numbers
  5. NamingEnumeration listResults=null;
    Properties env = new Properties();
    env.put("java.naming.factory.initial","com.ibm.jndi.LDAPCtxFactory");
    env.put("java.naming.factory.url.pkgs","com.ibm.jndi");
    String hrUrl="ldap://ldapserver:711/ou=hr,o=IBM,c=US";
    String rdn="cn=mary smith";
    try {

      DirContext ctx = new InitialDirContext(defProp);
      listResults=ctx.list(hrUrl);
      while (listResults.hasMore()) {
        NameClassPair ncp=listResults.next();
        String thisName=ncp.getName();
        if(thisName.equals(rdn)) {
          DirContext ctx2=(DirContext)ctx.lookup(rdn);
          String attrs[]=new String[2];
          attrs[0]="telephonenumber";
          attrs[1]="roomnumber";
          Attributes rdnRes=ctx2.getAttributes("",attrs);
        }
      }
    }   catch (NamingException ne) {
      System.err.println("browse error " + ne.getMessage());
    }
Everything should look familar except using the null string to return the attributes for
ctx2. The empty name tells JNDI to refer to the current context for the operation. Also, note how the
lookup of the rdn is relative to the context for "ou=hr,o=IBM,c=US".
 

Handling Binary Attributes

LDAP servers can return binary objects. Examples of a binary object a user might want to
read from LDAP are Jpeg photos or X.509 certificates. LDAP V3 defines the special attribute description
option "binary" that tell LDAP V3 servers to return the attribute in binary format. This does not affect
how the object is stored by  the server and only tells the server to return the data in a binary format.
LDAP V2 has no such option and will return binary data if the attribute is declared binary in the schema.

This example shows how to read a binary entry from a LDAP V3 server. You should note the binary option
after the attribute name "jpegphoto" in the URL:
 

To do a V2 search, change the URL to: This example shows how to write a binary attribute to a server, we assume the method
readPhotoFromDisk reads in a jpeg image file and returns it in a byte array. We will add the
attribute to the entry:
 

URL Processing

Because of the DSSeries LDAP V3's flexibility, it's possible to have conflicts between URLs and JNDI
properties. Consider the following:
   
Note that there is a conflict between the property "java.naming.provider.url" and the URL passed to the modifyAttributes method. In cases such as this, the URL passed into the method takes priority. The
search would be performed on ldapserver2, at port 1200.

It is possible to get a context relative to some part of the directory hierarchy; but do an LDAP operation on a totally different machine and base:
 

In the above  example, the context is bound to "o=Ace Industry, c=US" on server ldapserver and at port 711;
but the attributes received are from ldapserver2, port 1200, and entry "cn=bill smith,o=IBM,c=US".
 

Property Manipulation

Several methods exist to manipulate  environment properties. You can examine, set, add
properties to, and delete properties from a context's properties. The example below demonstrate
using those methods to change a context's default properties. First we will get a context's
setting, than add  ("add.this","to.the.environment") to the context's settings,  finally we  remove
the setting "add.this". Attempts to delete nonexistent properties are ignored.  Attempts to add properties that exist already
over write them.
 

Referrals

A LDAP V3 server can  return an array of referral URLs  when it receives a request for an operation
against a directory hierarchy the server does not serve.  The LDAP server administrator is responsible for configuring the server to respond with referrals.

Referral Looping

When DSSeries LDAP V3 receives a referral from a server, it attempts to perform the LDAP operation on the new server(s). It is possible for that server(s)  to return an array of referral URLs. Each new set of referrals is called a "referral hop"; DSSeries LDAP V3 will attempt ten referral hops to satisfy an request. In addition, it's possible for LDAP servers to return referrals to each other (referral looping). DSSeries LDAP V3 detects when servers are configured to generate referral loops, and will avoid referral looping between servers. It's also possible to be referred from a LDAP V3 server to a LDAP V2 server,  DSSeries LDAP V3 will first attempt to perform the operation using LDAP V3, if that fails, step down to LDAP V2 and try the operation.

Referral Authentication

At this time, all referral operations will be attempted using anonymous binds.  In the future,  the LDAP V3 URL extension mechanism could  provide binding information for referrals, as well as other methods of providing referral authentication  currently being developed for DSSeries LDAP V3.

Referral Performance

LDAP V3 referrals are expensive operations, developers should consider if chasing referrals automatically is required. Below are some of the referral chasing issues we' ve experienced:

Referral Exceptions

Setting  JNDI property "java.naming.referral" to follow  tells DSSeries LDAP V3 to automatically chase LDAP V3 referrals. They will throw a referral exception if the property is set to  throw and a referral is received.  Finally, if the property is set to ignore, the referral will be ignored. If the property is not set, DSSeries LDAP V3 automatically chases referrals. Below is an example of using the "java.naming.referral"  property to automatically follow referrals:
  The next example sets the property to throw, catches  the referral exception and exits. See the SearchRef.java file (ldaegs/ldap directory) for more robust example.
  Set the property it ignore; if the referral should be ignored.

Miscellaneous

When you only need to talk to LDAP V2 servers;  the special property "ldap.version" should be set to 2. This avoids having DSSeries LDAP V3 first try connecting at V3 and then stepping down to V2 when a protocol error is
received. LDAP V3 allows for servers to return data without doing a bind first. To tell DSSeries LDAP V3 to attempt an LDAP operation without binding first, set the special property "ldap.noBind" to true.  When servers receive an LDAP operation with no bind, they treat the operation as an anonymous bind. The default is try a LDAP bind first (ldap.noBind=false).
  Searches and  getAttributes are excellent candidates for no bind operations, since they are read only operations and are more likely to succeed with anonymous privileges.  Currently DSSeries LDAP V3 will  not automatically retry a bind at LDAP V2  if a noBind LDAP V3 operation fails.

The java.naming.batchsize property can be used to limit the amount of entries returned from a search.
 

The above code tells the LDAP server to only return 10 results. It's possible to have a conflict between explicitly setting the batchsize via the Search Controls setCountSize() method and the implicitly setting it via the property. When there is a conflict the explicit setting takes precedence.
  In this example the count limit is  set to 100.

Properties Not Implemented

Currently the following JNDI properties are currently not implemented: In addition the java.naming.security.authentication supports only none and simple.