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:
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.
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 enumerationattribute we want, adding it to a list. and retrieve the
The traditional way of implementing this person name search method in Java LDAP is this:
package se.jayway.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=jayway,dc=se");
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;
}
}
The method above produces a list containing the cn
person
objects found in the database. It is important to always close the naming enumeration and the context. (common name) attribute of the
LdapTemplate is a framework 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 DirContext
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 distinguished names. An LDAP filter corresponds somewhat to the WHERE
clause in a SQL SELECT
statement. A distinguished name (dn
) can be seen as a handle or the path to a specific object in the LDAP database. If the dn
is available, an object can be looked up directly, rather than having to be searched for.
This article will walk you through how to perform different tasks using LdapTemplate. We will start off by going through the basic setup. Then we will show how to perform basic searches, filtering, and distinguished name management, as well as updates. Towards the end, we will display some powerful additional features that can be very useful in some cases.
We will use the syntax of the Spring context configuration file to display how objects and classes relate to each other. The following example shows the two objects someBean
and anotherBean
, where someBean
has a reference to anotherBean
:
class="se.jayway.SomeClass">
name="someProperty" value="42" />
name="anotherProperty" ref="anotherBean" />
class="se.jayway.AnotherClass">
A reader that is not familiar with Spring and the context file format might find the above format intimidating. However, the same configuration can also be performed manually using plain Java code:
SomeClass someBean = new SomeClass();
someBean.setSomeProperty(42);
AnotherClass anotherBean = new AnotherClass("someValue");
someBean.setAnotherProperty(anotherBean);
In order to use LdapTemplate, this is what you need to do.
Include the following libraries in your project:
LdapTemplate also requires Spring:
Alternatively, just pick the necessary Spring components:
Create an LdapContextSource
and an LdapTemplate
object. You can do it in plain Java, or use the Spring context file:
class="net.sf.ldaptemplate.support.LdapContextSource">
class="net.sf.ldaptemplate.LdapTemplate">
Add a property and a corresponding setter for the LdapTemplate
in your Data Access Object (DAO) class:
public class PersonDaoImpl implements PersonDao {
private LdapTemplate ldapTemplate;
public void setLdapTemplate(LdapTemplate ldapTemplate) {
this.ldapTemplate = ldapTemplate;
}
}
Inject the LdapTemplate
into your DAO by calling the setLdapTemplate
, or, if you use the Spring context file:
...
class="se.jayway.dao.PersonDaoImpl">
AttributesMapper
In this example, we will use an AttributesMapper
to easily build a list of all common names of all person objects. This is exactly what we did in the earlier example using the traditional way.
package se.jayway.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:
package se.jayway.dao;
public class PersonDaoImpl implements PersonDao {
private LdapTemplate ldapTemplate;
...
private static 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:
package se.jayway.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.
We can build dynamic filters to use in searches, using the classes from the net.sf.ldaptemplate.support.filter
(&(objectclass=person)(sn=?))
, where we want the ?
lastName
. This is how we do it using the filter support classes: package. Let's say that we want the following filter: to be replaced with the value of the parameter
package se.jayway.dao;
public class PersonDaoImpl implements PersonDao {
public static final String BASE_DN = "dc=jayway,dc=se";
...
public List getPersonNamesByLastName(String lastName) {
AndFilter filter = new AndFilter();
filter.and(new EqualsFilter("objectclass", "person"));
filter.and(new EqualsFilter("sn", lastName));
return ldapTemplate.search(
BASE_DN,
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
:
AndFilter filter = new AndFilter();
filter.and(new EqualsFilter("objectclass", "person"));
filter.and(new WhitespaceWildcardsFilter("cn", cn));
If cn
is john doe
, the above code would result in the following filter: (&(objectclass=person)(cn=*john*doe*))
.
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 greatly simplifies 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:
package se.jayway.dao;
import net.sf.ldaptemplate.support.DistinguishedName;
import javax.naming.Name;
public class PersonDaoImpl implements PersonDao {
public static final String BASE_DN = "dc=jayway,dc=se";
...
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:
country: Sweden
company: Some Company
fullname: Some Person
then the result of the code above would be the following dn
: cn=Some Person, ou=Some Company, c=Sweden, dc=jayway, dc=se
In Java 5, there is an implementation of the Name
LdapName
. If you are in the Java 5 world, you might as well use LdapName
. However, it is still possible to use DistinguishedName
if you so wish. interface:
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:
package se.jayway.dao;
public class PersonDaoImpl implements PersonDao {
...
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 the section DirObjectFactory
and the DirContextAdapter
.
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:
package se.jayway.dao;
public class PersonDaoImpl implements PersonDao {
...
public void delete(Person p) {
Name dn = buildDn(p);
ldapTemplate.unbind(dn);
}
}
In Java LDAP, data can be modified in two ways: either using rebind or modifyAttributes.
A rebind
is a very crude way to modify data. It's basically an unbind
followed by a bind
. It looks like this:
package se.jayway.dao;
public class PersonDaoImpl implements PersonDao {
...
public void update(Person p) {
Name dn = buildDn(p);
ldapTemplate.rebind(dn, null, buildAttributes(p));
}
}
If only the modified attributes should be replaced, there is a method called modifyAttributes
that takes an array of modifications:
package se.jayway.dao;
public class PersonDaoImpl implements PersonDao {
...
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 DirObjectFactory
and the DirContextAdapter
, the update operations can be simplified.
DirObjectFactory
and the DirContextAdapter
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 LdapTemplate framework 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.
First of all, we need to register the DefaultDirObjectFactory
with the ContextSource
. This is done using the dirObjectFactory
property:
...
class="net.sf.ldaptemplate.support.LdapContextSource" >
value="net.sf.ldaptemplate.support.DefaultDirObjectFactory">
Now, whenever a context is found in the LDAP tree, its Attributes
will be used to construct a DirContextAdapter
. This enables us to use a ContextMapper
instead of an AttributesMapper
to transform found values:
package se.jayway.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.
The DirContextAdapter
can also be used to hide the Attributes
when binding and modifying data. This is an example of an improved implementation of the create
Binding Data. DAO method. Compare it with the previous implementation in
package se.jayway.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);
}
}
Obviously, the code would be pretty much identical for a 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:
package se.jayway.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 duplicate 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:
package se.jayway.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());
}
}
We have seen how LdapTemplate can provide substantial improvements compared to code using JNDI directly. There is no longer any need for creating contexts manually or looping through naming enumerations. Nor is there any risk of forgetting to close any of those.
By now it should be clear that the LdapTemplate framework will be of great help for any Java project that communicates with LDAP. For further examples, consult the integration unit tests in the source (src/iutest
) and the Javadocs. If you have any comments, problems or questions, please let us know. Subscribe to our mailing list, or just drop us a note. drop us a note.
To illustrate the power of LdapTemplate, here is a complete Person DAO implementation for LDAP in just 82 lines:
package se.jayway.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 l) {
ldapTemplate = l;
}
public void create(Person p) {
DirContextAdapter context = new DirContextAdapter();
mapToContext(p, context);
ldapTemplate.bind(buildDn(p), 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());
}
public void delete(Person p) {
ldapTemplate.unbind(buildDn(p));
}
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 p) {
return buildDn(p.getFullname(),
p.getCompany(), p.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 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());
}
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;
}
}
}