[java-shib-attribute] branch main updated: IDP-1963 Update ldaptive to version 2.

Daniel Fisher dfisher at vt.edu
Thu Oct 13 03:28:46 UTC 2022


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

dfisher pushed a commit to branch main
in repository java-shib-attribute.

View the commit online:
http://git.shibboleth.net/view/?p=java-shib-attribute.git;a=commit;h=00bdebb243e0b8a882e3c09686edbc9941bb253b

The following commit(s) were added to refs/heads/main by this push:
     new 00bdebb24 IDP-1963 Update ldaptive to version 2.
00bdebb24 is described below

commit 00bdebb243e0b8a882e3c09686edbc9941bb253b
Author: Daniel Fisher <dfisher at vt.edu>
AuthorDate: Wed Oct 12 22:56:42 2022 -0400

    IDP-1963 Update ldaptive to version 2.
    
    https://shibboleth.atlassian.net/browse/IDP-1963
    
    Updates to LDAP related classes for shib-attribute-resolver-api, shib-attribute-resolver-impl and shib-attribute-resolver-spring.
---
 .../AbstractExecutableSearchFilterBuilder.java     |  19 +-
 .../resolver/dc/ldap/ExecutableSearchFilter.java   |  12 +-
 ...ParameterizedExecutableSearchFilterBuilder.java |   4 +-
 .../dc/ldap/SearchResultMappingStrategy.java       |   6 +-
 .../ldap/StringAttributeValueMappingStrategy.java  |  12 +-
 .../TemplatedExecutableSearchFilterBuilder.java    |  10 +-
 .../dc/ldap/impl/ConnectionFactoryValidator.java   |   8 +-
 .../resolver/dc/ldap/impl/LDAPDataConnector.java   |  43 ++--
 .../dc/ldap/impl/LDAPDataConnectorTest.java        |  39 +--
 .../resolver/dc/ldap/impl/Regressions.java         |  11 +-
 .../dc/ldap/impl/LDAPDataConnectorParser.java      | 261 ++++++++++-----------
 .../dc/ldap/impl/LDAPDataConnectorParserTest.java  | 167 ++++++-------
 .../ldap-attribute-resolver-spring-context.xml     |  82 +++----
 ...dap-attribute-resolver-spring-props-context.xml |  89 ++++---
 14 files changed, 362 insertions(+), 401 deletions(-)

diff --git a/shib-attribute-resolver-api/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/AbstractExecutableSearchFilterBuilder.java b/shib-attribute-resolver-api/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/AbstractExecutableSearchFilterBuilder.java
index 1900ad569..a88e2b97c 100644
--- a/shib-attribute-resolver-api/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/AbstractExecutableSearchFilterBuilder.java
+++ b/shib-attribute-resolver-api/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/AbstractExecutableSearchFilterBuilder.java
@@ -21,12 +21,11 @@ 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.ResultCode;
-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;
 
@@ -49,7 +48,7 @@ public abstract class AbstractExecutableSearchFilterBuilder extends AbstractInit
      * @return executable search filter
      */
     // Checkstyle: AnonInnerLength OFF
-    protected ExecutableSearchFilter build(@Nonnull final SearchFilter searchFilter) {
+    protected ExecutableSearchFilter build(@Nonnull final FilterTemplate searchFilter) {
         return new ExecutableSearchFilter() {
 
             /** {@inheritDoc} */
@@ -58,20 +57,22 @@ public abstract class AbstractExecutableSearchFilterBuilder extends AbstractInit
             }
 
             /** {@inheritDoc} */
-            @Nonnull public SearchResult execute(@Nonnull final SearchExecutor executor,
+            @Nonnull public SearchResponse execute(@Nonnull final SearchOperation operation,
                     @Nonnull final ConnectionFactory factory) throws LdapException {
-                final Response<SearchResult> response = executor.search(factory, searchFilter);
+                final SearchOperation op = SearchOperation.copy(operation);
+                op.setConnectionFactory(factory);
+                final SearchResponse response = op.execute(searchFilter);
                 log.trace("Search returned response {}", response);
                 if (response.getResultCode() != ResultCode.SUCCESS) {
                     // 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 LdapException("Search operation did not return success: " + response.getResultCode());
                 }
-                return response.getResult();
+                return response;
             }
 
             /** {@inheritDoc} */
-            @Nonnull public SearchFilter getSearchFilter() {
+            @Nonnull public FilterTemplate getSearchFilter() {
                 return searchFilter;
             }
 
diff --git a/shib-attribute-resolver-api/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/ExecutableSearchFilter.java b/shib-attribute-resolver-api/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/ExecutableSearchFilter.java
index 669003250..811ce0d43 100644
--- a/shib-attribute-resolver-api/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/ExecutableSearchFilter.java
+++ b/shib-attribute-resolver-api/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/ExecutableSearchFilter.java
@@ -21,9 +21,9 @@ import javax.annotation.Nonnull;
 
 import org.ldaptive.ConnectionFactory;
 import org.ldaptive.LdapException;
-import org.ldaptive.SearchExecutor;
-import org.ldaptive.SearchFilter;
-import org.ldaptive.SearchResult;
+import org.ldaptive.FilterTemplate;
+import org.ldaptive.SearchOperation;
+import org.ldaptive.SearchResponse;
 
 import net.shibboleth.idp.attribute.resolver.dc.ExecutableSearch;
 
@@ -33,14 +33,14 @@ public interface ExecutableSearchFilter extends ExecutableSearch {
     /**
      * Performs an LDAP search and returns the results.
      * 
-     * @param executor configured to perform searches
+     * @param operation configured to perform searches
      * @param factory ready-to-use connection factory
      * 
      * @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, @Nonnull ConnectionFactory factory)
             throws LdapException;
 
     /**
@@ -48,5 +48,5 @@ public interface ExecutableSearchFilter extends ExecutableSearch {
      *
      * @return  search filter
      */
-    @Nonnull SearchFilter getSearchFilter();
+    @Nonnull FilterTemplate getSearchFilter();
 }
\ No newline at end of file
diff --git a/shib-attribute-resolver-api/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/ParameterizedExecutableSearchFilterBuilder.java b/shib-attribute-resolver-api/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/ParameterizedExecutableSearchFilterBuilder.java
index 90f3d496c..ee4c60b8b 100644
--- a/shib-attribute-resolver-api/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/ParameterizedExecutableSearchFilterBuilder.java
+++ b/shib-attribute-resolver-api/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/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;
@@ -68,7 +68,7 @@ public class ParameterizedExecutableSearchFilterBuilder extends AbstractExecutab
     @Override public ExecutableSearchFilter build(@Nonnull final AttributeResolutionContext resolutionContext,
             @Nonnull final Map<String, List<IdPAttributeValue>> dependencyAttributes) throws ResolutionException {
         checkComponentActive();
-        final SearchFilter sf = new SearchFilter(searchFilter);
+        final FilterTemplate sf = new FilterTemplate(searchFilter);
         sf.setParameter("principalName", resolutionContext.getPrincipal());
         if (dependencyAttributes != null && !dependencyAttributes.isEmpty()) {
             for (final Map.Entry<String, List<IdPAttributeValue>> entry : dependencyAttributes.entrySet()) {
diff --git a/shib-attribute-resolver-api/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/SearchResultMappingStrategy.java b/shib-attribute-resolver-api/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/SearchResultMappingStrategy.java
index 00422d07d..465729bfc 100644
--- a/shib-attribute-resolver-api/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/SearchResultMappingStrategy.java
+++ b/shib-attribute-resolver-api/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/SearchResultMappingStrategy.java
@@ -17,13 +17,13 @@
 
 package net.shibboleth.idp.attribute.resolver.dc.ldap;
 
-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/shib-attribute-resolver-api/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/StringAttributeValueMappingStrategy.java b/shib-attribute-resolver-api/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/StringAttributeValueMappingStrategy.java
index 5e7994814..758a0ace9 100644
--- a/shib-attribute-resolver-api/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/StringAttributeValueMappingStrategy.java
+++ b/shib-attribute-resolver-api/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/StringAttributeValueMappingStrategy.java
@@ -36,7 +36,7 @@ import net.shibboleth.shared.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,21 @@ 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.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/shib-attribute-resolver-api/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/TemplatedExecutableSearchFilterBuilder.java b/shib-attribute-resolver-api/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/TemplatedExecutableSearchFilterBuilder.java
index 4d2009590..bc01bfbe7 100644
--- a/shib-attribute-resolver-api/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/TemplatedExecutableSearchFilterBuilder.java
+++ b/shib-attribute-resolver-api/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/TemplatedExecutableSearchFilterBuilder.java
@@ -31,7 +31,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,7 +190,7 @@ public class TemplatedExecutableSearchFilterBuilder extends AbstractExecutableSe
         }
 
         try {
-            final SearchFilter searchFilter = new SearchFilter(merge(context));
+            final FilterTemplate searchFilter = new FilterTemplate(merge(context));
             return super.build(searchFilter);
         } catch (final VelocityException e) {
             log.error("Error running template: {}", e.getMessage());
@@ -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/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/ConnectionFactoryValidator.java b/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/ConnectionFactoryValidator.java
index 81f1ed86a..1f1e18ba9 100644
--- a/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/ConnectionFactoryValidator.java
+++ b/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/ConnectionFactoryValidator.java
@@ -93,9 +93,7 @@ public class ConnectionFactoryValidator extends AbstractInitializableComponent i
 
     /** {@inheritDoc} */
     @Override public void validate() throws ValidationException {
-        Connection connection = null;
-        try {
-            connection = connectionFactory.getConnection();
+        try (Connection connection = connectionFactory.getConnection()) {
             if (connection == null) {
                 log.error("Unable to retrieve connections from configured connection factory");
                 if (isThrowValidateError()) {
@@ -108,10 +106,6 @@ public class ConnectionFactoryValidator extends AbstractInitializableComponent i
             if (isThrowValidateError()) {
                 throw new ValidationException(e);
             }
-        } finally {
-            if (connection != null) {
-                connection.close();
-            }
         }
     }
 }
diff --git a/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/LDAPDataConnector.java b/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/LDAPDataConnector.java
index ce89f99ce..94c2dfb3b 100644
--- a/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/LDAPDataConnector.java
+++ b/shib-attribute-resolver-impl/src/main/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/LDAPDataConnector.java
@@ -27,8 +27,8 @@ import org.ldaptive.Connection;
 import org.ldaptive.ConnectionConfig;
 import org.ldaptive.ConnectionFactory;
 import org.ldaptive.LdapException;
-import org.ldaptive.SearchExecutor;
-import org.ldaptive.SearchResult;
+import org.ldaptive.SearchOperation;
+import org.ldaptive.SearchResponse;
 import org.ldaptive.ssl.SSLContextInitializer;
 import org.ldaptive.ssl.SslConfig;
 import org.ldaptive.ssl.X509SSLContextInitializer;
@@ -59,7 +59,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,22 +93,22 @@ 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.
      * 
-     * @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) {
         checkSetterPreconditions();
-        searchExecutor = Constraint.isNotNull(executor, "LDAP search executor can not be null");
+        searchOperation = Constraint.isNotNull(operation, "LDAP search operation can not be null");
     }
 
     /** {@inheritDoc} */
@@ -128,7 +128,7 @@ public class LDAPDataConnector extends AbstractSearchDataConnector<ExecutableSea
         if (connectionFactory == null) {
             throw new ComponentInitializationException(getLogPrefix() + " No connection factory was configured");
         }
-        if (searchExecutor == null) {
+        if (searchOperation == null) {
             throw new ComponentInitializationException(getLogPrefix() + " No search executor was configured");
         }
 
@@ -160,16 +160,9 @@ 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://")) {
                 final SslConfig sslConfig = connConfig.getSslConfig();
                 if (sslConfig != null) {
@@ -183,16 +176,8 @@ public class LDAPDataConnector extends AbstractSearchDataConnector<ExecutableSea
                     }
                 }
             }
-        } catch (final GeneralSecurityException | LdapException e) {
+        } catch (final GeneralSecurityException e) {
             log.debug("{} Failed to inspect TLS implementation", getLogPrefix(), e);
-        } finally {
-            if (conn != null) {
-                try {
-                    conn.close();
-                } catch (final Exception e) {
-                    log.debug("{} Error closing LDAP connection", getLogPrefix(), e);
-                }
-            }
         }
     }
  // CheckStyle: CyclomaticComplexity ON
@@ -213,7 +198,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, connectionFactory);
             log.trace("{} Search returned {}", getLogPrefix(), result);
             return getMappingStrategy().map(result);
         } catch (final LdapException e) {
diff --git a/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/LDAPDataConnectorTest.java b/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/LDAPDataConnectorTest.java
index d5da5d528..6a4858bc7 100644
--- a/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/LDAPDataConnectorTest.java
+++ b/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/LDAPDataConnectorTest.java
@@ -34,7 +34,8 @@ import javax.annotation.Nonnull;
 import org.apache.velocity.runtime.RuntimeConstants;
 import org.ldaptive.ConnectionFactory;
 import org.ldaptive.DefaultConnectionFactory;
-import org.ldaptive.SearchExecutor;
+import org.ldaptive.SearchOperation;
+import org.ldaptive.SearchRequest;
 import org.testng.annotations.AfterClass;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
@@ -124,10 +125,10 @@ 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));
@@ -156,10 +157,10 @@ 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");
@@ -198,7 +199,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);
     }
@@ -209,7 +210,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);
 
@@ -268,10 +269,10 @@ public class LDAPDataConnectorTest {
         final ExecutableSearchFilter filter = builder.build(context, dependsAttributes);
         assertEquals(
                 filter.getSearchFilter().format(),
-                "(&(cn=PETER_THE_PRINCIPAL)(eduPersonEntitlement=entitlement1)(eduPersonEntitlement=entitlement\\2a))");
+                "(&(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 {
@@ -282,8 +283,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.getSearchFilter().format(), "(cn=domain\\5Cuser\\2A)");
+        assertEquals(filter.getResultCacheKey(), "(cn=domain\\5Cuser\\2A)");
     }
 
     @Test public void resolveTemplate() throws ComponentInitializationException, ResolutionException {
@@ -342,10 +343,10 @@ public class LDAPDataConnectorTest {
         final ExecutableSearchFilter filter = builder.build(context, dependsAttributes);
         assertEquals(
                 filter.getSearchFilter().format(),
-                "(&(cn=PETER_THE_PRINCIPAL)(eduPersonEntitlement=entitlement1)(eduPersonEntitlement=entitlement\\2a))");
+                "(&(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 {
@@ -357,8 +358,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.getSearchFilter().format(), "(cn=domain\\5Cuser\\2A)");
+        assertEquals(filter.getResultCacheKey(), "(cn=domain\\5Cuser\\2A)");
     }
 
     protected void resolve(final ExecutableSearchBuilder<ExecutableSearchFilter> builder) throws ComponentInitializationException,
diff --git a/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/Regressions.java b/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/Regressions.java
index 8c7cd4c07..72710b017 100644
--- a/shib-attribute-resolver-impl/src/test/java/net/shibboleth/idp/attribute/resolver/dc/ldap/impl/Regressions.java
+++ b/shib-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.AfterClass;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
@@ -108,10 +109,10 @@ 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/shib-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/impl/LDAPDataConnectorParser.java b/shib-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/impl/LDAPDataConnectorParser.java
index 4f67587af..b7f0fd4fb 100644
--- a/shib-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/impl/LDAPDataConnectorParser.java
+++ b/shib-attribute-resolver-spring/src/main/java/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/impl/LDAPDataConnectorParser.java
@@ -31,24 +31,21 @@ import org.ldaptive.ConnectionConfig;
 import org.ldaptive.ConnectionStrategy;
 import org.ldaptive.Credential;
 import org.ldaptive.DefaultConnectionFactory;
+import org.ldaptive.FilterTemplate;
+import org.ldaptive.PooledConnectionFactory;
 import org.ldaptive.RandomConnectionStrategy;
 import org.ldaptive.RoundRobinConnectionStrategy;
-import org.ldaptive.SearchExecutor;
-import org.ldaptive.SearchFilter;
+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.handler.SearchResultHandler;
 import org.ldaptive.pool.IdlePruneStrategy;
-import org.ldaptive.pool.PoolConfig;
-import org.ldaptive.pool.PooledConnectionFactory;
-import org.ldaptive.pool.SearchValidator;
-import org.ldaptive.referral.SearchReferralHandler;
-import org.ldaptive.sasl.DigestMd5Config;
-import org.ldaptive.sasl.GssApiConfig;
+import org.ldaptive.referral.FollowSearchReferralHandler;
 import org.ldaptive.sasl.Mechanism;
 import org.ldaptive.sasl.SaslConfig;
 import org.ldaptive.ssl.AllowAnyHostnameVerifier;
@@ -147,10 +144,6 @@ 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 String searchBuilderID = v2Parser.getBeanSearchBuilderID();
         if (searchBuilderID != null) {
             builder.addPropertyReference("executableSearchBuilder", searchBuilderID);
@@ -161,18 +154,19 @@ 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());
+        final BeanDefinitionBuilder connectionFactory =
+            BeanDefinitionBuilder.genericBeanDefinition(DefaultConnectionFactory.class);
+        connectionFactory.addConstructorArgValue(v2Parser.createConnectionConfig(parserContext));
+
+        final BeanDefinition pooledConnectionFactory = v2Parser.createPooledConnectionFactory(parserContext);
+        if (pooledConnectionFactory != null) {
+            builder.addPropertyValue("connectionFactory", pooledConnectionFactory);
         } else {
             builder.addPropertyValue("connectionFactory", connectionFactory.getBeanDefinition());
         }
 
-        final BeanDefinition searchExecutor = v2Parser.createSearchExecutor();
-        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) {
@@ -189,8 +183,7 @@ public class LDAPDataConnectorParser extends AbstractDataConnectorParser {
             builder.addPropertyReference("validator", validatorID);
         } else {
             if (pooledConnectionFactory != null) {
-                builder.addPropertyValue("validator",
-                        v2Parser.createValidator(pooledConnectionFactory.getBeanDefinition()));
+                builder.addPropertyValue("validator", v2Parser.createValidator(pooledConnectionFactory));
             } else {
                 builder.addPropertyValue("validator", v2Parser.createValidator(connectionFactory.getBeanDefinition()));
             }
@@ -325,7 +318,7 @@ public class LDAPDataConnectorParser extends AbstractDataConnectorParser {
                 connectionInitializer.addPropertyValue("bindSaslConfig", saslConfig);
             }
             if (principal != null || principalCredential != null || saslConfig != null) {
-                connectionConfig.addPropertyValue("connectionInitializer", connectionInitializer.getBeanDefinition());
+                connectionConfig.addPropertyValue("connectionInitializers", connectionInitializer.getBeanDefinition());
             }
             final String connectionStrategy = AttributeSupport.getAttributeValue(
                 configElement, new QName("connectionStrategy"));
@@ -465,12 +458,12 @@ public class LDAPDataConnectorParser extends AbstractDataConnectorParser {
         }
 
         /**
-         * Creates a new search executor bean definition from a v2 XML configuration.
+         * Creates a new search operation bean definition from a v2 XML configuration.
          *
-         * @return search executor bean definition
+         * @return search operation bean definition
          */
         // CheckStyle: CyclomaticComplexity|MethodLength OFF
-        @Nonnull public BeanDefinition createSearchExecutor() {
+        @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"));
@@ -482,38 +475,41 @@ 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);
+                searchRequest.addPropertyValue("derefAliases", derefAliases);
             }
+
+            final BeanDefinitionBuilder searchOperation =
+                BeanDefinitionBuilder.genericBeanDefinition(SearchOperation.class);
             if (followReferrals != null) {
                 final BeanDefinitionBuilder handler =
-                    BeanDefinitionBuilder.rootBeanDefinition(V2Parser.class, "buildReferralHandler");
+                    BeanDefinitionBuilder.rootBeanDefinition(V2Parser.class, "buildReferralHandlers");
                 handler.addConstructorArgValue(followReferrals);
-                searchExecutor.addPropertyValue("referralHandler", handler.getBeanDefinition());
+                searchOperation.addPropertyValue("searchResultHandlers", handler.getBeanDefinition());
             }
             if (searchTimeLimit != null) {
-                searchExecutor.addPropertyValue("timeLimit", searchTimeLimit);
+                searchRequest.addPropertyValue("timeLimit", searchTimeLimit);
             } else {
-                searchExecutor.addPropertyValue("timeLimit", Duration.ofSeconds(3));
+                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());
+            searchOperation.addPropertyValue("entryHandlers", handlers.getBeanDefinition());
 
             final List<Element> returnAttrsElements = ElementSupport.getChildElementsByTagNameNS(configElement, 
                     AttributeResolverNamespaceHandler.NAMESPACE, "ReturnAttributes");
@@ -528,7 +524,7 @@ 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,
@@ -544,10 +540,11 @@ public class LDAPDataConnectorParser extends AbstractDataConnectorParser {
                 final BeanDefinitionBuilder binaryAttrs =
                   BeanDefinitionBuilder.rootBeanDefinition(V2Parser.class, "buildStringList");
                 binaryAttrs.addConstructorArgValue(ElementSupport.getElementContentAsString(binaryAttrsElement));
-                searchExecutor.addPropertyValue("binaryAttributes", binaryAttrs.getBeanDefinition());
+                searchRequest.addPropertyValue("binaryAttributes", binaryAttrs.getBeanDefinition());
             }
 
-            return searchExecutor.getBeanDefinition();
+            searchOperation.addPropertyValue("request", searchRequest.getBeanDefinition());
+            return searchOperation.getBeanDefinition();
         }
         // CheckStyle: CyclomaticComplexity|MethodLength ON
 
@@ -579,7 +576,7 @@ public class LDAPDataConnectorParser extends AbstractDataConnectorParser {
          * @return connection pool bean definition
          */
         // CheckStyle: MethodLength OFF
-        @Nullable public BeanDefinition createConnectionPool(final BeanDefinition connectionFactory) {
+        @Nullable public BeanDefinition createPooledConnectionFactory(@Nonnull final ParserContext parserContext) {
 
             final Element poolConfigElement = getConnectionPoolElement();
             if (null == poolConfigElement) {
@@ -597,11 +594,12 @@ public class LDAPDataConnectorParser extends AbstractDataConnectorParser {
             final String expirationTime =
                     AttributeSupport.getAttributeValue(poolConfigElement, new QName("expirationTime"));
 
-            final BeanDefinitionBuilder pool =
-                    BeanDefinitionBuilder.genericBeanDefinition(BlockingConnectionPool.class);
-            pool.addPropertyValue("name", "resolver-pool");
+            final BeanDefinitionBuilder connectionFactory =
+                BeanDefinitionBuilder.genericBeanDefinition(PooledConnectionFactory.class);
+            connectionFactory.addPropertyValue("name", "resolver-pool");
+            connectionFactory.addPropertyValue("connectionConfig", createConnectionConfig(parserContext));
             if (blockWaitTime != null) {
-                pool.addPropertyValue("blockWaitTime", blockWaitTime);
+                connectionFactory.addPropertyValue("blockWaitTime", blockWaitTime);
             }
             if (expirationTime != null) {
                 final String prunePeriod =
@@ -612,85 +610,60 @@ public class LDAPDataConnectorParser extends AbstractDataConnectorParser {
                     strategy.addConstructorArgValue(prunePeriod);
                 }
                 strategy.addConstructorArgValue(expirationTime);
-                pool.addPropertyValue("pruneStrategy", strategy.getBeanDefinition());
+                connectionFactory.addPropertyValue("pruneStrategy", strategy.getBeanDefinition());
+            }
+
+            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 validateOnCheckOut =
+                AttributeSupport.getAttributeValue(poolConfigElement, new QName("validateOnCheckOut"));
+
+            if (minPoolSize == null) {
+                connectionFactory.addPropertyValue("minPoolSize", 0);
+            } else {
+                connectionFactory.addPropertyValue("minPoolSize", minPoolSize);
+            }
+            if (maxPoolSize == null) {
+                connectionFactory.addPropertyValue("maxPoolSize", 3);
+            } else {
+                connectionFactory.addPropertyValue("maxPoolSize", maxPoolSize);
+            }
+            if (validatePeriodically != null) {
+                connectionFactory.addPropertyValue("validatePeriodically", validatePeriodically);
+            }
+            if (validateOnCheckOut != null) {
+                connectionFactory.addPropertyValue("validateOnCheckOut", validateOnCheckOut);
             }
-            pool.addPropertyValue("poolConfig", createPoolConfig());
 
             final BeanDefinitionBuilder validator =
                     BeanDefinitionBuilder.rootBeanDefinition(V2Parser.class, "buildSearchValidator");
-            validator.addConstructorArgValue(
-                    AttributeSupport.getAttributeValue(poolConfigElement, new QName("validatePeriodically")));
             validator.addConstructorArgValue(
                     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 Deprecation
                 DeprecationSupport.warnOnce(ObjectType.ATTRIBUTE, "failfastInitialize (on a ConnectionPool element)",
                         null, "failfastInitialize (on a DataConnector)");
-                pool.addPropertyValue("failFastInitialize", failFastInitialize);
+                connectionFactory.addPropertyValue("failFastInitialize", failFastInitialize);
             } else if (configElement.hasAttributeNS(null, ATTR_FAIL_FAST)) {
-                pool.addPropertyValue("failFastInitialize",
+                connectionFactory.addPropertyValue("failFastInitialize",
                         StringSupport.trimOrNull(configElement.getAttributeNS(null, ATTR_FAIL_FAST)));
             }
-            pool.setInitMethodName("initialize");
-            return pool.getBeanDefinition();
+            connectionFactory.setInitMethodName("initialize");
+            return connectionFactory.getBeanDefinition();
         }
 
         // CheckStyle: MethodLength ON
 
-        /**
-         * Creates a new pool config bean definition from a v2 XML configuration.
-         * 
-         * @return pool config bean definition
-         */
-        @Nullable protected BeanDefinition createPoolConfig() {
-            final Element poolConfigElement = getConnectionPoolElement();
-            if (poolConfigElement == null) {
-                return null;
-            }
-
-            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 String validateOnCheckout =
-                    AttributeSupport.getAttributeValue(poolConfigElement, new QName("validateOnCheckout"));
-
-            final BeanDefinitionBuilder poolConfig = BeanDefinitionBuilder.genericBeanDefinition(PoolConfig.class);
-            if (minPoolSize == null) {
-                poolConfig.addPropertyValue("minPoolSize", 0);
-            } else {
-                poolConfig.addPropertyValue("minPoolSize", minPoolSize);
-            }
-            if (maxPoolSize == null) {
-                poolConfig.addPropertyValue("maxPoolSize", 3);
-            } else {
-                poolConfig.addPropertyValue("maxPoolSize", maxPoolSize);
-            }
-            if (validatePeriodically != null) {
-                poolConfig.addPropertyValue("validatePeriodically", validatePeriodically);
-            }
-            if (validateTimerPeriod != null) {
-                poolConfig.addPropertyValue("validatePeriod", validateTimerPeriod);
-            } else {
-                poolConfig.addPropertyValue("validatePeriod", Duration.ofMinutes(30));
-            }
-            if (validateOnCheckout != null)  {
-                poolConfig.addPropertyValue("validateOnCheckOut", validateOnCheckout);
-            } else {
-                poolConfig.addPropertyValue("validateOnCheckOut", false);
-            }
-            return poolConfig.getBeanDefinition();
-        }
-
         /**
          * Creates a new sasl config bean definition from a v2 XML configuration.
          *
@@ -715,41 +688,41 @@ public class LDAPDataConnectorParser extends AbstractDataConnectorParser {
             final List<Element> saslProperties = ElementSupport.getChildElementsByTagNameNS(saslConfigElement,
                     AttributeResolverNamespaceHandler.NAMESPACE, "SASLProperty");
 
-            final BeanDefinitionBuilder saslConfig;
-            if ("DIGEST_MD5".equals(mechanism)) {
-                saslConfig = BeanDefinitionBuilder.genericBeanDefinition(DigestMd5Config.class);
-                if (realm != null) {
-                    saslConfig.addPropertyValue("realm", realm);
-                }
-            } else if ("GSSAPI".equals(mechanism)) {
-                saslConfig = BeanDefinitionBuilder.genericBeanDefinition(GssApiConfig.class);
-                if (realm != null) {
-                    saslConfig.addPropertyValue("realm", realm);
-                }
-            } else {
-                saslConfig = BeanDefinitionBuilder.genericBeanDefinition(SaslConfig.class);
-                saslConfig.addPropertyValue("mechanism", mechanism);
-            }
+            final BeanDefinitionBuilder saslConfig = BeanDefinitionBuilder.genericBeanDefinition(SaslConfig.class);
+            saslConfig.addPropertyValue("mechanism", mechanism);
             if (authorizationId != null) {
                 saslConfig.addPropertyValue("authorizationId", authorizationId);
             }
+            if (realm != null) {
+                saslConfig.addPropertyValue("realm", realm);
+            }
 
             if (!saslProperties.isEmpty()) {
                 for (final Element property : saslProperties) {
                     final String name = AttributeSupport.getAttributeValue(property, null, "name");
                     final String value = AttributeSupport.getAttributeValue(property, null, "value");
                     if ("javax.security.sasl.qop".equals(name)) {
-                        if ("auth".equalsIgnoreCase(value)) {
-                            saslConfig.addPropertyValue("qualityOfProtection", "AUTH");
-                        } else if ("auth-int".equalsIgnoreCase(value)) {
-                            saslConfig.addPropertyValue("qualityOfProtection", "AUTH_INT");
-                        } else if ("auth-conf".equalsIgnoreCase(value)) {
-                            saslConfig.addPropertyValue("qualityOfProtection", "AUTH_CONF");
-                        } else {
-                            saslConfig.addPropertyValue("qualityOfProtection", value);
+                        final String[] splitValues = value.split(",");
+                        final String[] values = new String[splitValues.length];
+                        for (int i = 0; i < splitValues.length; i++) {
+                            if ("auth".equalsIgnoreCase(splitValues[i].trim())) {
+                                values[i] = "AUTH";
+                            } else if ("auth-int".equalsIgnoreCase(splitValues[i].trim())) {
+                                values[i] = "AUTH_INT";
+                            } else if ("auth-conf".equalsIgnoreCase(splitValues[i].trim())) {
+                                values[i] = "AUTH_CONF";
+                            } else {
+                                values[i] = splitValues[i].trim();
+                            }
                         }
+                        saslConfig.addPropertyValue("qualityOfProtection", values);
                     } else if ("javax.security.sasl.strength".equals(name)) {
-                        saslConfig.addPropertyValue("securityStrength", value);
+                        final String[] splitValues = value.split(",");
+                        final String[] values = new String[splitValues.length];
+                        for (int i = 0; i < splitValues.length; i++) {
+                            values[i] = splitValues[i].trim();
+                        }
+                        saslConfig.addPropertyValue("securityStrength", values);
                     } else if ("javax.security.sasl.server.authentication".equals(name)) {
                         saslConfig.addPropertyValue("mutualAuthentication", value);
                     }
@@ -853,11 +826,10 @@ public class LDAPDataConnectorParser extends AbstractDataConnectorParser {
          *
          * @return  search validator or null
          */
-        @Nullable public static SearchValidator buildSearchValidator(@Nullable final String validatePeriodically,
-                @Nullable final String validateDN, @Nullable final String validateFilter) {
-            if (!Boolean.valueOf(validatePeriodically)) {
-                return null;
-            }
+        @Nullable public static SearchConnectionValidator buildSearchValidator(
+                @Nullable final String validateDN,
+                @Nullable final String validateFilter,
+                @Nullable final String validatePeriod) {
             final SearchRequest searchRequest = new SearchRequest();
             searchRequest.setReturnAttributes("1.1");
             searchRequest.setSearchScope(SearchScope.OBJECT);
@@ -867,14 +839,17 @@ public class LDAPDataConnectorParser extends AbstractDataConnectorParser {
             } else {
                 searchRequest.setBaseDn("");
             }
-            final SearchFilter searchFilter = new SearchFilter();
+            final FilterTemplate searchFilter = new FilterTemplate();
             if (validateFilter != null) {
                 searchFilter.setFilter(validateFilter);
             } else {
                 searchFilter.setFilter("(objectClass=*)");
             }
-            searchRequest.setSearchFilter(searchFilter);
-            final SearchValidator validator = new SearchValidator();
+            searchRequest.setFilter(searchFilter);
+            final SearchConnectionValidator validator = new SearchConnectionValidator();
+            if (validatePeriod != null) {
+                validator.setValidatePeriod(Duration.parse(validatePeriod));
+            }
             validator.setSearchRequest(searchRequest);
             return validator;
         }
@@ -904,9 +879,9 @@ public class LDAPDataConnectorParser extends AbstractDataConnectorParser {
          * @param lowercaseAttributeNames boolean string value
          * @return list of search entry handlers
          */
-        @Nonnull public static List<SearchEntryHandler> buildSearchEntryHandlers(
+        @Nonnull public static List<LdapEntryHandler> buildSearchEntryHandlers(
                 @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();
@@ -917,16 +892,16 @@ public class LDAPDataConnectorParser extends AbstractDataConnectorParser {
         }
 
         /**
-         * Returns a search referral handler or null if followReferrals is false.
+         * Returns search result handlers or null if followReferrals is false.
          *
          * @param followReferrals whether to create a search referral handler
          *
-         * @return  search referral handler or null
+         * @return  search result handlers or null
          */
-        @Nonnull public static SearchReferralHandler buildReferralHandler(
+        @Nonnull public static List<SearchResultHandler> buildReferralHandlers(
                 @Nullable final String followReferrals) {
             if (followReferrals != null && Boolean.valueOf(followReferrals)) {
-                return new SearchReferralHandler();
+                return List.of(new FollowSearchReferralHandler());
             }
             return null;
         }
diff --git a/shib-attribute-resolver-spring/src/test/java/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/impl/LDAPDataConnectorParserTest.java b/shib-attribute-resolver-spring/src/test/java/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/impl/LDAPDataConnectorParserTest.java
index a724a310e..b7fbc9081 100644
--- a/shib-attribute-resolver-spring/src/test/java/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/impl/LDAPDataConnectorParserTest.java
+++ b/shib-attribute-resolver-spring/src/test/java/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/impl/LDAPDataConnectorParserTest.java
@@ -35,23 +35,26 @@ import java.util.Map;
 import java.util.stream.Collectors;
 
 import org.ldaptive.BindConnectionInitializer;
+import org.ldaptive.BindResponse;
 import org.ldaptive.ConnectionConfig;
 import org.ldaptive.DefaultConnectionFactory;
 import org.ldaptive.RandomConnectionStrategy;
-import org.ldaptive.SearchExecutor;
-import org.ldaptive.pool.BlockingConnectionPool;
+import org.ldaptive.PooledConnectionFactory;
+import org.ldaptive.SearchOperation;
+import org.ldaptive.filter.EqualityFilter;
+import org.ldaptive.SearchConnectionValidator;
 import org.ldaptive.pool.IdlePruneStrategy;
-import org.ldaptive.pool.PoolConfig;
-import org.ldaptive.pool.PooledConnectionFactory;
-import org.ldaptive.pool.SearchValidator;
-import org.ldaptive.referral.SearchReferralHandler;
-import org.ldaptive.sasl.DigestMd5Config;
+import org.ldaptive.referral.FollowSearchReferralHandler;
+import org.ldaptive.sasl.DefaultSaslClientRequest;
+import org.ldaptive.sasl.DigestMD5BindRequest;
 import org.ldaptive.sasl.Mechanism;
 import org.ldaptive.sasl.QualityOfProtection;
+import org.ldaptive.sasl.SaslClient;
 import org.ldaptive.sasl.SaslConfig;
 import org.ldaptive.sasl.SecurityStrength;
 import org.ldaptive.ssl.CredentialConfig;
 import org.ldaptive.ssl.SslConfig;
+import org.ldaptive.transport.TransportConnection;
 import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
 import org.springframework.context.support.GenericApplicationContext;
 import org.springframework.core.io.ClassPathResource;
@@ -102,7 +105,17 @@ public class LDAPDataConnectorParserTest {
     private InMemoryDirectoryServer directoryServer;
     
     private GenericApplicationContext pendingTeardownContext = null;
-    
+
+    /** Override the default SASL client for testing. */
+    public static class TestSaslClient implements SaslClient<DefaultSaslClientRequest>
+    {
+
+        @Override
+        public BindResponse bind(final TransportConnection conn, final DefaultSaslClientRequest request) {
+            return BindResponse.builder().resultCode(org.ldaptive.ResultCode.SUCCESS).build();
+        }
+    }
+
     @AfterMethod public void tearDownTestContext() {
         if (null == pendingTeardownContext ) {
             return;
@@ -165,6 +178,9 @@ public class LDAPDataConnectorParserTest {
         directoryServer.importFromLDIF(true,
                 "src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/ldapDataConnectorTest.ldif");
         directoryServer.startListening();
+        System.setProperty(
+                "org.ldaptive.sasl.defaultSaslClient",
+                TestSaslClient.class.getName());
     }
 
     /**
@@ -172,6 +188,7 @@ public class LDAPDataConnectorParserTest {
      */
     @AfterClass public void teardownDirectoryServer() {
         directoryServer.shutDown(true);
+        System.clearProperty("org.ldaptive.sasl.defaultSaslClient");
     }
 
     @Test public void v2Config() throws Exception {
@@ -235,9 +252,8 @@ public class LDAPDataConnectorParserTest {
         final ConnectionConfig connConfig = connFactory.getConnectionConfig();
         assertNotNull(connConfig);
         assertEquals(connConfig.getLdapUrl(), "ldap://localhost:10389");
-        assertFalse(connConfig.getUseSSL());
         assertFalse(connConfig.getUseStartTLS());
-        final BindConnectionInitializer connInitializer = (BindConnectionInitializer) connConfig.getConnectionInitializer();
+        final BindConnectionInitializer connInitializer = (BindConnectionInitializer) connConfig.getConnectionInitializers()[0];
         assertEquals(connInitializer.getBindDn(), "cn=Directory Manager");
         assertEquals(connInitializer.getBindCredential().getString(), "password");
         assertEquals(connConfig.getConnectTimeout(), Duration.ofSeconds(3));
@@ -248,12 +264,12 @@ public class LDAPDataConnectorParserTest {
         final CredentialConfig credentialConfig = sslConfig.getCredentialConfig();
         assertNotNull(credentialConfig);
 
-        final SearchExecutor searchExecutor = dataConnector.getSearchExecutor();
-        assertNotNull(searchExecutor);
-        assertEquals(searchExecutor.getBaseDn(), "");
-        assertNull(searchExecutor.getSearchFilter());
-        assertEquals(searchExecutor.getTimeLimit(), Duration.ofSeconds(3));
-        assertNull(searchExecutor.getReferralHandler());
+        final SearchOperation searchOperation = dataConnector.getSearchOperation();
+        assertNotNull(searchOperation);
+        assertEquals(searchOperation.getRequest().getBaseDn(), "");
+        assertNull(searchOperation.getRequest().getFilter());
+        assertEquals(searchOperation.getRequest().getTimeLimit(), Duration.ofSeconds(3));
+        assertNull(searchOperation.getSearchResultHandlers());
 
         final ConnectionFactoryValidator validator = (ConnectionFactoryValidator) dataConnector.getValidator();
         assertNotNull(validator);
@@ -287,45 +303,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);
-        assertNull(connPool.getBlockWaitTime());
-        assertEquals("resolver-pool", connPool.getName());
-        final PoolConfig poolConfig = connPool.getPoolConfig();
-        assertNotNull(poolConfig);
-        assertEquals(poolConfig.getMinPoolSize(), 0);
-        assertEquals(poolConfig.getMaxPoolSize(), 3);
-        assertFalse(poolConfig.isValidatePeriodically());
-        assertEquals(poolConfig.getValidatePeriod(), Duration.ofMinutes(30));
-        assertTrue(connPool.getFailFastInitialize());
-        assertNull(connPool.getValidator());
-
-        final IdlePruneStrategy pruneStrategy = (IdlePruneStrategy) connPool.getPruneStrategy();
+        // note that default value changed from null to PT1M
+        assertEquals(connFactory.getBlockWaitTime(), Duration.ofMinutes(1));
+        assertEquals("resolver-pool", connFactory.getName());
+        assertEquals(connFactory.getMinPoolSize(), 0);
+        assertEquals(connFactory.getMaxPoolSize(), 3);
+        assertFalse(connFactory.isValidatePeriodically());
+        // note that pooled connection factories have a validator by default
+        assertNotNull(connFactory.getValidator());
+        assertEquals(connFactory.getValidator().getValidatePeriod(), Duration.ofMinutes(30));
+        assertTrue(connFactory.getFailFastInitialize());
+
+        final IdlePruneStrategy pruneStrategy = (IdlePruneStrategy) connFactory.getPruneStrategy();
         assertNotNull(pruneStrategy);
         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(connConfig.getLdapUrl(), "ldap://localhost:10389");
-        assertFalse(connConfig.getUseSSL());
         assertFalse(connConfig.getUseStartTLS());
-        final BindConnectionInitializer connInitializer = (BindConnectionInitializer) connConfig.getConnectionInitializer();
+        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 = connPool.getConnectionFactory().getConnectionConfig().getSslConfig();
+        final SslConfig sslConfig = connFactory.getConnectionConfig().getSslConfig();
         assertNotNull(sslConfig);
         final CredentialConfig credentialConfig = sslConfig.getCredentialConfig();
         assertNotNull(credentialConfig);
 
-        final SearchExecutor searchExecutor = dataConnector.getSearchExecutor();
-        assertNotNull(searchExecutor);
-        assertEquals(searchExecutor.getBaseDn(), "");
-        assertNull(searchExecutor.getSearchFilter());
-        assertEquals(searchExecutor.getTimeLimit(), Duration.ofSeconds(3));
+        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);
@@ -371,7 +384,7 @@ public class LDAPDataConnectorParserTest {
         assertNotNull(attrs.get("mail"));
         assertNotNull(attrs.get("entryDN"));
 
-        assertEquals(dataConnector.getConnectionFactory().getConnection().getConnectionConfig().getConnectionStrategy().getClass(),
+        assertEquals(dataConnector.getConnectionFactory().getConnectionConfig().getConnectionStrategy().getClass(),
                 RandomConnectionStrategy.class);
     }
 
@@ -386,7 +399,7 @@ public class LDAPDataConnectorParserTest {
         assertNotNull(connFactory);
         final ConnectionConfig connConfig = connFactory.getConnectionConfig();
         assertNotNull(connConfig);
-        final BindConnectionInitializer connInitializer = (BindConnectionInitializer) connConfig.getConnectionInitializer();
+        final BindConnectionInitializer connInitializer = (BindConnectionInitializer) connConfig.getConnectionInitializers()[0];
         assertNotNull(connInitializer);
         assertEquals(connInitializer.getBindDn(), "manager at shibboleth.net");
         assertEquals(connInitializer.getBindCredential().getString(), "password");
@@ -395,9 +408,9 @@ public class LDAPDataConnectorParserTest {
         assertEquals(saslConfig.getMechanism(), Mechanism.DIGEST_MD5);
         assertEquals(saslConfig.getAuthorizationId(), "authzID");
         assertEquals(saslConfig.getMutualAuthentication(), Boolean.TRUE);
-        assertEquals(saslConfig.getQualityOfProtection(), QualityOfProtection.AUTH_INT);
-        assertEquals(saslConfig.getSecurityStrength(), SecurityStrength.HIGH);
-        assertEquals(((DigestMd5Config) saslConfig).getRealm(), "shibboleth.net");
+        assertEquals(saslConfig.getQualityOfProtection()[0], QualityOfProtection.AUTH_INT);
+        assertEquals(saslConfig.getSecurityStrength()[0], SecurityStrength.HIGH);
+        assertEquals(saslConfig.getRealm(), "shibboleth.net");
     }
 
     @Test public void v2AuthenticationTypeConfig() throws Exception {
@@ -411,14 +424,14 @@ public class LDAPDataConnectorParserTest {
         assertNotNull(connFactory);
         final ConnectionConfig connConfig = connFactory.getConnectionConfig();
         assertNotNull(connConfig);
-        final BindConnectionInitializer connInitializer = (BindConnectionInitializer) connConfig.getConnectionInitializer();
+        final BindConnectionInitializer connInitializer = (BindConnectionInitializer) connConfig.getConnectionInitializers()[0];
         assertNotNull(connInitializer);
         assertEquals(connInitializer.getBindDn(), "manager at shibboleth.net");
         assertEquals(connInitializer.getBindCredential().getString(), "password");
         final SaslConfig saslConfig = connInitializer.getBindSaslConfig();
         assertNotNull(saslConfig);
         assertEquals(saslConfig.getMechanism(), Mechanism.DIGEST_MD5);
-        assertEquals(saslConfig.getAuthorizationId(), "");
+        assertNull(saslConfig.getAuthorizationId());
         assertNull(saslConfig.getMutualAuthentication());
         assertNull(saslConfig.getQualityOfProtection());
         assertNull(saslConfig.getSecurityStrength());
@@ -435,17 +448,18 @@ public class LDAPDataConnectorParserTest {
         assertNotNull(connFactory);
         final ConnectionConfig connConfig = connFactory.getConnectionConfig();
         assertNotNull(connConfig);
-        final BindConnectionInitializer connInitializer = (BindConnectionInitializer) connConfig.getConnectionInitializer();
+        final BindConnectionInitializer connInitializer = (BindConnectionInitializer) connConfig.getConnectionInitializers()[0];
         assertNotNull(connInitializer);
         final SaslConfig saslConfig = connInitializer.getBindSaslConfig();
         assertNull(saslConfig);
 
-        final SearchExecutor searchExecutor = dataConnector.getSearchExecutor();
-        assertNotNull(searchExecutor);
-        assertEquals(searchExecutor.getBaseDn(), "");
-        assertNull(searchExecutor.getSearchFilter());
-        assertEquals(searchExecutor.getTimeLimit(), Duration.ofSeconds(3));
-        final SearchReferralHandler referralHandler = (SearchReferralHandler) searchExecutor.getReferralHandler();
+        final SearchOperation searchOperation = dataConnector.getSearchOperation();
+        assertNotNull(searchOperation);
+        assertNotNull(searchOperation.getRequest());
+        assertEquals(searchOperation.getRequest().getBaseDn(), "");
+        assertNull(searchOperation.getRequest().getFilter());
+        assertEquals(searchOperation.getRequest().getTimeLimit(), Duration.ofSeconds(3));
+        final FollowSearchReferralHandler referralHandler = (FollowSearchReferralHandler) searchOperation.getSearchResultHandlers()[0];
         assertNotNull(referralHandler);
     }
 
@@ -460,7 +474,7 @@ public class LDAPDataConnectorParserTest {
         assertNotNull(connFactory);
         final ConnectionConfig connConfig = connFactory.getConnectionConfig();
         assertNotNull(connConfig);
-        final BindConnectionInitializer connInitializer = (BindConnectionInitializer) connConfig.getConnectionInitializer();
+        final BindConnectionInitializer connInitializer = (BindConnectionInitializer) connConfig.getConnectionInitializers()[0];
         assertNotNull(connInitializer);
         assertNull(connInitializer.getBindDn());
         assertNull(connInitializer.getBindCredential());
@@ -592,50 +606,45 @@ public class LDAPDataConnectorParserTest {
 
         final PooledConnectionFactory connFactory = (PooledConnectionFactory) dataConnector.getConnectionFactory();
         assertNotNull(connFactory);
-        final BlockingConnectionPool connPool = (BlockingConnectionPool) connFactory.getConnectionPool();
-        assertNotNull(connPool);
-        assertEquals(connPool.getBlockWaitTime(), Duration.ofSeconds(5));
-        assertEquals(connPool.getName(), "resolver-pool");
-        final PoolConfig poolConfig = connPool.getPoolConfig();
-        assertNotNull(poolConfig);
-        assertEquals(poolConfig.getMinPoolSize(), 5);
-        assertEquals(poolConfig.getMaxPoolSize(), 10);
-        assertTrue(poolConfig.isValidatePeriodically());
-        assertEquals(poolConfig.getValidatePeriod(), Duration.ofMinutes(15));
-        assertFalse(connPool.getFailFastInitialize());
-
-        final SearchValidator searchValidator = (SearchValidator) connPool.getValidator();
+        assertEquals(connFactory.getBlockWaitTime(), Duration.ofSeconds(5));
+        assertEquals(connFactory.getName(), "resolver-pool");
+        assertEquals(connFactory.getMinPoolSize(), 5);
+        assertEquals(connFactory.getMaxPoolSize(), 10);
+        assertTrue(connFactory.isValidatePeriodically());
+        assertEquals(connFactory.getValidator().getValidatePeriod(), Duration.ofMinutes(15));
+        assertFalse(connFactory.getFailFastInitialize());
+
+        final SearchConnectionValidator searchValidator = (SearchConnectionValidator) connFactory.getValidator();
         assertNotNull(searchValidator);
         assertEquals(searchValidator.getSearchRequest().getBaseDn(), "dc=shibboleth,dc=net");
-        assertEquals(searchValidator.getSearchRequest().getSearchFilter().getFilter(), "(ou=people)");
+        assertEquals(searchValidator.getSearchRequest().getFilter(), new EqualityFilter("ou", "people"));
 
-        final IdlePruneStrategy pruneStrategy = (IdlePruneStrategy) connPool.getPruneStrategy();
+        final IdlePruneStrategy pruneStrategy = (IdlePruneStrategy) connFactory.getPruneStrategy();
         assertNotNull(pruneStrategy);
         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(connConfig.getLdapUrl(), "ldap://localhost:10389");
-        assertFalse(connConfig.getUseSSL());
         assertTrue(connConfig.getUseStartTLS());
-        final BindConnectionInitializer connInitializer = (BindConnectionInitializer) connConfig.getConnectionInitializer();
+        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 = connPool.getConnectionFactory().getConnectionConfig().getSslConfig();
+        final SslConfig sslConfig = connFactory.getConnectionConfig().getSslConfig();
         assertNotNull(sslConfig);
         final CredentialConfig credentialConfig = sslConfig.getCredentialConfig();
         assertNotNull(credentialConfig);
 
-        final SearchExecutor searchExecutor = dataConnector.getSearchExecutor();
-        assertNotNull(searchExecutor);
-        assertEquals(searchExecutor.getBaseDn(), "ou=people,dc=shibboleth,dc=net");
-        assertNull(searchExecutor.getSearchFilter());
-        assertEquals(searchExecutor.getTimeLimit(), Duration.ofSeconds(7));
-        assertNull(searchExecutor.getReferralHandler());
+        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));
+        assertNull(searchOperation.getSearchResultHandlers());
 
         final ConnectionFactoryValidator validator = (ConnectionFactoryValidator) dataConnector.getValidator();
         assertNotNull(validator);
diff --git a/shib-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/ldap-attribute-resolver-spring-context.xml b/shib-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/ldap-attribute-resolver-spring-context.xml
index 932903987..e156f4a73 100644
--- a/shib-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/ldap-attribute-resolver-spring-context.xml
+++ b/shib-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/ldap-attribute-resolver-spring-context.xml
@@ -2,53 +2,53 @@
 <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     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="PT5S" 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="PT15M" />
-                </constructor-arg>
-                <constructor-arg index="1">
-                    <bean class="org.ldaptive.DefaultConnectionFactory">
-                        <property name="connectionConfig">
-                            <bean class="org.ldaptive.ConnectionConfig" p:ldapUrl="ldap://localhost:10389"
-                                p:useStartTLS="true" p:connectTimeout="PT2S" p:responseTimeout="PT4S">
-                                <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>
+    <bean id="connectionFactory" class="org.ldaptive.PooledConnectionFactory" init-method="initialize"
+          p:blockWaitTime="PT5S"
+          p:failFastInitialize="false"
+          p:name="resolver-pool"
+          p:minPoolSize="5"
+          p:maxPoolSize="10"
+          p:validatePeriodically="true">
+        <property name="connectionConfig">
+            <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="PT5M" p:idleTime="PT10M" />
+            </bean>
+        </property>
+        <property name="validator">
+            <bean class="org.ldaptive.SearchConnectionValidator" p:validatePeriod="PT15M">
+                <property name="searchRequest">
+                    <bean class="org.ldaptive.SearchRequest">
+                        <constructor-arg value="dc=shibboleth,dc=net" />
+                        <constructor-arg value="(ou=people)" />
+                    </bean>
                 </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="PT7S" />
     <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.TemplatedExecutableSearchFilterBuilder"
         p:templateText="(uid=${resolutionContext.principal})" p:velocityEngine-ref="shibboleth.VelocityEngine"
@@ -62,5 +62,5 @@
         </property>
     </bean>
     <bean id="validator" class="net.shibboleth.idp.attribute.resolver.dc.ldap.impl.ConnectionFactoryValidator"
-      p:connectionFactory-ref="connectionFactory" p:throwValidateError="true" />
+        p:connectionFactory-ref="connectionFactory" p:throwValidateError="true" />
 </beans>
\ No newline at end of file
diff --git a/shib-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/ldap-attribute-resolver-spring-props-context.xml b/shib-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/ldap-attribute-resolver-spring-props-context.xml
index c2fd91d9a..e11b6a604 100644
--- a/shib-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/ldap-attribute-resolver-spring-props-context.xml
+++ b/shib-attribute-resolver-spring/src/test/resources/net/shibboleth/idp/attribute/resolver/spring/dc/ldap/ldap-attribute-resolver-spring-props-context.xml
@@ -4,57 +4,53 @@
     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="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>
+    <bean id="connectionFactory" class="org.ldaptive.PooledConnectionFactory" init-method="initialize"
+          p:blockWaitTime="%{connectionPool.blockWaitTime}"
+          p:failFastInitialize="%{connectionPool.failFastInitialize}"
+          p:name="resolver-pool"
+          p:minPoolSize="%{connectionPool.minPoolSize}"
+          p:maxPoolSize="%{connectionPool.maxPoolSize}"
+          p:validatePeriodically="%{connectionPool.validatePeriodically}">
+        <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="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>
+        </property>
+        <property name="validator">
+            <bean class="org.ldaptive.SearchConnectionValidator" p:validatePeriod="%{connectionPool.validatePeriod}">
+                <property name="searchRequest">
+                    <bean class="org.ldaptive.SearchRequest">
+                        <constructor-arg value="%{connectionPool.validator.baseDn}" />
+                        <constructor-arg value="%{connectionPool.validator.filter}" />
+                    </bean>
                 </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>
@@ -70,7 +66,6 @@
             </map>
         </property>
     </bean>
-        <bean id="validator" class="net.shibboleth.idp.attribute.resolver.dc.ldap.impl.ConnectionFactoryValidator"
-              p:connectionFactory-ref="connectionFactory" p:throwValidateError="true" />
-    
+    <bean id="validator" class="net.shibboleth.idp.attribute.resolver.dc.ldap.impl.ConnectionFactoryValidator"
+        p:connectionFactory-ref="connectionFactory" p:throwValidateError="true" />
 </beans>

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


More information about the commits mailing list