# Simpler Attribute Access and Manipulation with DirContextAdapter

## 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.

## 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:

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. This is particularly useful when working with multi-value attributes. Extracting values from multi-value attributes normally requires looping through a `NamingEnumeration` of attribute values returned from the `Attributes` implementation. The `DirContextAdapter` can do this for you, using the `getStringAttributes()` or `getObjectAttributes()` methods:

Getting multi-value attribute values using `getStringAttributes()`

    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"));
          // The roleNames property of Person is an String array
          p.setRoleNames(context.getStringAttributes("roleNames"));
          return p;
       }
    }

### The AbstractContextMapper

Spring LDAP provides an abstract base implementation of `ContextMapper`, `AbstractContextMapper`. This automatically takes care of the casting of the supplied `Object` parameter to `DirContexOperations`. The `PersonContextMapper` above can thus be re-written as follows:

Using an AbstractContextMapper

      private static class PersonContextMapper extends AbstractContextMapper {
          public Object doMapFromContext(DirContextOperations ctx) {
             Person p = new Person();
             p.setFullName(context.getStringAttribute("cn"));
             p.setLastName(context.getStringAttribute("sn"));
             p.setDescription(context.getStringAttribute("description"));
             return p;
          }
      }

## Binding and Modifying Using DirContextAdapter

While very useful when extracting attribute values, `DirContextAdapter` is even more powerful for hiding attribute details when binding and modifying data.

### Binding

This is an example of an improved implementation of the create DAO method. Compare it with the previous implementation in .

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(context);
       }
    }

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

Also note the use of the `setAttributeValues()` method when setting the `objectclass` attribute values. The `objectclass` attribute is multi-value, and similar to the troubles of extracting muti-value attribute data, building multi-value attributes is tedious and verbose work. Using the `setAttributeValues()` mehtod you can have `DirContextAdapter` handle that work for you.

### Modifying

The code for a `rebind` would be pretty much identical to , except that the method called would be `rebind`. As we saw in a more correct approach would be to build a `ModificationItem` array containing the actual modifications you want to do. This would require you to determine the actual modifications compared to the data present in the LDAP tree. Again, this is something that `DirContextAdapter` can help you with; the `DirContextAdapter` has the ability to keep track of its modified attributes. The following example takes advantage of this feature:

Modifying using `DirContextAdapter`

    package com.example.dao;

    public class PersonDaoImpl implements PersonDao {
       ...
       public void update(Person p) {
          Name dn = buildDn(p);
          DirContextOperations context = ldapTemplate.lookupContext(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(context);
       }
    }

When no mapper is passed to a `ldapTemplate.lookup()` operation, the result will be a `DirContextAdapter` instance. While the `lookup` method returns an `Object`, the convenience method `lookupContext` method automatically casts the return value to a `DirContextOperations` (the interface that `DirContextAdapter` implements.

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:

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(context);
       }

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

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

## A Complete PersonDao Class

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

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 org.springframework.ldap.core.AttributesMapper;
    import org.springframework.ldap.core.ContextMapper;
    import org.springframework.ldap.core.LdapTemplate;
    import org.springframework.ldap.core.DirContextAdapter;
    import org.springframework.ldap.core.support.DistinguishedName;
    import org.springframework.ldap.filter.AndFilter;
    import org.springframework.ldap.filter.EqualsFilter;
    import org.springframework.ldap.filter.WhitespaceWildcardsFilter;

    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(buildDn(person));
          mapToContext(person, context);
          ldapTemplate.bind(context);
       }

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

       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 findByName(String name) {
          AndFilter filter = new AndFilter();
          filter.and(new EqualsFilter("objectclass", "person")).and(new WhitespaceWildcardsFilter("cn",name));
          return ldapTemplate.search(DistinguishedName.EMPTY_PATH, filter.encode(), 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, DirContextOperations 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 extends AbstractContextMapper {
          public Object doMapFromContext(DirContextOperations context) {
             Person person = new Person();
             person.setFullName(context.getStringAttribute("cn"));
             person.setLastName(context.getStringAttribute("sn"));
             person.setDescription(context.getStringAttribute("description"));
             return person;
          }
       }
    }

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.