2.10 - Why use the LdapConnectionTemplate?
The LdapConnectionTemplate provides simplified access to the API functions. It does so by
- Managing Connections
- Providing Factory Methods For Model Objects
- Providing CRUD Methods
- Handling Search Result Iteration
- Providing Simplified, Password Policy Aware, Authentication/Password Modification Methods
- Other Useful Methods
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" ) ) );