Spring LDAP

Reference Documentation

Authors

Mattias Arthursson, Ulrik Sandberg

Version 1.1

Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically.

August 2006


Table of Contents

Preface
1. Introduction
1.1. Overview
1.2. Packaging overview
1.3. Package structure
1.3.1. org.springframework.ldap
1.3.2. org.springframework.ldap.support
1.3.3. org.springframework.ldap.support.authentication
1.3.4. org.springframework.ldap.support.filter
1.4. Support
2. Basic Operations
2.1. Search and Lookup Using AttributesMapper
2.2. Building Dynamic Filters
2.3. Building Dynamic Distinguished Names
2.4. Binding and Unbinding
2.4.1. Binding Data
2.4.2. Unbinding Data
2.5. Modifying
2.5.1. Modifying using rebind
2.5.2. Modifying using modifyAttributes
2.6. Sample applications
3. DirObjectFactory and DirContextAdapter
3.1. Introduction
3.2. Search and Lookup Using ContextMapper
3.3. Binding and Modifying Using ContextMapper
3.3.1. Binding
3.3.2. Modifying
3.4. A Complete PersonDao Class
4. Adding Missing Overloaded API Methods
4.1. Implementing Custom Search Methods
4.2. Implementing Other Custom Context Methods
5. Configuration
5.1. ContextSource Configuration
5.1.1. LDAP Server URLs
5.1.2. Authentication
5.1.2.1. Custom Authentication Using Acegi
5.1.2.2. Default Authentication
5.1.3. Pooling
5.1.4. Advanced ContextSource Configuration
5.1.4.1. Alternate ContextFactory
5.1.4.2. Custom DirObjectFactory
5.1.4.3. Custom DirContext Environment Properties
5.2. LdapTemplate Configuration
5.2.1. Ignoring PartialResultExceptions

Preface

The Java Naming and Directory Interface (JNDI) is for LDAP programming what Java Database Connectivity (JDBC) is for SQL programming. There are several similarities between JDBC and JNDI/LDAP (Java LDAP). Despite being two completely different APIs with different pros and cons, they share a number of less flattering characteristics:

  • They require extensive plumbing code, even to perform the simplest of tasks.

  • All resources need to be correctly closed, no matter what happens.

  • Exception handling is difficult.

The above points often lead to massive code duplication in common usages of the APIs. As we all know, code duplication is one of the worst code smells. All in all, it boils down to this: JDBC and LDAP programming in Java are both incredibly dull and repetitive.

Spring JDBC, a part of the Spring framework, provides excellent utilities for simplifying SQL programming. We need a similar framework for Java LDAP programming.

Chapter 1. Introduction

1.1. Overview

Spring-LDAP (http://www.springframework.org/ldap) is a library for simpler LDAP programming in Java, built on the same principles as the JdbcTemplate in Spring JDBC. It completely eliminates the need to worry about creating and closing LdapContext and looping through NamingEnumeration. It also provides a more comprehensive unchecked Exception hierarchy, built on Spring's DataAccessException. As a bonus, it also contains classes for dynamically building LDAP filters and DNs (Distinguished Names).

Consider, for example, a method that should search some storage for all persons and return their names in a list. Using JDBC, we would create a connection and execute a query using a statement. We would then loop over the result set and retrieve the column we want, adding it to a list. In contrast, using Java LDAP, we would create a context and perform a search using a search filter. We would then loop over the resulting naming enumeration and retrieve the attribute we want, adding it to a list.

The traditional way of implementing this person name search method in Java LDAP looks like this:

package com.example.dao;

public class TraditionalPersonDaoImpl implements PersonDao {
   public List getAllPersonNames() {
      Hashtable env = new Hashtable();
      env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
      env.put(Context.PROVIDER_URL, "ldap://localhost:389/dc=example,dc=com");

      DirContext ctx;
      try {
         ctx = new InitialDirContext(env);
      } catch (NamingException e) {
         throw new RuntimeException(e);
      }

      LinkedList list = new LinkedList();
      NamingEnumeration results = null;
      try {
         SearchControls controls = new SearchControls();
         controls.setSearchScope(SearchControls.SUBTREE_SCOPE);
         results = ctx.search("", "(objectclass=person)", controls);

         while (results.hasMore()) {
            SearchResult searchResult = (SearchResult) results.next();
            Attributes attributes = searchResult.getAttributes();
            Attribute attr = attributes.get("cn");
            String cn = (String) attr.get();
            list.add(cn);
         }
      } catch (NameNotFoundException e) {
         // The base context was not found.
         // Just clean up and exit.
      } catch (NamingException e) {
         throw new RuntimeException(e);
      } finally {
         if (results != null) {
            try {
               results.close();
            } catch (Exception e) {
               // Never mind this.
            }
         }
         if (ctx != null) {
            try {
               ctx.close();
            } catch (Exception e) {
               // Never mind this.
            }
         }
      }
      return list;
   }
}

By using the Spring LDAP AttributesMapper, we get the exact same functionality with the following code:

package com.example.dao;

public class PersonDaoImpl implements PersonDao {
   private LdapTemplate ldapTemplate;

   public void setLdapTemplate(LdapTemplate ldapTemplate) {
      this.ldapTemplate = ldapTemplate;
   }

   public List getAllPersonNames() {
      return ldapTemplate.search(
         "", "(objectclass=person)",
         new AttributesMapper() {
            public Object mapFromAttributes(Attributes attrs)
               throws NamingException {
               return attrs.get("cn").get();
            }
         });
   }
}

1.2. Packaging overview

At a minimum, to use Spring LDAP you need:

  • spring-ldap (the spring-ldap library)

  • spring-core (miscellaneous utility classes used internally by the framework)

  • spring-beans (contains interfaces and classes for manipulating Java beans)

  • spring-context (adds the ability for application objects to obtain resources using a consistent API)

  • spring-dao (exception hierarchy enabling sophisticated error handling independent of the data access approach in use)

  • commons-logging (a simple logging facade, used internally)

Set up the required beans in your Spring context file and inject the LdapTemplate to your data-access object:

<beans>
   ...
   <bean id="contextSource" class="org.springframework.ldap.support.LdapContextSource">
      <property name="url" value="ldap://localhost:389" />
      <property name="base" value="dc=example,dc=com" />
      <property name="userName" value="cn=Manager" />
      <property name="password" value="secret" />
   </bean>

   <bean id="ldapTemplate" class="org.springframework.ldap.LdapTemplate">
      <constructor-arg ref="contextSource" />
   </bean>
   ...
</beans>

1.3. Package structure

This section provides an overview of the logical package structure of the Spring LDAP codebase. The dependencies for each package are clearly noted. A package dependency noted as (optional) means that the dependency is needed to compile the package but is optionally needed at runtime (depending on your use of the package). For example, use of Spring LDAP together with Acegi Security entails use of the org.acegisecurity package.

1.3.1. org.springframework.ldap

The ldap package contains the central abstractions of the library. These abstractions include AttributesMapper, ContextSource, and NameClassPairCallbackHandler. This package also contains the central class LdapTemplate.

  • Dependencies: spring-core, spring-beans, spring-context, spring-dao, commons-logging (TBD)

1.3.2. org.springframework.ldap.support

The support package contains supporting implementations of the central interfaces as well as the DirContextAdapter abstraction.

  • Dependencies: ldap (TBD)

1.3.3. org.springframework.ldap.support.authentication

The support.authentication package contains an implementation of the AuthenticationSource interface that can be used with Acegi Security.

  • Dependencies: ldap, acegi-security (optional)

1.3.4. org.springframework.ldap.support.filter

The support.filter package contains the Filter abstraction and several implementations of it.

  • Dependencies: commons-lang

For the exact list of dependencies, see the Spring LDAP Ivy dependency manager descriptor located within the Spring LDAP distribution at spring-ldap/ivy.xml

1.4. Support

Spring LDAP 1.0.3 is supported on Spring 1.2.7 or later.

The community support forum is located at http://forum.springframework.org.

Chapter 2. Basic Operations

2.1. Search and Lookup Using AttributesMapper

In this example we will use an AttributesMapper to easily build a List of all common names of all person objects.

Example 2.1. AttributesMapper that returns a single attribute

package com.example.dao;

public class PersonDaoImpl implements PersonDao {
   private LdapTemplate ldapTemplate;

   public void setLdapTemplate(LdapTemplate ldapTemplate) {
      this.ldapTemplate = ldapTemplate;
   }

   public List getAllPersonNames() {
      return ldapTemplate.search(
         "", "(objectclass=person)",
         new AttributesMapper() {
            public Object mapFromAttributes(Attributes attrs)
               throws NamingException {
               return attrs.get("cn").get();
            }
         });
   }
}

The inline implementation of AttributesMapper just gets the desired attribute value from the Attributes and returns it. Internally, LdapTemplate iterates over all entries found, calling the given AttributesMapper for each entry, and collects the results in a list. The list is then returned by the search method.

Note that the AttributesMapper implementation could easily be modified to return a full Person object:

Example 2.2. AttributesMapper that returns a Person object

package com.example.dao;

public class PersonDaoImpl implements PersonDao {
   private LdapTemplate ldapTemplate;
   ...
   private class PersonAttributesMapper implements AttributesMapper() {
      public Object mapFromAttributes(Attributes attrs) throws NamingException {
         Person person = new Person();
         person.setFullName((String)attrs.get("cn").get());
         person.setLastName((String)attrs.get("sn").get());
         person.setDescription((String)attrs.get("description").get());
         return person;
      }
   }

   public List getAllPersons() {
      return ldapTemplate.search("", "(objectclass=person)", new PersonAttributesMapper();
   }
}

If you have the distinguished name (dn) that identifies an entry, you can retrieve the entry directly, without searching for it. This is called a lookup in Java LDAP. The following example shows how a lookup results in a Person object:

Example 2.3. A lookup resulting in a Person object

package com.example.dao;

public class PersonDaoImpl implements PersonDao {
   private LdapTemplate ldapTemplate;
   ...
   public Person findPerson(String dn) {
      return (Person) ldapTemplate.lookup(dn, new PersonAttributesMapper());
   }
}

This will look up the specified dn and pass the found attributes to the supplied AttributesMapper, in this case resulting in a Person object.

2.2. Building Dynamic Filters

We can build dynamic filters to use in searches, using the classes from the org.springframework.ldap.support.filter package. Let's say that we want the following filter: (&(objectclass=person)(sn=?)), where we want the ? to be replaced with the value of the parameter lastName. This is how we do it using the filter support classes:

Example 2.4. Building a search filter dynamically

package com.example.dao;

public class PersonDaoImpl implements PersonDao {
   private LdapTemplate ldapTemplate;
   ...
   public List getPersonNamesByLastName(String lastName) {
      AndFilter filter = new AndFilter();
      filter.and(new EqualsFilter("objectclass", "person"));
      filter.and(new EqualsFilter("sn", lastName));
      return ldapTemplate.search(
         "", filter.encode(),
         new AttributesMapper() {
            public Object mapFromAttributes(Attributes attrs)
               throws NamingException {
               return attrs.get("cn").get();
            }
         });
   }
}

To perform a wildcard search, it's possible to use the WhitespaceWildcardsFilter:

Example 2.5. Building a wildcard search filter

AndFilter filter = new AndFilter();
filter.and(new EqualsFilter("objectclass", "person"));
filter.and(new WhitespaceWildcardsFilter("cn", cn));

2.3. Building Dynamic Distinguished Names

The standard Name interface represents a generic name, which is basically an ordered sequence of components. The Name interface also provides operations on that sequence; e.g., add or remove. LdapTemplate provides an implementation of the Name interface: DistinguishedName. Using this class will greatly simplify building distinguished names, especially considering the sometimes complex rules regarding escapings and encodings. The following example illustrates how DistinguishedName can be used to dynamically construct a distinguished name:

Example 2.6. Building a distinguished name dynamically

package com.example.dao;

import net.sf.ldaptemplate.support.DistinguishedName;
import javax.naming.Name;

public class PersonDaoImpl implements PersonDao {
   public static final String BASE_DN = "dc=example,dc=com";
   ...
   protected Name buildDn(Person p) {
      DistinguishedName dn = new DistinguishedName(BASE_DN);
      dn.add("c", p.getCountry());
      dn.add("ou", p.getCompany());
      dn.add("cn", p.getFullname());
      return dn;
   }
}

Assuming that a Person has the following attributes:

countrySweden
companySome Company
fullnameSome Person

The code above would then result in the following distinguished name:

cn=Some Person, ou=Some Company, c=Sweden, dc=example, dc=com

In Java 5, there is an implementation of the Name interface: LdapName. If you are in the Java 5 world, you might as well use LdapName. However, you may still use DistinguishedName if you so wish.

2.4. Binding and Unbinding

2.4.1. Binding Data

Inserting data in Java LDAP is called binding. In order to do that, a distinguished name that uniquely identifies the new entry is required. The following example shows how data is bound using LdapTemplate:

Example 2.7. Binding data using Attributes

package com.example.dao;

public class PersonDaoImpl implements PersonDao {
   private LdapTemplate ldapTemplate;
   ...
   public void create(Person p) {
      Name dn = buildDn(p);
      ldapTemplate.bind(dn, null, buildAttributes(p));
   }

   private Attributes buildAttributes(Person p) {
      Attributes attrs = new BasicAttributes();
      BasicAttribute ocattr = new BasicAttribute("objectclass");
      ocattr.add("top");
      ocattr.add("person");
      attrs.put(ocattr);
      attrs.put("cn", "Some Person");
      attrs.put("sn", "Person");
      return attrs;
   }
}

The Attributes building is--while dull and verbose--sufficient for many purposes. It is, however, possible to simplify the binding operation further, which will be described in Chapter 3, DirObjectFactory and DirContextAdapter.

2.4.2. Unbinding Data

Removing data in Java LDAP is called unbinding. A distinguished name (dn) is required to identify the entry, just as in the binding operation. The following example shows how data is unbound using LdapTemplate:

Example 2.8. Unbinding data

package com.example.dao;

public class PersonDaoImpl implements PersonDao {
   private LdapTemplate ldapTemplate;
   ...
   public void delete(Person p) {
      Name dn = buildDn(p);
      ldapTemplate.unbind(dn);
   }
}

2.5. Modifying

In Java LDAP, data can be modified in two ways: either using rebind or modifyAttributes.

2.5.1. Modifying using rebind

A rebind is a very crude way to modify data. It's basically an unbind followed by a bind. It looks like this:

Example 2.9. Modifying using rebind

package com.example.dao;

public class PersonDaoImpl implements PersonDao {
   private LdapTemplate ldapTemplate;
   ...
   public void update(Person p) {
      Name dn = buildDn(p);
      ldapTemplate.rebind(dn, null, buildAttributes(p));
   }
}

2.5.2. Modifying using modifyAttributes

If only the modified attributes should be replaced, there is a method called modifyAttributes that takes an array of modifications:

Example 2.10. Modifying using modifyAttributes

package com.example.dao;

public class PersonDaoImpl implements PersonDao {
   private LdapTemplate ldapTemplate;
   ...
   public void updateDescription(Person p) {
      Name dn = buildDn(p);
      Attribute attr = new BasicAttribute("description", p.getDescription())
      ModificationItem item = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, attr);
      ldapTemplate.modifyAttributes(dn, new ModificationItem[] {item});
   }
}

Building Attributes and ModificationItem arrays is a lot of work, but as you will see in Chapter 3, DirObjectFactory and DirContextAdapter, the update operations can be simplified.

2.6. Sample applications

It is recommended that you review the Spring LDAP sample applications included in the release distribution for best-practice illustrations of the features of this library. A description of each sample is provided below:

  1. spring-ldap-person - the sample demonstrating most features.

  2. spring-ldap-article - the sample application that was written to accompany a java.net article about Spring LDAP.

Chapter 3. DirObjectFactory and DirContextAdapter

3.1. Introduction

A little-known--and probably underestimated--feature of the Java LDAP API is the ability to register a DirObjectFactory to automatically create objects from found contexts. One of the reasons why it is seldom used is that you will need an implementation of DirObjectFactory that creates instances of a meaningful implementation of DirContext. The Spring LDAP library provides the missing pieces: a default implementation of DirContext called DirContextAdapter, and a corresponding implementation of DirObjectFactory called DefaultDirObjectFactory. Used together with DefaultDirObjectFactory, the DirContextAdapter can be a very powerful tool.

3.2. Search and Lookup Using ContextMapper

The DefaultDirObjectFactory is registered with the ContextSource by default, which means that whenever a context is found in the LDAP tree, its Attributes and Distinguished Name (DN) will be used to construct a DirContextAdapter. This enables us to use a ContextMapper instead of an AttributesMapper to transform found values:

Example 3.1. Searching using a ContextMapper

package com.example.dao;

public class PersonDaoImpl implements PersonDao {
   ...
   private static class PersonContextMapper implements ContextMapper {
      public Object mapFromContext(Object ctx) {
         DirContextAdapter context = (DirContextAdapter)ctx;
         Person p = new Person();
         p.setFullName(context.getStringAttribute("cn"));
         p.setLastName(context.getStringAttribute("sn"));
         p.setDescription(context.getStringAttribute("description"));
         return p;
      }
   }

   public Person findByPrimaryKey(
      String name, String company, String country) {
      Name dn = buildDn(name, company, country);
      return ldapTemplate.lookup(dn, new PersonContextMapper());
   }
}

The above code shows that it is possible to retrieve the attributes directly by name, without having to go through the Attributes and BasicAttribute classes.

3.3. Binding and Modifying Using ContextMapper

The DirContextAdapter can also be used to hide the Attributes when binding and modifying data.

3.3.1. Binding

This is an example of an improved implementation of the create DAO method. Compare it with the previous implementation in Section 2.4.1, “Binding Data”.

Example 3.2. Binding using DirContextAdapter

package com.example.dao;

public class PersonDaoImpl implements PersonDao {
   ...
   public void create(Person p) {
      Name dn = buildDn(p);
      DirContextAdapter context = new DirContextAdapter(dn);

      context.setAttributeValues("objectclass", new String[] {"top", "person"});
      context.setAttributeValue("cn", p.getFullname());
      context.setAttributeValue("sn", p.getLastname());
      context.setAttributeValue("description", p.getDescription());

      ldapTemplate.bind(dn, context, null);
   }
}

Note that we use the retrieved DirContextAdapter as the second parameter to bind, which should be a Context. The third parameter is null, since we're not using any Attributes.

3.3.2. Modifying

The code for a rebind would be pretty much identical to Example 3.2, except that the method called would be rebind. However, let's say that you don't want to remove and re-create the entry, but instead update only the attributes that have changed. The DirContextAdapter has the ability to keep track of its modified attributes. The following example takes advantage of this feature:

Example 3.3. Modifying using DirContextAdapter

package com.example.dao;

public class PersonDaoImpl implements PersonDao {
   ...
   public void update(Person p) {
      Name dn = buildDn(p);
      DirContextAdapter context = (DirContextAdapter)ldapTemplate.lookup(dn);

      context.setAttributeValues("objectclass", new String[] {"top", "person"});
      context.setAttributeValue("cn", p.getFullname());
      context.setAttributeValue("sn", p.getLastname());
      context.setAttributeValue("description", p.getDescription());

      ldapTemplate.modifyAttributes(dn, context.getModificationItems());
   }
}

The observant reader will see that we have duplicated code in the create and update methods. This code maps from a domain object to a context. It can be extracted to a separate method:

Example 3.4. Binding and modifying using DirContextAdapter

package com.example.dao;

public class PersonDaoImpl implements PersonDao {
   private LdapTemplate ldapTemplate;

   ...
   public void create(Person p) {
      Name dn = buildDn(p);
      DirContextAdapter context = new DirContextAdapter(dn);
      mapToContext(p, context);
      ldapTemplate.bind(dn, context, null);
   }

   public void update(Person p) {
      Name dn = buildDn(p);
      DirContextAdapter context = (DirContextAdapter)ldapTemplate.lookup(dn);
      mapToContext(person, context);
      ldapTemplate.modifyAttributes(dn, context.getModificationItems());
   }

   protected void mapToContext (Person p, DirContextAdapter context) {
      context.setAttributeValues("objectclass", new String[] {"top", "person"});
      context.setAttributeValue("cn", p.getFullName());
      context.setAttributeValue("sn", p.getLastName());
      context.setAttributeValue("description", p.getDescription());
   }
}

3.4. A Complete PersonDao Class

To illustrate the power of Spring LDAP, here is a complete Person DAO implementation for LDAP in just 68 lines:

Example 3.5. A complete PersonDao class

package com.example.dao;

import java.util.List;

import javax.naming.Name;
import javax.naming.NamingException;
import javax.naming.directory.Attributes;

import net.sf.ldaptemplate.AttributesMapper;
import net.sf.ldaptemplate.ContextMapper;
import net.sf.ldaptemplate.LdapTemplate;
import net.sf.ldaptemplate.support.DirContextAdapter;
import net.sf.ldaptemplate.support.DistinguishedName;
import net.sf.ldaptemplate.support.filter.EqualsFilter;

public class PersonDaoImpl implements PersonDao {
   private LdapTemplate ldapTemplate;

   public void setLdapTemplate(LdapTemplate ldapTemplate) {
      this.ldapTemplate = ldapTemplate;
   }

   public void create(Person person) {
      DirContextAdapter context = new DirContextAdapter();
      mapToContext(person, context);
      ldapTemplate.bind(buildDn(person), context, null);
   }

   public void update(Person person) {
      Name dn = buildDn(person);
      DirContextAdapter context = (DirContextAdapter)ldapTemplate.lookup(dn);
      mapToContext(person, context);
      ldapTemplate.modifyAttributes(dn, context.getModificationItems());
   }

   public void delete(Person person) {
      ldapTemplate.unbind(buildDn(person));
   }

   public Person findByPrimaryKey(String name, String company, String country) {
      Name dn = buildDn(name, company, country);
      return (Person) ldapTemplate.lookup(dn, getContextMapper());
   }

   public List findAll() {
      EqualsFilter filter = new EqualsFilter("objectclass", "person");
      return ldapTemplate.search(DistinguishedName.EMPTY_PATH, filter.encode(), getContextMapper());
   }

   protected ContextMapper getContextMapper() {
      return new PersonContextMapper();
   }

   protected Name buildDn(Person person) {
      return buildDn(person.getFullname(), person.getCompany(), person.getCountry());
   }

   protected Name buildDn(String fullname, String company, String country) {
      DistinguishedName dn = new DistinguishedName();
      dn.add("c", country);
      dn.add("ou", company);
      dn.add("cn", fullname);
      return dn;
   }

   protected void mapToContext(Person person, DirContextAdapter context) {
      context.setAttributeValues("objectclass", new String[] {"top", "person"});
      context.setAttributeValue("cn", person.getFullName());
      context.setAttributeValue("sn", person.getLastName());
      context.setAttributeValue("description", person.getDescription());
   }

   private static class PersonContextMapper implements ContextMapper {
      public Object mapFromContext(Object ctx) {
         DirContextAdapter context = (DirContextAdapter)ctx;
         Person person = new Person();
         person.setFullName(context.getStringAttribute("cn"));
         person.setLastName(context.getStringAttribute("sn"));
         person.setDescription(context.getStringAttribute("description"));
         return person;
      }
   }
}
[Note]Note

In several cases the Distinguished Name (DN) of an object is constructed using properties of the object. E.g. in the above example, the country, company and full name of the Person are used in the DN, which means that updating any of these properties will actually require moving the entry in the LDAP tree using the rename() operation in addition to updating the Attribute values. Since this is highly implementation specific this is something you'll need to keep track of yourself - either by disallowing the user to change these properties or performing the rename() operation in your update() method if needed.

Chapter 4. Adding Missing Overloaded API Methods

4.1. Implementing Custom Search Methods

While LdapTemplate contains several overloaded versions of the most common operations in DirContext, we have not provided an alternative for each and every method signature, mostly because there are so many of them. We have, however, provided a means to call whichever overloaded method you want. Let's say, for example, that you want to use the following method:

NamingEnumeration search(Name name, String filterExpr, Object[] filterArgs, SearchControls ctls)

The way to do this is to use a custom SearchExecutor implementation:

public interface SearchExecutor {
   public NamingEnumeration executeSearch(DirContext ctx) throws NamingException;
}

Example 4.1. A custom search method using SearchExecutor and AttributesMapper

package com.example.dao;

public class PersonDaoImpl implements PersonDao {
   ...
   public List search(final Name base, final String filter, final String[] params,
         final SearchControls ctls) {
      SearchExecutor executor = new SearchExecutor() {
         public NamingEnumeration executeSearch(DirContext ctx) {
            return ctx.search(base, filter, params, ctls);
         }
      };

      NameClassPairCallbackHandler handler =
         ldapTemplate.new AttributesMapperCallbackHandler(new PersonAttributesMapper());

      return ldapTemplate.search(executor, handler);
   }
}

If you prefer the ContextMapper to the AttributesMapper, this is what it would look like:

Example 4.2. A custom search method using SearchExecutor and ContextMapper

package com.example.dao;

public class PersonDaoImpl implements PersonDao {
   ...
   public List search(final Name base, final String filter, final String[] params,
         final SearchControls ctls) {
      SearchExecutor executor = new SearchExecutor() {
         public NamingEnumeration executeSearch(DirContext ctx) {
            return ctx.search(base, filter, params, ctls);
         }
      };

      NameClassPairCallbackHandler handler =
         ldapTemplate.new ContextMapperCallbackHandler(new PersonContextMapper());

      return ldapTemplate.search(executor, handler);
   }
}
[Note]Note

When using the LdapTemplate.ContextMapperCallbackHandler you must make sure that you have called setReturningObjFlag(true) on your SearchControls instance.

4.2. Implementing Other Custom Context Methods

In the same manner as for custom search methods, you can actually execute any method in DirContext by using a ContextExecutor.

public interface ContextExecutor {
   public Object executeWithContext(DirContext ctx) throws NamingException;
}

When implementing a custom ContextExecutor, you can choose between using the executeReadOnly() or the executeReadWrite() method. Let's say that we want to call this method:

Object lookupLink(Name name)

It's available in DirContext, but there is no matching method in LdapTemplate. It's a lookup method, so it should be read-only. We can implement it like this:

Example 4.3. A custom DirContext method using ContextExecutor

package com.example.dao;

public class PersonDaoImpl implements PersonDao {
   ...
   public Object lookupLink(final Name name) {
      ContextExecutor executor = new ContextExecutor() {
         public Object executeWithContext(DirContext ctx) {
            return ctx.lookupLink(name);
         }
      };

      return ldapTemplate.executeReadOnly(executor);
   }
}

In the same manner you can execute a read-write operation using the executeReadWrite() method.

Chapter 5. Configuration

5.1. ContextSource Configuration

There are several properties in AbstractContextSource (superclass of DirContextSource and LdapContextSource) that can be used to modify its beahaviour.

5.1.1. LDAP Server URLs

The URL of the LDAP server is specified using the url property. The URL should be in the format ldap://myserver.example.com:389. For SSL access, use the ldaps protocol and the appropriate port, e.g. ldaps://myserver.example.com:636

It is possible to configure multiple alternate LDAP servers using the urls property. In this case, supply all server urls in a String array to the urls property.

5.1.2. Authentication

Authenticated contexts are created for both read-only and read-write operations by default. You specify userName and password of the LDAP user to be used for authentication on the ContextSource.

[Note]Note

The userName needs to be the full Distinguished Name (DN) of the user.

Some LDAP server setups allow anonymous read-only access. If you want to use anonymous Contexts for read-only operations, set the anonymousReadOnly property to true.

5.1.2.1. Custom Authentication Using Acegi

While the user name (i.e. user DN) and password used for creating an authenticated Context are static by default - the ones set on the ContextSource on startup will be used throughout the lifetime of the ContextSource - there are however several cases in which this is not the desired behaviour. A common scenario is that the principal and credentials of the current user should be used when executing LDAP operations for that user. The default behaviour can be modified by supplying a custom AuthenticationSource implementation to the ContextSource on startup, instead of explicitly specifying the userName and password. The AuthenticationSource will be queried by the ContextSource for principal and credentials each time an authenticated Context is to be created.

To use the authentication information of the currently logged in user using Acegi Security, use the AcegiAuthenticationSource:

Example 5.1. The Spring bean definition for an AcegiAuthenticationSource

<beans>
   ...
   <bean id="contextSource" class="org.springframework.ldap.support.LdapContextSource">
      <property name="url" value="ldap://localhost:389" />
      <property name="base" value="dc=example,dc=com" />
      <property name="acegiAuthenticationSource" ref="authenticationSource" />
   </bean>

   <bean id="acegiAuthenticationSource"
      class="org.springframework.ldap.support.authentication.AcegiAuthenticationSource" />
   ...
</beans>
[Note]Note

We don't specify any userName or password to our ContextSource when using an AuthenticationSource - these properties are needed only when the default behaviour is used.

[Note]Note

When using the AcegiAuthenticationSource you need to use Acegi's LdapAuthenticationProvider to authenticate the users against LDAP.

5.1.2.2. Default Authentication

When using AcegiAuthenticationSource, authenticated contexts will only be possible to create once the user is logged in using Acegi. To use default authentication information when no user is logged in, use the DefaultValuesAuthenticationSourceDecorator:

Example 5.2. Configuring a DefaultValuesAuthenticationSourceDecorator

<beans>
   ...
   <bean id="contextSource" class="org.springframework.ldap.support.LdapContextSource">
      <property name="url" value="ldap://localhost:389" />
      <property name="base" value="dc=example,dc=com" />
      <property name="authenticationSource" ref="authenticationSource" />
   </bean>

   <bean id="authenticationSource"
      class="org.springframework.ldap.support.DefaultValuesAuthenticationSourceDecorator">
      <property name="target" ref="acegiAuthenticationSource" />
      <property name="defaultUser" value="cn=myDefaultUser" />
      <property name="defaultPassword" value="pass" />
   </bean>

   <bean id="acegiAuthenticationSource"
      class="org.springframework.ldap.support.authentication.AcegiAuthenticationSource" />
   ...
</beans>

5.1.3. Pooling

LDAP connection pooling can be turned on/off using the pooled flag. Default is true. The configuration of LDAP connection pooling is managed using System properties, so this needs to be handled manually. Details of pooling configuration can be found here.

5.1.4. Advanced ContextSource Configuration

5.1.4.1. Alternate ContextFactory

It is possible to configure the ContextFactory that the ContextSource is to use when creating Contexts using the contextFactory property. The default value is com.sun.jndi.ldap.LdapCtxFactory.

5.1.4.2. Custom DirObjectFactory

As described in Chapter 3, DirObjectFactory and DirContextAdapter, a DirObjectFactory can be used to translate the Attributes of found Contexts to a more useful DirContext implementation. This can be configured using the dirObjectFactory property. You can use this property if you have your own, custom DirObjectFactory implementation.

The default value is DefaultDirObjectFactory.

5.1.4.3. Custom DirContext Environment Properties

In some cases the user might want to specify additional environment setup properties in addition to the ones directly configurable from AbstractContextSource. Such properties should be set in a Map and supplied to the baseEnvironmentProperties property.

5.2. LdapTemplate Configuration

5.2.1. Ignoring PartialResultExceptions

Some Active Directory (AD) servers are unable to automatically following referrals, which often leads to a PartialResultException being thrown in searches. You can specify that PartialResultException is to be ignored by setting the ignorePartialResultException property to true.

[Note]Note
This causes all referrals to be ignored, and no notice will be given that a PartialResultException has been encountered. There is currently no way of manually following referrals using LdapTemplate.