[java-identity-provider] 01/01: Changes to support ldaptive v2 API.

Daniel Fisher dfisher at vt.edu
Fri Nov 8 11:09:23 EST 2019


This is an automated email from the git hooks/post-receive script.

dfisher pushed a commit to branch ldaptive-v2
in repository java-identity-provider.

View the commit online:
http://git.shibboleth.net/view/?p=java-identity-provider.git;a=commit;h=161c5129aaf0cb06a380ca76a20461e00ac1015e

commit 161c5129aaf0cb06a380ca76a20461e00ac1015e
Author: Daniel Fisher <dfisher at vt.edu>
AuthorDate: Fri Nov 8 10:57:52 2019 -0500

    Changes to support ldaptive v2 API.
---
 .gitignore                                         |   1 +
 .../AbstractExecutableSearchFilterBuilder.java     |  27 +-
 .../dc/ldap/impl/ExecutableSearchFilter.java       |  17 +-
 .../resolver/dc/ldap/impl/LDAPDataConnector.java   |  55 ++--
 ...ParameterizedExecutableSearchFilterBuilder.java |  12 +-
 .../dc/ldap/impl/SearchResultMappingStrategy.java  |   6 +-
 .../impl/StringAttributeValueMappingStrategy.java  |  17 +-
 .../TemplatedExecutableSearchFilterBuilder.java    |  12 +-
 .../dc/ldap/impl/LDAPDataConnectorTest.java        |  71 ++--
 .../resolver/dc/ldap/impl/Regressions.java         |  10 +-
 idp-attribute-resolver-spring/pom.xml              |   5 -
 .../dc/ldap/impl/LDAPDataConnectorParser.java      | 364 ++++++++-------------
 .../spring/impl/StringToDurationConverter.java     |  20 +-
 .../dc/ldap/LDAPDataConnectorParserTest.java       | 242 +++++---------
 .../ldap-attribute-resolver-spring-context.xml     |  99 +++---
 ...dap-attribute-resolver-spring-props-context.xml | 110 +++----
 .../resolver/spring/dc/ldap/ldap-v2.properties     |   3 -
 .../resolver/spring/dc/ldap/ldap-v3.properties     |  17 +-
 .../resolver/ldap-attribute-resolver-v2-hybrid.xml |   2 -
 .../resolver/ldap-attribute-resolver-v2-jndi.xml   |  34 --
 .../resolver/ldap-attribute-resolver-v2-multi.xml  |   5 -
 .../resolver/ldap-attribute-resolver-v2-nosec.xml  |   4 -
 .../resolver/ldap-attribute-resolver-v2-props.xml  |   2 -
 .../ldap/resolver/ldap-attribute-resolver-v2.xml   |   4 -
 .../authn/AbstractTemplateSearchDnResolver.java    |  16 +-
 .../idp/authn/PooledTemplateSearchDnResolver.java  |  84 -----
 .../idp/authn/TemplateSearchDnResolver.java        |  28 +-
 .../idp/authn/impl/LDAPCredentialValidator.java    |  21 +-
 .../principal/impl/LDAPPrincipalSerializer.java    |   4 +-
 .../DefaultAuthenticationResultSerializerTest.java |  31 +-
 .../authn/impl/LDAPCredentialValidatorTest.java    |  36 +-
 .../idp/authn/impl/ValidateCredentialsTest.java    |   6 +-
 idp-conf/pom.xml                                   |   5 -
 .../resources/conf/authn/ldap-authn-config.xml     |  44 ++-
 idp-conf/src/main/resources/conf/ldap.properties   |   1 -
 .../main/resources/system/conf/global-system.xml   |   1 -
 .../net/shibboleth/idp/test/InMemoryDirectory.java |  15 +-
 idp-distribution/pom.xml                           |  10 -
 .../schema/shibboleth-attribute-resolver.xsd       |  20 +-
 idp-war-distribution/pom.xml                       |  11 -
 40 files changed, 513 insertions(+), 959 deletions(-)

diff --git a/.gitignore b/.gitignore
index 674deb6..b77e0c5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,6 +10,7 @@
 *.pyc
 *.pyo
 *.rej
+*.iml
 *~
 *.#*
 .*.swp
diff --git a/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/AbstractExecutableSearchFilterBuilder.java b/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/AbstractExecutableSearchFilterBuilder.java
index 3d2bb8d..44dbade 100644
--- a/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/AbstractExecutableSearchFilterBuilder.java
+++ b/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/AbstractExecutableSearchFilterBuilder.java
@@ -20,12 +20,10 @@ package net.shibboleth.idp.attribute.resolver.dc.ldap.impl;
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
-import org.ldaptive.ConnectionFactory;
+import org.ldaptive.FilterTemplate;
 import org.ldaptive.LdapException;
-import org.ldaptive.Response;
-import org.ldaptive.SearchExecutor;
-import org.ldaptive.SearchFilter;
-import org.ldaptive.SearchResult;
+import org.ldaptive.SearchOperation;
+import org.ldaptive.SearchResponse;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -44,34 +42,33 @@ public abstract class AbstractExecutableSearchFilterBuilder extends AbstractInit
     /**
      * Creates an executable search filter that performs a search with the search filter.
      * 
-     * @param searchFilter to execute a search with
+     * @param filterTemplate to execute a search with
      * @return executable search filter
      */
     // Checkstyle: AnonInnerLength OFF
-    protected ExecutableSearchFilter build(@Nonnull final SearchFilter searchFilter) {
+    protected ExecutableSearchFilter build(@Nonnull final FilterTemplate filterTemplate) {
         return new ExecutableSearchFilter() {
 
             /** {@inheritDoc} */
             @Nullable public String getResultCacheKey() {
-                return searchFilter.format();
+                return filterTemplate.format();
             }
 
             /** {@inheritDoc} */
-            @Nonnull public SearchResult execute(@Nonnull final SearchExecutor executor,
-                    @Nonnull final ConnectionFactory factory) throws LdapException {
-                final Response<SearchResult> response = executor.search(factory, searchFilter);
+            @Nonnull public SearchResponse execute(@Nonnull final SearchOperation operation) throws LdapException {
+                final SearchResponse response = operation.execute(filterTemplate);
                 log.trace("Search returned response {}", response);
-                return response.getResult();
+                return response;
             }
 
             /** {@inheritDoc} */
-            @Nonnull public SearchFilter getSearchFilter() {
-                return searchFilter;
+            @Nonnull public FilterTemplate getFilterTemplate() {
+                return filterTemplate;
             }
 
             /** {@inheritDoc} */
             public String toString() {
-                return searchFilter.toString();
+                return filterTemplate.toString();
             }
         };
     }
diff --git a/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/ExecutableSearchFilter.java b/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/ExecutableSearchFilter.java
index 2c9a696..2892a88 100644
--- a/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/ExecutableSearchFilter.java
+++ b/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/ExecutableSearchFilter.java
@@ -19,11 +19,10 @@ package net.shibboleth.idp.attribute.resolver.dc.ldap.impl;
 
 import javax.annotation.Nonnull;
 
-import org.ldaptive.ConnectionFactory;
+import org.ldaptive.FilterTemplate;
 import org.ldaptive.LdapException;
-import org.ldaptive.SearchExecutor;
-import org.ldaptive.SearchFilter;
-import org.ldaptive.SearchResult;
+import org.ldaptive.SearchOperation;
+import org.ldaptive.SearchResponse;
 
 import net.shibboleth.idp.attribute.resolver.dc.impl.ExecutableSearch;
 
@@ -33,14 +32,13 @@ public interface ExecutableSearchFilter extends ExecutableSearch {
     /**
      * Performs an LDAP search and returns the results.
      * 
-     * @param executor configured to perform searches
-     * @param factory ready-to-use connection factory
-     * 
+     * @param operation configured to perform searches
+     *
      * @return the result of this search filter
      * 
      * @throws LdapException thrown if there is an error performing the search
      */
-    @Nonnull SearchResult execute(@Nonnull SearchExecutor executor, @Nonnull ConnectionFactory factory)
+    @Nonnull SearchResponse execute(@Nonnull SearchOperation operation)
             throws LdapException;
 
     /**
@@ -48,5 +46,6 @@ public interface ExecutableSearchFilter extends ExecutableSearch {
      *
      * @return  search filter
      */
-    @Nonnull SearchFilter getSearchFilter();
+    @Nonnull
+    FilterTemplate getFilterTemplate();
 }
\ No newline at end of file
diff --git a/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/LDAPDataConnector.java b/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/LDAPDataConnector.java
index ff9fead..3f58b08 100644
--- a/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/LDAPDataConnector.java
+++ b/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/LDAPDataConnector.java
@@ -17,7 +17,6 @@
 
 package net.shibboleth.idp.attribute.resolver.dc.ldap.impl;
 
-import java.security.GeneralSecurityException;
 import java.util.Map;
 
 import javax.annotation.Nonnull;
@@ -26,9 +25,11 @@ import javax.annotation.Nullable;
 import org.ldaptive.Connection;
 import org.ldaptive.ConnectionConfig;
 import org.ldaptive.ConnectionFactory;
+import org.ldaptive.DefaultConnectionFactory;
 import org.ldaptive.LdapException;
-import org.ldaptive.SearchExecutor;
-import org.ldaptive.SearchResult;
+import org.ldaptive.PooledConnectionFactory;
+import org.ldaptive.SearchOperation;
+import org.ldaptive.SearchResponse;
 import org.ldaptive.ssl.SSLContextInitializer;
 import org.ldaptive.ssl.SslConfig;
 import org.ldaptive.ssl.X509SSLContextInitializer;
@@ -57,7 +58,7 @@ public class LDAPDataConnector extends AbstractSearchDataConnector<ExecutableSea
     private ConnectionFactory connectionFactory;
 
     /** For executing LDAP searches. */
-    private SearchExecutor searchExecutor;
+    private SearchOperation searchOperation;
 
     /** Whether the default validator is being used. */
     private boolean defaultValidator = true;
@@ -93,24 +94,24 @@ public class LDAPDataConnector extends AbstractSearchDataConnector<ExecutableSea
     }
 
     /**
-     * Gets the search executor for executing searches.
+     * Gets the search operation for executing searches.
      * 
-     * @return search executor for executing searches
+     * @return search operation for executing searches
      */
-    public SearchExecutor getSearchExecutor() {
-        return searchExecutor;
+    public SearchOperation getSearchOperation() {
+        return searchOperation;
     }
 
     /**
-     * Sets the search executor for executing searches.
+     * Sets the search operation for executing searches.
      * 
-     * @param executor search executor for executing searches
+     * @param operation for executing searches
      */
-    public void setSearchExecutor(@Nonnull final SearchExecutor executor) {
+    public void setSearchOperation(@Nonnull final SearchOperation operation) {
         ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
         ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
 
-        searchExecutor = Constraint.isNotNull(executor, "LDAP search executor can not be null");
+        searchOperation = Constraint.isNotNull(operation, "LDAP search operation can not be null");
     }
 
     /** {@inheritDoc} */
@@ -130,10 +131,11 @@ public class LDAPDataConnector extends AbstractSearchDataConnector<ExecutableSea
         if (connectionFactory == null) {
             throw new ComponentInitializationException(getLogPrefix() + " No connection factory was configured");
         }
-        if (searchExecutor == null) {
-            throw new ComponentInitializationException(getLogPrefix() + " No search executor was configured");
+        if (searchOperation == null) {
+            throw new ComponentInitializationException(getLogPrefix() + " No search operation was configured");
         }
 
+        searchOperation.setConnectionFactory(connectionFactory);
         if (defaultValidator) {
             final ConnectionFactoryValidator validator = new ConnectionFactoryValidator();
             validator.setConnectionFactory(connectionFactory);
@@ -162,17 +164,10 @@ public class LDAPDataConnector extends AbstractSearchDataConnector<ExecutableSea
      * @throws ComponentInitializationException if we detect an SSL issue
      */
     private void policeForJVMTrust() throws ComponentInitializationException {
-        Connection conn = null;
         try {
-            conn = connectionFactory.getConnection();
-            if (conn == null) {
-                log.debug("{} No connection to probe", getLogPrefix());
-                return;
-            }
-            final ConnectionConfig connConfig = conn.getConnectionConfig();
+            final ConnectionConfig connConfig = connectionFactory.getConnectionConfig();
             if (connConfig.getUseStartTLS() ||
-                    connConfig.getUseSSL() ||
-                    connConfig.getLdapUrl().toLowerCase().contains("ldaps://")) {
+                (connConfig.getLdapUrl() != null && connConfig.getLdapUrl().toLowerCase().contains("ldaps://"))) {
                 final SslConfig sslConfig = connConfig.getSslConfig();
                 if (sslConfig != null) {
                     final SSLContextInitializer cxtInit = sslConfig.getCredentialConfig() != null ?
@@ -185,16 +180,8 @@ public class LDAPDataConnector extends AbstractSearchDataConnector<ExecutableSea
                     }
                 }
             }
-        } catch (final GeneralSecurityException | LdapException e) {
-            log.debug("{} Failed to inspect SLL implementation", getLogPrefix(), e);
-        } finally {
-            if (conn != null) {
-                try {
-                    conn.close();
-                } catch (final Exception e) {
-                    log.debug("{} Error closing LDAP connection", getLogPrefix(), e);
-                }
-            }
+        } catch (final Exception e) {
+            log.debug("{} Failed to inspect SSL implementation", getLogPrefix(), e);
         }
     }
  // CheckStyle: CyclomaticComplexity ON
@@ -215,7 +202,7 @@ public class LDAPDataConnector extends AbstractSearchDataConnector<ExecutableSea
             throw new ResolutionException(getLogPrefix() + " Search filter cannot be null");
         }
         try {
-            final SearchResult result = filter.execute(searchExecutor, connectionFactory);
+            final SearchResponse result = filter.execute(searchOperation);
             log.trace("{} Search returned {}", getLogPrefix(), result);
             return getMappingStrategy().map(result);
         } catch (final LdapException e) {
diff --git a/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/ParameterizedExecutableSearchFilterBuilder.java b/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/ParameterizedExecutableSearchFilterBuilder.java
index 8029e92..937158a 100644
--- a/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/ParameterizedExecutableSearchFilterBuilder.java
+++ b/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/ParameterizedExecutableSearchFilterBuilder.java
@@ -22,7 +22,7 @@ import java.util.Map;
 
 import javax.annotation.Nonnull;
 
-import org.ldaptive.SearchFilter;
+import org.ldaptive.FilterTemplate;
 
 import net.shibboleth.idp.attribute.IdPAttributeValue;
 import net.shibboleth.idp.attribute.resolver.ResolutionException;
@@ -69,20 +69,20 @@ public class ParameterizedExecutableSearchFilterBuilder extends AbstractExecutab
     @Override public ExecutableSearchFilter build(@Nonnull final AttributeResolutionContext resolutionContext,
             @Nonnull final Map<String, List<IdPAttributeValue>> dependencyAttributes) throws ResolutionException {
         ComponentSupport.ifNotInitializedThrowUninitializedComponentException(this);
-        final SearchFilter sf = new SearchFilter(searchFilter);
-        sf.setParameter("principalName", resolutionContext.getPrincipal());
+        final FilterTemplate template = new FilterTemplate(searchFilter);
+        template.setParameter("principalName", resolutionContext.getPrincipal());
         if (dependencyAttributes != null && !dependencyAttributes.isEmpty()) {
             for (final Map.Entry<String, List<IdPAttributeValue>> entry : dependencyAttributes.entrySet()) {
                 int i = 0;
                 for (final IdPAttributeValue value : entry.getValue()) {
                     if (i == 0) {
-                        sf.setParameter(String.format("%s", entry.getKey(), i), value.getNativeValue());
+                        template.setParameter(String.format("%s", entry.getKey(), i), value.getNativeValue());
                     }
-                    sf.setParameter(String.format("%s[%s]", entry.getKey(), i++), value.getNativeValue());
+                    template.setParameter(String.format("%s[%s]", entry.getKey(), i++), value.getNativeValue());
                 }
             }
         }
-        return super.build(sf);
+        return super.build(template);
     }
     
 }
diff --git a/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/SearchResultMappingStrategy.java b/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/SearchResultMappingStrategy.java
index 92fc2d1..b8cf633 100644
--- a/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/SearchResultMappingStrategy.java
+++ b/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/SearchResultMappingStrategy.java
@@ -17,13 +17,13 @@
 
 package net.shibboleth.idp.attribute.resolver.dc.ldap.impl;
 
-import org.ldaptive.SearchResult;
+import org.ldaptive.SearchResponse;
 
 import net.shibboleth.idp.attribute.resolver.dc.MappingStrategy;
 
 /**
- * Strategy for mapping from a {@link SearchResult} to a collection of 
+ * Strategy for mapping from a {@link SearchResponse} to a collection of
  * {@link net.shibboleth.idp.attribute.IdPAttribute}s.
  */
-public interface SearchResultMappingStrategy extends MappingStrategy<SearchResult> {
+public interface SearchResultMappingStrategy extends MappingStrategy<SearchResponse> {
 }
\ No newline at end of file
diff --git a/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/StringAttributeValueMappingStrategy.java b/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/StringAttributeValueMappingStrategy.java
index 9435b31..a8360a6 100644
--- a/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/StringAttributeValueMappingStrategy.java
+++ b/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/StringAttributeValueMappingStrategy.java
@@ -36,7 +36,7 @@ import net.shibboleth.utilities.java.support.logic.Constraint;
 
 import org.ldaptive.LdapAttribute;
 import org.ldaptive.LdapEntry;
-import org.ldaptive.SearchResult;
+import org.ldaptive.SearchResponse;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -44,7 +44,7 @@ import org.slf4j.LoggerFactory;
  * A simple {@link SearchResultMappingStrategy} that iterates over all result entries and includes all attribute values
  * as strings.
  */
-public class StringAttributeValueMappingStrategy extends AbstractMappingStrategy<SearchResult>
+public class StringAttributeValueMappingStrategy extends AbstractMappingStrategy<SearchResponse>
         implements SearchResultMappingStrategy {
 
     /** Class logger. */
@@ -52,21 +52,26 @@ public class StringAttributeValueMappingStrategy extends AbstractMappingStrategy
 
  // Checkstyle: CyclomaticComplexity OFF
     /** {@inheritDoc} */
-    @Override @Nullable public Map<String,IdPAttribute> map(@Nonnull final SearchResult results)
+    @Override @Nullable public Map<String,IdPAttribute> map(@Nonnull final SearchResponse results)
             throws ResolutionException {
         Constraint.isNotNull(results, "Results can not be null");
 
-        if (results.size() == 0) {
+        if (!results.isSuccess()) {
+            // It's possible for the LDAP to return partial results and report either a size limit or
+            // time limit result code. Throw if we don't receive all results.
+            throw new ResolutionException("Search operation did not return success: " + results.getResultCode());
+        }
+        if (results.entrySize() == 0) {
             log.debug("Results did not contain any entries, nothing to map");
             if (isNoResultAnError()) {
                 throw new NoResultAnErrorResolutionException("No entries returned from search");
             }
             return null;
-        } else if (results.size() > 1 && isMultipleResultsAnError()) {
+        } else if (results.entrySize() > 1 && isMultipleResultsAnError()) {
             throw new MultipleResultAnErrorResolutionException("Multiple entries returned from search");
         }
 
-        final Map<String,IdPAttribute> attributes = new HashMap<>(results.size());
+        final Map<String,IdPAttribute> attributes = new HashMap<>(results.entrySize());
         
         final Map<String,String> aliases = getResultRenamingMap();
 
diff --git a/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/TemplatedExecutableSearchFilterBuilder.java b/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/TemplatedExecutableSearchFilterBuilder.java
index e5a3414..1568a2a 100644
--- a/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/TemplatedExecutableSearchFilterBuilder.java
+++ b/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/TemplatedExecutableSearchFilterBuilder.java
@@ -40,7 +40,7 @@ import org.apache.velocity.app.event.EventCartridge;
 import org.apache.velocity.app.event.ReferenceInsertionEventHandler;
 import org.apache.velocity.context.Context;
 import org.apache.velocity.exception.VelocityException;
-import org.ldaptive.SearchFilter;
+import org.ldaptive.FilterTemplate;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -190,8 +190,8 @@ public class TemplatedExecutableSearchFilterBuilder extends AbstractExecutableSe
         }
 
         try {
-            final SearchFilter searchFilter = new SearchFilter(merge(context));
-            return super.build(searchFilter);
+            final FilterTemplate template = new FilterTemplate(merge(context));
+            return super.build(template);
         } catch (final VelocityException e) {
             log.error("Error running template", e);
             throw new ResolutionException("Error running template", e);
@@ -255,7 +255,7 @@ public class TemplatedExecutableSearchFilterBuilder extends AbstractExecutableSe
         }
 
         /**
-         * Returns {@link SearchFilter#encodeValue} if value is a string.
+         * Returns {@link FilterTemplate#encodeValue} if value is a string.
          * 
          * @param value to encode
          *
@@ -263,9 +263,9 @@ public class TemplatedExecutableSearchFilterBuilder extends AbstractExecutableSe
          */
         private Object encode(final Object value) {
             if (value instanceof String){ 
-                return SearchFilter.encodeValue((String) value);
+                return FilterTemplate.encodeValue((String) value);
             } else if (value instanceof byte[]) {
-                return SearchFilter.encodeValue((byte[]) value);
+                return FilterTemplate.encodeValue((byte[]) value);
             }
             return value;
         }
diff --git a/idp-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/LDAPDataConnectorTest.java b/idp-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/LDAPDataConnectorTest.java
index 5d43540..f2e96bb 100644
--- a/idp-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/LDAPDataConnectorTest.java
+++ b/idp-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/LDAPDataConnectorTest.java
@@ -33,7 +33,8 @@ import javax.annotation.Nonnull;
 
 import org.ldaptive.ConnectionFactory;
 import org.ldaptive.DefaultConnectionFactory;
-import org.ldaptive.SearchExecutor;
+import org.ldaptive.SearchOperation;
+import org.ldaptive.SearchRequest;
 import org.testng.annotations.AfterTest;
 import org.testng.annotations.BeforeTest;
 import org.testng.annotations.Test;
@@ -115,10 +116,9 @@ public class LDAPDataConnectorTest {
         connector.setId(TEST_CONNECTOR_NAME);
         final ConnectionFactory connectionFactory = new DefaultConnectionFactory("ldap://localhost:10389");
         connector.setConnectionFactory(connectionFactory);
-        final SearchExecutor searchExecutor = new SearchExecutor();
-        searchExecutor.setBaseDn(TEST_BASE_DN);
-        searchExecutor.setReturnAttributes(TEST_RETURN_ATTRIBUTES);
-        connector.setSearchExecutor(searchExecutor);
+        final SearchOperation searchOperation = new SearchOperation();
+        searchOperation.setRequest(SearchRequest.builder().dn(TEST_BASE_DN).returnAttributes(TEST_RETURN_ATTRIBUTES).build());
+        connector.setSearchOperation(searchOperation);
         connector.setExecutableSearchBuilder(builder == null ? 
                                              newParameterizedExecutableSearchFilterBuilder("(uid={principalName})") : builder);
         connector.setValidator(newConnectionFactoryValidator(connectionFactory));
@@ -147,10 +147,9 @@ public class LDAPDataConnectorTest {
         }
         connector.setFailFastInitialize(true);
 
-        final SearchExecutor searchExecutor = new SearchExecutor();
-        searchExecutor.setBaseDn(TEST_BASE_DN);
-        searchExecutor.setReturnAttributes(TEST_RETURN_ATTRIBUTES);
-        connector.setSearchExecutor(searchExecutor);
+        final SearchOperation searchOperation = new SearchOperation();
+        searchOperation.setRequest(SearchRequest.builder().dn(TEST_BASE_DN).returnAttributes(TEST_RETURN_ATTRIBUTES).build());
+        connector.setSearchOperation(searchOperation);
         try {
             connector.initialize();
             fail("No filter builder");
@@ -189,7 +188,7 @@ public class LDAPDataConnectorTest {
             // OK
         }
         assertEquals(connector.getConnectionFactory(), connectionFactory);
-        assertEquals(connector.getSearchExecutor(), searchExecutor);
+        assertEquals(connector.getSearchOperation(), searchOperation);
         assertEquals(connector.getExecutableSearchBuilder(), requestBuilder);
         assertEquals(connector.getMappingStrategy(), mappingStrategy);
     }
@@ -200,7 +199,7 @@ public class LDAPDataConnectorTest {
 
         final ConnectionFactory connectionFactory = new DefaultConnectionFactory("ldap://localhost:55555");
         connector.setConnectionFactory(connectionFactory);
-        connector.setSearchExecutor(new SearchExecutor());
+        connector.setSearchOperation(new SearchOperation());
         connector.setExecutableSearchBuilder(newParameterizedExecutableSearchFilterBuilder("(uid={principalName})"));
         connector.setFailFastInitialize(true);
 
@@ -239,7 +238,7 @@ public class LDAPDataConnectorTest {
         attributeValues.add(new StringAttributeValue("student"));
         dependsAttributes.put("affiliation", attributeValues);
         final ExecutableSearchFilter filter = builder.build(context, dependsAttributes);
-        assertEquals(filter.getSearchFilter().format(), "(&(cn=PETER_THE_PRINCIPAL)(eduPersonAffiliation=student))");
+        assertEquals(filter.getFilterTemplate().format(), "(&(cn=PETER_THE_PRINCIPAL)(eduPersonAffiliation=student))");
         assertEquals(filter.getResultCacheKey(), "(&(cn=PETER_THE_PRINCIPAL)(eduPersonAffiliation=student))");
     }
 
@@ -258,11 +257,11 @@ public class LDAPDataConnectorTest {
         dependsAttributes.put("entitlement", attributeValues);
         final ExecutableSearchFilter filter = builder.build(context, dependsAttributes);
         assertEquals(
-                filter.getSearchFilter().format(),
-                "(&(cn=PETER_THE_PRINCIPAL)(eduPersonEntitlement=entitlement1)(eduPersonEntitlement=entitlement\\2a))");
+                filter.getFilterTemplate().format(),
+                "(&(cn=PETER_THE_PRINCIPAL)(eduPersonEntitlement=entitlement1)(eduPersonEntitlement=entitlement\\2A))");
         assertEquals(
                 filter.getResultCacheKey(),
-                "(&(cn=PETER_THE_PRINCIPAL)(eduPersonEntitlement=entitlement1)(eduPersonEntitlement=entitlement\\2a))");
+                "(&(cn=PETER_THE_PRINCIPAL)(eduPersonEntitlement=entitlement1)(eduPersonEntitlement=entitlement\\2A))");
     }
 
     @Test public void escape() throws ComponentInitializationException, ResolutionException {
@@ -273,8 +272,8 @@ public class LDAPDataConnectorTest {
                 TestSources.createResolutionContext("domain\\user*", TestSources.IDP_ENTITY_ID,
                         TestSources.SP_ENTITY_ID);
         final ExecutableSearchFilter filter = builder.build(context, null);
-        assertEquals(filter.getSearchFilter().format(), "(cn=domain\\5cuser\\2a)");
-        assertEquals(filter.getResultCacheKey(), "(cn=domain\\5cuser\\2a)");
+        assertEquals(filter.getFilterTemplate().format(), "(cn=domain\\5Cuser\\2A)");
+        assertEquals(filter.getResultCacheKey(), "(cn=domain\\5Cuser\\2A)");
     }
 
     @Test public void resolveTemplate() throws ComponentInitializationException, ResolutionException {
@@ -312,7 +311,7 @@ public class LDAPDataConnectorTest {
         attributeValues.add(new StringAttributeValue("student"));
         dependsAttributes.put("affiliation", attributeValues);
         final ExecutableSearchFilter filter = builder.build(context, dependsAttributes);
-        assertEquals(filter.getSearchFilter().format(), "(&(cn=PETER_THE_PRINCIPAL)(eduPersonAffiliation=student))");
+        assertEquals(filter.getFilterTemplate().format(), "(&(cn=PETER_THE_PRINCIPAL)(eduPersonAffiliation=student))");
         assertEquals(filter.getResultCacheKey(), "(&(cn=PETER_THE_PRINCIPAL)(eduPersonAffiliation=student))");
     }
 
@@ -332,11 +331,11 @@ public class LDAPDataConnectorTest {
         dependsAttributes.put("entitlement", attributeValues);
         final ExecutableSearchFilter filter = builder.build(context, dependsAttributes);
         assertEquals(
-                filter.getSearchFilter().format(),
-                "(&(cn=PETER_THE_PRINCIPAL)(eduPersonEntitlement=entitlement1)(eduPersonEntitlement=entitlement\\2a))");
+                filter.getFilterTemplate().format(),
+                "(&(cn=PETER_THE_PRINCIPAL)(eduPersonEntitlement=entitlement1)(eduPersonEntitlement=entitlement\\2A))");
         assertEquals(
                 filter.getResultCacheKey(),
-                "(&(cn=PETER_THE_PRINCIPAL)(eduPersonEntitlement=entitlement1)(eduPersonEntitlement=entitlement\\2a))");
+                "(&(cn=PETER_THE_PRINCIPAL)(eduPersonEntitlement=entitlement1)(eduPersonEntitlement=entitlement\\2A))");
     }
 
     @Test public void escapeTemplate() throws ComponentInitializationException, ResolutionException {
@@ -348,8 +347,8 @@ public class LDAPDataConnectorTest {
                 TestSources.createResolutionContext("domain\\user*", TestSources.IDP_ENTITY_ID,
                         TestSources.SP_ENTITY_ID);
         final ExecutableSearchFilter filter = builder.build(context, null);
-        assertEquals(filter.getSearchFilter().format(), "(cn=domain\\5cuser\\2a)");
-        assertEquals(filter.getResultCacheKey(), "(cn=domain\\5cuser\\2a)");
+        assertEquals(filter.getFilterTemplate().format(), "(cn=domain\\5Cuser\\2A)");
+        assertEquals(filter.getResultCacheKey(), "(cn=domain\\5Cuser\\2A)");
     }
 
     protected void resolve(final ExecutableSearchBuilder<ExecutableSearchFilter> builder) throws ComponentInitializationException,
@@ -363,21 +362,21 @@ public class LDAPDataConnectorTest {
         final Map<String, IdPAttribute> attrs = connector.resolve(context);
         assertNotNull(attrs);
         // check total attributes: uid, cn, sn, mail
-        assertEquals(attrs.size(), 4);
+        assertEquals(attrs.size(), 4, attrs + " should only contain 4 elements");
         // check uid
-        assertEquals(attrs.get("uid").getValues().size(), 1);
+        assertEquals(attrs.get("uid").getValues().size(), 1, attrs.get("uid") + " should only contain 1 element");
         assertEquals(new StringAttributeValue(TestSources.PRINCIPAL_ID), attrs.get("uid").getValues().iterator()
                 .next());
         // check cn
-        assertEquals(attrs.get("cn").getValues().size(), 3);
+        assertEquals(attrs.get("cn").getValues().size(), 3, attrs.get("cn") + " should only contain 3 elements");
         assertTrue(attrs.get("cn").getValues().contains(new StringAttributeValue("Peter Principal")));
         assertTrue(attrs.get("cn").getValues().contains(new StringAttributeValue("Peter J Principal")));
         assertTrue(attrs.get("cn").getValues().contains(new StringAttributeValue("pete principal")));
         // check sn
-        assertEquals(attrs.get("sn").getValues().size(), 1);
+        assertEquals(attrs.get("sn").getValues().size(), 1, attrs.get("sn") + " should only contain 1 element");
         assertEquals(new StringAttributeValue("Principal"), attrs.get("sn").getValues().iterator().next());
         // check mail
-        assertEquals(attrs.get("mail").getValues().size(), 2);
+        assertEquals(attrs.get("mail").getValues().size(), 2, attrs.get("mail") + " should only contain 2 elements");
         assertTrue(attrs.get("mail").getValues().contains(new StringAttributeValue("peter.principal at shibboleth.net")));
         assertTrue(attrs.get("mail").getValues().contains(new StringAttributeValue("peterprincipal at shibboleth.net")));
     }
@@ -395,22 +394,22 @@ public class LDAPDataConnectorTest {
         // check total attributes: uid, cn, sn, mail
         assertEquals(attrs.size(), 4);
         // check uid
-        assertEquals(attrs.get("uid").getValues().size(), 3);
+        assertEquals(attrs.get("uid").getValues().size(), 3, attrs.get("uid") + " should only contain 3 elements");
         assertTrue(attrs.get("uid").getValues().contains(new StringAttributeValue(TestSources.PRINCIPAL_ID)));
         assertTrue(attrs.get("uid").getValues().contains(new StringAttributeValue("PAUL_THE_PRINCIPAL")));
         assertTrue(attrs.get("uid").getValues().contains(new StringAttributeValue("PHILIP_THE_PRINCIPAL")));
         // check cn
-        assertEquals(attrs.get("cn").getValues().size(), 5);
+        assertEquals(attrs.get("cn").getValues().size(), 5, attrs.get("cn") + " should only contain 5 elements");
         assertTrue(attrs.get("cn").getValues().contains(new StringAttributeValue("Peter Principal")));
         assertTrue(attrs.get("cn").getValues().contains(new StringAttributeValue("Peter J Principal")));
         assertTrue(attrs.get("cn").getValues().contains(new StringAttributeValue("pete principal")));
         assertTrue(attrs.get("cn").getValues().contains(new StringAttributeValue("Paul Principal")));
         assertTrue(attrs.get("cn").getValues().contains(new StringAttributeValue("Philip Principal")));
         // check sn
-        assertEquals(attrs.get("sn").getValues().size(), 3);
+        assertEquals(attrs.get("sn").getValues().size(), 3, attrs.get("sn") + " should only contain 3 elements");
         assertTrue(attrs.get("sn").getValues().contains(new StringAttributeValue("Principal")));
         // check mail
-        assertEquals(attrs.get("mail").getValues().size(), 8);
+        assertEquals(attrs.get("mail").getValues().size(), 8, attrs.get("mail") + " should only contain 8 elements");
         assertTrue(attrs.get("mail").getValues().contains(new StringAttributeValue("peter.principal at shibboleth.net")));
         assertTrue(attrs.get("mail").getValues().contains(new StringAttributeValue("peterprincipal at shibboleth.net")));
         assertTrue(attrs.get("mail").getValues().contains(new StringAttributeValue("paul.principal at shibboleth.net")));
@@ -535,19 +534,19 @@ public class LDAPDataConnectorTest {
         final Map<String, IdPAttribute> attrs = connector.resolve(context);
         assertNotNull(attrs);
         // check total attributes: uid, cn, sn, mail
-        assertEquals(attrs.size(), 4);
+        assertEquals(attrs.size(), 4, attrs + " should only contain 4 elements");
         // check uid
         assertEquals(attrs.get("uid").getValues().size(), 1);
         assertEquals(attrs.get("uid").getValues().iterator().next(), new StringAttributeValue(
                 "PHILIP_THE_PRINCIPAL"));
         // check cn
-        assertEquals(attrs.get("cn").getValues().size(), 1);
+        assertEquals(attrs.get("cn").getValues().size(), 1, attrs.get("cn") + " should only contain 1 element");
         assertTrue(attrs.get("cn").getValues().contains(new StringAttributeValue("Philip Principal")));
         // check sn
-        assertEquals(attrs.get("sn").getValues().size(), 1);
+        assertEquals(attrs.get("sn").getValues().size(), 1, attrs.get("sn") + " should only contain 1 element");
         assertEquals(attrs.get("sn").getValues().iterator().next(), new StringAttributeValue("Principal"));
         // check mail
-        assertEquals(attrs.get("mail").getValues().size(), 4);
+        assertEquals(attrs.get("mail").getValues().size(), 4, attrs.get("mail") + " should only contain 4 elements");
         assertTrue(attrs.get("mail").getValues().contains(new EmptyAttributeValue(EmptyType.ZERO_LENGTH_VALUE)));
         assertTrue(attrs.get("mail").getValues().contains(new StringAttributeValue("\"\"")));
         assertTrue(attrs.get("mail").getValues().contains(new StringAttributeValue("  ")));
diff --git a/idp-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/Regressions.java b/idp-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/Regressions.java
index f250d43..fc158ca 100644
--- a/idp-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/Regressions.java
+++ b/idp-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/Regressions.java
@@ -25,7 +25,8 @@ import java.util.Map;
 
 import org.ldaptive.ConnectionFactory;
 import org.ldaptive.DefaultConnectionFactory;
-import org.ldaptive.SearchExecutor;
+import org.ldaptive.SearchOperation;
+import org.ldaptive.SearchRequest;
 import org.testng.annotations.AfterTest;
 import org.testng.annotations.BeforeTest;
 import org.testng.annotations.Test;
@@ -104,10 +105,9 @@ public class Regressions {
         connector.setId(TEST_CONNECTOR_NAME);
         final ConnectionFactory connectionFactory = new DefaultConnectionFactory("ldap://localhost:10390");
         connector.setConnectionFactory(connectionFactory);
-        final SearchExecutor searchExecutor = new SearchExecutor();
-        searchExecutor.setBaseDn(TEST_BASE_DN);
-        searchExecutor.setReturnAttributes(TEST_RETURN_ATTRIBUTES);
-        connector.setSearchExecutor(searchExecutor);
+        final SearchOperation searchOperation = new SearchOperation();
+        searchOperation.setRequest(SearchRequest.builder().dn(TEST_BASE_DN).returnAttributes(TEST_RETURN_ATTRIBUTES).build());
+        connector.setSearchOperation(searchOperation);
         connector.setExecutableSearchBuilder(builder == null ? LDAPDataConnectorTest.newParameterizedExecutableSearchFilterBuilder(
                 "(uid={principalName})") : builder);
         connector.setValidator(LDAPDataConnectorTest.newConnectionFactoryValidator(connectionFactory));
diff --git a/idp-attribute-resolver-spring/pom.xml b/idp-attribute-resolver-spring/pom.xml
index 0ff653d..8632475 100644
--- a/idp-attribute-resolver-spring/pom.xml
+++ b/idp-attribute-resolver-spring/pom.xml
@@ -181,11 +181,6 @@
             <scope>test</scope>
         </dependency>
         <dependency>
-            <groupId>org.ldaptive</groupId>
-            <artifactId>ldaptive-unboundid</artifactId>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
             <groupId>com.unboundid</groupId>
             <artifactId>unboundid-ldapsdk</artifactId>
             <scope>test</scope>
diff --git a/idp-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/impl/LDAPDataConnectorParser.java b/idp-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/impl/LDAPDataConnectorParser.java
index 1d49f05..62e47c4 100644
--- a/idp-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/impl/LDAPDataConnectorParser.java
+++ b/idp-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/impl/LDAPDataConnectorParser.java
@@ -25,25 +25,25 @@ import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 import javax.xml.namespace.QName;
 
+import org.ldaptive.ActivePassiveConnectionStrategy;
 import org.ldaptive.BindConnectionInitializer;
 import org.ldaptive.ConnectionConfig;
 import org.ldaptive.Credential;
 import org.ldaptive.DefaultConnectionFactory;
-import org.ldaptive.SearchExecutor;
-import org.ldaptive.SearchFilter;
+import org.ldaptive.FilterTemplate;
+import org.ldaptive.PooledConnectionFactory;
+import org.ldaptive.RandomConnectionStrategy;
+import org.ldaptive.RoundRobinConnectionStrategy;
+import org.ldaptive.SearchConnectionValidator;
+import org.ldaptive.SearchOperation;
 import org.ldaptive.SearchRequest;
 import org.ldaptive.SearchScope;
 import org.ldaptive.handler.CaseChangeEntryHandler;
 import org.ldaptive.handler.CaseChangeEntryHandler.CaseChange;
 import org.ldaptive.handler.DnAttributeEntryHandler;
-import org.ldaptive.handler.SearchEntryHandler;
-import org.ldaptive.pool.BlockingConnectionPool;
+import org.ldaptive.handler.LdapEntryHandler;
 import org.ldaptive.pool.IdlePruneStrategy;
 import org.ldaptive.pool.PoolConfig;
-import org.ldaptive.pool.PooledConnectionFactory;
-import org.ldaptive.pool.SearchValidator;
-import org.ldaptive.pool.SoftLimitConnectionPool;
-import org.ldaptive.provider.ConnectionStrategy;
 import org.ldaptive.sasl.Mechanism;
 import org.ldaptive.sasl.SaslConfig;
 import org.ldaptive.ssl.SslConfig;
@@ -95,9 +95,9 @@ public class LDAPDataConnectorParser extends AbstractDataConnectorParser {
      * Parses a version 2 configuration. <br/>
      * The following automatically created & injected beans acquire hard wired defaults:
      * <ul>
-     * <li>{@link SearchExecutor#setTimeLimit(long)} defaults to 3000, overridden by the "searchTimeLimit" attribute.
+     * <li>{@link SearchRequest#setTimeLimit(Duration)} defaults to 3s, overridden by the "searchTimeLimit" attribute.
      * </li>
-     * <li>{@link SearchExecutor#setSizeLimit(long)} defaults to 1, overridden by the "maxResultSize" attribute.</li>
+     * <li>{@link SearchRequest#setSizeLimit(int)} defaults to 1, overridden by the "maxResultSize" attribute.</li>
      * <li>{@link SearchRequest#setBaseDn(String)} default to "", overridden by the "validateDN" attribute.</li>
      * <li>{@link SearchFilter#SearchFilter(String)} defaults to "(objectClass=*)", overridden by the "validateFilter"
      * attribute.</li>
@@ -105,7 +105,7 @@ public class LDAPDataConnectorParser extends AbstractDataConnectorParser {
      * attribute "minPoolSize" are set.</li>
      * <li>{@link PoolConfig#setMaxPoolSize(int)} defaults to 3 if neither the attribute "poolMaxIdleSize" nor the
      * attribute "maxPoolSize" are set.</li>
-     * <li>{@link PoolConfig#setValidatePeriod(long)} defaults to 1800, overridden by the attribute
+     * <li>{@link PoolConfig#setValidatePeriod(Duration)} defaults to 1800, overridden by the attribute
      * "validateTimerPeriod"</li>
      * </ul>
      * 
@@ -119,49 +119,15 @@ public class LDAPDataConnectorParser extends AbstractDataConnectorParser {
 
         final V2Parser v2Parser = new V2Parser(config, getLogPrefix());
 
-        final BeanDefinitionBuilder connectionFactory =
-                BeanDefinitionBuilder.genericBeanDefinition(DefaultConnectionFactory.class);
-        connectionFactory.addConstructorArgValue(v2Parser.createConnectionConfig(parserContext));
-
-        final BeanDefinitionBuilder provider =
-                BeanDefinitionBuilder.genericBeanDefinition(DefaultConnectionFactory.getDefaultProvider().getClass());
-        final BeanDefinitionBuilder providerConfig =
-                BeanDefinitionBuilder.genericBeanDefinition(DefaultConnectionFactory.getDefaultProvider()
-                        .getProviderConfig().getClass());
-        final String connectionStrategy = AttributeSupport.getAttributeValue(config, new QName("connectionStrategy"));
-        if (connectionStrategy == null) {
-            providerConfig.addPropertyValue("connectionStrategy", ConnectionStrategy.ACTIVE_PASSIVE);
+        final BeanDefinitionBuilder connectionFactory;
+        final Element poolConfigElement = getConnectionPoolElement(config);
+        if (poolConfigElement == null) {
+            connectionFactory = BeanDefinitionBuilder.genericBeanDefinition(DefaultConnectionFactory.class);
         } else {
-            switch (connectionStrategy) {
-                case "DEFAULT":
-                    providerConfig.addPropertyValue("connectionStrategy", ConnectionStrategy.DEFAULT);
-                    break;
-
-                case "ROUND_ROBIN":
-                    providerConfig.addPropertyValue("connectionStrategy", ConnectionStrategy.ROUND_ROBIN);
-                    break;
-
-                case "RANDOM":
-                    providerConfig.addPropertyValue("connectionStrategy", ConnectionStrategy.RANDOM);
-                    break;
-
-                default:
-                    providerConfig.addPropertyValue("connectionStrategy", ConnectionStrategy.ACTIVE_PASSIVE);
-                    break;
-            }
-        }
-
-        final ManagedMap<String, String> props = new ManagedMap<>();
-        final List<Element> propertyElements =
-                ElementSupport.getChildElements(config,
-                        new QName(AttributeResolverNamespaceHandler.NAMESPACE, "LDAPProperty"));
-        for (final Element e : propertyElements) {
-            props.put(AttributeSupport.getAttributeValue(e, new QName("name")),
-                    AttributeSupport.getAttributeValue(e, new QName("value")));
+            connectionFactory = v2Parser.createPooledConnectionFactory(poolConfigElement);
         }
-        providerConfig.addPropertyValue("properties", props);
-        provider.addPropertyValue("providerConfig", providerConfig.getBeanDefinition());
-        connectionFactory.addPropertyValue("provider", provider.getBeanDefinition());
+        connectionFactory.addConstructorArgValue(v2Parser.createConnectionConfig(parserContext));
+        builder.addPropertyValue("connectionFactory", connectionFactory.getBeanDefinition());
 
         final String searchBuilderID = v2Parser.getBeanSearchBuilderID();
         if (searchBuilderID != null) {
@@ -173,18 +139,8 @@ public class LDAPDataConnectorParser extends AbstractDataConnectorParser {
             }
         }
 
-        final BeanDefinition connectionPool = v2Parser.createConnectionPool(connectionFactory.getBeanDefinition());
-        BeanDefinitionBuilder pooledConnectionFactory = null;
-        if (connectionPool != null) {
-            pooledConnectionFactory = BeanDefinitionBuilder.genericBeanDefinition(PooledConnectionFactory.class);
-            pooledConnectionFactory.addConstructorArgValue(connectionPool);
-            builder.addPropertyValue("connectionFactory", pooledConnectionFactory.getBeanDefinition());
-        } else {
-            builder.addPropertyValue("connectionFactory", connectionFactory.getBeanDefinition());
-        }
-
-        final BeanDefinition searchExecutor = v2Parser.createSearchExecutor(props);
-        builder.addPropertyValue("searchExecutor", searchExecutor);
+        final BeanDefinition searchOperation = v2Parser.createSearchOperation();
+        builder.addPropertyValue("searchOperation", searchOperation);
 
         final String mappingStrategyID = AttributeSupport.getAttributeValue(config, new QName("mappingStrategyRef"));
         if (mappingStrategyID != null) {
@@ -200,12 +156,7 @@ public class LDAPDataConnectorParser extends AbstractDataConnectorParser {
         if (validatorID != null) {
             builder.addPropertyReference("validator", validatorID);
         } else {
-            if (pooledConnectionFactory != null) {
-                builder.addPropertyValue("validator",
-                        v2Parser.createValidator(pooledConnectionFactory.getBeanDefinition()));
-            } else {
-                builder.addPropertyValue("validator", v2Parser.createValidator(connectionFactory.getBeanDefinition()));
-            }
+            builder.addPropertyValue("validator", v2Parser.createValidator(connectionFactory.getBeanDefinition()));
         }
         
         final String resultCacheBeanID = CacheConfigParser.getBeanResultCacheID(config);
@@ -219,6 +170,24 @@ public class LDAPDataConnectorParser extends AbstractDataConnectorParser {
         builder.setDestroyMethodName("destroy");
     }
 
+    /** Get the Pool configuration <ConnectionPool> element contents, warning if there is more than one.
+     * @return the <ConnectionPool> or null if there isn't one.
+     */
+    @Nullable Element getConnectionPoolElement(final Element element) {
+        final List<Element> poolConfigElements =
+            ElementSupport.getChildElementsByTagNameNS(element,
+                AttributeResolverNamespaceHandler.NAMESPACE, "ConnectionPool");
+        if (poolConfigElements.isEmpty()) {
+            return null;
+        }
+        if (poolConfigElements.size() > 1) {
+            log.warn("{} Only one <ConnectionPool> should be specified; only the first has been consulted.",
+                getLogPrefix());
+        }
+
+        return poolConfigElements.get(0);
+    }
+
     // Checkstyle: CyclomaticComplexity|MethodLength ON
 
     /**
@@ -278,22 +247,14 @@ public class LDAPDataConnectorParser extends AbstractDataConnectorParser {
                 connectionConfig.addPropertyValue("useStartTLS", useStartTLS);
             }
             if (connectTimeout != null) {
-                final BeanDefinitionBuilder timeout =
-                        BeanDefinitionBuilder.rootBeanDefinition(V2Parser.class, "buildDuration");
-                timeout.addConstructorArgValue(connectTimeout);
-                timeout.addConstructorArgValue(1);
-                connectionConfig.addPropertyValue("connectTimeout", timeout.getBeanDefinition());
+                connectionConfig.addPropertyValue("connectTimeout", connectTimeout);
             } else {
-                connectionConfig.addPropertyValue("connectTimeout", 3000);
+                connectionConfig.addPropertyValue("connectTimeout", Duration.ofSeconds(3));
             }
             if (responseTimeout != null) {
-                final BeanDefinitionBuilder timeout =
-                        BeanDefinitionBuilder.rootBeanDefinition(V2Parser.class, "buildDuration");
-                timeout.addConstructorArgValue(responseTimeout);
-                timeout.addConstructorArgValue(1);
-                connectionConfig.addPropertyValue("responseTimeout", timeout.getBeanDefinition());
+                connectionConfig.addPropertyValue("responseTimeout", responseTimeout);
             } else {
-                connectionConfig.addPropertyValue("responseTimeout", 3000);
+                connectionConfig.addPropertyValue("responseTimeout", Duration.ofSeconds(3));
             }
             final BeanDefinitionBuilder sslConfig = BeanDefinitionBuilder.genericBeanDefinition(SslConfig.class);
             sslConfig.addPropertyValue("credentialConfig", createCredentialConfig(parserContext));
@@ -317,8 +278,28 @@ public class LDAPDataConnectorParser extends AbstractDataConnectorParser {
                 }
             }
             if (principal != null || principalCredential != null || authenticationType != null) {
-                connectionConfig.addPropertyValue("connectionInitializer", connectionInitializer.getBeanDefinition());
+                connectionConfig.addPropertyValue("connectionInitializers", connectionInitializer.getBeanDefinition());
             }
+            final String connectionStrategy = AttributeSupport.getAttributeValue(
+                configElement, new QName("connectionStrategy"));
+            if (connectionStrategy == null) {
+                connectionConfig.addPropertyValue("connectionStrategy", new ActivePassiveConnectionStrategy());
+            } else {
+                switch (connectionStrategy) {
+                case "ROUND_ROBIN":
+                    connectionConfig.addPropertyValue("connectionStrategy", new RoundRobinConnectionStrategy());
+                    break;
+
+                case "RANDOM":
+                    connectionConfig.addPropertyValue("connectionStrategy", new RandomConnectionStrategy());
+                    break;
+
+                default:
+                    connectionConfig.addPropertyValue("connectionStrategy", new ActivePassiveConnectionStrategy());
+                    break;
+                }
+            }
+
             return connectionConfig.getBeanDefinition();
         }
         // CheckStyle: CyclomaticComplexity ON
@@ -453,12 +434,10 @@ public class LDAPDataConnectorParser extends AbstractDataConnectorParser {
         /**
          * Creates a new search executor bean definition from a v2 XML configuration.
          *
-         * @param props ldap properties
-         *
          * @return search executor bean definition
          */
         // CheckStyle: CyclomaticComplexity|MethodLength OFF
-        @Nonnull public BeanDefinition createSearchExecutor(final ManagedMap<String, String> props) {
+        @Nonnull public BeanDefinition createSearchOperation() {
             final String baseDn = AttributeSupport.getAttributeValue(configElement, new QName("baseDN"));
             final String searchScope = AttributeSupport.getAttributeValue(configElement, new QName("searchScope"));
             final String derefAliases = AttributeSupport.getAttributeValue(configElement, new QName("derefAliases"));
@@ -468,43 +447,30 @@ public class LDAPDataConnectorParser extends AbstractDataConnectorParser {
             final String lowercaseAttributeNames =
                     AttributeSupport.getAttributeValue(configElement, new QName("lowercaseAttributeNames"));
 
-            final BeanDefinitionBuilder searchExecutor =
-                    BeanDefinitionBuilder.genericBeanDefinition(SearchExecutor.class);
+            final BeanDefinitionBuilder searchRequest =
+                BeanDefinitionBuilder.genericBeanDefinition(SearchRequest.class);
             if (baseDn != null) {
-                searchExecutor.addPropertyValue("baseDn", baseDn);
+                searchRequest.addPropertyValue("baseDn", baseDn);
             }
             if (searchScope != null) {
-                searchExecutor.addPropertyValue("searchScope", searchScope);
+                searchRequest.addPropertyValue("searchScope", searchScope);
             }
             if (derefAliases != null) {
-                searchExecutor.addPropertyValue("derefAliases", derefAliases);
-            } else if (props.containsKey("java.naming.ldap.derefAliases")) {
-                searchExecutor.addPropertyValue(
-                  "derefAliases", props.get("java.naming.ldap.derefAliases").toUpperCase());
+                searchRequest.addPropertyValue("derefAliases", derefAliases);
             }
             if (searchTimeLimit != null) {
-                final BeanDefinitionBuilder duration =
-                        BeanDefinitionBuilder.rootBeanDefinition(V2Parser.class, "buildDuration");
-                duration.addConstructorArgValue(searchTimeLimit);
-                duration.addConstructorArgValue(1);
-                searchExecutor.addPropertyValue("timeLimit", duration.getBeanDefinition());
+                searchRequest.addPropertyValue("timeLimit", searchTimeLimit);
             } else {
-                searchExecutor.addPropertyValue("timeLimit", 3000);
+                searchRequest.addPropertyValue("timeLimit", Duration.ofSeconds(3));
             }
             if (maxResultSize != null) {
-                searchExecutor.addPropertyValue("sizeLimit", maxResultSize);
+                searchRequest.addPropertyValue("sizeLimit", maxResultSize);
             } else {
-                searchExecutor.addPropertyValue("sizeLimit", 1);
+                searchRequest.addPropertyValue("sizeLimit", 1);
             }
 
-            final BeanDefinitionBuilder handlers =
-                    BeanDefinitionBuilder.rootBeanDefinition(V2Parser.class, "buildSearchEntryHandlers");
-            handlers.addConstructorArgValue(lowercaseAttributeNames);
-            searchExecutor.addPropertyValue("searchEntryHandlers", handlers.getBeanDefinition());
-
-            final List<Element> returnAttrsElements = ElementSupport.getChildElementsByTagNameNS(configElement, 
+            final List<Element> returnAttrsElements = ElementSupport.getChildElementsByTagNameNS(configElement,
                     AttributeResolverNamespaceHandler.NAMESPACE, "ReturnAttributes");
-            
             if (!returnAttrsElements.isEmpty()) {
                 if (returnAttrsElements.size() > 1) {
                     log.warn("{} Only one <ReturnAttributes> element can be specified; "+
@@ -515,12 +481,11 @@ public class LDAPDataConnectorParser extends AbstractDataConnectorParser {
                 final BeanDefinitionBuilder returnAttrs =
                         BeanDefinitionBuilder.rootBeanDefinition(V2Parser.class, "buildStringList");
                 returnAttrs.addConstructorArgValue(ElementSupport.getElementContentAsString(returnAttrsElement));
-                searchExecutor.addPropertyValue("returnAttributes", returnAttrs.getBeanDefinition());
+                searchRequest.addPropertyValue("returnAttributes", returnAttrs.getBeanDefinition());
             }
 
             final List<Element> binaryAttrsElements = ElementSupport.getChildElementsByTagNameNS(configElement,
               AttributeResolverNamespaceHandler.NAMESPACE, "BinaryAttributes");
-
             if (!binaryAttrsElements.isEmpty()) {
                 if (binaryAttrsElements.size() > 1) {
                     log.warn("{} Only one <BinaryAttributes> element can be specified; "+
@@ -531,83 +496,53 @@ public class LDAPDataConnectorParser extends AbstractDataConnectorParser {
                 final BeanDefinitionBuilder binaryAttrs =
                   BeanDefinitionBuilder.rootBeanDefinition(V2Parser.class, "buildStringList");
                 binaryAttrs.addConstructorArgValue(ElementSupport.getElementContentAsString(binaryAttrsElement));
-                searchExecutor.addPropertyValue("binaryAttributes", binaryAttrs.getBeanDefinition());
-            } else if (props.containsKey("java.naming.ldap.attributes.binary")) {
-                final BeanDefinitionBuilder binaryAttrs =
-                  BeanDefinitionBuilder.rootBeanDefinition(V2Parser.class, "buildStringList");
-                binaryAttrs.addConstructorArgValue(props.get("java.naming.ldap.attributes.binary"));
-                searchExecutor.addPropertyValue("binaryAttributes", binaryAttrs.getBeanDefinition());
+                searchRequest.addPropertyValue("binaryAttributes", binaryAttrs.getBeanDefinition());
             }
 
-            return searchExecutor.getBeanDefinition();
-        }
-        // CheckStyle: CyclomaticComplexity|MethodLength ON
-
-        /** Get the Pool configuration <ConnectionPool> element contents, warning if there is more than one.
-         * @return the <ConnectionPool> or null if there isn't one.
-         */
-        @Nullable Element getConnectionPoolElement() {
-            final List<Element> poolConfigElements =
-                    ElementSupport.getChildElementsByTagNameNS(configElement,
-                            AttributeResolverNamespaceHandler.NAMESPACE, "ConnectionPool");
-            if (poolConfigElements.isEmpty()) {
-                return null;
-            }
-            if (poolConfigElements.size() > 1) {
-                log.warn("{} Only one <ConnectionPool> should be specified; only the first has been consulted.",
-                        getLogPrefix());
-            }
+            final BeanDefinitionBuilder searchOperation =
+                BeanDefinitionBuilder.genericBeanDefinition(SearchOperation.class);
+            searchOperation.addPropertyValue("request", searchRequest.getBeanDefinition());
+            final BeanDefinitionBuilder handlers =
+                BeanDefinitionBuilder.rootBeanDefinition(V2Parser.class, "buildEntryHandlers");
+            handlers.addConstructorArgValue(lowercaseAttributeNames);
+            searchOperation.addPropertyValue("entryHandlers", handlers.getBeanDefinition());
 
-            return poolConfigElements.get(0);
+            return searchOperation.getBeanDefinition();
         }
-        
+        // CheckStyle: CyclomaticComplexity|MethodLength ON
         // CheckStyle: CyclomaticComplexity ON
 
         /**
-         * Creates a new connection pool bean definition from a v2 XML configuration.
-         * 
-         * @param connectionFactory used by the connection pool
-         * 
-         * @return connection pool bean definition
+         * Initializes the supplied connectionFactory with configuration from the supplied config element.
+         *
+         * @param  poolConfigElement to parse configuration from
+         *
+         * @return pooled connection factory bean definition builder
          */
         // CheckStyle: MethodLength OFF
-        @Nullable public BeanDefinition createConnectionPool(final BeanDefinition connectionFactory) {
+        public BeanDefinitionBuilder createPooledConnectionFactory(final Element poolConfigElement) {
+
+            final BeanDefinitionBuilder connectionFactory = BeanDefinitionBuilder.genericBeanDefinition(
+                PooledConnectionFactory.class);
+            connectionFactory.addPropertyValue("name", "resolver-pool");
 
-            final Element poolConfigElement = getConnectionPoolElement();
-            if (null == poolConfigElement) {
-                return null;
-            }
             final String blockWaitTime =
                     AttributeSupport.getAttributeValue(poolConfigElement, new QName("blockWaitTime"));
             final String expirationTime =
                     AttributeSupport.getAttributeValue(poolConfigElement, new QName("expirationTime"));
 
-            final BeanDefinitionBuilder pool =
-                    BeanDefinitionBuilder.rootBeanDefinition(V2Parser.class, "buildConnectionPool");
-            pool.addConstructorArgValue(AttributeSupport.getAttributeValue(configElement, new QName("blockWhenEmpty")));
             if (blockWaitTime != null) {
-                final BeanDefinitionBuilder duration =
-                        BeanDefinitionBuilder.rootBeanDefinition(V2Parser.class, "buildDuration");
-                duration.addConstructorArgValue(blockWaitTime);
-                duration.addConstructorArgValue(1);
-                pool.addPropertyValue("blockWaitTime", duration.getBeanDefinition());
+                connectionFactory.addPropertyValue("blockWaitTime", blockWaitTime);
+            } else {
+                connectionFactory.addPropertyValue("blockWaitTime", Duration.ZERO);
             }
             if (expirationTime != null) {
-                final BeanDefinitionBuilder period =
-                        BeanDefinitionBuilder.rootBeanDefinition(V2Parser.class, "buildDuration");
-                period.addConstructorArgValue(expirationTime);
-                period.addConstructorArgValue(2000);
-                final BeanDefinitionBuilder idle =
-                        BeanDefinitionBuilder.rootBeanDefinition(V2Parser.class, "buildDuration");
-                idle.addConstructorArgValue(expirationTime);
-                idle.addConstructorArgValue(1000);
                 final BeanDefinitionBuilder strategy =
                         BeanDefinitionBuilder.genericBeanDefinition(IdlePruneStrategy.class);
-                strategy.addConstructorArgValue(period.getBeanDefinition());
-                strategy.addConstructorArgValue(idle.getBeanDefinition());
-                pool.addPropertyValue("pruneStrategy", strategy.getBeanDefinition());
+                strategy.addConstructorArgValue(expirationTime);
+                connectionFactory.addPropertyValue("pruneStrategy", strategy.getBeanDefinition());
             }
-            pool.addPropertyValue("poolConfig", createPoolConfig());
+            connectionFactory.addPropertyValue("poolConfig", createPoolConfig(poolConfigElement));
 
             final BeanDefinitionBuilder validator =
                     BeanDefinitionBuilder.rootBeanDefinition(V2Parser.class, "buildSearchValidator");
@@ -617,40 +552,36 @@ public class LDAPDataConnectorParser extends AbstractDataConnectorParser {
                     AttributeSupport.getAttributeValue(poolConfigElement, new QName("validateDN")));
             validator.addConstructorArgValue(
                     AttributeSupport.getAttributeValue(poolConfigElement, new QName("validateFilter")));
-            pool.addPropertyValue("validator", validator.getBeanDefinition());
+            validator.addConstructorArgValue(
+                    AttributeSupport.getAttributeValue(poolConfigElement, new QName("validateTimerPeriod")));
+            connectionFactory.addPropertyValue("validator", validator.getBeanDefinition());
 
-            pool.addPropertyValue("connectionFactory", connectionFactory);
             final String failFastInitialize =
                     AttributeSupport.getAttributeValue(poolConfigElement, new QName("failFastInitialize"));
             if (failFastInitialize != null) {
                 // V4 Deprecations
                 DeprecationSupport.warnOnce(ObjectType.ATTRIBUTE, "failfastInitialize (on a ConnectionPool element)", 
                         null, "failfastInitialize (on a DataConnector)");
-                pool.addPropertyValue("failFastInitialize", failFastInitialize);
+                connectionFactory.addPropertyValue("failFastInitialize", failFastInitialize);
             }
-            pool.setInitMethodName("initialize");
-            return pool.getBeanDefinition();
+            connectionFactory.setInitMethodName("initialize");
+            return connectionFactory;
         }
 
         // CheckStyle: MethodLength ON
 
         /**
          * Creates a new pool config bean definition from a v2 XML configuration.
-         * 
+         *
+         * @param  poolConfigElement to parse configuration from
+         *
          * @return pool config bean definition
          */
-        @Nullable protected BeanDefinition createPoolConfig() {
-            final Element poolConfigElement = getConnectionPoolElement();
-            if (poolConfigElement == null) {
-                return null;
-            }
-
+        @Nullable protected BeanDefinition createPoolConfig(final Element poolConfigElement) {
             final String minPoolSize = AttributeSupport.getAttributeValue(poolConfigElement, new QName("minPoolSize"));
             final String maxPoolSize = AttributeSupport.getAttributeValue(poolConfigElement, new QName("maxPoolSize"));
             final String validatePeriodically =
                     AttributeSupport.getAttributeValue(poolConfigElement, new QName("validatePeriodically"));
-            final String validateTimerPeriod =
-                    AttributeSupport.getAttributeValue(poolConfigElement, new QName("validateTimerPeriod"));
 
             final BeanDefinitionBuilder poolConfig = BeanDefinitionBuilder.genericBeanDefinition(PoolConfig.class);
             if (minPoolSize == null) {
@@ -666,16 +597,6 @@ public class LDAPDataConnectorParser extends AbstractDataConnectorParser {
             if (validatePeriodically != null) {
                 poolConfig.addPropertyValue("validatePeriodically", validatePeriodically);
             }
-            if (validateTimerPeriod != null) {
-                final BeanDefinitionBuilder period =
-                        BeanDefinitionBuilder.rootBeanDefinition(V2Parser.class, "buildDuration");
-                period.addConstructorArgValue(validateTimerPeriod);
-                // Convert to seconds.
-                period.addConstructorArgValue(1000);
-                poolConfig.addPropertyValue("validatePeriod", period.getBeanDefinition());
-            } else {
-                poolConfig.addPropertyValue("validatePeriod", 1800);
-            }
             return poolConfig.getBeanDefinition();
         }
 
@@ -753,19 +674,6 @@ public class LDAPDataConnectorParser extends AbstractDataConnectorParser {
         }
 
         /**
-         * Converts the supplied duration to milliseconds and divides it by the divisor. Useful for modifying durations
-         * while resolving property replacement.
-         * 
-         * @param duration the duration (which may have gone through spring translation from iso to long)
-         * @param divisor to modify the duration with
-         * 
-         * @return result of the division
-         */
-        public static long buildDuration(@Nonnull final Duration duration, final long divisor) {
-            return duration.toMillis() / divisor;
-        } 
-
-        /**
          * Converts the supplied value to a list of strings delimited by {@link XMLConstants#LIST_DELIMITERS} and comma.
          * 
          * @param value to convert to a list
@@ -777,34 +685,21 @@ public class LDAPDataConnectorParser extends AbstractDataConnectorParser {
         }
 
         /**
-         * Returns a soft limit connection pool if blockWhenEmpty is false, otherwise return a blocking connection pool.
-         * 
-         * @param blockWhenEmpty boolean string indicating the type of blocking connection pool
-         * 
-         * @return soft limit or blocking connection pool
-         */
-        @Nonnull public static BlockingConnectionPool buildConnectionPool(@Nullable final String blockWhenEmpty) {
-            BlockingConnectionPool pool = null;
-            if (blockWhenEmpty == null || Boolean.valueOf(blockWhenEmpty)) {
-                pool = new BlockingConnectionPool();
-            } else {
-                pool = new SoftLimitConnectionPool();
-            }
-            pool.setName("resolver-pool");
-            return pool;
-        }
-
-        /**
          * Returns a search validator or null if validatePeriodically is false.
          *
          * @param validatePeriodically whether to create a search validator
          * @param validateDN baseDN to search on
          * @param validateFilter to search with
+         * @param validatePeriod on which to search
          *
          * @return  search validator or null
          */
-        @Nullable public static SearchValidator buildSearchValidator(@Nullable final String validatePeriodically,
-                @Nullable final String validateDN, @Nullable final String validateFilter) {
+        @Nullable public static SearchConnectionValidator buildSearchValidator(
+            @Nullable final String validatePeriodically,
+            @Nullable final String validateDN,
+            @Nullable final String validateFilter,
+            @Nullable final String validatePeriod)
+        {
             if (!Boolean.valueOf(validatePeriodically)) {
                 return null;
             }
@@ -817,15 +712,20 @@ public class LDAPDataConnectorParser extends AbstractDataConnectorParser {
             } else {
                 searchRequest.setBaseDn("");
             }
-            final SearchFilter searchFilter = new SearchFilter();
+            final FilterTemplate template = new FilterTemplate();
             if (validateFilter != null) {
-                searchFilter.setFilter(validateFilter);
+                template.setFilter(validateFilter);
             } else {
-                searchFilter.setFilter("(objectClass=*)");
+                template.setFilter("(objectClass=*)");
             }
-            searchRequest.setSearchFilter(searchFilter);
-            final SearchValidator validator = new SearchValidator();
+            searchRequest.setFilter(template);
+            final SearchConnectionValidator validator = new SearchConnectionValidator();
             validator.setSearchRequest(searchRequest);
+            if (validatePeriod != null) {
+                validator.setValidatePeriod(Duration.parse(validatePeriod));
+            } else {
+                validator.setValidatePeriod(Duration.ofMinutes(30));
+            }
             return validator;
         }
 
@@ -834,11 +734,11 @@ public class LDAPDataConnectorParser extends AbstractDataConnectorParser {
          * Adds a {@link CaseChangeEntryHandler} if lowercaseAttributeNames is true. 
          * 
          * @param lowercaseAttributeNames boolean string value
-         * @return list of search entry handlers
+         * @return list of ldap entry handlers
          */
-        @Nonnull public static List<SearchEntryHandler> buildSearchEntryHandlers(
+        @Nonnull public static List<LdapEntryHandler> buildEntryHandlers(
                 @Nullable final String lowercaseAttributeNames) {
-            final List<SearchEntryHandler> handlers = new ArrayList<>();
+            final List<LdapEntryHandler> handlers = new ArrayList<>();
             handlers.add(new DnAttributeEntryHandler());
             if (Boolean.valueOf(lowercaseAttributeNames)) {
                 final CaseChangeEntryHandler entryHandler = new CaseChangeEntryHandler();
diff --git a/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/SearchResultMappingStrategy.java b/idp-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/impl/StringToDurationConverter.java
similarity index 66%
copy from idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/SearchResultMappingStrategy.java
copy to idp-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/impl/StringToDurationConverter.java
index 92fc2d1..76701dd 100644
--- a/idp-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/SearchResultMappingStrategy.java
+++ b/idp-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/impl/StringToDurationConverter.java
@@ -15,15 +15,19 @@
  * limitations under the License.
  */
 
-package net.shibboleth.idp.attribute.resolver.dc.ldap.impl;
+package net.shibboleth.idp.attribute.resolver.spring.impl;
 
-import org.ldaptive.SearchResult;
-
-import net.shibboleth.idp.attribute.resolver.dc.MappingStrategy;
+import java.time.Duration;
+import org.springframework.core.convert.converter.Converter;
 
 /**
- * Strategy for mapping from a {@link SearchResult} to a collection of 
- * {@link net.shibboleth.idp.attribute.IdPAttribute}s.
+ * Spring converter that produces a {@link java.time.Duration} from it's ISO time format.
  */
-public interface SearchResultMappingStrategy extends MappingStrategy<SearchResult> {
-}
\ No newline at end of file
+public class StringToDurationConverter implements Converter<String, Duration> {
+
+    @Override
+    public Duration convert(final String s)
+    {
+        return Duration.parse(s);
+    }
+}
diff --git a/idp-attribute-resolver-spring/src/test/java/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/LDAPDataConnectorParserTest.java b/idp-attribute-resolver-spring/src/test/java/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/LDAPDataConnectorParserTest.java
index c4deec9..5fcb94b 100644
--- a/idp-attribute-resolver-spring/src/test/java/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/LDAPDataConnectorParserTest.java
+++ b/idp-attribute-resolver-spring/src/test/java/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/LDAPDataConnectorParserTest.java
@@ -30,23 +30,17 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.Map;
 import java.util.stream.Collectors;
 
 import org.ldaptive.BindConnectionInitializer;
 import org.ldaptive.ConnectionConfig;
 import org.ldaptive.DefaultConnectionFactory;
-import org.ldaptive.DerefAliases;
-import org.ldaptive.SearchExecutor;
-import org.ldaptive.SearchScope;
-import org.ldaptive.pool.BlockingConnectionPool;
+import org.ldaptive.PooledConnectionFactory;
+import org.ldaptive.SearchConnectionValidator;
+import org.ldaptive.SearchOperation;
 import org.ldaptive.pool.IdlePruneStrategy;
-import org.ldaptive.pool.PoolConfig;
-import org.ldaptive.pool.PooledConnectionFactory;
-import org.ldaptive.pool.SearchValidator;
-import org.ldaptive.provider.ProviderConfig;
-import org.ldaptive.ssl.CredentialConfig;
+import org.ldaptive.pool.PoolConfig;import org.ldaptive.ssl.CredentialConfig;
 import org.ldaptive.ssl.SslConfig;
 import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
 import org.springframework.context.support.GenericApplicationContext;
@@ -188,35 +182,30 @@ public class LDAPDataConnectorParserTest {
                 getLdapDataConnector(new String[] {"net/shibboleth/idp/attribute/resolver/spring/dc/ldap/resolver/ldap-attribute-resolver-v2-minimal.xml"});
         assertNotNull(dataConnector);
         assertTrue(dataConnector.isFailFastInitialize());
-        assertEquals(Duration.ZERO, dataConnector.getNoRetryDelay());
+        assertEquals(dataConnector.getNoRetryDelay(), Duration.ZERO);
         final DefaultConnectionFactory connFactory = (DefaultConnectionFactory) dataConnector.getConnectionFactory();
         assertNotNull(connFactory);
 
         final ConnectionConfig connConfig = connFactory.getConnectionConfig();
         assertNotNull(connConfig);
-        assertEquals("ldap://localhost:10389", connConfig.getLdapUrl());
-        assertEquals(false, connConfig.getUseSSL());
-        assertEquals(false, connConfig.getUseStartTLS());
-        final BindConnectionInitializer connInitializer = (BindConnectionInitializer) connConfig.getConnectionInitializer();
-        assertEquals("cn=Directory Manager", connInitializer.getBindDn());
-        assertEquals("password", connInitializer.getBindCredential().getString());
-        assertEquals(3000, connConfig.getConnectTimeout());
-        assertEquals(3000, connConfig.getResponseTimeout());
+        assertEquals(connConfig.getLdapUrl(), "ldap://localhost:10389");
+        assertFalse(connConfig.getUseStartTLS());
+        final BindConnectionInitializer connInitializer = (BindConnectionInitializer) connConfig.getConnectionInitializers()[0];
+        assertEquals(connInitializer.getBindDn(), "cn=Directory Manager");
+        assertEquals(connInitializer.getBindCredential().getString(), "password");
+        assertEquals(connConfig.getConnectTimeout(), Duration.ofSeconds(3));
+        assertEquals(connConfig.getResponseTimeout(), Duration.ofSeconds(3));
 
         final SslConfig sslConfig = connFactory.getConnectionConfig().getSslConfig();
         assertNotNull(sslConfig);
         final CredentialConfig credentialConfig = sslConfig.getCredentialConfig();
         assertNotNull(credentialConfig);
 
-        final ProviderConfig<?> providerConfig = connFactory.getProvider().getProviderConfig();
-        assertNotNull(providerConfig);
-        assertTrue(providerConfig.getProperties().isEmpty());
-
-        final SearchExecutor searchExecutor = dataConnector.getSearchExecutor();
-        assertNotNull(searchExecutor);
-        assertEquals("", searchExecutor.getBaseDn());
-        assertNull(searchExecutor.getSearchFilter());
-        assertEquals(3000, searchExecutor.getTimeLimit());
+        final SearchOperation searchOperation = dataConnector.getSearchOperation();
+        assertNotNull(searchOperation);
+        assertEquals(searchOperation.getRequest().getBaseDn(), "");
+        assertNull(searchOperation.getRequest().getFilter());
+        assertEquals(searchOperation.getRequest().getTimeLimit(), Duration.ofSeconds(3));
 
         final ConnectionFactoryValidator validator = (ConnectionFactoryValidator) dataConnector.getValidator();
         assertNotNull(validator);
@@ -250,49 +239,42 @@ public class LDAPDataConnectorParserTest {
         assertEquals(Duration.ZERO, dataConnector.getNoRetryDelay());
         final PooledConnectionFactory connFactory = (PooledConnectionFactory) dataConnector.getConnectionFactory();
         assertNotNull(connFactory);
-        final BlockingConnectionPool connPool = (BlockingConnectionPool) connFactory.getConnectionPool();
-        assertNotNull(connPool);
-        assertEquals(0, connPool.getBlockWaitTime());
-        assertEquals("resolver-pool", connPool.getName());
-        final PoolConfig poolConfig = connPool.getPoolConfig();
+        assertEquals(connFactory.getBlockWaitTime(), Duration.ZERO);
+        assertEquals("resolver-pool", connFactory.getName());
+        final PoolConfig poolConfig = connFactory.getPoolConfig();
         assertNotNull(poolConfig);
-        assertEquals(0, poolConfig.getMinPoolSize());
-        assertEquals(3, poolConfig.getMaxPoolSize());
+        assertEquals(poolConfig.getMinPoolSize(), 0);
+        assertEquals(poolConfig.getMaxPoolSize(), 3);
         assertFalse(poolConfig.isValidatePeriodically());
-        assertEquals(1800, poolConfig.getValidatePeriod());
-        assertTrue(connPool.getFailFastInitialize());
-        assertNull(connPool.getValidator());
+        assertEquals(connFactory.getValidator().getValidatePeriod(), Duration.ofMinutes(30));
+        assertTrue(connFactory.getFailFastInitialize());
+        assertNotNull(connFactory.getValidator());
 
-        final IdlePruneStrategy pruneStrategy = (IdlePruneStrategy) connPool.getPruneStrategy();
+        final IdlePruneStrategy pruneStrategy = (IdlePruneStrategy) connFactory.getPruneStrategy();
         assertNotNull(pruneStrategy);
-        assertEquals(300, pruneStrategy.getPrunePeriod());
-        assertEquals(600, pruneStrategy.getIdleTime());
+        assertEquals(pruneStrategy.getPrunePeriod(), Duration.ofMinutes(5));
+        assertEquals(pruneStrategy.getIdleTime(), Duration.ofMinutes(10));
 
-        final ConnectionConfig connConfig = connPool.getConnectionFactory().getConnectionConfig();
+        final ConnectionConfig connConfig = connFactory.getConnectionConfig();
         assertNotNull(connConfig);
-        assertEquals("ldap://localhost:10389", connConfig.getLdapUrl());
-        assertEquals(false, connConfig.getUseSSL());
-        assertEquals(false, connConfig.getUseStartTLS());
-        final BindConnectionInitializer connInitializer = (BindConnectionInitializer) connConfig.getConnectionInitializer();
-        assertEquals("cn=Directory Manager", connInitializer.getBindDn());
-        assertEquals("password", connInitializer.getBindCredential().getString());
-        assertEquals(3000, connConfig.getConnectTimeout());
-        assertEquals(3000, connConfig.getResponseTimeout());
-
-        final SslConfig sslConfig = connPool.getConnectionFactory().getConnectionConfig().getSslConfig();
+        assertEquals(connConfig.getLdapUrl(), "ldap://localhost:10389");
+        assertFalse(connConfig.getUseStartTLS());
+        final BindConnectionInitializer connInitializer = (BindConnectionInitializer) connConfig.getConnectionInitializers()[0];
+        assertEquals(connInitializer.getBindDn(), "cn=Directory Manager");
+        assertEquals(connInitializer.getBindCredential().getString(), "password");
+        assertEquals(connConfig.getConnectTimeout(), Duration.ofSeconds(3));
+        assertEquals(connConfig.getResponseTimeout(), Duration.ofSeconds(3));
+
+        final SslConfig sslConfig = connFactory.getConnectionConfig().getSslConfig();
         assertNotNull(sslConfig);
         final CredentialConfig credentialConfig = sslConfig.getCredentialConfig();
         assertNotNull(credentialConfig);
 
-        final ProviderConfig<?> providerConfig = connPool.getConnectionFactory().getProvider().getProviderConfig();
-        assertNotNull(providerConfig);
-        assertTrue(providerConfig.getProperties().isEmpty());
-
-        final SearchExecutor searchExecutor = dataConnector.getSearchExecutor();
-        assertNotNull(searchExecutor);
-        assertEquals("", searchExecutor.getBaseDn());
-        assertNull(searchExecutor.getSearchFilter());
-        assertEquals(3000, searchExecutor.getTimeLimit());
+        final SearchOperation searchOperation = dataConnector.getSearchOperation();
+        assertNotNull(searchOperation);
+        assertEquals(searchOperation.getRequest().getBaseDn(), "");
+        assertNull(searchOperation.getRequest().getFilter());
+        assertEquals(searchOperation.getRequest().getTimeLimit(), Duration.ofSeconds(3));
 
         final ConnectionFactoryValidator validator = (ConnectionFactoryValidator) dataConnector.getValidator();
         assertNotNull(validator);
@@ -318,74 +300,6 @@ public class LDAPDataConnectorParserTest {
         assertNotNull(attrs.get("entryDN"));
     }
 
-    @Test public void v2JndiConfig() throws Exception {
-        final LDAPDataConnector dataConnector =
-          getLdapDataConnector(new String[] {"net/shibboleth/idp/attribute/resolver/spring/dc/ldap/resolver/ldap-attribute-resolver-v2-jndi.xml"});
-        assertNotNull(dataConnector);
-        assertEquals(Duration.ofMinutes(5), dataConnector.getNoRetryDelay());
-        final DefaultConnectionFactory connFactory = (DefaultConnectionFactory) dataConnector.getConnectionFactory();
-        assertNotNull(connFactory);
-
-        final ConnectionConfig connConfig = connFactory.getConnectionConfig();
-        assertNotNull(connConfig);
-        assertEquals("ldap://localhost:10389", connConfig.getLdapUrl());
-        assertEquals(false, connConfig.getUseSSL());
-        assertEquals(true, connConfig.getUseStartTLS());
-        final BindConnectionInitializer connInitializer = (BindConnectionInitializer) connConfig.getConnectionInitializer();
-        assertEquals("cn=Directory Manager", connInitializer.getBindDn());
-        assertEquals("password", connInitializer.getBindCredential().getString());
-        assertEquals(2000, connConfig.getConnectTimeout());
-        assertEquals(4000, connConfig.getResponseTimeout());
-
-        final SslConfig sslConfig = connFactory.getConnectionConfig().getSslConfig();
-        assertNotNull(sslConfig);
-        final CredentialConfig credentialConfig = sslConfig.getCredentialConfig();
-        assertNotNull(credentialConfig);
-
-        final ProviderConfig<?> providerConfig = connFactory.getProvider().getProviderConfig();
-        assertNotNull(providerConfig);
-        assertEquals("value1", providerConfig.getProperties().get("name1"));
-        assertEquals("finding", providerConfig.getProperties().get("java.naming.ldap.derefAliases"));
-        assertEquals("jpegPhoto employeeNumber", providerConfig.getProperties().get("java.naming.ldap.attributes.binary"));
-
-        final SearchExecutor searchExecutor = dataConnector.getSearchExecutor();
-        assertNotNull(searchExecutor);
-        assertEquals("ou=people,dc=shibboleth,dc=net", searchExecutor.getBaseDn());
-        assertNull(searchExecutor.getSearchFilter());
-        assertEquals(7000, searchExecutor.getTimeLimit());
-        assertEquals(SearchScope.SUBTREE, searchExecutor.getSearchScope());
-        assertEquals(DerefAliases.FINDING, searchExecutor.getDerefAliases());
-        assertEquals(new String[] {"jpegPhoto", "employeeNumber"}, searchExecutor.getBinaryAttributes());
-
-        final ConnectionFactoryValidator validator = (ConnectionFactoryValidator) dataConnector.getValidator();
-        assertNotNull(validator);
-        assertFalse(validator.isThrowValidateError());
-        assertNotNull(validator.getConnectionFactory());
-
-        final ExecutableSearchBuilder<?> searchBuilder = dataConnector.getExecutableSearchBuilder();
-        assertNotNull(searchBuilder);
-
-        final StringAttributeValueMappingStrategy mappingStrategy =(StringAttributeValueMappingStrategy) dataConnector.getMappingStrategy();
-        assertNotNull(mappingStrategy);
-        assertTrue(mappingStrategy.isNoResultAnError());
-        assertTrue(mappingStrategy.isMultipleResultsAnError());
-
-        assertNull(dataConnector.getResultsCache());
-
-        dataConnector.initialize();
-        final AttributeResolutionContext context =
-          TestSources.createResolutionContext(TestSources.PRINCIPAL_ID, TestSources.IDP_ENTITY_ID,
-            TestSources.SP_ENTITY_ID);
-        final Map<String, IdPAttribute> attrs = dataConnector.resolve(context);
-        assertNotNull(attrs);
-        assertEquals(5, attrs.size());
-        assertNotNull(attrs.get("cn"));
-        assertNotNull(attrs.get("sn"));
-        assertNotNull(attrs.get("jpegPhoto"));
-        assertEquals(attrs.get("employeeNumber").getValues().get(0).getNativeValue(), "C2J20hMNp7NlUwQ+");
-        assertNotNull(attrs.get("entryDN"));
-    }
-
     @Test public void v2PropsConfig() throws Exception {
         final Resource props = new ClassPathResource("net/shibboleth/idp/attribute/resolver/spring/dc/ldap/ldap-v2.properties");
         final LDAPDataConnector dataConnector =
@@ -525,61 +439,51 @@ public class LDAPDataConnectorParserTest {
     protected void doTest(final LDAPDataConnector dataConnector) throws ResolutionException {
 
         final String id = dataConnector.getId();
-        assertEquals("myLDAP", id);
-        assertEquals(Duration.ofMinutes(5), dataConnector.getNoRetryDelay());
+        assertEquals(id, "myLDAP");
+        assertEquals(dataConnector.getNoRetryDelay(), Duration.ofMinutes(5));
 
         final PooledConnectionFactory connFactory = (PooledConnectionFactory) dataConnector.getConnectionFactory();
         assertNotNull(connFactory);
-        final BlockingConnectionPool connPool = (BlockingConnectionPool) connFactory.getConnectionPool();
-        assertNotNull(connPool);
-        assertEquals(5000, connPool.getBlockWaitTime());
-        assertEquals("resolver-pool", connPool.getName());
-        final PoolConfig poolConfig = connPool.getPoolConfig();
+        assertEquals(connFactory.getBlockWaitTime(), Duration.ofSeconds(5));
+        assertEquals(connFactory.getName(), "resolver-pool");
+        final PoolConfig poolConfig = connFactory.getPoolConfig();
         assertNotNull(poolConfig);
-        assertEquals(5, poolConfig.getMinPoolSize());
-        assertEquals(10, poolConfig.getMaxPoolSize());
+        assertEquals(poolConfig.getMinPoolSize(), 5);
+        assertEquals(poolConfig.getMaxPoolSize(), 10);
         assertTrue(poolConfig.isValidatePeriodically());
-        assertEquals(900, poolConfig.getValidatePeriod());
-        assertFalse(connPool.getFailFastInitialize());
+        assertEquals(connFactory.getValidator().getValidatePeriod(), Duration.ofMinutes(15));
+        assertFalse(connFactory.getFailFastInitialize());
 
-        final SearchValidator searchValidator = (SearchValidator) connPool.getValidator();
+        final SearchConnectionValidator searchValidator = (SearchConnectionValidator) connFactory.getValidator();
         assertNotNull(searchValidator);
-        assertEquals("dc=shibboleth,dc=net", searchValidator.getSearchRequest().getBaseDn());
-        assertEquals("(ou=people)", searchValidator.getSearchRequest().getSearchFilter().getFilter());
+        assertEquals(searchValidator.getSearchRequest().getBaseDn(), "dc=shibboleth,dc=net");
+        //assertEquals(searchValidator.getSearchRequest().getFilter().toString(), "(ou=people)");
 
-        final IdlePruneStrategy pruneStrategy = (IdlePruneStrategy) connPool.getPruneStrategy();
+        final IdlePruneStrategy pruneStrategy = (IdlePruneStrategy) connFactory.getPruneStrategy();
         assertNotNull(pruneStrategy);
-        assertEquals(300, pruneStrategy.getPrunePeriod());
-        assertEquals(600, pruneStrategy.getIdleTime());
+        assertEquals(pruneStrategy.getPrunePeriod(), Duration.ofMinutes(5));
+        assertEquals(pruneStrategy.getIdleTime(), Duration.ofMinutes(10));
 
-        final ConnectionConfig connConfig = connPool.getConnectionFactory().getConnectionConfig();
+        final ConnectionConfig connConfig = connFactory.getConnectionConfig();
         assertNotNull(connConfig);
-        assertEquals("ldap://localhost:10389", connConfig.getLdapUrl());
-        assertEquals(false, connConfig.getUseSSL());
-        assertEquals(true, connConfig.getUseStartTLS());
-        final BindConnectionInitializer connInitializer = (BindConnectionInitializer) connConfig.getConnectionInitializer();
-        assertEquals("cn=Directory Manager", connInitializer.getBindDn());
-        assertEquals("password", connInitializer.getBindCredential().getString());
-        assertEquals(2000, connConfig.getConnectTimeout());
-        assertEquals(4000, connConfig.getResponseTimeout());
-
-        final SslConfig sslConfig = connPool.getConnectionFactory().getConnectionConfig().getSslConfig();
+        assertEquals(connConfig.getLdapUrl(), "ldap://localhost:10389");
+        assertTrue(connConfig.getUseStartTLS());
+        final BindConnectionInitializer connInitializer = (BindConnectionInitializer) connConfig.getConnectionInitializers()[0];
+        assertEquals(connInitializer.getBindDn(), "cn=Directory Manager");
+        assertEquals(connInitializer.getBindCredential().getString(), "password");
+        assertEquals(connConfig.getConnectTimeout(), Duration.ofSeconds(2));
+        assertEquals(connConfig.getResponseTimeout(), Duration.ofSeconds(4));
+
+        final SslConfig sslConfig = connFactory.getConnectionConfig().getSslConfig();
         assertNotNull(sslConfig);
         final CredentialConfig credentialConfig = sslConfig.getCredentialConfig();
         assertNotNull(credentialConfig);
 
-        final Map<String, Object> providerProps = new HashMap<>();
-        providerProps.put("name1", "value1");
-        providerProps.put("name2", "value2");
-        final ProviderConfig<?> providerConfig = connPool.getConnectionFactory().getProvider().getProviderConfig();
-        assertNotNull(providerConfig);
-        assertEquals(providerProps, providerConfig.getProperties());
-
-        final SearchExecutor searchExecutor = dataConnector.getSearchExecutor();
-        assertNotNull(searchExecutor);
-        assertEquals("ou=people,dc=shibboleth,dc=net", searchExecutor.getBaseDn());
-        assertNull(searchExecutor.getSearchFilter());
-        assertEquals(7000, searchExecutor.getTimeLimit());
+        final SearchOperation searchOperation = dataConnector.getSearchOperation();
+        assertNotNull(searchOperation);
+        assertEquals(searchOperation.getRequest().getBaseDn(), "ou=people,dc=shibboleth,dc=net");
+        assertNull(searchOperation.getRequest().getFilter());
+        assertEquals(searchOperation.getRequest().getTimeLimit(), Duration.ofSeconds(7));
 
         final ConnectionFactoryValidator validator = (ConnectionFactoryValidator) dataConnector.getValidator();
         assertNotNull(validator);
diff --git a/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/ldap-attribute-resolver-spring-context.xml b/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/ldap-attribute-resolver-spring-context.xml
index e58fa3d..b64ef23 100644
--- a/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/ldap-attribute-resolver-spring-context.xml
+++ b/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/ldap-attribute-resolver-spring-context.xml
@@ -1,68 +1,57 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xmlns:c="http://www.springframework.org/schema/c"
     xmlns:p="http://www.springframework.org/schema/p"
-    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
-    <bean id="connectionFactory" class="org.ldaptive.pool.PooledConnectionFactory">
-        <property name="connectionPool">
-            <bean class="org.ldaptive.pool.BlockingConnectionPool" init-method="initialize" p:blockWaitTime="5000" p:failFastInitialize="false" p:name="resolver-pool">
-                <constructor-arg index="0">
-                    <bean class="org.ldaptive.pool.PoolConfig" p:minPoolSize="5" p:maxPoolSize="10"
-                        p:validatePeriodically="true" p:validatePeriod="900" />
-                </constructor-arg>
-                <constructor-arg index="1">
-                    <bean class="org.ldaptive.DefaultConnectionFactory">
-                        <property name="provider">
-                            <bean class="org.ldaptive.provider.jndi.JndiProvider">
-                                <property name="providerConfig">
-                                    <bean class="org.ldaptive.provider.jndi.JndiProviderConfig">
-                                        <property name="properties">
-                                            <map>
-                                                <entry key="name1" value="value1" />
-                                                <entry key="name2" value="value2" />
-                                            </map>
-                                        </property>
-                                    </bean>
-                                </property>
-                            </bean>
-                        </property>
-                        <property name="connectionConfig">
-                            <bean class="org.ldaptive.ConnectionConfig" p:ldapUrl="ldap://localhost:10389"
-                                p:useStartTLS="true" p:connectTimeout="2000" p:responseTimeout="4000">
-                                <property name="connectionInitializer">
-                                    <bean class="org.ldaptive.BindConnectionInitializer" p:bindDn="cn=Directory Manager"
-                                        p:bindCredential="password" />
-                                </property>
-                                <property name="sslConfig">
-                                    <bean class="org.ldaptive.ssl.SslConfig">
-                                        <property name="credentialConfig">
-                                            <bean class="org.ldaptive.ssl.X509CredentialConfig"
-                                                p:trustCertificates="file:src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/server.crt"
-                                                p:authenticationCertificate="file:src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/client.crt"
-                                                p:authenticationKey="file:src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/client.pkcs8" />
-                                        </property>
-                                    </bean>
-                                </property>
-                            </bean>
-                        </property>
-                    </bean>
-                </constructor-arg>
-                <property name="validator">
-                    <bean class="org.ldaptive.pool.SearchValidator">
-                        <property name="searchRequest">
-                            <bean class="org.ldaptive.SearchRequest">
-                                <constructor-arg value="dc=shibboleth,dc=net" />
-                                <constructor-arg value="(ou=people)" />
-                            </bean>
+    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd">
+
+    <bean id="conversionService"
+          class="org.springframework.context.support.ConversionServiceFactoryBean">
+        <property name="converters">
+            <set>
+                <bean class="net.shibboleth.idp.attribute.resolver.spring.impl.StringToDurationConverter"/>
+            </set>
+        </property>
+    </bean>
+
+    <bean id="connectionFactory" class="org.ldaptive.PooledConnectionFactory" init-method="initialize" p:blockWaitTime="PT5S" p:failFastInitialize="false" p:name="resolver-pool">
+        <constructor-arg index="0">
+            <bean class="org.ldaptive.ConnectionConfig" p:ldapUrl="ldap://localhost:10389"
+                  p:useStartTLS="true" p:connectTimeout="PT2S" p:responseTimeout="PT4S">
+                <property name="connectionInitializers">
+                    <bean class="org.ldaptive.BindConnectionInitializer" p:bindDn="cn=Directory Manager"
+                          p:bindCredential="password" />
+                </property>
+                <property name="sslConfig">
+                    <bean class="org.ldaptive.ssl.SslConfig">
+                        <property name="credentialConfig">
+                            <bean class="org.ldaptive.ssl.X509CredentialConfig"
+                                  p:trustCertificates="file:src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/server.crt"
+                                  p:authenticationCertificate="file:src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/client.crt"
+                                  p:authenticationKey="file:src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/client.pkcs8" />
                         </property>
                     </bean>
                 </property>
-                <property name="pruneStrategy">
-                    <bean class="org.ldaptive.pool.IdlePruneStrategy" p:prunePeriod="300" p:idleTime="600" />
+            </bean>
+        </constructor-arg>
+        <constructor-arg index="1">
+            <bean class="org.ldaptive.pool.PoolConfig" p:minPoolSize="5" p:maxPoolSize="10" p:validatePeriodically="true" />
+        </constructor-arg>
+        <property name="validator">
+            <bean class="org.ldaptive.SearchConnectionValidator" p:validatePeriod="PT15M">
+                <property name="searchRequest">
+                    <bean class="org.ldaptive.SearchRequest" c:_0="dc=shibboleth,dc=net" c:_1="(ou=people)" />
                 </property>
             </bean>
         </property>
+        <property name="pruneStrategy">
+            <bean class="org.ldaptive.pool.IdlePruneStrategy" p:prunePeriod="PT5M" p:idleTime="PT10M" />
+        </property>
+    </bean>
+    <bean class="org.ldaptive.SearchOperation">
+        <property name="request">
+            <bean class="org.ldaptive.SearchRequest" p:baseDn="ou=people,dc=shibboleth,dc=net" p:returnAttributes="uid,homephone,mail" p:timeLimit="PT7S"/>
+        </property>
     </bean>
-    <bean class="org.ldaptive.SearchExecutor" p:baseDn="ou=people,dc=shibboleth,dc=net" p:returnAttributes="uid,homephone,mail" p:timeLimit="7000" />
     <bean id="cache" class="com.google.common.cache.Cache" factory-bean="cacheBuilderExternal" factory-method="build" />
     <bean id="filter" class="net.shibboleth.idp.attribute.resolver.dc.ldap.impl.TemplatedExecutableSearchFilterBuilder"
         p:templateText="(uid=${resolutionContext.principal})" p:velocityEngine-ref="shibboleth.VelocityEngine"
diff --git a/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/ldap-attribute-resolver-spring-props-context.xml b/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/ldap-attribute-resolver-spring-props-context.xml
index 23b4676..bdabca2 100644
--- a/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/ldap-attribute-resolver-spring-props-context.xml
+++ b/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/ldap-attribute-resolver-spring-props-context.xml
@@ -1,74 +1,62 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xmlns:c="http://www.springframework.org/schema/c"
     xmlns:p="http://www.springframework.org/schema/p"
-    xmlns:context="http://www.springframework.org/schema/context"
-    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
-                        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">
-    <bean id="connectionFactory" class="org.ldaptive.pool.PooledConnectionFactory">
-        <property name="connectionPool">
-            <bean class="org.ldaptive.pool.BlockingConnectionPool" init-method="initialize"
-                p:blockWaitTime="%{connectionPool.blockWaitTime}" p:failFastInitialize="%{connectionPool.failFastInitialize}" p:name="resolver-pool">
-                <constructor-arg index="0">
-                    <bean class="org.ldaptive.pool.PoolConfig" p:minPoolSize="%{connectionPool.minPoolSize}"
-                        p:maxPoolSize="%{connectionPool.maxPoolSize}" p:validatePeriodically="%{connectionPool.validatePeriodically}"
-                        p:validatePeriod="%{connectionPool.validatePeriod}" />
-                </constructor-arg>
-                <constructor-arg index="1">
-                    <bean class="org.ldaptive.DefaultConnectionFactory">
-                        <property name="provider">
-                            <bean class="org.ldaptive.provider.jndi.JndiProvider">
-                                <property name="providerConfig">
-                                    <bean class="org.ldaptive.provider.jndi.JndiProviderConfig">
-                                        <property name="properties">
-                                            <map>
-                                                <entry key="name1" value="%{jndi.property1}" />
-                                                <entry key="name2" value="%{jndi.property2}" />
-                                            </map>
-                                        </property>
-                                    </bean>
-                                </property>
-                            </bean>
-                        </property>
-                        <property name="connectionConfig">
-                            <bean class="org.ldaptive.ConnectionConfig" p:ldapUrl="%{connectionConfig.ldapURL}"
-                                p:useStartTLS="%{connectionConfig.useStartTLS}"
-                                p:connectTimeout="%{connectionConfig.connectTimeout}" p:responseTimeout="%{connectionConfig.responseTimeout}">
-                                <property name="connectionInitializer">
-                                    <bean class="org.ldaptive.BindConnectionInitializer" p:bindDn="%{connectionConfig.bindDn}"
-                                        p:bindCredential="%{connectionConfig.bindCredential}" />
-                                </property>
-                                <property name="sslConfig">
-                                    <bean class="org.ldaptive.ssl.SslConfig">
-                                        <property name="credentialConfig">
-                                            <bean class="org.ldaptive.ssl.X509CredentialConfig"
-                                                p:trustCertificates="file:src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/server.crt"
-                                                p:authenticationCertificate="file:src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/client.crt"
-                                                p:authenticationKey="file:src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/client.pkcs8" />
-                                        </property>
-                                    </bean>
-                                </property>
-                            </bean>
-                        </property>
-                    </bean>
-                </constructor-arg>
-                <property name="validator">
-                    <bean class="org.ldaptive.pool.SearchValidator">
-                        <property name="searchRequest">
-                            <bean class="org.ldaptive.SearchRequest">
-                                <constructor-arg value="%{connectionPool.validator.baseDn}" />
-                                <constructor-arg value="%{connectionPool.validator.filter}" />
-                            </bean>
+    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd">
+
+    <bean id="conversionService"
+          class="org.springframework.context.support.ConversionServiceFactoryBean">
+        <property name="converters">
+            <set>
+                <bean class="net.shibboleth.idp.attribute.resolver.spring.impl.StringToDurationConverter"/>
+            </set>
+        </property>
+    </bean>
+
+    <bean id="connectionFactory" class="org.ldaptive.PooledConnectionFactory" init-method="initialize"
+          p:blockWaitTime="%{connectionPool.blockWaitTime}" p:failFastInitialize="%{connectionPool.failFastInitialize}" p:name="resolver-pool">
+        <constructor-arg index="0">
+            <bean class="org.ldaptive.ConnectionConfig" p:ldapUrl="%{connectionConfig.ldapURL}"
+                  p:useStartTLS="%{connectionConfig.useStartTLS}"
+                  p:connectTimeout="%{connectionConfig.connectTimeout}" p:responseTimeout="%{connectionConfig.responseTimeout}">
+                <property name="connectionInitializers">
+                    <bean class="org.ldaptive.BindConnectionInitializer" p:bindDn="%{connectionConfig.bindDn}"
+                          p:bindCredential="%{connectionConfig.bindCredential}" />
+                </property>
+                <property name="sslConfig">
+                    <bean class="org.ldaptive.ssl.SslConfig">
+                        <property name="credentialConfig">
+                            <bean class="org.ldaptive.ssl.X509CredentialConfig"
+                                  p:trustCertificates="file:src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/server.crt"
+                                  p:authenticationCertificate="file:src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/client.crt"
+                                  p:authenticationKey="file:src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/client.pkcs8" />
                         </property>
                     </bean>
                 </property>
-                <property name="pruneStrategy">
-                    <bean class="org.ldaptive.pool.IdlePruneStrategy" p:prunePeriod="%{connectionPool.pruneStrategy.prunePeriod}"
-                        p:idleTime="%{connectionPool.pruneStrategy.idleTime}" />
+            </bean>
+        </constructor-arg>
+        <constructor-arg index="1">
+            <bean class="org.ldaptive.pool.PoolConfig" p:minPoolSize="%{connectionPool.minPoolSize}"
+                  p:maxPoolSize="%{connectionPool.maxPoolSize}" p:validatePeriodically="%{connectionPool.validatePeriodically}" />
+        </constructor-arg>
+        <property name="validator">
+            <bean class="org.ldaptive.SearchConnectionValidator" p:validatePeriod="%{connectionPool.validatePeriod}">
+                <property name="searchRequest">
+                    <bean class="org.ldaptive.SearchRequest" c:_0="%{connectionPool.validator.baseDn}" c:_1="%{connectionPool.validator.filter}" />
                 </property>
             </bean>
         </property>
+        <property name="pruneStrategy">
+            <bean class="org.ldaptive.pool.IdlePruneStrategy" p:prunePeriod="%{connectionPool.pruneStrategy.prunePeriod}"
+                  p:idleTime="%{connectionPool.pruneStrategy.idleTime}" />
+        </property>
+    </bean>
+
+    <bean class="org.ldaptive.SearchOperation">
+        <property name="request">
+            <bean class="org.ldaptive.SearchRequest" p:baseDn="%{search.baseDn}" p:returnAttributes="%{search.returnAttributes}" p:timeLimit="%{search.timeLimit}" />
+        </property>
     </bean>
-    <bean class="org.ldaptive.SearchExecutor" p:baseDn="%{search.baseDn}" p:returnAttributes="%{search.returnAttributes}" p:timeLimit="%{search.timeLimit}" />
     <bean id="cacheBuilder" class="com.google.common.cache.CacheBuilder" factory-method="from">
         <constructor-arg value="%{cache.cacheBuilderSpec}" />
     </bean>
diff --git a/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/ldap-v2.properties b/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/ldap-v2.properties
index c1a509c..8fb999d 100644
--- a/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/ldap-v2.properties
+++ b/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/ldap-v2.properties
@@ -14,9 +14,6 @@ search.filter=(uid=${resolutionContext.principal})
 search.returnAttributes=uid,homephone,mail
 search.timeLimit=PT7S
 
-jndi.property1=value1
-jndi.property2=value2
-
 connectionPool.blockWaitTime=PT5S
 connectionPool.expirationTime=PT10M
 connectionPool.minPoolSize=5
diff --git a/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/ldap-v3.properties b/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/ldap-v3.properties
index 69203de..8d90f8f 100644
--- a/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/ldap-v3.properties
+++ b/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/ldap-v3.properties
@@ -1,7 +1,7 @@
 connectionConfig.ldapURL=ldap://localhost:10389
 connectionConfig.useStartTLS=true
-connectionConfig.connectTimeout=2000
-connectionConfig.responseTimeout=4000
+connectionConfig.connectTimeout=PT2S
+connectionConfig.responseTimeout=PT4S
 connectionConfig.bindDn=cn=Directory Manager
 connectionConfig.bindCredential=password
 
@@ -12,20 +12,17 @@ noRetryDelay=PT5M
 search.baseDn=ou=people,dc=shibboleth,dc=net
 search.filter=(uid=${resolutionContext.principal})
 search.returnAttributes=uid,homephone,mail
-search.timeLimit=7000
+search.timeLimit=PT7S
 
-jndi.property1=value1
-jndi.property2=value2
-
-connectionPool.blockWaitTime=5000
+connectionPool.blockWaitTime=PT5S
 connectionPool.minPoolSize=5
 connectionPool.maxPoolSize=10
 connectionPool.validatePeriodically=true
-connectionPool.validatePeriod=900
+connectionPool.validatePeriod=PT15M
 connectionPool.validator.baseDn=dc=shibboleth,dc=net
 connectionPool.validator.filter=(ou=people)
-connectionPool.pruneStrategy.prunePeriod=300
-connectionPool.pruneStrategy.idleTime=600
+connectionPool.pruneStrategy.prunePeriod=PT5M
+connectionPool.pruneStrategy.idleTime=PT10M
 connectionPool.failFastInitialize=false
 
 cache.cacheBuilderSpec=expireAfterAccess=10s,maximumSize=25
diff --git a/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/resolver/ldap-attribute-resolver-v2-hybrid.xml b/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/resolver/ldap-attribute-resolver-v2-hybrid.xml
index 7ab6cff..67321e5 100644
--- a/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/resolver/ldap-attribute-resolver-v2-hybrid.xml
+++ b/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/resolver/ldap-attribute-resolver-v2-hybrid.xml
@@ -7,8 +7,6 @@
         useStartTLS="true" searchTimeLimit="PT7S"
         executableSearchBuilderRef="filter" mappingStrategyRef="mappings" validatorRef="validator" noRetryDelay="PT5M"
         connectTimeout="PT2S" responseTimeout="PT4S">
-        <LDAPProperty name="name1" value="value1" />
-        <LDAPProperty name="name2" value="value2" />
         <StartTLSTrustCredential xsi:type="security:X509Inline" xmlns:security="urn:mace:shibboleth:2.0:security"
             id="LDAPtoIdPCredential">
             <security:Certificate>
diff --git a/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/resolver/ldap-attribute-resolver-v2-jndi.xml b/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/resolver/ldap-attribute-resolver-v2-jndi.xml
deleted file mode 100644
index 0d0942c..0000000
--- a/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/resolver/ldap-attribute-resolver-v2-jndi.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<AttributeResolver xmlns="urn:mace:shibboleth:2.0:resolver" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:sec="urn:mace:shibboleth:2.0:security"
-        xsi:schemaLocation="urn:mace:shibboleth:2.0:resolver http://shibboleth.net/schema/idp/shibboleth-attribute-resolver.xsd
-                            urn:mace:shibboleth:2.0:security http://shibboleth.net/schema/idp/shibboleth-security.xsd">
-
-    <DataConnector id="myLDAP" xsi:type="LDAPDirectory"
-        ldapURL="ldap://localhost:10389"
-        baseDN="ou=people,dc=shibboleth,dc=net"
-        principal="cn=Directory Manager"
-        principalCredential="password"
-        useStartTLS="true"
-        searchTimeLimit="PT7S"
-        noResultIsError="true"
-        multipleResultsIsError="true"
-        noRetryDelay="PT5M"
-        trustFile="net/shibboleth/idp/attribute/resolver/spring/dc/ldap/resolver/ldap.crt"
-        connectTimeout="PT2S"
-        responseTimeout="PT4S">
-        
-        <LDAPProperty name="name1" value="value1"/>
-        <LDAPProperty name="java.naming.ldap.attributes.binary" value="jpegPhoto employeeNumber"/>
-        <LDAPProperty name="java.naming.ldap.derefAliases" value="finding"/>
-
-        <ReturnAttributes>cn sn jpegPhoto employeeNumber</ReturnAttributes>
-
-        <FilterTemplate>
-            <![CDATA[
-                (uid=${resolutionContext.principal})
-            ]]>
-        </FilterTemplate>
-        
-    </DataConnector>
-    
-</AttributeResolver>
diff --git a/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/resolver/ldap-attribute-resolver-v2-multi.xml b/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/resolver/ldap-attribute-resolver-v2-multi.xml
index 960316c..c0ee482 100644
--- a/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/resolver/ldap-attribute-resolver-v2-multi.xml
+++ b/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/resolver/ldap-attribute-resolver-v2-multi.xml
@@ -54,11 +54,6 @@
             <sec:Certificate>src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/client.crt</sec:Certificate>
         </StartTLSTrustCredential>
 
-
-        <LDAPProperty name="name2" value="value2"/>
-                
-        <LDAPProperty name="name1" value="value1"/>
-        
         <ResultCacheBean>cache</ResultCacheBean>
         <ResultCacheBean>cache</ResultCacheBean>
         
diff --git a/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/resolver/ldap-attribute-resolver-v2-nosec.xml b/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/resolver/ldap-attribute-resolver-v2-nosec.xml
index 709a3e5..b0d1a51 100644
--- a/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/resolver/ldap-attribute-resolver-v2-nosec.xml
+++ b/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/resolver/ldap-attribute-resolver-v2-nosec.xml
@@ -42,12 +42,8 @@
             <sec:Certificate>src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/client.crt</sec:Certificate>
         </StartTLSAuthenticationCredential>
 
-        <LDAPProperty name="name2" value="value2"/>
-                
         <ReturnAttributes>a b g t</ReturnAttributes>
         
-        <LDAPProperty name="name1" value="value1"/>
-        
         <ReturnAttributes>a b g t</ReturnAttributes>
         
         <FilterTemplate>
diff --git a/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/resolver/ldap-attribute-resolver-v2-props.xml b/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/resolver/ldap-attribute-resolver-v2-props.xml
index 7dc1bf6..cc87424 100644
--- a/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/resolver/ldap-attribute-resolver-v2-props.xml
+++ b/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/resolver/ldap-attribute-resolver-v2-props.xml
@@ -21,8 +21,6 @@
             ]]>
         </FilterTemplate>
         <ReturnAttributes>%{search.returnAttributes}</ReturnAttributes>
-        <LDAPProperty name="name1" value="%{jndi.property1}"/>
-        <LDAPProperty name="name2" value="%{jndi.property2}"/>
         <StartTLSTrustCredential xsi:type="security:X509Inline" xmlns:security="urn:mace:shibboleth:2.0:security" id="LDAPtoIdPCredential">
             <security:Certificate>
               MIICATCCAWoCCQCZy4ovDlQTvDANBgkqhkiG9w0BAQUFADBFMRMwEQYKCZImiZPy
diff --git a/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/resolver/ldap-attribute-resolver-v2.xml b/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/resolver/ldap-attribute-resolver-v2.xml
index 508ddfd..ff1c684 100644
--- a/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/resolver/ldap-attribute-resolver-v2.xml
+++ b/idp-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/resolver/ldap-attribute-resolver-v2.xml
@@ -58,12 +58,8 @@
 
         <InputAttributeDefinition ref="321"/>
 
-        <LDAPProperty name="name2" value="value2"/>
-                
         <ReturnAttributes>a b g t employeeNumber</ReturnAttributes>
         
-        <LDAPProperty name="name1" value="value1"/>
-        
         <ReturnAttributes>a b g t employeeNumber</ReturnAttributes>
 
         <BinaryAttributes>g employeeNumber</BinaryAttributes>
diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AbstractTemplateSearchDnResolver.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AbstractTemplateSearchDnResolver.java
index 40497cf..66eaa9d 100644
--- a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AbstractTemplateSearchDnResolver.java
+++ b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AbstractTemplateSearchDnResolver.java
@@ -29,14 +29,14 @@ import org.apache.velocity.app.event.EventCartridge;
 import org.apache.velocity.app.event.ReferenceInsertionEventHandler;
 import org.apache.velocity.context.Context;
 import org.apache.velocity.exception.VelocityException;
-import org.ldaptive.SearchFilter;
-import org.ldaptive.auth.AbstractSearchDnResolver;
+import org.ldaptive.FilterTemplate;
+import org.ldaptive.auth.SearchDnResolver;
 import org.ldaptive.auth.User;
 
 /**
  * Base class for {@link Template} based search dn resolvers.
  */
-public abstract class AbstractTemplateSearchDnResolver extends AbstractSearchDnResolver {
+public abstract class AbstractTemplateSearchDnResolver extends SearchDnResolver {
 
     /** Template. */
     private final Template template;
@@ -66,8 +66,8 @@ public abstract class AbstractTemplateSearchDnResolver extends AbstractSearchDnR
         return template;
     }
 
-    @Override protected SearchFilter createSearchFilter(final User user) {
-        final SearchFilter filter = new SearchFilter();
+    @Override protected FilterTemplate createFilterTemplate(final User user) {
+        final FilterTemplate filter = new FilterTemplate();
         if (user != null && user.getContext() != null) {
             final VelocityContext context = (VelocityContext) user.getContext();
             final EventCartridge cartridge = new EventCartridge();
@@ -116,7 +116,7 @@ public abstract class AbstractTemplateSearchDnResolver extends AbstractSearchDnR
         }
 
         /**
-         * Returns {@link SearchFilter#encodeValue} if value is a string or byte array.
+         * Returns {@link FilterTemplate#encodeValue} if value is a string or byte array.
          * 
          * @param value to encode
          *
@@ -124,9 +124,9 @@ public abstract class AbstractTemplateSearchDnResolver extends AbstractSearchDnR
          */
         private Object encode(final Object value) {
             if (value instanceof String){ 
-                return SearchFilter.encodeValue((String) value);
+                return FilterTemplate.encodeValue((String) value);
             } else if (value instanceof byte[]) {
-                return SearchFilter.encodeValue((byte[]) value);
+                return FilterTemplate.encodeValue((byte[]) value);
             }
             return value;
         }
diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/PooledTemplateSearchDnResolver.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/PooledTemplateSearchDnResolver.java
deleted file mode 100644
index 76cc4b6..0000000
--- a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/PooledTemplateSearchDnResolver.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Licensed to the University Corporation for Advanced Internet Development,
- * Inc. (UCAID) under one or more contributor license agreements.  See the
- * NOTICE file distributed with this work for additional information regarding
- * copyright ownership. The UCAID licenses this file to You under the Apache
- * License, Version 2.0 (the "License"); you may not use this file except in
- * compliance with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.shibboleth.idp.authn;
-
-import java.util.Arrays;
-import org.apache.velocity.app.VelocityEngine;
-import org.apache.velocity.exception.VelocityException;
-import org.ldaptive.Connection;
-import org.ldaptive.LdapException;
-import org.ldaptive.pool.PooledConnectionFactory;
-import org.ldaptive.pool.PooledConnectionFactoryManager;
-
-/**
- * {@link net.shibboleth.utilities.java.support.velocity.Template}-based pooled search dn resolver.
- */
-public class PooledTemplateSearchDnResolver extends AbstractTemplateSearchDnResolver
-        implements PooledConnectionFactoryManager {
-
-    /** Connection factory. */
-    private PooledConnectionFactory factory;
-
-    /**
-     * Creates a new pooled template search DN resolver.
-     *
-     * @param engine velocity engine
-     * @param filter filter template
-     *
-     * @throws VelocityException if velocity is not configured properly or the filter template is invalid
-     */
-    public PooledTemplateSearchDnResolver(final VelocityEngine engine, final String filter) throws VelocityException {
-        super(engine, filter);
-    }
-
-    /**
-     * Creates a new pooled template search DN resolver.
-     *
-     * @param cf connection factory
-     * @param engine velocity engine
-     * @param filter filter template
-     *
-     * @throws VelocityException if velocity is not configured properly or the filter template is invalid
-     */
-    public PooledTemplateSearchDnResolver(final PooledConnectionFactory cf, final VelocityEngine engine,
-            final String filter) throws VelocityException {
-        super(engine, filter);
-        setConnectionFactory(cf);
-    }
-
-    @Override public PooledConnectionFactory getConnectionFactory() {
-        return factory;
-    }
-
-    @Override public void setConnectionFactory(final PooledConnectionFactory cf) {
-        factory = cf;
-    }
-
-    @Override protected Connection getConnection() throws LdapException {
-        return factory.getConnection();
-    }
-
-    @Override public String toString() {
-        return String.format(
-                "[%s@%d::factory=%s, templateName=%s, baseDn=%s, userFilter=%s, userFilterParameters=%s, "
-                        + "allowMultipleDns=%s, subtreeSearch=%s, derefAliases=%s, followReferrals=%s]",
-                getClass().getName(), hashCode(), factory, getTemplate().getTemplateName(), getBaseDn(),
-                getUserFilter(), Arrays.toString(getUserFilterParameters()), getAllowMultipleDns(), getSubtreeSearch(),
-                getDerefAliases(), getFollowReferrals());
-    }
-}
diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/TemplateSearchDnResolver.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/TemplateSearchDnResolver.java
index f0d415d..73768c0 100644
--- a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/TemplateSearchDnResolver.java
+++ b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/TemplateSearchDnResolver.java
@@ -20,18 +20,12 @@ package net.shibboleth.idp.authn;
 import java.util.Arrays;
 import org.apache.velocity.app.VelocityEngine;
 import org.apache.velocity.exception.VelocityException;
-import org.ldaptive.Connection;
 import org.ldaptive.ConnectionFactory;
-import org.ldaptive.ConnectionFactoryManager;
-import org.ldaptive.LdapException;
 
 /**
  * {@link net.shibboleth.utilities.java.support.velocity.Template}-based search dn resolver.
  */
-public class TemplateSearchDnResolver extends AbstractTemplateSearchDnResolver implements ConnectionFactoryManager {
-
-    /** Connection factory. */
-    private ConnectionFactory factory;
+public class TemplateSearchDnResolver extends AbstractTemplateSearchDnResolver {
 
     /**
      * Creates a new template search DN resolver.
@@ -60,26 +54,12 @@ public class TemplateSearchDnResolver extends AbstractTemplateSearchDnResolver i
         setConnectionFactory(cf);
     }
 
-    @Override public ConnectionFactory getConnectionFactory() {
-        return factory;
-    }
-
-    @Override public void setConnectionFactory(final ConnectionFactory cf) {
-        factory = cf;
-    }
-
-    @Override protected Connection getConnection() throws LdapException {
-        final Connection conn = factory.getConnection();
-        conn.open();
-        return conn;
-    }
-
     @Override public String toString() {
         return String.format(
                 "[%s@%d::factory=%s, templateName=%s, baseDn=%s, userFilter=%s, userFilterParameters=%s, "
-                        + "allowMultipleDns=%s, subtreeSearch=%s, derefAliases=%s, followReferrals=%s]",
-                getClass().getName(), hashCode(), factory, getTemplate().getTemplateName(), getBaseDn(),
+                        + "allowMultipleDns=%s, subtreeSearch=%s, derefAliases=%s]",
+                getClass().getName(), hashCode(), getConnectionFactory(), getTemplate().getTemplateName(), getBaseDn(),
                 getUserFilter(), Arrays.toString(getUserFilterParameters()), getAllowMultipleDns(), getSubtreeSearch(),
-                getDerefAliases(), getFollowReferrals());
+                getDerefAliases());
     }
 }
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/LDAPCredentialValidator.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/LDAPCredentialValidator.java
index b9e19f1..9d0c385 100644
--- a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/LDAPCredentialValidator.java
+++ b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/LDAPCredentialValidator.java
@@ -33,6 +33,7 @@ import net.shibboleth.utilities.java.support.component.ComponentSupport;
 import net.shibboleth.utilities.java.support.logic.Constraint;
 
 import org.apache.velocity.VelocityContext;
+import org.ldaptive.ConnectException;
 import org.ldaptive.Credential;
 import org.ldaptive.LdapException;
 import org.ldaptive.ResultCode;
@@ -141,7 +142,7 @@ public class LDAPCredentialValidator extends AbstractUsernamePasswordCredentialV
                             new Credential(usernamePasswordContext.getPassword()), returnAttributes);
             final AuthenticationResponse response = authenticator.authenticate(request);
             log.trace("{} Authentication response {}", getLogPrefix(), response);
-            if (response.getResult()) {
+            if (response.isSuccess()) {
                 log.info("{} Login by '{}' succeeded", getLogPrefix(), username);
                 authenticationContext.getSubcontext(
                         LDAPResponseContext.class, true).setAuthenticationResponse(response);
@@ -152,7 +153,7 @@ public class LDAPCredentialValidator extends AbstractUsernamePasswordCredentialV
                                 profileRequestContext,
                                 authenticationContext,
                                 String.format("%s:%s:%s", error != null ? error : "ACCOUNT_WARNING",
-                                        response.getResultCode(), response.getMessage()),
+                                        response.getResultCode(), response.getDiagnosticMessage()),
                                 AuthnEventIds.ACCOUNT_WARNING);
                     }
                 }
@@ -165,23 +166,25 @@ public class LDAPCredentialValidator extends AbstractUsernamePasswordCredentialV
             if (AuthenticationResultCode.DN_RESOLUTION_FAILURE == response.getAuthenticationResultCode()
                     || AuthenticationResultCode.INVALID_CREDENTIAL == response.getAuthenticationResultCode()) {
                 throw new LdapException(
-                        String.format("%s:%s", response.getAuthenticationResultCode(), response.getMessage()));
+                        String.format("%s:%s", response.getAuthenticationResultCode(), response.getDiagnosticMessage()));
             } else if (response.getAccountState() != null) {
                 final AccountState state = response.getAccountState();
                 eventToSignal = AuthnEventIds.ACCOUNT_ERROR;
                 throw new LdapException(
-                        String.format("%s:%s:%s", state.getError(), response.getResultCode(), response.getMessage())
-                        );
+                    String.format("%s:%s:%s", state.getError(), response.getResultCode(), response.getDiagnosticMessage()));
             } else if (response.getResultCode() == ResultCode.INVALID_CREDENTIALS) {
-                throw new LdapException(String.format("%s:%s", response.getResultCode(), response.getMessage()));
+                throw new LdapException(String.format("%s:%s", response.getResultCode(), response.getDiagnosticMessage()));
             } else {
                 eventToSignal = AuthnEventIds.AUTHN_EXCEPTION;
-                final LdapException e =
-                        new LdapException(response.getMessage(), response.getResultCode(), response.getMatchedDn(),
-                        response.getControls(), response.getReferralURLs(), response.getMessageId());
+                final LdapException e = new LdapException(response);
                 log.warn("{} Login by {} produced exception", getLogPrefix(), username, e);
                 throw e;
             }
+        } catch (final ConnectException e) {
+            if (errorHandler != null) {
+                errorHandler.handleError(profileRequestContext, authenticationContext, e, AuthnEventIds.AUTHN_EXCEPTION);
+            }
+            throw e;
         } catch (final LdapException e) {
             if (errorHandler != null) {
                 errorHandler.handleError(profileRequestContext, authenticationContext, e, eventToSignal);
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/principal/impl/LDAPPrincipalSerializer.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/principal/impl/LDAPPrincipalSerializer.java
index a81ea1d..c97b159 100644
--- a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/principal/impl/LDAPPrincipalSerializer.java
+++ b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/principal/impl/LDAPPrincipalSerializer.java
@@ -135,9 +135,9 @@ public class LDAPPrincipalSerializer extends AbstractPrincipalSerializer<String>
                             } else {
                                 final LdapAttribute attr = new LdapAttribute(e.getKey());
                                 for (final JsonValue v : (JsonArray) e.getValue()) {
-                                    attr.addStringValue(((JsonString) v).getString());
+                                    attr.addStringValues(((JsonString) v).getString());
                                 }
-                                entry.addAttribute(attr);
+                                entry.addAttributes(attr);
                             }
                         }
                     }
diff --git a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/DefaultAuthenticationResultSerializerTest.java b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/DefaultAuthenticationResultSerializerTest.java
index a69bb96..38789e2 100644
--- a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/DefaultAuthenticationResultSerializerTest.java
+++ b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/DefaultAuthenticationResultSerializerTest.java
@@ -51,7 +51,6 @@ import net.shibboleth.utilities.java.support.security.impl.BasicKeystoreKeyStrat
 
 import org.ldaptive.LdapAttribute;
 import org.ldaptive.LdapEntry;
-import org.ldaptive.SortBehavior;
 import org.ldaptive.jaas.LdapPrincipal;
 import org.springframework.core.io.ClassPathResource;
 import org.testng.Assert;
@@ -208,16 +207,26 @@ public class DefaultAuthenticationResultSerializerTest {
         serializer.initialize();
         
         final AuthenticationResult result = createResult("test", new Subject());
-        final LdapEntry entry = new LdapEntry(SortBehavior.SORTED);
-        entry.setDn("uid=1234,ou=people,dc=shibboleth,dc=net");
-        final LdapAttribute givenName = new LdapAttribute(SortBehavior.SORTED);
-        givenName.setName("givenName");
-        givenName.addStringValue("Bob", "Robert");
-        entry.addAttribute(
-                new LdapAttribute("cn", "Bob Cobb"),
-                givenName,
-                new LdapAttribute("sn", "Cobb"),
-                new LdapAttribute("mail", "bob at shibboleth.net"));
+        final LdapEntry entry = LdapEntry.sort(LdapEntry.builder()
+            .dn("uid=1234,ou=people,dc=shibboleth,dc=net")
+            .attributes(
+                LdapAttribute.builder()
+                    .name("givenName")
+                    .values("Bob", "Robert")
+                    .build(),
+                LdapAttribute.builder()
+                    .name("cn")
+                    .values("Bob Cobb")
+                    .build(),
+                LdapAttribute.builder()
+                    .name("sn")
+                    .values("Cobb")
+                    .build(),
+                LdapAttribute.builder()
+                    .name("mail")
+                    .values("bob at shibboleth.net")
+                    .build())
+            .build());
         result.getSubject().getPrincipals().add(new LdapPrincipal("bob", entry));
 
         final String s = serializer.serialize(result);
diff --git a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/LDAPCredentialValidatorTest.java b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/LDAPCredentialValidatorTest.java
index 1477529..9012461 100644
--- a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/LDAPCredentialValidatorTest.java
+++ b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/LDAPCredentialValidatorTest.java
@@ -17,6 +17,7 @@
 
 package net.shibboleth.idp.authn.impl;
 
+import java.time.ZonedDateTime;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
@@ -43,8 +44,8 @@ import org.ldaptive.auth.AuthenticationResponse;
 import org.ldaptive.auth.AuthenticationResponseHandler;
 import org.ldaptive.auth.AuthenticationResultCode;
 import org.ldaptive.auth.Authenticator;
-import org.ldaptive.auth.BindAuthenticationHandler;
 import org.ldaptive.auth.SearchDnResolver;
+import org.ldaptive.auth.SimpleBindAuthenticationHandler;
 import org.ldaptive.auth.ext.PasswordPolicyAccountState;
 import org.ldaptive.control.PasswordPolicyControl;
 import org.ldaptive.jaas.LdapPrincipal;
@@ -75,7 +76,7 @@ public class LDAPCredentialValidatorTest extends BaseAuthenticationContextTest {
 
     private TemplateSearchDnResolver dnResolver;
 
-    private BindAuthenticationHandler authHandler;
+    private SimpleBindAuthenticationHandler authHandler;
 
     private Authenticator authenticator;
 
@@ -103,7 +104,7 @@ public class LDAPCredentialValidatorTest extends BaseAuthenticationContextTest {
                 VelocityEngine.newVelocityEngine(), "(uid=$usernamePasswordContext.username)");
         dnResolver.setBaseDn("ou=people,dc=shibboleth,dc=net");
 
-        authHandler = new BindAuthenticationHandler(new DefaultConnectionFactory("ldap://localhost:10389"));
+        authHandler = new SimpleBindAuthenticationHandler(new DefaultConnectionFactory("ldap://localhost:10389"));
 
         authenticator = new Authenticator(dnResolver, authHandler);
     }
@@ -227,7 +228,7 @@ public class LDAPCredentialValidatorTest extends BaseAuthenticationContextTest {
         ac.setAttemptedFlow(authenticationFlows.get(0));
         
         validator.setAuthenticator(new Authenticator(dnResolver,
-                new BindAuthenticationHandler(new DefaultConnectionFactory("ldap://unknown:389"))));
+                new SimpleBindAuthenticationHandler(new DefaultConnectionFactory("ldap://unknown:389"))));
         validator.initialize();
         
         action.initialize();
@@ -238,9 +239,7 @@ public class LDAPCredentialValidatorTest extends BaseAuthenticationContextTest {
 
         Assert.assertNull(ac.getAuthenticationResult());
         LDAPResponseContext lrc = ac.getSubcontext(LDAPResponseContext.class);
-        Assert.assertNotNull(lrc.getAuthenticationResponse());
-        Assert.assertEquals(lrc.getAuthenticationResponse().getAuthenticationResultCode(),
-                AuthenticationResultCode.AUTHENTICATION_HANDLER_FAILURE);
+        Assert.assertNull(lrc);
 
         AuthenticationErrorContext aec = ac.getSubcontext(AuthenticationErrorContext.class);
         Assert.assertNotNull(aec);
@@ -334,11 +333,8 @@ public class LDAPCredentialValidatorTest extends BaseAuthenticationContextTest {
         ac.setAttemptedFlow(authenticationFlows.get(0));
 
         Authenticator errorAuthenticator = new Authenticator(dnResolver, authHandler);
-        errorAuthenticator.setAuthenticationResponseHandlers(new AuthenticationResponseHandler() {
-            public void handle(AuthenticationResponse response) throws LdapException {
-                response.setAccountState(new PasswordPolicyAccountState(PasswordPolicyControl.Error.PASSWORD_EXPIRED));
-            }
-        });
+        errorAuthenticator.setResponseHandlers(response ->
+            response.setAccountState(new PasswordPolicyAccountState(PasswordPolicyControl.Error.PASSWORD_EXPIRED)));
         validator.setAuthenticator(errorAuthenticator);
         validator.initialize();
         
@@ -369,12 +365,8 @@ public class LDAPCredentialValidatorTest extends BaseAuthenticationContextTest {
         ac.setAttemptedFlow(authenticationFlows.get(0));
 
         Authenticator errorAuthenticator = new Authenticator(dnResolver, authHandler);
-        errorAuthenticator.setAuthenticationResponseHandlers(new AuthenticationResponseHandler() {
-            public void handle(AuthenticationResponse response) throws LdapException {
-                response.setAccountState(
-                        new PasswordPolicyAccountState(PasswordPolicyControl.Error.CHANGE_AFTER_RESET));
-            }
-        });
+        errorAuthenticator.setResponseHandlers(response -> response.setAccountState(
+                new PasswordPolicyAccountState(PasswordPolicyControl.Error.CHANGE_AFTER_RESET)));
         validator.setAuthenticator(errorAuthenticator);
         validator.initialize();
         
@@ -415,12 +407,8 @@ public class LDAPCredentialValidatorTest extends BaseAuthenticationContextTest {
         ac.setAttemptedFlow(authenticationFlows.get(0));
 
         Authenticator warningAuthenticator = new Authenticator(dnResolver, authHandler);
-        warningAuthenticator.setAuthenticationResponseHandlers(new AuthenticationResponseHandler() {
-            public void handle(AuthenticationResponse response) throws LdapException {
-                response.setAccountState(
-                        new AccountState(new AccountState.DefaultWarning(java.util.Calendar.getInstance(), 10)));
-            }
-        });
+        warningAuthenticator.setResponseHandlers(response -> response.setAccountState(
+                new AccountState(new AccountState.DefaultWarning(ZonedDateTime.now(), 10))));
         validator.setAuthenticator(warningAuthenticator);
         validator.initialize();
         
diff --git a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/ValidateCredentialsTest.java b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/ValidateCredentialsTest.java
index 99e4e70..ae1a2e5 100644
--- a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/ValidateCredentialsTest.java
+++ b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/ValidateCredentialsTest.java
@@ -38,7 +38,7 @@ import net.shibboleth.utilities.java.support.velocity.VelocityEngine;
 import org.ldaptive.DefaultConnectionFactory;
 import org.ldaptive.auth.AuthenticationResultCode;
 import org.ldaptive.auth.Authenticator;
-import org.ldaptive.auth.BindAuthenticationHandler;
+import org.ldaptive.auth.SimpleBindAuthenticationHandler;
 import org.ldaptive.jaas.LdapPrincipal;
 import org.springframework.core.io.FileSystemResource;
 import org.springframework.mock.web.MockHttpServletRequest;
@@ -65,7 +65,7 @@ public class ValidateCredentialsTest extends BaseAuthenticationContextTest {
 
     private TemplateSearchDnResolver dnResolver;
 
-    private BindAuthenticationHandler authHandler;
+    private SimpleBindAuthenticationHandler authHandler;
 
     private Authenticator authenticator;
 
@@ -93,7 +93,7 @@ public class ValidateCredentialsTest extends BaseAuthenticationContextTest {
                 VelocityEngine.newVelocityEngine(), "(uid=$usernamePasswordContext.username)");
         dnResolver.setBaseDn("ou=people,dc=shibboleth,dc=net");
 
-        authHandler = new BindAuthenticationHandler(new DefaultConnectionFactory("ldap://localhost:10389"));
+        authHandler = new SimpleBindAuthenticationHandler(new DefaultConnectionFactory("ldap://localhost:10389"));
 
         authenticator = new Authenticator(dnResolver, authHandler);
     }
diff --git a/idp-conf/pom.xml b/idp-conf/pom.xml
index 426ca71..605aa9b 100644
--- a/idp-conf/pom.xml
+++ b/idp-conf/pom.xml
@@ -99,11 +99,6 @@
 
         <!-- Test Dependencies -->
         <dependency>
-            <groupId>org.ldaptive</groupId>
-            <artifactId>ldaptive-unboundid</artifactId>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
             <groupId>com.unboundid</groupId>
             <artifactId>unboundid-ldapsdk</artifactId>
             <scope>test</scope>
diff --git a/idp-conf/src/main/resources/conf/authn/ldap-authn-config.xml b/idp-conf/src/main/resources/conf/authn/ldap-authn-config.xml
index 44e1b60..1933084 100644
--- a/idp-conf/src/main/resources/conf/authn/ldap-authn-config.xml
+++ b/idp-conf/src/main/resources/conf/authn/ldap-authn-config.xml
@@ -18,9 +18,8 @@
     <!-- Connection Configuration -->
     <bean id="connectionConfig" class="org.ldaptive.ConnectionConfig" abstract="true" p:ldapUrl="%{idp.authn.LDAP.ldapURL}"
         p:useStartTLS="%{idp.authn.LDAP.useStartTLS:true}"
-        p:useSSL="%{idp.authn.LDAP.useSSL:false}"
-        p:connectTimeoutDuration="%{idp.authn.LDAP.connectTimeout:PT3S}"
-        p:responseTimeoutDuration="%{idp.authn.LDAP.responseTimeout:PT3S}"
+        p:connectTimeout="%{idp.authn.LDAP.connectTimeout:PT3S}"
+        p:responseTimeout="%{idp.authn.LDAP.responseTimeout:PT3S}"
         p:sslConfig-ref="sslConfig" />
 
     <alias name="%{idp.authn.LDAP.sslConfig:certificateTrust}" alias="sslConfig" />
@@ -38,10 +37,9 @@
     </bean>
 
     <!-- Authentication handler -->
-    <bean id="authHandler" class="org.ldaptive.auth.PooledBindAuthenticationHandler" p:connectionFactory-ref="bindPooledConnectionFactory" />
-    <bean id="bindPooledConnectionFactory" class="org.ldaptive.pool.PooledConnectionFactory" p:connectionPool-ref="bindConnectionPool" />
-    <bean id="bindConnectionPool" class="org.ldaptive.pool.BlockingConnectionPool" parent="connectionPool"
-        p:connectionFactory-ref="bindConnectionFactory" p:name="bind-pool" />
+    <bean id="authHandler" class="org.ldaptive.auth.SimpleBindAuthenticationHandler" p:connectionFactory-ref="bindPooledConnectionFactory" />
+    <bean id="bindPooledConnectionFactory" class="org.ldaptive.PooledConnectionFactory" parent="connectionPool"
+        p:defaultConnectionFactory-ref="bindConnectionFactory" p:name="bind-pool" />
     <bean id="bindConnectionFactory" class="org.ldaptive.DefaultConnectionFactory" p:connectionConfig-ref="bindConnectionConfig" />
     <bean id="bindConnectionConfig" parent="connectionConfig" />
 
@@ -50,7 +48,7 @@
 
     <!-- Pool Configuration -->
     <bean id="connectionPool" class="org.ldaptive.pool.BlockingConnectionPool" abstract="true"
-        p:blockWaitTimeDuration="%{idp.pool.LDAP.blockWaitTime:PT3S}"
+        p:blockWaitTime="%{idp.pool.LDAP.blockWaitTime:PT3S}"
         p:poolConfig-ref="poolConfig"
         p:pruneStrategy-ref="pruneStrategy"
         p:validator-ref="searchValidator"
@@ -59,29 +57,27 @@
         p:minPoolSize="%{idp.pool.LDAP.minSize:3}"
         p:maxPoolSize="%{idp.pool.LDAP.maxSize:10}"
         p:validateOnCheckOut="%{idp.pool.LDAP.validateOnCheckout:false}"
-        p:validatePeriodically="%{idp.pool.LDAP.validatePeriodically:true}"
-        p:validatePeriodDuration="%{idp.pool.LDAP.validatePeriod:PT5M}" />
+        p:validatePeriodically="%{idp.pool.LDAP.validatePeriodically:true}" />
     <bean id="pruneStrategy" class="org.ldaptive.pool.IdlePruneStrategy"
-        p:prunePeriodDuration="%{idp.pool.LDAP.prunePeriod:PT5M}"
-        p:idleTimeDuration="%{idp.pool.LDAP.idleTime:PT10M}" />
-    <bean id="searchValidator" class="org.ldaptive.pool.SearchValidator" />
+        p:prunePeriod="%{idp.pool.LDAP.prunePeriod:PT5M}"
+        p:idleTime="%{idp.pool.LDAP.idleTime:PT10M}" />
+    <bean id="searchValidator" class="org.ldaptive.SearchConnectionValidator"
+        p:validatePeriod="%{idp.pool.LDAP.validatePeriod:PT5M}" />
 
     <!-- Anonymous Search Configuration -->
     <bean name="anonSearchAuthenticator" class="org.ldaptive.auth.Authenticator" p:resolveEntryOnFailure="%{idp.authn.LDAP.resolveEntryOnFailure:false}">
         <constructor-arg index="0" ref="anonSearchDnResolver" />
         <constructor-arg index="1" ref="authHandler" />
     </bean>
-    <bean id="anonSearchDnResolver" class="net.shibboleth.idp.authn.PooledTemplateSearchDnResolver"
+    <bean id="anonSearchDnResolver" class="net.shibboleth.idp.authn.TemplateSearchDnResolver"
         p:baseDn="#{'%{idp.authn.LDAP.baseDN:undefined}'.trim()}"
         p:subtreeSearch="%{idp.authn.LDAP.subtreeSearch:false}"
         p:connectionFactory-ref="anonSearchPooledConnectionFactory" >
         <constructor-arg index="0" ref="shibboleth.VelocityEngine" />
         <constructor-arg index="1" value="#{'%{idp.authn.LDAP.userFilter:undefined}'.trim()}" />
     </bean>
-    <bean id="anonSearchPooledConnectionFactory" class="org.ldaptive.pool.PooledConnectionFactory"
-        p:connectionPool-ref="anonSearchConnectionPool" />
-    <bean id="anonSearchConnectionPool" class="org.ldaptive.pool.BlockingConnectionPool" parent="connectionPool"
-        p:connectionFactory-ref="anonSearchConnectionFactory" p:name="search-pool" />
+    <bean id="anonSearchPooledConnectionFactory" class="org.ldaptive.PooledConnectionFactory" parent="connectionPool"
+        p:defaultConnectionFactory-ref="anonSearchConnectionFactory" p:name="search-pool" />
     <bean id="anonSearchConnectionFactory" class="org.ldaptive.DefaultConnectionFactory" p:connectionConfig-ref="anonSearchConnectionConfig" />
     <bean id="anonSearchConnectionConfig" parent="connectionConfig" />
 
@@ -90,19 +86,17 @@
         <constructor-arg index="0" ref="bindSearchDnResolver" />
         <constructor-arg index="1" ref="authHandler" />
     </bean>
-    <bean id="bindSearchDnResolver" class="net.shibboleth.idp.authn.PooledTemplateSearchDnResolver"
+    <bean id="bindSearchDnResolver" class="net.shibboleth.idp.authn.TemplateSearchDnResolver"
         p:baseDn="#{'%{idp.authn.LDAP.baseDN:undefined}'.trim()}"
         p:subtreeSearch="%{idp.authn.LDAP.subtreeSearch:false}"
         p:connectionFactory-ref="bindSearchPooledConnectionFactory" >
         <constructor-arg index="0" ref="shibboleth.VelocityEngine" />
         <constructor-arg index="1" value="#{'%{idp.authn.LDAP.userFilter:undefined}'.trim()}" />
     </bean>
-    <bean id="bindSearchPooledConnectionFactory" class="org.ldaptive.pool.PooledConnectionFactory"
-        p:connectionPool-ref="bindSearchConnectionPool" />
-    <bean id="bindSearchConnectionPool" class="org.ldaptive.pool.BlockingConnectionPool" parent="connectionPool"
-        p:connectionFactory-ref="bindSearchConnectionFactory" p:name="search-pool" />
+    <bean id="bindSearchPooledConnectionFactory" class="org.ldaptive.PooledConnectionFactory" parent="connectionPool"
+        p:defaultConnectionFactory-ref="bindSearchConnectionFactory" p:name="search-pool" />
     <bean id="bindSearchConnectionFactory" class="org.ldaptive.DefaultConnectionFactory" p:connectionConfig-ref="bindSearchConnectionConfig" />
-    <bean id="bindSearchConnectionConfig" parent="connectionConfig" p:connectionInitializer-ref="bindConnectionInitializer" />
+    <bean id="bindSearchConnectionConfig" parent="connectionConfig" p:connectionInitializers-ref="bindConnectionInitializer" />
     <bean id="bindConnectionInitializer" class="org.ldaptive.BindConnectionInitializer"
             p:bindDn="#{'%{idp.authn.LDAP.bindDN:undefined}'.trim()}">
         <property name="bindCredential">
@@ -123,7 +117,7 @@
         class="org.ldaptive.control.PasswordPolicyControl" /> add p:authenticationControls-ref="authenticationControl" to the authHandler -->
 
     <!-- Active Directory Configuration -->
-    <bean id="adAuthenticator" class="org.ldaptive.auth.Authenticator" p:authenticationResponseHandlers-ref="authenticationResponseHandler"
+    <bean id="adAuthenticator" class="org.ldaptive.auth.Authenticator" p:responseHandlers-ref="authenticationResponseHandler"
         p:resolveEntryOnFailure="%{idp.authn.LDAP.resolveEntryOnFailure:false}">
         <constructor-arg index="0" ref="formatDnResolver" />
         <constructor-arg index="1" ref="authHandler" />
diff --git a/idp-conf/src/main/resources/conf/ldap.properties b/idp-conf/src/main/resources/conf/ldap.properties
index c2bca67..641d30f 100644
--- a/idp-conf/src/main/resources/conf/ldap.properties
+++ b/idp-conf/src/main/resources/conf/ldap.properties
@@ -7,7 +7,6 @@
 ## Connection properties ##
 idp.authn.LDAP.ldapURL                          = ldap://localhost:10389
 #idp.authn.LDAP.useStartTLS                     = true
-#idp.authn.LDAP.useSSL                          = false
 # Time in milliseconds that connects will block
 #idp.authn.LDAP.connectTimeout                  = PT3S
 # Time in milliseconds to wait for responses
diff --git a/idp-conf/src/main/resources/system/conf/global-system.xml b/idp-conf/src/main/resources/system/conf/global-system.xml
index 084ff3d..f719000 100644
--- a/idp-conf/src/main/resources/system/conf/global-system.xml
+++ b/idp-conf/src/main/resources/system/conf/global-system.xml
@@ -58,7 +58,6 @@
         <property name="arguments">
             <props>
                 <prop key="org.apache.xml.security.ignoreLineBreaks">%{idp.apache.xml.security.ignoreLineBreaks:true}</prop>
-                <prop key="org.ldaptive.provider">%{idp.ldaptive.provider:%{org.ldaptive.provider:org.ldaptive.provider.unboundid.UnboundIDProvider}}</prop>
             </props>
         </property>
     </bean>
diff --git a/idp-conf/src/test/java/net/shibboleth/idp/test/InMemoryDirectory.java b/idp-conf/src/test/java/net/shibboleth/idp/test/InMemoryDirectory.java
index 19f7b7a..068a888 100644
--- a/idp-conf/src/test/java/net/shibboleth/idp/test/InMemoryDirectory.java
+++ b/idp-conf/src/test/java/net/shibboleth/idp/test/InMemoryDirectory.java
@@ -23,9 +23,9 @@ import java.security.KeyStore;
 
 import javax.annotation.Nonnull;
 
+import com.unboundid.util.ssl.SSLUtil;
 import org.ldaptive.ssl.CredentialConfigFactory;
-import org.ldaptive.ssl.SslConfig;
-import org.ldaptive.ssl.TLSSocketFactory;
+import org.ldaptive.ssl.SSLContextInitializer;
 import org.springframework.core.io.Resource;
 
 import com.unboundid.ldap.listener.InMemoryDirectoryServer;
@@ -67,12 +67,11 @@ public class InMemoryDirectory {
             final KeyStore ks = KeyStore.getInstance("JKS");
             final String ksPass = "changeit";
             ks.load(keystore.getInputStream(), ksPass.toCharArray());
-            final TLSSocketFactory socketFactory = new TLSSocketFactory();
-            socketFactory.setSslConfig(new SslConfig(CredentialConfigFactory.createKeyStoreCredentialConfig(ks, ksPass)));
-            socketFactory.initialize();
-            
-            config.setListenerConfigs(InMemoryListenerConfig.createLDAPConfig("default", null, port,
-                    socketFactory));
+            final SSLContextInitializer sslInit =
+                CredentialConfigFactory.createKeyStoreCredentialConfig(ks, ksPass).createSSLContextInitializer();
+            final SSLUtil sslUtil = new SSLUtil(sslInit.getKeyManagers(), sslInit.getTrustManagers());
+            config.setListenerConfigs(
+                InMemoryListenerConfig.createLDAPConfig("default", null, port, sslUtil.createSSLSocketFactory()));
         } catch (final GeneralSecurityException e) {
             throw new IOException("Error reading keystore", e);
         }
diff --git a/idp-distribution/pom.xml b/idp-distribution/pom.xml
index 7cd7f44..22e8c7e 100644
--- a/idp-distribution/pom.xml
+++ b/idp-distribution/pom.xml
@@ -77,16 +77,6 @@
             <version>${metrics.version}</version>
             <scope>runtime</scope>
         </dependency>
-        <dependency>
-            <groupId>org.ldaptive</groupId>
-            <artifactId>ldaptive-unboundid</artifactId>
-            <scope>runtime</scope>
-        </dependency>
-        <dependency>
-            <groupId>com.unboundid</groupId>
-            <artifactId>unboundid-ldapsdk</artifactId>
-            <scope>runtime</scope>
-        </dependency>
         
         <!-- Test Dependencies -->
 
diff --git a/idp-schema/src/main/resources/schema/shibboleth-attribute-resolver.xsd b/idp-schema/src/main/resources/schema/shibboleth-attribute-resolver.xsd
index 0512951..9308298 100644
--- a/idp-schema/src/main/resources/schema/shibboleth-attribute-resolver.xsd
+++ b/idp-schema/src/main/resources/schema/shibboleth-attribute-resolver.xsd
@@ -775,13 +775,6 @@
                             </documentation>
                         </annotation>
                     </element>
-                    <element name="LDAPProperty" type="resolver:PropertyType">
-                        <annotation>
-                            <documentation>
-                                LDAP connection properties relevant to the underlying provider being used.
-                            </documentation>
-                        </annotation>
-                    </element>
                     <element name="StartTLSTrustCredential" type="sec:X509Credential">
                         <annotation>
                             <documentation>
@@ -816,7 +809,7 @@
                 <attribute name="connectionStrategy" type="resolver:ConnectionStrategyType">
                     <annotation>
                         <documentation>
-                            The LDAP connection strategy. Acceptable values are DEFAULT, ACTIVE_PASSIVE, ROUND_ROBIN, and RANDOM.
+                            The LDAP connection strategy. Acceptable values are ACTIVE_PASSIVE, ROUND_ROBIN and RANDOM.
                             Default value is ACTIVE_PASSIVE.
                         </documentation>
                     </annotation>
@@ -1581,7 +1574,6 @@
             <documentation>Supported connection strategies.</documentation>
         </annotation>
         <restriction base="string">
-            <enumeration value="DEFAULT"/>
             <enumeration value="ACTIVE_PASSIVE"/>
             <enumeration value="ROUND_ROBIN"/>
             <enumeration value="RANDOM"/>
@@ -1646,16 +1638,6 @@
                 </documentation>
             </annotation>
         </attribute>
-        <attribute name="blockWhenEmpty" type="resolver:string">
-            <annotation>
-                <documentation>
-                    Whether the pool should block when maxPoolSize objects are in use.
-                    If false, pool will grow beyond the maxPoolSize value.
-                    Note that a properly configured blocking pool will yield better performance.
-                    Default value is true.
-                </documentation>
-            </annotation>
-        </attribute>
         <attribute name="blockWaitTime" type="resolver:string">
             <annotation>
                 <documentation>
diff --git a/idp-war-distribution/pom.xml b/idp-war-distribution/pom.xml
index e8ce3e9..5bfe382 100644
--- a/idp-war-distribution/pom.xml
+++ b/idp-war-distribution/pom.xml
@@ -44,17 +44,6 @@
             <version>${metrics.version}</version>
             <scope>runtime</scope>
         </dependency>
-        <!-- IDP-1401 - UnboundID as default ldaptive LDAP provider -->
-        <dependency>
-            <groupId>org.ldaptive</groupId>
-            <artifactId>ldaptive-unboundid</artifactId>
-            <scope>runtime</scope>
-        </dependency>
-        <dependency>
-            <groupId>com.unboundid</groupId>
-            <artifactId>unboundid-ldapsdk</artifactId>
-            <scope>runtime</scope>
-        </dependency>
     </dependencies>
 
     <distributionManagement>

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list