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.
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 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 (common name) attribute of the person objects found in the database. It is important to always close the naming enumeration and the context.
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:
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 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 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:
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:
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 interface: 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.
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.
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:
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 DAO method. Compare it with the previous implementation in Binding Data.
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
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;
}
}
}
Hi, seems like a nice project, have you considered using annotations instead of bean configurations?
about ldap
2006-12-19 00:45:59 kiranmayi
[Reply | View]
actually i want to implement this ldap service in my home pc may i know what r the requirements for this
Great idea
2006-05-27 04:41:09 bouayame
[Reply | View]
It is very powerfull and make LDAP integration so smooth... Thanks.
I think people who r not used with Spring may have difficulties understanding (but the author already mentionnend that)
I highly recommand it
come on and bring it in for the real thing
2006-05-18 21:10:08 mentata
[Reply | View]
I'm not trying to be critical, because we obviously share thinking, but I've been at this same problem for a while now and am probably further along. Consider this directory person object in 67 lines of source code (I do use comments):
I once used the Netscape LDAP API, which split into three versions none of which was well maintained. The Novell JLDAP API, on the other hand is open source, hosted by the OpenLDAP project, actively maintained by multiple developers, and has many LDAP specific features that trump JNDI. I agree that further abstraction is needed, which is why I do what I do. Let me know if you want synergy, because I'm still just waiting for an excuse to launch a cooperative project with my code, which still runs in only a few large enterprises as far as I know.
Not usefull
2006-05-03 02:13:29 kdeboer
[Reply | View]
As with a lot of Spring stuff: you still have to know a lot about the underlying technology to be successfull. So for me this just adds complexity. It is an abstraction on top of other abstractions like JNDI.
Not usefull
2006-05-09 10:34:37 oroebuck
[Reply | View]
Dude,
Everything is an abstraction on top of another abstraction. When's the last time you wrote something in machine language???
Orlando
Using LdapTemplate in multiuser env.
2006-04-28 03:34:24 zahar
[Reply | View]
Great article! It's very useful framework for me! Thanks!
But I have question. I want to use different userName and password for different users logged in my web application. How should I use LdapTemplate in this case.
Using LdapTemplate in multiuser env.
2006-05-01 00:46:54 marthursson
[Reply | View]
Good to hear you found it useful.
In the final 1.0 release (which is now available for download at sourceforge) we added support for individual authentication. You specify an AuthenticationSource that the ContextSource should query for user name and password for each Context to be created. Included with this release is also an AuthenticationSource implementation for Acegi security.
Using LdapTemplate in multiuser env.
2006-05-24 06:48:21 muthu_mj28
[Reply | View]
Hi,
Can you send me some code samples for "individual authentication".
I can't understand how to acheive it inside the spring framework with LDAP template.
Its very urgent,Rply ASAP
Thanks in advance
Using LdapTemplate in multiuser env.
2006-04-28 03:31:34 zahar
[Reply | View]
I want to use different userName and password for different users logged in my web application. How should I
JLDAP and DSML
2006-04-19 07:57:57 zver_
[Reply | View]
Nice article, thanks!
I'm using OpenLDAP, when i was searching for API for it found LDAP, it can be found here: http://www.openldap.org/jldap/
Novell has also developed DSML, service thet retrives data from LDAP in XML. It's for eDirectory, don't know if it works for others... I think it should :) Here is the url : http://developer.novell.com/dsml/
I think it's a good idea, after all we do live in a world of web services and soap. :)
Mozilla (Netscape) Directory SDK for Java.
2006-04-19 07:19:48 jaquemet
[Reply | View]
wow... sounds a very complicated....
I would recommend you to have a look at the Mozilla (Netscape) Directory SDK for Java.
http://www.mozilla.org/directory/javasdk.html
The API is very simple and well documented.
It was (is) used in Sun Directory Server so it's quite robust ;)
Mozilla (Netscape) Directory SDK for Java.
2006-04-20 05:16:30 syberchic
[Reply | View]
Not to mention the JDBC-LDAP api developed by OctetString and donated to OpenLDAP.
http://www.openldap.org/jdbcldap/
Mozilla (Netscape) Directory SDK for Java.
2006-04-19 12:03:03 marthursson
[Reply | View]
I was under the impression that the Netscape Directory SDK was no longer maintained. The latest source i was able to find (the one for version 4.17) is from 2002.
That aside, the use of inline implementations and nested classes that commonly follows with the Strategy pattern might seem intimidating at a first glance. Once you get used to it however, I'd like to argue that it's much more efficient than the common, procedural approach used in standard JNDI, as well as in Netscape Directory SDK.
The main point of the LdapTemplate approach, as stated in the article, is that all of the unnecessary work (opening/closing connections, looping through results and identifying exceptions) is encapsulated, enabling the developer to focus only on what is interesting in each particular project.