Mozilla LDAP SDK Programmer's Guide/Searching the Directory With LDAP Java SDK

From MozillaWiki
Jump to: navigation, search

This section explains how to use the LDAP Java classes to search the directory to retrieve entries. The chapter also describes how to get attributes and attribute values from an entry.

Searching With the LDAP Java Classes

In LDAP Java SDK, searches are represented by objects of the following classes:

  • You can send a search request by invoking the search method of the LDAPConnection object.
  • You can specify a set of search constraints by using an LDAPSearchConstraints object. The constraints can specify the maximum number of results to return. The constraints can also specify the maximum amount of time that is allowed for a search.
  • You can specify different parts of the search criteria in separate arguments. Alternatively, you can construct an LDAPUrl object to specify the search criteria.
  • You can search for a single entry by invoking the read method of the LDAPConnection object.
  • The server returns the search results to the LDAP Java classes, which represents the results as an LDAPSearchResults object.

Sending a Search Request With LDAP Java SDK

To search the directory, use the search method of the LDAPConnection object. The search results are returned in the form of an LDAPSearchResults object.

public LDAPSearchResults search(String base, int scope,
    String filter, String attrs[], boolean attrsOnly,
    LDAPSearchConstraints cons) throws LDAPException

You need to specify the following parameters as arguments to the search method.

  • base: Specifies the base DN, which is the entry on and under which the search is carried out.
    For example, when searching entries with DNs such as uid=bjensen,ou=People,dc=example,dc=com, the base could be ou=People,dc=example,dc=com or dc=example,dc=com.
  • scope: Specifies the scope of the search.
    You can adjust the scope of the search to examine only the entry identified by the base, only those entries one level down the tree from the base, or the entire subtree underneath the base.
  • filter: Specifies what to search for.
    A search filter specifies what search results to return. The filter meaning can be simple, such as “find entries where the last name is Jensen”. The filter meaning can also be complex, such as “find entries that belong to Dept. #17 and with first names that start with the letter F.”
  • attrs or attrsOnly: Specify the entry attributes to retrieve.
    For example, you can use attrs to retrieve only email addresses and phone numbers. Alternatively, you can set up a search to return all attributes in an entry. You can also specify to return only the names of attributes, not the values, by setting attrsOnly to true.
  • cons: Specifies constraints to apply to the search when you do not want to use the default constraints.

The following figure illustrates how search criteria work.

Hierarchy of Entries in a Directory

You can also specify the criteria in the form of an LDAP URL. An LDAP URL allows you to specify the host name and port number of the LDAP server that you want to search. To search a different LDAP server than the server you are connected to, you can invoke the search method. You then specify an LDAP URL in the form of an LDAPUrl object. See LDAP URLs With Directory SDK for Java for details.

Specifying the Base DN and Scope

When sending a search request, you need to specify the base DN and scope of the search to identify the entries that you want searched. The base DN is the DN of the entry that serves as the starting point of the search.

To specify the scope of the search, you pass one of the following values as the scope parameter:

  • LDAPv3.SCOPE_SUB — Search the base entry and all entries at all levels under the base entry.
    Subtree scope applies to everything below the base DN.
  • LDAPv3.SCOPE_ONE — Search all entries at one level under the base entry.
    One level scope applies to all entries just below the base DN.
    The base entry is not included in the search. Use this setting if you just want a list of the entries under a given entry.
  • LDAPv3.SCOPE_BASE — Search only the base entry.
    Base scope applies only to the base DN entry.
    Use this setting if you want to read the attributes of only the base entry.

Specifying a Search Filter

When you search the directory, you use a search filter to define the search. Here is the basic syntax for a search filter:

(''attribute'' ''operator'' ''value'')

Here is a simple example of a search filter:

(cn=Barbara Jensen)

In this example, cn is the attribute. = is the operator. Barbara Jensen is the value. The filter finds entries with the common name Barbara Jensen.

Valid attributes that you can use in your search filter are provided in the documentation for the LDAP server.

Following are descriptions of valid operators for search filters, and example filters that use the operators.

  • =: Return entries whose attributes are equal to the value provided.
    For example, the following filter matches Barbara Jensen's entry:
(cn=Barbara Jensen)
  • >=: Return entries whose attributes are greater than or equal to the value provided.
    For example, the following filter matches Barbara Jensen's entry and entries for people with surnames following Jensen in alphabetic order, such as entries with sn=Seuss and sn=Zhivago:
(sn>=jensen)
  • <=: Return entries whose attributes are less than or equal to the value provided.
    For example, the following filter matches Barbara Jensen's entry and entries for people with surnames that precede Jensen in alphabetic order, such as entries with sn=Anderton and sn=Cubbins:
(sn<=jensen)
  • =*: Return entries that have a value set for the attribute (presence).
    For example, the following filter matches all entries that have a value for the surname:
(sn=*)
  • ~=: Return entries whose attribute value approximately matches the specified value, such as the value sounds like the specified value.
    For example, the following filter matches all entries with values for surname that sound like Jensen, such as Barbara Jensen's entry, but also Emanuel Johnson's entry:
(sn~=jensen)

With Boolean operators and with parentheses, you can combine different sets of conditions. Here is the syntax for combining search filters:

(''boolean''(''filter1'')(''filter2'')(...))

Following are descriptions of the valid boolean operators.

  • &</tt: Return entries that match all specified filters.
  • <tt>|: Return entries that match one or more of the specified filters.
  • !: Return entries that do not match the specified filter.
    This operator is unary because you can apply the operator only to a single set of results. In other words, to specify “entries that match neither filter1 nor filter2,” use the syntax:
(!(|(''filter1'')(''filter2'')))

You can also include wildcard characters to search for entries that start with, contain, or end with a given value. For example, you can use the following filter to search for all entries with first names that begin with the letter F:

(givenName=F*)

Specifying the Attributes to Retrieve

With the attrs parameter, you can retrieve all attributes in entries returned by the search. Alternatively, you can specify the attributes that you want returned in the search results. For example, you can specify to return the attributes in one of the following ways:

  • To return selected attributes, pass an array of the attribute names as the attrs parameter. For example, to return only email addresses and phone numbers, pass the array {"mail", "telephoneNumber"} as the attrs parameter.
  • To return all attributes in an entry, pass null as the attrs parameter.
  • To return no attributes from an entry, pass LDAPv3.NO_ATTRS as the attrs parameter.

You might plan to sort the results on your client as described in Sorting the Search Results With Directory SDK for Java. Return the attributes that you plan to use for sorting.

For example, if you plan to sort by email address, make sure that the mail attribute is returned in the search results.

Some attributes are used by servers for administering the directory. For example, the creatorsName attribute specifies the DN of the user who added the entry. These attributes are called operational attributes.

Servers do not normally return operational attributes in search results unless you specify the attributes by name. For example, if you pass null as the attrs parameter to retrieve all of the attributes in entries found by the search, the operational attribute creatorsName is not returned to your client. You need to explicitly specify the creatorsName attribute in the attrs parameter.

To return all attributes in an entry with selected operational attributes, pass a string array containing LDAPv3.ALL_USER_ATTRS, and also the names of the operational attributes as the attrs parameter.

Following are a few operational attributes and a description of what each attribute contains.

  • createTimestamp: The time when the entry was added to the directory.
  • modifyTimestamp: The time when the entry was last modified.
  • creatorsName: Distinguished name (DN) of the user who added the entry to the directory.
  • modifiersName: DN of the user who last modified the entry.
  • subschemaSubentry: DN of the subschema entry, that controls the schema for this entry.

Setting Search Preferences

For a given search, you can apply a set of preferences that determine how the search is performed. For example, you can specify the maximum number of results to be returned or the maximum amount of time to wait for a search. The LDAPSearchConstraints class represents a set of search constraints. The methods of this class allow you to get and set the constraints.

Setting Preferences for All Searches

The LDAPConnection object, which represents a connection to the LDAP server, is associated with a default set of search constraints. These constraints apply to all searches that you perform over the connection.

  • To get the default set of search constraints for the connection, you can use the getSearchConstraints method.
  • To get or set any of the search constraints individually, you can use the getOption method and the setOption method.

For example, if you want to specify the maximum number of results returned, you can set this constraint for the connection:

LDAPConnection ld = new LDAPConnection();
ld.connect("ldap.example.com", LDAPv3.DEFAULT_PORT);
ld.setOption(LDAPv3.SIZELIMIT, new Integer(100));

Overriding Preferences for Individual Searches

To override the default set of search constraints for a given search request, construct your own LDAPSearchConstraints object. Pass the object to the search method of the LDAPConnection object.

You can also modify a copy of the existing search constraints. Pass the modified set of constraints to the search method. Invoke the getSearchConstraints method of the LDAPConnection object to get the default set of constraints for that connection.

Then invoke the clone method of the LDAPSearchConstraints object to make a copy of the set that you can then modify.

Configuring the Search to Wait for All Results

By default, the search method of the LDAPConnection object does not block until all results are received. Instead, the search method returns as soon as one of the results has been received.

If you want the search method to block until all results are received, you can do one of the following:

  • Use the setOption method of the LDAPConnection object to set the LDAPv3.BATCHSIZE preference to 0.
  • Pass a 0 to the setBatchSize method of the LDAPSearchConstraints object to change the behavior for a particular set of search constraints.

Whether waiting for one or all results of the search method, you still need to invoke the next method of the returned LDAPSearchResults object to retrieve each individual result.

Setting Size and Time Limits

By default, when you search the directory from a client that you built with LDAP Java SDK, the maximum number of entries to return is set to 1000. No maximum time limit is set for waiting on an operation to complete.

To change these default values, you can do one of the following:

  • Use the setOption method of the LDAPConnection object to set the LDAPv3.SIZELIMIT and LDAPv3.TIMELIMIT preferences.
  • Use the setMaxResults method and the setTimeLimit method of the LDAPSearchConstraints object to change the behavior for a particular set of search constraints.

When you set the size limit or time limit, you might cause an LDAPException to be returned. The exception is returned when the limit is exceeded.

  • If the size limit is exceeded, the server returns an LDAPException.SIZE_LIMIT_EXCEEDED result code.
  • If the time limit is exceeded, the server returns an LDAPException.TIME_LIMIT_EXCEEDED result code.

Search Request Example

The following section of code searches for all entries with surname Jensen. The search retrieves the names and values of the cn, mail, and telephoneNumber attributes.

LDAPConnection ld = null;
try {
    /* Create a new LDAPConnection object. */
    ld = new LDAPConnection();
    /* Connect and bind to the server. */
    String HOSTNAME = "localhost";
    ld.connect(HOSTNAME, LDAPv3.DEFAULT_PORT, null, null);
    /* Specify the search criteria. */
    String baseDN = "dc=example,dc=com";
    int searchScope = LDAPv3.SCOPE_SUB;
    String searchFilter = "(sn=Jensen)";
    String getAttrs[] = {"cn", "mail", "telephoneNumber"};
    /* Send the search request. */
    LDAPSearchResults res = ld.search(baseDN, searchScope,
    searchFilter, getAttrs, false);
} catch(LDAPException e) {
    System.out.println("Error: " + e.toString);
}

Getting the Search Results With LDAP Java SDK

When you invoke the search method of an LDAPConnection object to search the directory, the method returns the search results in the form of an LDAPSearchResults object. The search results consist of an enumeration of entries, which are represented by LDAPEntry objects. The search results can also include smart referrals, also known as search references, and exceptions.

Each entry contains a set of attributes, which are represented by LDAPAttributeSet objects. Individual attributes are represented by LDAPAttribute objects. Each attribute has a set of values that you can get.

The following figure illustrates the relationship between entries, attributes, values, and search results.

Relationship between entries, attributes, values, and search results

Getting Entries

The LDAPSearchResults object represents the results of the search. These results can include entries found by the search, search references, and result codes. Your LDAP client can receive an ADMIN_LIMIT_EXCEEDED, TIME_LIMIT_EXCEEDED, or SIZE_LIMIT_EXCEEDED result code from the server. When the result code is received, LDAP Java SDK adds an exception for this result code to the search results.

To get entries from the LDAPSearchResults object, you can either invoke the next method or the nextElement method.

  • When you invoke the next method, if the next item in the search results is an entry, the method returns an LDAPEntry object.

If the next item is a search reference, one of the following can occur:

  • If referrals are not followed automatically, an LDAPReferralException is returned. The exception is also returned if the referral hop limit is exceeded.
  • The LDAP Java classes follow the referral when two conditions are fulfilled. Referrals must be followed automatically, the referral hop limit must not be exceeded.
    The classes also retrieve the entry for you. The method creates a new connection to the server that is specified in the referral and attempts to retrieve the entry from that server. See Handling Referrals With Directory SDK for Java for more information about referrals and search references. If the next item is an LDAP result code such as ADMIN_LIMIT_EXCEEDED, TIME_LIMIT_EXCEEDED, or SIZE_LIMIT_EXCEEDED, the LDAP Java classes return an LDAPException.
  • When you invoke the nextElement method, the method returns an object that you must cast. The object is an LDAPEntry object, an LDAPReferralException, or an LDAPException.

As you iterate through the search results, you can invoke the hasMoreElements method to determine if you have reached the end of the search results.

LDAPConnection ld = null;
try {
    /* Create a new LDAPConnection object. */
    ld = new LDAPConnection();

    /* Set up parameters for the search request... */

    /* Send the search request. */
    LDAPSearchResults res = ld.search(baseDN, searchScope,
        searchFilter, getAttrs, false);

    /* Iterate through the results until finished. */
    while (res.hasMoreElements()) {

        /* Get the next entry in the results. */
        LDAPEntry findEntry = null;
        try {
            findEntry = res.next();

        /* If it is a referral, print the LDAP URLs. */
        } catch (LDAPReferralException e) {
            System.out.println("Search references: ");
            LDAPUrl refUrls[] = e.getURLs();
            for (int i=0; i  refUrls.length; i++) {
                System.out.println("\t" + refUrls[i].getUrl());
            }
            continue;
        } catch (LDAPException e) {
            System.out.println("Error: " + e.toString());
            continue;
        }
        /* Do something with the entry... */
    }
} catch (LDAPException e) {
    /* Handle exceptions arising outside the search... */
}

Getting Distinguished Names

To get the distinguished name of an LDAPEntry object, invoke the getDN method. This method returns a String.

LDAPEntry nextEntry = res.next();
String nextDN = nextEntry.getDN();

Although the netscape.ldap package includes an LDAPDN class, you typically do not construct objects of this class to represent DNs. The LDAPDN class is mainly a utility class that provides methods for manipulating string DNs.

Getting Attributes

To get the set of attributes in an LDAPEntry object, invoke the getAttributeSet method. This method returns an LDAPAttributeSet object.

LDAPEntry nextEntry = res.next();
LDAPAttributeSet entryAttrs = nextEntry.getAttributeSet();

To get individual attributes from an LDAPAttributeSet object, invoke the getAttributes method. This method returns an enumeration of attributes. You can then iterate through the elements in this enumeration to retrieve individual LDAPAttribute objects.

/* Get the set of attributes for an entry. */
LDAPAttributeSet entryAttrs = nextEntry.getAttributeSet();
/* Get an enumeration of those attribute. */
Enumeration enumAttrs = entryAttrs.getAttributes();
/* Loop through the enumeration to get each attribute. */
while (enumAttrs.hasMoreElements()) {
    LDAPAttribute attr = (LDAPAttribute)enumAttrs.nextElement();
    System.out.println("Attribute type: " + attr.getName());
}

To determine the number of attributes in the LDAPAttributeSet object, invoke the size method. You can also retrieve a specific attribute from the entry or from the attribute set.

  • To get a specific attribute from an LDAPEntry object, invoke the getAttribute method.
  • To get a specific attribute from an LDAPAttributeSet object, invoke the getAttribute method.

Both methods return an LDAPAttribute object.

LDAPEntry nextEntry = res.next();
LDAPAttribute anAttr = nextEntry.getAttribute("cn");

Getting Attribute Types and Values

To get the name of an LDAPAttribute object, invoke the getName method.

LDAPAttribute nextAttr = (LDAPAttribute)enumAttrs.nextElement();
String attrName = nextAttr.getName();

To get the values in an LDAPAttribute object, you can use the following methods:

  • To get the String values, invoke the getStringValues method.
  • To get the binary values as byte arrays, invoke the getByteValues method.

Both methods return an enumeration that you can iterate through to retrieve individual results. For example, if an error occurs when you invoke getStringValues, although the values are binary data, the methods return null.

You can also count the number of values in an attribute by invoking the size method of the LDAPAttribute object.

LDAPAttribute nextAttr = (LDAPAttribute)enumAttrs.nextElement();

/* Get and print the attribute name. */
String attrName = nextAttr.getName();
System.out.println("\t" + attrName + ":");

/* Iterate through the attribute's values. */
Enumeration enumVals = nextAttr.getStringValues();
if (enumVals != null) {
    while (enumVals.hasMoreElements()) {
        String nextValue = (String)enumVals.nextElement();
        System.out.println("\t\t" + nextValue);
    }
}

Sorting the Search Results With LDAP Java SDK

With LDAP Java SDK, you can sort the search results in two ways.

  • You can specify that the LDAP server should sort the results before returning the results to your client.
    Send a server-side sort control to the server as described in LDAP Controls With Directory SDK for Java. Server-side sorting might work best if you specify a filter that uses an indexed attribute.
  • After you receive the results from the server, you can sort the results on your client.
    Specify the names of the attributes that you want to use for sorting. You also need to specify whether or not the sorting is done in ascending or descending order.
    You can sort the results on the client by invoking the sort method of the LDAPSearchResults object.

When invoking this method, you need to pass a comparator object, which is an object of a class that implements the LDAPEntryComparator interface. LDAP Java SDK includes an LDAPCompareAttrNames class that implements this interface. This class specifies how entries are compared with each other and sorted.

To construct an LDAPCompareAttrNames object, you need to specify the attributes that you want to use for sorting and, optionally, the sort order. When sorting on the client side, the attributes used for sorting must be returned in the search results. If you are returning only a subset of attributes in the search results, include the attributes that you specify in the LDAPCompareAttrNames constructor. For example, the following section of code sorts first by surname, sn, and then by common name, cn, in ascending order:

LDAPConnection ld = new LDAPConnection();
ld.connect("localhost", LDAPv3.DEFAULT_PORT);
LDAPSearchResults res = ld.search("dc=example,dc=com", LDAPv3.SCOPE_SUB,
    "(objectclass=inetOrgPerson)", null, false);
String[]  sortAttrs = {"sn", "cn"};
boolean[] ascending = {true, true};
res.sort(new LDAPCompareAttrNames(sortAttrs, ascending));

If all search results have not yet been returned, the sort method blocks until all results have been received.

Abandoning a Search With LDAP Java SDK

At any point during a search operation, you can send a request to the server to abandon (cancel) the search. To abandon the search, use the abandon method of the LDAPConnection object. Pass in the LDAPSearchResults object that was returned to you when you first invoked the search method.

Searching the Directory With LDAP Java SDK

The following example prints the values of all attributes in the entries returned by a search.

import netscape.ldap.*;
import java.util.*;
 
public class Search {
    public static void main(String[] args) {
        try {
            UserArgs userArgs = new UserArgs("Search", args, false);
            LDAPConnection ld = new LDAPConnection();
            ld.connect(userArgs.getHost(), userArgs.getPort());
 
            /* search for all entries with surname of Jensen */
            String MY_FILTER = "sn=Jensen";
            String MY_SEARCHBASE = "dc=example,dc=com";
 
            LDAPSearchConstraints cons = ld.getSearchConstraints();
            /* Setting the batchSize to one will cause the result
               enumeration below to block on one result at a time,
               enabling an update of a list or other things as
               results come in. */
            /* This could be set  to 0 in order to get all
               results and to block until then. */
            cons.setBatchSize(1);
            LDAPSearchResults res = ld.search(MY_SEARCHBASE,
                LDAPConnection.SCOPE_SUB, MY_FILTER, null, false, cons);
 
            /* Loop on results until finished */
            while (res.hasMoreElements()) {
                LDAPEntry findEntry = null;
                try {
                    findEntry = res.next();
                } catch (LDAPReferralException e) {
                    System.out.println("Search reference: ");
                    LDAPUrl refUrls[] = e.getURLs();
                    for (int i=0; irefUrls.length; i++) {
                        System.out.println("\t" + refUrls[i].getUrl());
                    }
                    continue;
                } catch (LDAPException e) {
                    System.out.println("Error: " + e.toString());
                    continue;
                }
                System.out.println(findEntry.getDN());
 
                /* Get the attributes of the entry */
                LDAPAttributeSet findAttrs = findEntry.getAttributeSet();
                Enumeration enumAttrs = findAttrs.getAttributes();
                System.out.println("\tAttributes: ");
 
                /* Loop on attributes */
                while (enumAttrs.hasMoreElements()) {
                    LDAPAttribute anAttr =
                        (LDAPAttribute)enumAttrs.nextElement();
                    String attrName = anAttr.getName();
                    System.out.println("\t\t" + attrName);
 
                    /* Loop on values for this attribute */
                    Enumeration enumVals = anAttr.getStringValues();
                    if (enumVals != null) {
                        while (enumVals.hasMoreElements()) {
                            String aVal = (String)enumVals.nextElement();
                            System.out.println("\t\t\t" + aVal);
                        }
                    }
                }
            }
            ld.disconnect();
        } catch(LDAPException e) {
            System.out.println("Error: " + e.toString());
        }
    }
}

Reading an Entry With LDAP Java SDK

To get a single entry from the directory, use the read method of the LDAPConnection object. You can specify the DN of the entry with the attributes that you want to retrieve, instead of retrieving all attributes of the entry. You can also specify an LDAP URL that identifies the entry that you want to retrieve.

To retrieve data from the entry, you can use the same classes with their methods, as described in Getting Attributes and in Getting Attribute Types and Values.

The following example retrieves an entry and prints the values of its attributes.

import netscape.ldap.*;
import java.util.*;
 
public class RdEntry {
    public static void main(String[] args) {
        try {
            UserArgs userArgs = new UserArgs("PasswordPolicy", args, false);
            LDAPConnection ld = new LDAPConnection();
            ld.connect(userArgs.getHost(), userArgs.getPort());
                         
            String ENTRYDN = "uid=bjensen, ou=People, dc=example,dc=com";
 
            /* Read all attributes */
            LDAPEntry findEntry = ld.read(ENTRYDN);
            System.out.println(findEntry.getDN());
 
            /* Get the attributes of the entry */
            LDAPAttributeSet findAttrs = findEntry.getAttributeSet();
            Enumeration enumAttrs = findAttrs.getAttributes();
            System.out.println("\tAttributes: ");
 
            /* Loop on attributes */
            while (enumAttrs.hasMoreElements()) {
                LDAPAttribute anAttr =
                    (LDAPAttribute)enumAttrs.nextElement();
                String attrName = anAttr.getName();
                System.out.println("\t\t" + attrName);
 
                /* Loop on values for this attribute */
                Enumeration enumVals = anAttr.getStringValues();
                if (enumVals != null) {
                    while (enumVals.hasMoreElements()) {
                        String aVal = (String)enumVals.nextElement();
                        System.out.println("\t\t\t" + aVal);
                    }
                }
            }
            ld.disconnect();
        } catch(LDAPException e) {
            System.out.println("Error: " + e.toString());
        }
    }
}

Listing Child Entries With LDAP Java SDK

To retrieve the entries directly beneath a particular entry, set the starting point of the search to the entry. Also, set the scope of the search to LDAPv3.SCOPE_ONE.

Search for child entries

The following code performs a one-level search:

LDAPConnection ld = null;
try {
    ld = new LDAPConnection();
    ld.connect("localhost", LDAPv3.DEFAULT_PORT);

    LDAPSearchResults res = ld.search("dc=example,dc=com", LDAPv3.SCOPE_ONE,
        "(objectclass=*)", null, false );

    /* Loop on results until finished */
    while (res.hasMoreElements()) {
        LDAPEntry findEntry = null;
        try {
            findEntry = res.next();

        /* If the next result is a referral, print the LDAP URLs. */
        } catch (LDAPReferralException e) {
            System.out.println("Search references: ");
            LDAPUrl refUrls[] = e.getURLs();
            for (int i=0; i  refUrls.length; i++) {
                System.out.println("\t" + refUrls[i].getUrl());
            }
            continue;
        } catch ( LDAPException e ) {
            System.out.println("Error: " + e.toString());
            continue;
        }

        /* Print the DN of the entry. */
        System.out.println(findEntry.getDN());

        /* Get the attributes of the entry */
        LDAPAttributeSet findAttrs = findEntry.getAttributeSet();
        Enumeration enumAttrs = findAttrs.getAttributes();
        System.out.println("\tAttributes: ");
        /* Loop on attributes */
        while (enumAttrs.hasMoreElements()) {
            LDAPAttribute anAttr = (LDAPAttribute)enumAttrs.nextElement();
            String attrName = anAttr.getName();
            System.out.println("\t\t" + attrName);

            /* Loop on values for this attribute */
            Enumeration enumVals = anAttr.getStringValues();
            if (enumVals != null) {
                while (enumVals.hasMoreElements()) {
                    String aVal = (String)enumVals.nextElement();
                    System.out.println("\t\t\t" + aVal);
                }
            }
        }
    }
} catch( LDAPException e ) {
    System.out.println("Error: " + e.toString());
}

/* Done, so disconnect. */
if ((ld != null)  ld.isConnected()) {
    try {
        ld.disconnect();
    } catch (LDAPException e) {
    System.out.println("Error: " + e.toString());
}