2.10 - Why use the LdapConnectionTemplate?

The LdapConnectionTemplate provides simplified access to the API functions. It does so by

Conceptually it uses the Template Method design pattern to do all of the boiler plate work for you. It can give control back as needed for special cases.

Managing Connections

The connection template manages connections through a connection pool. The connection pool must be supplied to the constructor:

    LdapConnectionConfig config = new LdapConnectionConfig();
    config.setLdapHost( hostname );
    config.setLdapPort( port );
    config.setName( adminDn );
    config.setCredentials( adminPassword );

    DefaultLdapConnectionFactory factory = new DefaultLdapConnectionFactory( config );
    factory.setTimeOut( connectionTimeout );

    // optional, values below are defaults
    GenericObjectPool.Config poolConfig = new GenericObjectPool.Config();
    poolConfig.lifo = true;
    poolConfig.maxActive = 8;
    poolConfig.maxIdle = 8;
    poolConfig.maxWait = -1L;
    poolConfig.minEvictableIdleTimeMillis = 1000L * 60L * 30L;
    poolConfig.minIdle = 0;
    poolConfig.numTestsPerEvictionRun = 3;
    poolConfig.softMinEvictableIdleTimeMillis = -1L;
    poolConfig.testOnBorrow = false;
    poolConfig.testOnReturn = false;
    poolConfig.testWhileIdle = false;
    poolConfig.timeBetweenEvictionRunsMillis = -1L;
    poolConfig.whenExhaustedAction = GenericObjectPool.WHEN_EXHAUSTED_BLOCK;

    // could use DefaultPoolableLdapConnectionFactory instead
    // see javadoc for ValidatingPoolableLdapConnectionFactory
    // for an explanation
    LdapConnectionTemplate ldapConnectionTemplate = 
        new LdapConnectionTemplate( new LdapConnectionPool(
            new ValidatingPoolableLdapConnectionFactory( factory ), poolConfig ) );

This may look complicated, but most of the parameters are optional and it’s an easy way for you to manage connections in a safe and efficient manner.

Providing Factory Methods For Model Objects

The connection template implements an interface called the ModelFactory. Any implementation of this factory can be injected into the template once it is constructed. By default, it uses ModelFactoryImpl which in turn constructs the standard Apache LDAP API model objects. This abstraction frees you from having to be concerned with implementation details while still giving you the power to override the default behavior as you see fit.

    ModelFactory modelFactory = new MyCustomModelFactory();
    ldapConnectionTemplate.setModelFactory( modelFactory );

Providing CRUD Methods

The standard CRUD methods are provided (though in this case Add, Lookup, Modify, Delete) with a couple useful overloads. Add, Delete, and Modify all provide at least 2 approaches. The first is creating your own XxxRequest objects and supplying that to the method. These request objects can be created using the ModelFactory methods provided by LdapConnectionTemplate. The second, more elegant solution, is to use the DN and RequestBuilder approach. This approach will generate the request for you from the internal ModelFactory and provide it to a callback for you to fill in the details. This has the added benefit of translating any LdapException’s that may occur (usually due to model implementation setter methods) to LdapRuntimeException, to remove the need for try/catch blocks. Now for some examples:

Add provides the standard approaches of supplying your own AddRequest and using RequestBuilder, as well as a third shortcut approach where you supply all the attributes instead of a RequestBuilder:

    // using RequestBuilder
    AddResponse response = ldapConnectionTemplate.add( 
        ldapConnectionTemplate.newDn( "uid=kermitthefrog,ou=muppets,dc=muppets,dc=org" ),
        new RequestBuilder<AddRequest>() {
            @Override
            public void buildRequest( AddRequest request ) throws LdapException {
                request.getEntry()
                    .add( "objectClass", "top", "person", "organizationalPerson", "inetOrgPerson" )
                    .add( "cn", "Kermit The Frog" )
                    .add( "givenName", "Kermit" )
                    .add( "sn", "The Frog" )
                    .add( "mail", "kermitthefrog@muppets.org" )
                    .add( "uid", "kermitthefrog" );
            }
        } );
    
    // using Attributes list
    AddResponse response = ldapConnectionTemplate.add( 
        ldapConnectionTemplate.newDn( "uid=misspiggy,ou=muppets,dc=muppets,dc=org" ),
        ldapConnectionTemplate.newAttribute( "objectClass", 
            "top", "person", "organizationalPerson", "inetOrgPerson" ),
        ldapConnectionTemplate.newAttribute( "cn", "Miss Piggy" ),
        ldapConnectionTemplate.newAttribute( "givenName", "Miss" ),
        ldapConnectionTemplate.newAttribute( "sn", "Piggy" ),
        ldapConnectionTemplate.newAttribute( "mail", "misspiggy@muppets.org" ),
        ldapConnectionTemplate.newAttribute( "uid", "misspiggy" ) );

Modify simply supplies the 2 standard approaches:

    // using RequestBuilder
    ModifyResponse response = ldapConnectionTemplate.modify( 
        ldapConnectionTemplate.newDn( "uid=misspiggy,ou=muppets,dc=muppets,dc=org" ),
        new RequestBuilder<ModifyRequest>() {
            @Override
            public void buildRequest( ModifyRequest request ) throws LdapException {
                request.replace( "sn", "The Frog" )
                    .replace( "cn", "Miss The Frog" )
                    .replace( "mail", "missthefrog@muppets.org" );
            }
        } );

Delete provides the 2 standard approaches plus an additional DN-only option as that is most likely enough:

    // using DN only
    DeleteResponse response = ldapConnectionTemplate.delete( 
        ldapConnectionTemplate.newDn( "uid=misspiggy,ou=muppets,dc=muppets,dc=org" ) );

Lookup is a different story in that it is an accessor (instead of mutator) so its interface must provide a way of handling the expected response data. To that end, we supply an EntryMapper. EntryMapper defines how a returned ldap entry should be interpreted. This is typically used to construct a domain object:

    // using a previously defined EntryMapper
    Muppet kermit = ldapConnectionTemplate.lookup( 
        ldapConnectionTemplate.newDn( "uid=kermitthefrog,ou=muppets,dc=muppets,dc=org" ),
        muppetEntryMapper );

    // using an inline defined EntityMapper 
    String email = ldapConnectionTemplate.lookup(
        ldapConnectionTemplate.newDn( "uid=kermitthefrog,ou=muppets,dc=muppets,dc=org" ),
        new String[] { "mail" }, // attribute list
        new EntryMapper<String>() {
            @Override
            public String map( Entry entry ) throws LdapException {
                return entry.get( "mail" ).getString();
            }
        } );

More information on EntryMapper can be found in the Handling Search Result Iteration section.

Handling Search Result Iteration

Searching usually contains a lot of boilerplate code for building requests and iterating through its responses. This template does the work for you. It iterates over the entire result set, feeds each entry through an EntryMapper, and assembles the results into a list tht is returned to the caller. All you have to do is provide the EntryMapper for mapping a single entry to a domain object. EntryMapper itself is a very simple interface with one method. As you saw before in the lookup documentation, they are typically defined as static members of your service classes:

    // typically mappers are reused, so define a static member
    private static final EntryMapper<Muppet> muppetEntryMapper =
        new EntryMapper<Muppet>() {
            @Override
            public Muppet map( Entry entry ) throws LdapException {
                return new Muppet.Builder()
                    .setId( entry.getDn() )
                    .setFirstName( entry.get( "givenName" ).getString() )
                    .setLastName( entry.get( "sn" ).getString() )
                    .setEmailAddress( entry.get( "mail" ).getString() )
                    .build();
            }
        };

And now your searches become much simpler:

    List<Muppet> allTheMuppets = ldapConnectionTemplate.search( 
        "ou=muppets,dc=muppets,dc=org", 
        "(objectClass=inetOrgPerson)", 
        SearchScope.ONELEVEL,
        muppetEntryMapper );

Now that is just plain SIMPLE. The search method has many overloads to simplify usages for the most common LDAP operations. There is also a searchFirst method which provides all the same overloads and is designed to return the first matching result:

    Muppet kermit = ldapConnectionTemplate.searchFirst( 
        "ou=muppets,dc=muppets,dc=org", 
        "(mail=kermitthefrog@muppets.org)", 
        SearchScope.ONELEVEL,
        muppetEntryMapper );

It is also natural to pair the search function with the FilterBuilder:

    import static org.apache.directory.ldap.client.api.search.FilterBuilder.equal;
    import static org.apache.directory.ldap.client.api.search.FilterBuilder.or;

    ...

        Muppet kermit = ldapConnectionTemplate.search( 
           "ou=muppets,dc=muppets,dc=org", 
            or(
                equal( "mail", "kermitthefrog@muppets.org" ), 
                equal( "mail", "misspiggy@muppets.org" ) ), 
            SearchScope.ONELEVEL,
            muppetEntryMapper );

This has the added benefit of ensuring that your search filter has been property encoded per RFC4515 section 3. For more information, see Filter Builder.

Providing Simplified, Password Policy Aware, Authentication/Password Modification Methods

One of the most common usages of LDAP is as an identity provider. As such, the most common operation is authentication, and password management. If your LDAP server supports the password policy control then the authenticate method is very handy:

    // throws PasswordException if authentication fails
    PasswordWarning warning = ldapConnectionTemplate.authenticate( 
        ldapConnectionTemplate.newDn( "uid=" + uid + ", ou=people, dc=example, dc=com" ),
        password );

    // or if you authenticate using an attribute not in the dn
    PasswordWarning warning = ldapConnectionTemplate.authenticate( 
        "ou=people,dc=example,dc=com",
        "(mail=kermitthefrog@muppets.com)",
        SearchScope.ONELEVEL,
        "set4now".toCharArray() );

In this case, if authentication failed, a PasswordException is thrown. If authentication was successful, any warnings will be returned in the PasswordWarning object, or null will be returned if there are no warnings.

Modifying a password is simple as well:

    // using administrator account to modify a users password
    ldapConnectionTemplate.modifyPassword( userDn, password );

    // or user account modifying their own password
    ldapConnectionTemplate.modifyPassword( userDn, oldPassword, newPassword );

    // or if you want want more control
    ldapConnectionTemplate.modifyPassword( userDn, oldPassword, newPassword, asAdmin );

If you modify the password as an administrator, then the oldPassword is not required, and if your password policy is set to, the password reset flag will be set causing a PasswordWarning to be returned the next time authenticate was called for that user.

Other Useful Methods

The template provides a method that checks the response and throws an exception if the request was unsuccessful. It was designed to be chained:

    // using DN only
    DeleteResponse response = ldapConnectionTemplate.responseOrException( 
        ldapConnectionTemplate.delete( 
            ldapConnectionTemplate.newDn( "uid=misspiggy,ou=muppets,dc=muppets,dc=org" ) ) );