You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by bt...@apache.org on 2021/06/11 07:36:56 UTC

[james-project] branch master updated (f344d7d -> af7bc42)

This is an automated email from the ASF dual-hosted git repository.

btellier pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git.


    from f344d7d  JAMES-3593 Recommend the upgrade to RabbitMQ 3.8.17
     new 393c65a  JAMES-3594 Implement ReadOnlyLDAPUsersDAO with UnboundID library
     new 8d04141  JAMES-3594 Implement group restrictions on top of UnboundID
     new 9235adc8 JAMES-3594 Use Reactor to implement LDAP retries
     new 2712b33  JAMES-3594 Implement ReadOnlyLDAPUsersDAO with UnboundID library
     new 8be22b3  JAMES-3594 Fix ReadOnlyUsersLDAPRepository JavaDoc
     new e483dc7  JAMES-3594 Document group/role based access restrictions as experimental
     new 740c672  JAMES-3594 Add a poolSize configuration option.
     new 0af0575  JAMES-3594 Use Filter instead of search templates
     new e080d39  JAMES-3594 Complement existing LDAP tests to cover all basic read operations
     new 5d214ea  JAMES-3594 Implement ReadOnlyLDAPUsersDAO with UnboundID library
     new bfd78ca  JAMES-3594 LDAP user listing: avoid extra requests for each users
     new 2fb75fc  JAMES-3594 Group restrictions should use connection pooling
     new 299a110  JAMES-3594 LDAP user counting: avoid extra requests for each users
     new 6b91bb4  JAMES-3594 Strong typing for DN
     new 15a86bf  JAMES-3594 Validate filters at ReadOnlyLDAPUsersDAO initialization
     new ead5318  JAMES-3594 Tests that extra filters are well applied
     new 9c59742  JAMES-3594 Upgrade instruction note
     new af7bc42  JAMES-3594 Drop manual retries and rely on UnboundID connection retries

The 18 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 pom.xml                                            |   6 +-
 .../test/java/org/apache/james/DockerLdapRule.java |   4 -
 server/data/data-ldap/pom.xml                      |   8 +-
 .../user/ldap/LdapRepositoryConfiguration.java     | 118 +----
 .../user/ldap/ReadOnlyLDAPGroupRestriction.java    |  38 +-
 .../apache/james/user/ldap/ReadOnlyLDAPUser.java   |  69 +--
 .../james/user/ldap/ReadOnlyLDAPUsersDAO.java      | 331 ++++++--------
 .../user/ldap/ReadOnlyUsersLDAPRepository.java     |  79 +---
 .../apache/james/user/ldap/api/LdapConstants.java  |  32 --
 .../user/ldap/retry/DoublingRetrySchedule.java     |  99 ----
 .../user/ldap/retry/ExceptionRetryHandler.java     | 131 ------
 .../ldap/retry/api/ExceptionRetryingProxy.java     |  49 --
 .../james/user/ldap/retry/api/RetryHandler.java    |  52 ---
 .../james/user/ldap/retry/api/RetrySchedule.java   |  35 --
 .../ldap/retry/naming/LoggingRetryHandler.java     |  54 ---
 .../retry/naming/NamingExceptionRetryHandler.java  |  72 ---
 .../user/ldap/retry/naming/RetryingContext.java    | 497 ---------------------
 .../retry/naming/directory/RetryingDirContext.java | 407 -----------------
 .../retry/naming/ldap/RetryingLdapContext.java     | 129 ------
 .../user/ldap/ReadOnlyUsersLDAPRepositoryTest.java | 112 ++++-
 .../user/ldap/retry/DoublingRetryScheduleTest.java |  71 ---
 .../user/ldap/retry/ExceptionRetryHandlerTest.java | 153 -------
 .../naming/NamingExceptionRetryHandlerTest.java    |  92 ----
 upgrade-instructions.md                            |  20 +
 24 files changed, 336 insertions(+), 2322 deletions(-)
 delete mode 100644 server/data/data-ldap/src/main/java/org/apache/james/user/ldap/api/LdapConstants.java
 delete mode 100644 server/data/data-ldap/src/main/java/org/apache/james/user/ldap/retry/DoublingRetrySchedule.java
 delete mode 100644 server/data/data-ldap/src/main/java/org/apache/james/user/ldap/retry/ExceptionRetryHandler.java
 delete mode 100644 server/data/data-ldap/src/main/java/org/apache/james/user/ldap/retry/api/ExceptionRetryingProxy.java
 delete mode 100644 server/data/data-ldap/src/main/java/org/apache/james/user/ldap/retry/api/RetryHandler.java
 delete mode 100644 server/data/data-ldap/src/main/java/org/apache/james/user/ldap/retry/api/RetrySchedule.java
 delete mode 100644 server/data/data-ldap/src/main/java/org/apache/james/user/ldap/retry/naming/LoggingRetryHandler.java
 delete mode 100644 server/data/data-ldap/src/main/java/org/apache/james/user/ldap/retry/naming/NamingExceptionRetryHandler.java
 delete mode 100644 server/data/data-ldap/src/main/java/org/apache/james/user/ldap/retry/naming/RetryingContext.java
 delete mode 100644 server/data/data-ldap/src/main/java/org/apache/james/user/ldap/retry/naming/directory/RetryingDirContext.java
 delete mode 100644 server/data/data-ldap/src/main/java/org/apache/james/user/ldap/retry/naming/ldap/RetryingLdapContext.java
 delete mode 100644 server/data/data-ldap/src/test/java/org/apache/james/user/ldap/retry/DoublingRetryScheduleTest.java
 delete mode 100644 server/data/data-ldap/src/test/java/org/apache/james/user/ldap/retry/ExceptionRetryHandlerTest.java
 delete mode 100644 server/data/data-ldap/src/test/java/org/apache/james/user/ldap/retry/naming/NamingExceptionRetryHandlerTest.java

---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[james-project] 04/18: JAMES-3594 Implement ReadOnlyLDAPUsersDAO with UnboundID library

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 2712b332588a0d2b755fac28936b2b19916a0f92
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Wed Jun 9 08:14:51 2021 +0700

    JAMES-3594 Implement ReadOnlyLDAPUsersDAO with UnboundID library
---
 .../apache/james/user/ldap/ReadOnlyLDAPUser.java   |  4 +--
 .../james/user/ldap/ReadOnlyLDAPUsersDAO.java      | 35 ++++++++--------------
 2 files changed, 14 insertions(+), 25 deletions(-)

diff --git a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUser.java b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUser.java
index b185e9f..09ecc6c 100644
--- a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUser.java
+++ b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUser.java
@@ -29,6 +29,7 @@ import org.slf4j.LoggerFactory;
 import com.unboundid.ldap.sdk.BindResult;
 import com.unboundid.ldap.sdk.LDAPConnectionPool;
 import com.unboundid.ldap.sdk.LDAPException;
+import com.unboundid.ldap.sdk.ResultCode;
 
 import reactor.core.publisher.Mono;
 
@@ -145,7 +146,6 @@ public class ReadOnlyLDAPUser implements User, Serializable {
 
     private boolean doVerifyPassword(String password) throws LDAPException {
         BindResult bindResult = connectionPool.bindAndRevertAuthentication(userDN, password);
-        return bindResult.getResultCode()
-            .intValue() == 0;
+        return bindResult.getResultCode() == ResultCode.SUCCESS;
     }
 }
diff --git a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUsersDAO.java b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUsersDAO.java
index 0dc4ecd..d400aed 100644
--- a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUsersDAO.java
+++ b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUsersDAO.java
@@ -113,7 +113,6 @@ public class ReadOnlyLDAPUsersDAO implements UsersDAO, Configurable {
         SocketFactory socketFactory = null;
         LDAPConnection ldapConnection = new LDAPConnection(socketFactory, connectionOptions, uri.getHost(), uri.getPort(), ldapConfiguration.getPrincipal(), ldapConfiguration.getCredentials());
         ldapConnectionPool = new LDAPConnectionPool(ldapConnection, 4);
-        // TODO implement retries
     }
 
     @PreDestroy
@@ -155,19 +154,14 @@ public class ReadOnlyLDAPUsersDAO implements UsersDAO, Configurable {
     }
 
     private Set<String> getAllUsersFromLDAP() throws LDAPException {
-        LDAPConnection connection = ldapConnectionPool.getConnection();
-        try {
-            SearchResult searchResult = connection.search(ldapConfiguration.getUserBase(),
-                SearchScope.SUB,
-                filterTemplate);
+        SearchResult searchResult = ldapConnectionPool.search(ldapConfiguration.getUserBase(),
+            SearchScope.SUB,
+            filterTemplate);
 
-            return searchResult.getSearchEntries()
-                .stream()
-                .map(entry -> entry.getObjectClassAttribute().getName())
-                .collect(Guavate.toImmutableSet());
-        } finally {
-            ldapConnectionPool.releaseConnection(connection);
-        }
+        return searchResult.getSearchEntries()
+            .stream()
+            .map(entry -> entry.getObjectClassAttribute().getName())
+            .collect(Guavate.toImmutableSet());
     }
 
     /**
@@ -229,16 +223,11 @@ public class ReadOnlyLDAPUsersDAO implements UsersDAO, Configurable {
     }
 
     private Optional<ReadOnlyLDAPUser> buildUser(String userDN) throws LDAPException {
-        LDAPConnection connection = ldapConnectionPool.getConnection();
-        try {
-            SearchResultEntry userAttributes = connection.getEntry(userDN);
-            Optional<String> userName = Optional.ofNullable(userAttributes.getAttributeValue(ldapConfiguration.getUserIdAttribute()));
-            return userName
-                .map(Username::of)
-                .map(username -> new ReadOnlyLDAPUser(username, userDN, ldapConnectionPool, ldapConfiguration));
-        } finally {
-            ldapConnectionPool.releaseConnection(connection);
-        }
+        SearchResultEntry userAttributes = ldapConnectionPool.getEntry(userDN);
+        Optional<String> userName = Optional.ofNullable(userAttributes.getAttributeValue(ldapConfiguration.getUserIdAttribute()));
+        return userName
+            .map(Username::of)
+            .map(username -> new ReadOnlyLDAPUser(username, userDN, ldapConnectionPool, ldapConfiguration));
     }
 
     @Override

---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[james-project] 12/18: JAMES-3594 Group restrictions should use connection pooling

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 2fb75fc636d39802f781725b8ded3143a23f70e1
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Thu Jun 10 10:47:17 2021 +0700

    JAMES-3594 Group restrictions should use connection pooling
---
 .../user/ldap/ReadOnlyLDAPGroupRestriction.java    |  3 +-
 .../james/user/ldap/ReadOnlyLDAPUsersDAO.java      | 62 +++++++++-------------
 2 files changed, 28 insertions(+), 37 deletions(-)

diff --git a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPGroupRestriction.java b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPGroupRestriction.java
index 9123f65..d9023b7 100644
--- a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPGroupRestriction.java
+++ b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPGroupRestriction.java
@@ -31,6 +31,7 @@ import org.apache.commons.configuration2.tree.ImmutableNode;
 
 import com.github.steveash.guavate.Guavate;
 import com.unboundid.ldap.sdk.LDAPConnection;
+import com.unboundid.ldap.sdk.LDAPConnectionPool;
 import com.unboundid.ldap.sdk.LDAPException;
 import com.unboundid.ldap.sdk.SearchResultEntry;
 
@@ -113,7 +114,7 @@ public class ReadOnlyLDAPGroupRestriction {
      *
      * @return Returns a map of groupDNs to userDN lists.
      */
-    protected Map<String, Collection<String>> getGroupMembershipLists(LDAPConnection connection) throws LDAPException {
+    protected Map<String, Collection<String>> getGroupMembershipLists(LDAPConnectionPool connection) throws LDAPException {
         Map<String, Collection<String>> result = new HashMap<>();
 
         for (String groupDN : groupDNs) {
diff --git a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUsersDAO.java b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUsersDAO.java
index f1c1819..81b8375 100644
--- a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUsersDAO.java
+++ b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUsersDAO.java
@@ -226,30 +226,25 @@ public class ReadOnlyLDAPUsersDAO implements UsersDAO, Configurable {
     }
 
     private ReadOnlyLDAPUser searchAndBuildUser(Username name) throws LDAPException {
-        LDAPConnection connection = ldapConnectionPool.getConnection();
-        try {
-            SearchResult searchResult = connection.search(ldapConfiguration.getUserBase(),
-                SearchScope.SUB,
-                createFilter(name.asString()),
-                ldapConfiguration.getUserIdAttribute());
-
-            SearchResultEntry result = searchResult.getSearchEntries()
-                .stream()
-                .findFirst()
-                .orElse(null);
-            if (result == null) {
-                return null;
-            }
-
-            if (!ldapConfiguration.getRestriction().isActivated()
-                || userInGroupsMembershipList(result.getDN(), ldapConfiguration.getRestriction().getGroupMembershipLists(connection))) {
+        SearchResult searchResult = ldapConnectionPool.search(ldapConfiguration.getUserBase(),
+            SearchScope.SUB,
+            createFilter(name.asString()),
+            ldapConfiguration.getUserIdAttribute());
 
-                return new ReadOnlyLDAPUser(name, result.getDN(), ldapConnectionPool, ldapConfiguration);
-            }
+        SearchResultEntry result = searchResult.getSearchEntries()
+            .stream()
+            .findFirst()
+            .orElse(null);
+        if (result == null) {
             return null;
-        } finally {
-            ldapConnectionPool.releaseConnection(connection);
         }
+
+        if (!ldapConfiguration.getRestriction().isActivated()
+            || userInGroupsMembershipList(result.getDN(), ldapConfiguration.getRestriction().getGroupMembershipLists(ldapConnectionPool))) {
+
+            return new ReadOnlyLDAPUser(name, result.getDN(), ldapConnectionPool, ldapConfiguration);
+        }
+        return null;
     }
 
     private Optional<ReadOnlyLDAPUser> buildUser(String userDN) throws LDAPException {
@@ -346,22 +341,17 @@ public class ReadOnlyLDAPUsersDAO implements UsersDAO, Configurable {
         Set<String> userDNs = getAllUsersDNFromLDAP();
         Collection<String> validUserDNs;
         if (ldapConfiguration.getRestriction().isActivated()) {
-            final LDAPConnection connection = ldapConnectionPool.getConnection();
-            try {
-                Map<String, Collection<String>> groupMembershipList = ldapConfiguration.getRestriction()
-                    .getGroupMembershipLists(connection);
-                validUserDNs = new ArrayList<>();
-
-                Iterator<String> userDNIterator = userDNs.iterator();
-                String userDN;
-                while (userDNIterator.hasNext()) {
-                    userDN = userDNIterator.next();
-                    if (userInGroupsMembershipList(userDN, groupMembershipList)) {
-                        validUserDNs.add(userDN);
-                    }
+            Map<String, Collection<String>> groupMembershipList = ldapConfiguration.getRestriction()
+                .getGroupMembershipLists(ldapConnectionPool);
+            validUserDNs = new ArrayList<>();
+
+            Iterator<String> userDNIterator = userDNs.iterator();
+            String userDN;
+            while (userDNIterator.hasNext()) {
+                userDN = userDNIterator.next();
+                if (userInGroupsMembershipList(userDN, groupMembershipList)) {
+                    validUserDNs.add(userDN);
                 }
-            } finally {
-                ldapConnectionPool.releaseConnection(connection);
             }
         } else {
             validUserDNs = userDNs;

---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[james-project] 09/18: JAMES-3594 Complement existing LDAP tests to cover all basic read operations

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit e080d390587f12b063962b0416a10a72ff041e3e
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Wed Jun 9 09:11:55 2021 +0700

    JAMES-3594 Complement existing LDAP tests to cover all basic read operations
---
 .../user/ldap/ReadOnlyUsersLDAPRepositoryTest.java | 44 +++++++++++++++++++++-
 1 file changed, 43 insertions(+), 1 deletion(-)

diff --git a/server/data/data-ldap/src/test/java/org/apache/james/user/ldap/ReadOnlyUsersLDAPRepositoryTest.java b/server/data/data-ldap/src/test/java/org/apache/james/user/ldap/ReadOnlyUsersLDAPRepositoryTest.java
index 7ae78e4..db551cc 100644
--- a/server/data/data-ldap/src/test/java/org/apache/james/user/ldap/ReadOnlyUsersLDAPRepositoryTest.java
+++ b/server/data/data-ldap/src/test/java/org/apache/james/user/ldap/ReadOnlyUsersLDAPRepositoryTest.java
@@ -47,6 +47,8 @@ import org.junit.jupiter.api.extension.RegisterExtension;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.collect.ImmutableList;
+
 class ReadOnlyUsersLDAPRepositoryTest {
 
     static final Logger LOGGER = LoggerFactory.getLogger(ReadOnlyUsersLDAPRepositoryTest.class);
@@ -98,6 +100,26 @@ class ReadOnlyUsersLDAPRepositoryTest {
         }
 
         @Test
+        void listShouldReturnExistingUsers() throws Exception {
+            assertThat(ImmutableList.copyOf(usersRepository.list())).containsOnly(JAMES_USER_MAIL);
+        }
+
+        @Test
+        void countUsersShouldReturnOne() throws Exception {
+            assertThat(usersRepository.countUsers()).isEqualTo(1);
+        }
+
+        @Test
+        void containsShouldReturnTrueWhenUserExists() throws Exception {
+            assertThat(usersRepository.contains(JAMES_USER_MAIL)).isTrue();
+        }
+
+        @Test
+        void containsShouldReturnFalseWhenUserDoesNotExists() throws Exception {
+            assertThat(usersRepository.contains(Username.of("unknown@" + DOMAIN))).isFalse();
+        }
+
+        @Test
         void testShouldStillWorkAfterRestartingLDAP() throws Exception {
             usersRepository.test(JAMES_USER_MAIL, PASSWORD);
 
@@ -172,6 +194,26 @@ class ReadOnlyUsersLDAPRepositoryTest {
         }
 
         @Test
+        void listShouldReturnExistingUsers() throws Exception {
+            assertThat(ImmutableList.copyOf(usersRepository.list())).containsOnly(JAMES_USER);
+        }
+
+        @Test
+        void countUsersShouldReturnOne() throws Exception {
+            assertThat(usersRepository.countUsers()).isEqualTo(1);
+        }
+
+        @Test
+        void containsShouldReturnTrueWhenUserExists() throws Exception {
+            assertThat(usersRepository.contains(JAMES_USER)).isTrue();
+        }
+
+        @Test
+        void containsShouldReturnFalseWhenUserDoesNotExists() throws Exception {
+            assertThat(usersRepository.contains(Username.of("unknown"))).isFalse();
+        }
+
+        @Test
         void knownUserShouldNotBeAbleToLogInWhenPasswordIsNotCorrect() throws Exception {
             assertThat(usersRepository.test(JAMES_USER, BAD_PASSWORD)).isFalse();
         }
@@ -284,7 +326,7 @@ class ReadOnlyUsersLDAPRepositoryTest {
         configuration.addProperty("[@ldapHost]", ldapContainer.getLdapHost());
         configuration.addProperty("[@principal]", "cn=admin,dc=james,dc=org");
         configuration.addProperty("[@credentials]", ADMIN_PASSWORD);
-        configuration.addProperty("[@userBase]", "ou=People,dc=james,dc=org");
+        configuration.addProperty("[@userBase]", "ou=people,dc=james,dc=org");
         configuration.addProperty("[@userObjectClass]", "inetOrgPerson");
         configuration.addProperty("[@maxRetries]", "1");
         configuration.addProperty("[@retryStartInterval]", "0");

---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[james-project] 03/18: JAMES-3594 Use Reactor to implement LDAP retries

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 9235adc8ea525bfad6f54707ea7c968a2bcdb20f
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Sun Jun 6 20:46:48 2021 +0700

    JAMES-3594 Use Reactor to implement LDAP retries
---
 .../user/ldap/LdapRepositoryConfiguration.java     | 11 +++
 .../apache/james/user/ldap/ReadOnlyLDAPUser.java   | 36 ++++++----
 .../james/user/ldap/ReadOnlyLDAPUsersDAO.java      | 78 ++++++++++++++++------
 3 files changed, 93 insertions(+), 32 deletions(-)

diff --git a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/LdapRepositoryConfiguration.java b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/LdapRepositoryConfiguration.java
index efc79ed..a87413b 100644
--- a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/LdapRepositoryConfiguration.java
+++ b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/LdapRepositoryConfiguration.java
@@ -19,6 +19,7 @@
 
 package org.apache.james.user.ldap;
 
+import java.time.Duration;
 import java.util.Objects;
 import java.util.Optional;
 
@@ -29,6 +30,10 @@ import org.apache.james.core.Username;
 
 import com.google.common.base.Preconditions;
 
+import reactor.core.scheduler.Schedulers;
+import reactor.util.retry.Retry;
+import reactor.util.retry.RetryBackoffSpec;
+
 public class LdapRepositoryConfiguration {
     public static final String SUPPORTS_VIRTUAL_HOSTING = "supportsVirtualHosting";
 
@@ -384,6 +389,12 @@ public class LdapRepositoryConfiguration {
         return administratorId;
     }
 
+    public RetryBackoffSpec retrySpec() {
+        return Retry.backoff(getMaxRetries(), Duration.ofMillis(getRetryStartInterval() * getScale()))
+            .maxBackoff(Duration.ofMillis(getRetryMaxInterval() * getScale()))
+            .scheduler(Schedulers.elastic());
+    }
+
     @Override
     public final boolean equals(Object o) {
         if (o instanceof LdapRepositoryConfiguration) {
diff --git a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUser.java b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUser.java
index 6125fb9..b185e9f 100644
--- a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUser.java
+++ b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUser.java
@@ -30,6 +30,8 @@ import com.unboundid.ldap.sdk.BindResult;
 import com.unboundid.ldap.sdk.LDAPConnectionPool;
 import com.unboundid.ldap.sdk.LDAPException;
 
+import reactor.core.publisher.Mono;
+
 /**
  * Encapsulates the details of a user as taken from an LDAP compliant directory.
  * Instances of this class are only applicable to the
@@ -68,27 +70,29 @@ public class ReadOnlyLDAPUser implements User, Serializable {
      */
     private final LDAPConnectionPool connectionPool;
 
+    private final LdapRepositoryConfiguration configuration;
+
     /**
      * Constructs an instance for the given user-details, and which will
      * authenticate against the given host.
-     *
-     * @param connectionPool
-     *            The connectionPool for the LDAP server on which the user details are held.
-     *            This is also the host against which the user will be
-     *            authenticated, when {@link #verifyPassword(String)} is
-     *            invoked.
-     * @param userName
+     *  @param userName
      *            The user-identifier/name. This is the value with which the
      *            field  will be initialised, and which will be
      *            returned by invoking {@link #getUserName()}.
      * @param userDN
      *            The distinguished (unique-key) of the user details as stored
-     *            on the LDAP directory.
+     * @param connectionPool
+ *            The connectionPool for the LDAP server on which the user details are held.
+ *            This is also the host against which the user will be
+ *            authenticated, when {@link #verifyPassword(String)} is
+ *            invoked.
+     * @param configuration
      */
-    public ReadOnlyLDAPUser(Username userName, String userDN, LDAPConnectionPool connectionPool) {
+    public ReadOnlyLDAPUser(Username userName, String userDN, LDAPConnectionPool connectionPool, LdapRepositoryConfiguration configuration) {
         this.userName = userName;
         this.userDN = userDN;
         this.connectionPool = connectionPool;
+        this.configuration = configuration;
     }
 
     /**
@@ -130,12 +134,18 @@ public class ReadOnlyLDAPUser implements User, Serializable {
     @Override
     public boolean verifyPassword(String password) {
         try {
-            BindResult bindResult = connectionPool.bindAndRevertAuthentication(userDN, password);
-            return bindResult.getResultCode()
-                .intValue() == 0;
-        } catch (LDAPException e) {
+            return Mono.fromCallable(() -> doVerifyPassword(password))
+                .retryWhen(configuration.retrySpec())
+                .block();
+        } catch (Exception e) {
             LOGGER.error("Unexpected error upon authentication", e);
             return false;
         }
     }
+
+    private boolean doVerifyPassword(String password) throws LDAPException {
+        BindResult bindResult = connectionPool.bindAndRevertAuthentication(userDN, password);
+        return bindResult.getResultCode()
+            .intValue() == 0;
+    }
 }
diff --git a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUsersDAO.java b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUsersDAO.java
index 3d406a1..0dc4ecd 100644
--- a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUsersDAO.java
+++ b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUsersDAO.java
@@ -55,6 +55,8 @@ import com.unboundid.ldap.sdk.SearchResult;
 import com.unboundid.ldap.sdk.SearchResultEntry;
 import com.unboundid.ldap.sdk.SearchScope;
 
+import reactor.core.publisher.Mono;
+
 public class ReadOnlyLDAPUsersDAO implements UsersDAO, Configurable {
     private static final Logger LOGGER = LoggerFactory.getLogger(ReadOnlyLDAPUsersDAO.class);
 
@@ -218,7 +220,7 @@ public class ReadOnlyLDAPUsersDAO implements UsersDAO, Configurable {
             if (!ldapConfiguration.getRestriction().isActivated()
                 || userInGroupsMembershipList(result.getDN(), ldapConfiguration.getRestriction().getGroupMembershipLists(connection))) {
 
-                return new ReadOnlyLDAPUser(name, result.getDN(), ldapConnectionPool);
+                return new ReadOnlyLDAPUser(name, result.getDN(), ldapConnectionPool, ldapConfiguration);
             }
             return null;
         } finally {
@@ -233,7 +235,7 @@ public class ReadOnlyLDAPUsersDAO implements UsersDAO, Configurable {
             Optional<String> userName = Optional.ofNullable(userAttributes.getAttributeValue(ldapConfiguration.getUserIdAttribute()));
             return userName
                 .map(Username::of)
-                .map(username -> new ReadOnlyLDAPUser(username, userDN, ldapConnectionPool));
+                .map(username -> new ReadOnlyLDAPUser(username, userDN, ldapConnectionPool, ldapConfiguration));
         } finally {
             ldapConnectionPool.releaseConnection(connection);
         }
@@ -241,44 +243,82 @@ public class ReadOnlyLDAPUsersDAO implements UsersDAO, Configurable {
 
     @Override
     public boolean contains(Username name) throws UsersRepositoryException {
-        return getUserByName(name).isPresent();
+        try {
+            return Mono.fromCallable(() -> doContains(name))
+                .retryWhen(ldapConfiguration.retrySpec())
+                .block();
+        } catch (Exception e) {
+            if (e.getCause() instanceof UsersRepositoryException) {
+                throw (UsersRepositoryException) e.getCause();
+            }
+            throw new UsersRepositoryException("Unable to check user existence from ldap", e);
+        }
+    }
+
+    private boolean doContains(Username name) throws LDAPException {
+        return doGetUserByName(name).isPresent();
     }
 
     @Override
     public int countUsers() throws UsersRepositoryException {
         try {
-            return Math.toIntExact(getValidUsers().stream()
-                .map(Throwing.function(this::buildUser).sneakyThrow())
-                .flatMap(Optional::stream)
-                .count());
-        } catch (LDAPException e) {
+            return Mono.fromCallable(() -> doCountUsers())
+                .retryWhen(ldapConfiguration.retrySpec())
+                .block();
+        } catch (Exception e) {
+            if (e.getCause() instanceof UsersRepositoryException) {
+                throw (UsersRepositoryException) e.getCause();
+            }
             throw new UsersRepositoryException("Unable to retrieve user count from ldap", e);
         }
     }
 
+    private int doCountUsers() throws LDAPException {
+        return Math.toIntExact(getValidUsers().stream()
+            .map(Throwing.function(this::buildUser).sneakyThrow())
+            .flatMap(Optional::stream)
+            .count());
+    }
+
     @Override
     public Optional<User> getUserByName(Username name) throws UsersRepositoryException {
         try {
-          return Optional.ofNullable(searchAndBuildUser(name));
-        } catch (LDAPException e) {
-            throw new UsersRepositoryException("Unable to retrieve user from ldap", e);
+            return Mono.fromCallable(() -> doGetUserByName(name))
+                .retryWhen(ldapConfiguration.retrySpec())
+                .block();
+        } catch (Exception e) {
+            if (e.getCause() instanceof UsersRepositoryException) {
+                throw (UsersRepositoryException) e.getCause();
+            }
+            throw new UsersRepositoryException("Unable check user existence from ldap", e);
         }
     }
 
+    private Optional<User> doGetUserByName(Username name) throws LDAPException {
+        return Optional.ofNullable(searchAndBuildUser(name));
+    }
+
     @Override
     public Iterator<Username> list() throws UsersRepositoryException {
         try {
-            return buildUserCollection(getValidUsers())
-                .stream()
-                .map(ReadOnlyLDAPUser::getUserName)
-                .iterator();
-        } catch (LDAPException namingException) {
-            throw new UsersRepositoryException(
-                    "Unable to retrieve users list from LDAP due to unknown naming error.",
-                    namingException);
+            return Mono.fromCallable(this::doList)
+                .retryWhen(ldapConfiguration.retrySpec())
+                .block();
+        } catch (Exception e) {
+            if (e.getCause() instanceof UsersRepositoryException) {
+                throw (UsersRepositoryException) e.getCause();
+            }
+            throw new UsersRepositoryException("Unable to list users from ldap", e);
         }
     }
 
+    private Iterator<Username> doList() throws LDAPException {
+        return buildUserCollection(getValidUsers())
+            .stream()
+            .map(ReadOnlyLDAPUser::getUserName)
+            .iterator();
+    }
+
     private Collection<String> getValidUsers() throws LDAPException {
         Set<String> userDNs = getAllUsersFromLDAP();
         Collection<String> validUserDNs;

---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[james-project] 16/18: JAMES-3594 Tests that extra filters are well applied

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit ead53185e03835d8c9c61225cdc0f2026a57dfc9
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Thu Jun 10 11:25:23 2021 +0700

    JAMES-3594 Tests that extra filters are well applied
---
 .../user/ldap/ReadOnlyUsersLDAPRepositoryTest.java | 61 ++++++++++++++++++----
 1 file changed, 52 insertions(+), 9 deletions(-)

diff --git a/server/data/data-ldap/src/test/java/org/apache/james/user/ldap/ReadOnlyUsersLDAPRepositoryTest.java b/server/data/data-ldap/src/test/java/org/apache/james/user/ldap/ReadOnlyUsersLDAPRepositoryTest.java
index 047ce35..7254907 100644
--- a/server/data/data-ldap/src/test/java/org/apache/james/user/ldap/ReadOnlyUsersLDAPRepositoryTest.java
+++ b/server/data/data-ldap/src/test/java/org/apache/james/user/ldap/ReadOnlyUsersLDAPRepositoryTest.java
@@ -75,15 +75,7 @@ class ReadOnlyUsersLDAPRepositoryTest {
 
     @Test
     void shouldNotStartWithInvalidFilter() throws Exception {
-        PropertyListConfiguration configuration = new PropertyListConfiguration();
-        configuration.addProperty("[@ldapHost]", ldapContainer.getLdapHost());
-        configuration.addProperty("[@principal]", "cn=admin,dc=james,dc=org");
-        configuration.addProperty("[@credentials]", ADMIN_PASSWORD);
-        configuration.addProperty("[@userBase]", "ou=people,dc=james,dc=org");
-        configuration.addProperty("[@userObjectClass]", "inetOrgPerson");
-        configuration.addProperty("[@userIdAttribute]", "uid");
-        configuration.addProperty("[@administratorId]", ADMIN_LOCAL_PART);
-
+        HierarchicalConfiguration<ImmutableNode> configuration = ldapRepositoryConfiguration(ldapContainer);
         configuration.addProperty("[@filter]", "INVALID!!!");
 
         ReadOnlyUsersLDAPRepository usersLDAPRepository = new ReadOnlyUsersLDAPRepository(new SimpleDomainList());
@@ -94,6 +86,57 @@ class ReadOnlyUsersLDAPRepositoryTest {
     }
 
     @Nested
+    class FilterTests {
+        @Test
+        void filterShouldKeepMatchingEntries() throws Exception {
+            HierarchicalConfiguration<ImmutableNode> configuration = ldapRepositoryConfiguration(ldapContainer);
+            configuration.addProperty("[@filter]", "(sn=james-user)");
+
+            ReadOnlyUsersLDAPRepository usersLDAPRepository = new ReadOnlyUsersLDAPRepository(new SimpleDomainList());
+            usersLDAPRepository.configure(configuration);
+            usersLDAPRepository.init();
+
+            assertThat(usersLDAPRepository.contains(JAMES_USER)).isTrue();
+        }
+
+        @Test
+        void filterShouldFilterOutNonMatchingEntries() throws Exception {
+            HierarchicalConfiguration<ImmutableNode> configuration = ldapRepositoryConfiguration(ldapContainer);
+            configuration.addProperty("[@filter]", "(sn=nomatch)");
+
+            ReadOnlyUsersLDAPRepository usersLDAPRepository = new ReadOnlyUsersLDAPRepository(new SimpleDomainList());
+            usersLDAPRepository.configure(configuration);
+            usersLDAPRepository.init();
+
+            assertThat(usersLDAPRepository.contains(JAMES_USER)).isFalse();
+        }
+
+        @Test
+        void countShouldTakeFilterIntoAccount() throws Exception {
+            HierarchicalConfiguration<ImmutableNode> configuration = ldapRepositoryConfiguration(ldapContainer);
+            configuration.addProperty("[@filter]", "(sn=nomatch)");
+
+            ReadOnlyUsersLDAPRepository usersLDAPRepository = new ReadOnlyUsersLDAPRepository(new SimpleDomainList());
+            usersLDAPRepository.configure(configuration);
+            usersLDAPRepository.init();
+
+            assertThat(usersLDAPRepository.countUsers()).isEqualTo(0);
+        }
+
+        @Test
+        void listShouldTakeFilterIntoAccount() throws Exception {
+            HierarchicalConfiguration<ImmutableNode> configuration = ldapRepositoryConfiguration(ldapContainer);
+            configuration.addProperty("[@filter]", "(sn=nomatch)");
+
+            ReadOnlyUsersLDAPRepository usersLDAPRepository = new ReadOnlyUsersLDAPRepository(new SimpleDomainList());
+            usersLDAPRepository.configure(configuration);
+            usersLDAPRepository.init();
+
+            assertThat(ImmutableList.copyOf(usersLDAPRepository.list())).isEmpty();
+        }
+    }
+
+    @Nested
     class WhenEnableVirtualHosting implements UsersRepositoryContract.WithVirtualHostingReadOnlyContract {
         @RegisterExtension
         UserRepositoryExtension extension = UserRepositoryExtension.withVirtualHost();

---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[james-project] 07/18: JAMES-3594 Add a poolSize configuration option.

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 740c672c7000e18789b806a0715c2e4ae3dd53e1
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Wed Jun 9 09:21:15 2021 +0700

    JAMES-3594 Add a poolSize configuration option.
---
 .../user/ldap/LdapRepositoryConfiguration.java     | 28 ++++++++++++++++++----
 .../user/ldap/ReadOnlyUsersLDAPRepository.java     |  4 ++++
 2 files changed, 27 insertions(+), 5 deletions(-)

diff --git a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/LdapRepositoryConfiguration.java b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/LdapRepositoryConfiguration.java
index a87413b..16e40f5 100644
--- a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/LdapRepositoryConfiguration.java
+++ b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/LdapRepositoryConfiguration.java
@@ -43,6 +43,7 @@ public class LdapRepositoryConfiguration {
     private static final ReadOnlyLDAPGroupRestriction NO_RESTRICTION = new ReadOnlyLDAPGroupRestriction(null);
     private static final String NO_FILTER = null;
     private static final Optional<String> NO_ADMINISTRATOR_ID = Optional.empty();
+    private static final int DEFAULT_POOL_SIZE = 4;
 
     public static class Builder {
         private Optional<String> ldapHost;
@@ -55,6 +56,7 @@ public class LdapRepositoryConfiguration {
         private Optional<Long> retryStartInterval;
         private Optional<Long> retryMaxInterval;
         private Optional<Integer> scale;
+        private Optional<Integer> poolSize;
 
         public Builder() {
             ldapHost = Optional.empty();
@@ -67,6 +69,7 @@ public class LdapRepositoryConfiguration {
             retryStartInterval = Optional.empty();
             retryMaxInterval = Optional.empty();
             scale = Optional.empty();
+            poolSize = Optional.empty();
         }
 
         public Builder ldapHost(String ldapHost) {
@@ -119,6 +122,11 @@ public class LdapRepositoryConfiguration {
             return this;
         }
 
+        public Builder poolSize(int poolSize) {
+            this.poolSize = Optional.of(poolSize);
+            return this;
+        }
+
         public LdapRepositoryConfiguration build() throws ConfigurationException {
             Preconditions.checkState(ldapHost.isPresent(), "'ldapHost' is mandatory");
             Preconditions.checkState(principal.isPresent(), "'principal' is mandatory");
@@ -145,6 +153,7 @@ public class LdapRepositoryConfiguration {
                 retryStartInterval.get(),
                 retryMaxInterval.get(),
                 scale.get(),
+                poolSize.orElse(DEFAULT_POOL_SIZE),
                 NO_RESTRICTION,
                 NO_FILTER,
                 NO_ADMINISTRATOR_ID);
@@ -187,6 +196,8 @@ public class LdapRepositoryConfiguration {
         String filter = configuration.getString("[@filter]");
 
         Optional<String> administratorId = Optional.ofNullable(configuration.getString("[@administratorId]"));
+        int poolSize = Optional.ofNullable(configuration.getInteger("[@poolSize]", null))
+                .orElse(DEFAULT_POOL_SIZE);
 
         return new LdapRepositoryConfiguration(
             ldapHost,
@@ -202,6 +213,7 @@ public class LdapRepositoryConfiguration {
             retryStartInterval,
             retryMaxInterval,
             scale,
+            poolSize,
             restriction,
             filter,
             administratorId);
@@ -267,6 +279,7 @@ public class LdapRepositoryConfiguration {
     private final long retryStartInterval;
     private final long retryMaxInterval;
     private final int scale;
+    private final int poolSize;
 
     /**
      * Encapsulates the information required to restrict users to LDAP groups or
@@ -288,10 +301,10 @@ public class LdapRepositoryConfiguration {
     private final Optional<Username> administratorId;
 
     private LdapRepositoryConfiguration(String ldapHost, String principal, String credentials, String userBase, String userIdAttribute,
-                                       String userObjectClass, int connectionTimeout, int readTimeout,
-                                       int maxRetries, boolean supportsVirtualHosting, long retryStartInterval, long retryMaxInterval,
-                                       int scale, ReadOnlyLDAPGroupRestriction restriction, String filter,
-                                       Optional<String> administratorId) throws ConfigurationException {
+                                        String userObjectClass, int connectionTimeout, int readTimeout,
+                                        int maxRetries, boolean supportsVirtualHosting, long retryStartInterval, long retryMaxInterval,
+                                        int scale, int poolSize, ReadOnlyLDAPGroupRestriction restriction, String filter,
+                                        Optional<String> administratorId) throws ConfigurationException {
         this.ldapHost = ldapHost;
         this.principal = principal;
         this.credentials = credentials;
@@ -305,6 +318,7 @@ public class LdapRepositoryConfiguration {
         this.retryStartInterval = retryStartInterval;
         this.retryMaxInterval = retryMaxInterval;
         this.scale = scale;
+        this.poolSize = poolSize;
         this.restriction = restriction;
         this.filter = filter;
         this.administratorId = administratorId.map(Username::of);
@@ -348,6 +362,9 @@ public class LdapRepositoryConfiguration {
         return userObjectClass;
     }
 
+    public int getPoolSize() {
+        return poolSize;
+    }
 
     public int getConnectionTimeout() {
         return connectionTimeout;
@@ -415,6 +432,7 @@ public class LdapRepositoryConfiguration {
                 && Objects.equals(this.userObjectClass, that.userObjectClass)
                 && Objects.equals(this.restriction, that.restriction)
                 && Objects.equals(this.filter, that.filter)
+                && Objects.equals(this.poolSize, that.poolSize)
                 && Objects.equals(this.administratorId, that.administratorId);
         }
         return false;
@@ -424,6 +442,6 @@ public class LdapRepositoryConfiguration {
     public final int hashCode() {
         return Objects.hash(ldapHost, principal, credentials, userBase, userIdAttribute, userObjectClass,
             connectionTimeout, readTimeout, maxRetries, supportsVirtualHosting, retryStartInterval, retryMaxInterval, scale,
-            restriction, filter, administratorId);
+            restriction, filter, administratorId, poolSize);
     }
 }
diff --git a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyUsersLDAPRepository.java b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyUsersLDAPRepository.java
index 2f8e309..5a3b44f 100644
--- a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyUsersLDAPRepository.java
+++ b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyUsersLDAPRepository.java
@@ -96,6 +96,10 @@ import org.apache.james.user.lib.UsersRepositoryImpl;
  * &quot;user&quot; for Microsoft Active Directory.</li>
  **
  * <li>
+ * <b>poolSize:</b> (optional, default = 4) The maximum number of connection
+ * in the pool.</li>
+ * <li>
+ * <li>
  * <b>maxRetries:</b> (optional, default = 0) The maximum number of times to
  * retry a failed operation. -1 means retry forever.</li>
  * <li>

---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[james-project] 17/18: JAMES-3594 Upgrade instruction note

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 9c59742e263e1dde15c49b43b484b9d7c93f44bb
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Wed Jun 9 09:27:40 2021 +0700

    JAMES-3594 Upgrade instruction note
---
 upgrade-instructions.md | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)

diff --git a/upgrade-instructions.md b/upgrade-instructions.md
index 4dd8d32..bb7049d 100644
--- a/upgrade-instructions.md
+++ b/upgrade-instructions.md
@@ -17,6 +17,24 @@ Changes to apply between 3.5.x and 3.6.x will be reported here.
 Change list:
 
  - [Drop Cassandra schema version prior version 8](#drop-cassandra-schema-version-prior-version-8)
+ - [Adopt UnboundID as a LDAP library](#drop-cassandra-schema-version-prior-version-8)
+ 
+### Adopt UnboundID as a LDAP library
+
+Date 09/06/2021
+
+JIRA: https://issues.apache.org/jira/browse/JAMES-3594
+
+The previous LDAP implementation was based on JNDI and was causing operational concerns by opening a connection
+for each user authentication. These limitations were inherent to JNDI thus to mitigate those we decided to migrate to
+a newer LDAP library: [UnboundID](https://ldap.com/unboundid-ldap-sdk-for-java/).
+
+As part of this migration the following change took place:
+
+ - `useConnectionPool` : Removed. UnboundId implementation relies on a pool by default.
+ - `poolSize` : Added. Allow controlling the count of connection in the pool.
+ 
+The "group restriction" feature should furthermore be considered experimental, its usage is discouraged.
 
 ### Drop Cassandra schema version prior version 8
 

---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[james-project] 18/18: JAMES-3594 Drop manual retries and rely on UnboundID connection retries

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit af7bc420be4697c2cc32875f6ccd2faffd4432a8
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Thu Jun 10 11:39:30 2021 +0700

    JAMES-3594 Drop manual retries and rely on UnboundID connection retries
---
 .../test/java/org/apache/james/DockerLdapRule.java |  4 -
 .../user/ldap/LdapRepositoryConfiguration.java     | 95 +---------------------
 .../apache/james/user/ldap/ReadOnlyLDAPUser.java   | 19 +----
 .../james/user/ldap/ReadOnlyLDAPUsersDAO.java      | 69 ++++------------
 .../user/ldap/ReadOnlyUsersLDAPRepository.java     | 50 ------------
 .../user/ldap/ReadOnlyUsersLDAPRepositoryTest.java |  4 -
 upgrade-instructions.md                            |  2 +
 7 files changed, 23 insertions(+), 220 deletions(-)

diff --git a/server/container/guice/cassandra-ldap-guice/src/test/java/org/apache/james/DockerLdapRule.java b/server/container/guice/cassandra-ldap-guice/src/test/java/org/apache/james/DockerLdapRule.java
index 96f937f..4ce36af 100644
--- a/server/container/guice/cassandra-ldap-guice/src/test/java/org/apache/james/DockerLdapRule.java
+++ b/server/container/guice/cassandra-ldap-guice/src/test/java/org/apache/james/DockerLdapRule.java
@@ -49,10 +49,6 @@ public class DockerLdapRule implements GuiceModuleTestRule {
                 .userBase("ou=People,dc=james,dc=org")
                 .userIdAttribute("uid")
                 .userObjectClass("inetOrgPerson")
-                .maxRetries(4)
-                .retryStartInterval(0)
-                .retryMaxInterval(8)
-                .scale(1000)
                 .build();
         } catch (ConfigurationException e) {
             throw new RuntimeException(e);
diff --git a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/LdapRepositoryConfiguration.java b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/LdapRepositoryConfiguration.java
index 16e40f5..fd1fce8 100644
--- a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/LdapRepositoryConfiguration.java
+++ b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/LdapRepositoryConfiguration.java
@@ -19,7 +19,6 @@
 
 package org.apache.james.user.ldap;
 
-import java.time.Duration;
 import java.util.Objects;
 import java.util.Optional;
 
@@ -30,10 +29,6 @@ import org.apache.james.core.Username;
 
 import com.google.common.base.Preconditions;
 
-import reactor.core.scheduler.Schedulers;
-import reactor.util.retry.Retry;
-import reactor.util.retry.RetryBackoffSpec;
-
 public class LdapRepositoryConfiguration {
     public static final String SUPPORTS_VIRTUAL_HOSTING = "supportsVirtualHosting";
 
@@ -52,10 +47,6 @@ public class LdapRepositoryConfiguration {
         private Optional<String> userBase;
         private Optional<String> userIdAttribute;
         private Optional<String> userObjectClass;
-        private Optional<Integer> maxRetries;
-        private Optional<Long> retryStartInterval;
-        private Optional<Long> retryMaxInterval;
-        private Optional<Integer> scale;
         private Optional<Integer> poolSize;
 
         public Builder() {
@@ -65,10 +56,6 @@ public class LdapRepositoryConfiguration {
             userBase = Optional.empty();
             userIdAttribute = Optional.empty();
             userObjectClass = Optional.empty();
-            maxRetries = Optional.empty();
-            retryStartInterval = Optional.empty();
-            retryMaxInterval = Optional.empty();
-            scale = Optional.empty();
             poolSize = Optional.empty();
         }
 
@@ -102,26 +89,6 @@ public class LdapRepositoryConfiguration {
             return this;
         }
 
-        public Builder maxRetries(int maxRetries) {
-            this.maxRetries = Optional.of(maxRetries);
-            return this;
-        }
-
-        public Builder retryStartInterval(long retryStartInterval) {
-            this.retryStartInterval = Optional.of(retryStartInterval);
-            return this;
-        }
-
-        public Builder retryMaxInterval(long retryMaxInterval) {
-            this.retryMaxInterval = Optional.of(retryMaxInterval);
-            return this;
-        }
-
-        public Builder scale(int scale) {
-            this.scale = Optional.of(scale);
-            return this;
-        }
-
         public Builder poolSize(int poolSize) {
             this.poolSize = Optional.of(poolSize);
             return this;
@@ -134,10 +101,6 @@ public class LdapRepositoryConfiguration {
             Preconditions.checkState(userBase.isPresent(), "'userBase' is mandatory");
             Preconditions.checkState(userIdAttribute.isPresent(), "'userIdAttribute' is mandatory");
             Preconditions.checkState(userObjectClass.isPresent(), "'userObjectClass' is mandatory");
-            Preconditions.checkState(maxRetries.isPresent(), "'maxRetries' is mandatory");
-            Preconditions.checkState(retryStartInterval.isPresent(), "'retryStartInterval' is mandatory");
-            Preconditions.checkState(retryMaxInterval.isPresent(), "'retryMaxInterval' is mandatory");
-            Preconditions.checkState(scale.isPresent(), "'scale' is mandatory");
 
             return new LdapRepositoryConfiguration(
                 ldapHost.get(),
@@ -148,11 +111,7 @@ public class LdapRepositoryConfiguration {
                 userObjectClass.get(),
                 NO_CONNECTION_TIMEOUT,
                 NO_READ_TIME_OUT,
-                maxRetries.get(),
                 !ENABLE_VIRTUAL_HOSTING,
-                retryStartInterval.get(),
-                retryMaxInterval.get(),
-                scale.get(),
                 poolSize.orElse(DEFAULT_POOL_SIZE),
                 NO_RESTRICTION,
                 NO_FILTER,
@@ -174,15 +133,7 @@ public class LdapRepositoryConfiguration {
         // Default is to use connection pooling
         int connectionTimeout = configuration.getInt("[@connectionTimeout]", NO_CONNECTION_TIMEOUT);
         int readTimeout = configuration.getInt("[@readTimeout]", NO_READ_TIME_OUT);
-        // Default maximum retries is 1, which allows an alternate connection to
-        // be found in a multi-homed environment
-        int maxRetries = configuration.getInt("[@maxRetries]", 1);
         boolean supportsVirtualHosting = configuration.getBoolean(SUPPORTS_VIRTUAL_HOSTING, !ENABLE_VIRTUAL_HOSTING);
-        // Default retry start interval is 0 second
-        long retryStartInterval = configuration.getLong("[@retryStartInterval]", 0);
-        // Default maximum retry interval is 60 seconds
-        long retryMaxInterval = configuration.getLong("[@retryMaxInterval]", 60);
-        int scale = configuration.getInt("[@retryIntervalScale]", 1000); // seconds
 
         HierarchicalConfiguration<ImmutableNode> restrictionConfig = null;
         // Check if we have a restriction we can use
@@ -208,11 +159,7 @@ public class LdapRepositoryConfiguration {
             userObjectClass,
             connectionTimeout,
             readTimeout,
-            maxRetries,
             supportsVirtualHosting,
-            retryStartInterval,
-            retryMaxInterval,
-            scale,
             poolSize,
             restriction,
             filter,
@@ -272,13 +219,7 @@ public class LdapRepositoryConfiguration {
     // The LDAP read timeout in milliseconds.
     private final int readTimeout;
 
-    // Maximum number of times to retry a connection attempts. Default is no
-    // retries.
-    private final int maxRetries;
     private final boolean supportsVirtualHosting;
-    private final long retryStartInterval;
-    private final long retryMaxInterval;
-    private final int scale;
     private final int poolSize;
 
     /**
@@ -302,8 +243,7 @@ public class LdapRepositoryConfiguration {
 
     private LdapRepositoryConfiguration(String ldapHost, String principal, String credentials, String userBase, String userIdAttribute,
                                         String userObjectClass, int connectionTimeout, int readTimeout,
-                                        int maxRetries, boolean supportsVirtualHosting, long retryStartInterval, long retryMaxInterval,
-                                        int scale, int poolSize, ReadOnlyLDAPGroupRestriction restriction, String filter,
+                                        boolean supportsVirtualHosting, int poolSize, ReadOnlyLDAPGroupRestriction restriction, String filter,
                                         Optional<String> administratorId) throws ConfigurationException {
         this.ldapHost = ldapHost;
         this.principal = principal;
@@ -313,11 +253,7 @@ public class LdapRepositoryConfiguration {
         this.userObjectClass = userObjectClass;
         this.connectionTimeout = connectionTimeout;
         this.readTimeout = readTimeout;
-        this.maxRetries = maxRetries;
         this.supportsVirtualHosting = supportsVirtualHosting;
-        this.retryStartInterval = retryStartInterval;
-        this.retryMaxInterval = retryMaxInterval;
-        this.scale = scale;
         this.poolSize = poolSize;
         this.restriction = restriction;
         this.filter = filter;
@@ -374,26 +310,10 @@ public class LdapRepositoryConfiguration {
         return readTimeout;
     }
 
-    public int getMaxRetries() {
-        return maxRetries;
-    }
-
     public boolean supportsVirtualHosting() {
         return supportsVirtualHosting;
     }
 
-    public long getRetryStartInterval() {
-        return retryStartInterval;
-    }
-
-    public long getRetryMaxInterval() {
-        return retryMaxInterval;
-    }
-
-    public int getScale() {
-        return scale;
-    }
-
     public ReadOnlyLDAPGroupRestriction getRestriction() {
         return restriction;
     }
@@ -406,12 +326,6 @@ public class LdapRepositoryConfiguration {
         return administratorId;
     }
 
-    public RetryBackoffSpec retrySpec() {
-        return Retry.backoff(getMaxRetries(), Duration.ofMillis(getRetryStartInterval() * getScale()))
-            .maxBackoff(Duration.ofMillis(getRetryMaxInterval() * getScale()))
-            .scheduler(Schedulers.elastic());
-    }
-
     @Override
     public final boolean equals(Object o) {
         if (o instanceof LdapRepositoryConfiguration) {
@@ -419,11 +333,7 @@ public class LdapRepositoryConfiguration {
 
             return Objects.equals(this.connectionTimeout, that.connectionTimeout)
                 && Objects.equals(this.readTimeout, that.readTimeout)
-                && Objects.equals(this.maxRetries, that.maxRetries)
                 && Objects.equals(this.supportsVirtualHosting, that.supportsVirtualHosting)
-                && Objects.equals(this.retryStartInterval, that.retryStartInterval)
-                && Objects.equals(this.retryMaxInterval, that.retryMaxInterval)
-                && Objects.equals(this.scale, that.scale)
                 && Objects.equals(this.ldapHost, that.ldapHost)
                 && Objects.equals(this.principal, that.principal)
                 && Objects.equals(this.credentials, that.credentials)
@@ -441,7 +351,6 @@ public class LdapRepositoryConfiguration {
     @Override
     public final int hashCode() {
         return Objects.hash(ldapHost, principal, credentials, userBase, userIdAttribute, userObjectClass,
-            connectionTimeout, readTimeout, maxRetries, supportsVirtualHosting, retryStartInterval, retryMaxInterval, scale,
-            restriction, filter, administratorId, poolSize);
+            connectionTimeout, readTimeout, supportsVirtualHosting, restriction, filter, administratorId, poolSize);
     }
 }
diff --git a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUser.java b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUser.java
index 1bc7147..7356212 100644
--- a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUser.java
+++ b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUser.java
@@ -29,11 +29,8 @@ import org.slf4j.LoggerFactory;
 import com.unboundid.ldap.sdk.BindResult;
 import com.unboundid.ldap.sdk.DN;
 import com.unboundid.ldap.sdk.LDAPConnectionPool;
-import com.unboundid.ldap.sdk.LDAPException;
 import com.unboundid.ldap.sdk.ResultCode;
 
-import reactor.core.publisher.Mono;
-
 /**
  * Encapsulates the details of a user as taken from an LDAP compliant directory.
  * Instances of this class are only applicable to the
@@ -71,8 +68,6 @@ public class ReadOnlyLDAPUser implements User, Serializable {
      */
     private final LDAPConnectionPool connectionPool;
 
-    private final LdapRepositoryConfiguration configuration;
-
     /**
      * Constructs an instance for the given user-details, and which will
      * authenticate against the given host.
@@ -87,13 +82,11 @@ public class ReadOnlyLDAPUser implements User, Serializable {
      *            This is also the host against which the user will be
      *            authenticated, when {@link #verifyPassword(String)} is
      *            invoked.
-     * @param configuration
      */
-    public ReadOnlyLDAPUser(Username userName, DN userDN, LDAPConnectionPool connectionPool, LdapRepositoryConfiguration configuration) {
+    public ReadOnlyLDAPUser(Username userName, DN userDN, LDAPConnectionPool connectionPool) {
         this.userName = userName;
         this.userDN = userDN;
         this.connectionPool = connectionPool;
-        this.configuration = configuration;
     }
 
     /**
@@ -135,17 +128,11 @@ public class ReadOnlyLDAPUser implements User, Serializable {
     @Override
     public boolean verifyPassword(String password) {
         try {
-            return Mono.fromCallable(() -> doVerifyPassword(password))
-                .retryWhen(configuration.retrySpec())
-                .block();
+            BindResult bindResult = connectionPool.bindAndRevertAuthentication(userDN.toString(), password);
+            return bindResult.getResultCode() == ResultCode.SUCCESS;
         } catch (Exception e) {
             LOGGER.error("Unexpected error upon authentication", e);
             return false;
         }
     }
-
-    private boolean doVerifyPassword(String password) throws LDAPException {
-        BindResult bindResult = connectionPool.bindAndRevertAuthentication(userDN.toString(), password);
-        return bindResult.getResultCode() == ResultCode.SUCCESS;
-    }
 }
diff --git a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUsersDAO.java b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUsersDAO.java
index 6901ab4..4588448 100644
--- a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUsersDAO.java
+++ b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUsersDAO.java
@@ -59,8 +59,6 @@ import com.unboundid.ldap.sdk.SearchResult;
 import com.unboundid.ldap.sdk.SearchResultEntry;
 import com.unboundid.ldap.sdk.SearchScope;
 
-import reactor.core.publisher.Mono;
-
 public class ReadOnlyLDAPUsersDAO implements UsersDAO, Configurable {
     private static final Logger LOGGER = LoggerFactory.getLogger(ReadOnlyLDAPUsersDAO.class);
 
@@ -106,8 +104,7 @@ public class ReadOnlyLDAPUsersDAO implements UsersDAO, Configurable {
                 + '\n' + "User baseDN: " + ldapConfiguration.getUserBase() + '\n' + "userIdAttribute: "
                 + ldapConfiguration.getUserIdAttribute() + '\n' + "Group restriction: " + ldapConfiguration.getRestriction()
                 + '\n' + "connectionTimeout: "
-                + ldapConfiguration.getConnectionTimeout() + '\n' + "readTimeout: " + ldapConfiguration.getReadTimeout()
-                + '\n' + "maxRetries: " + ldapConfiguration.getMaxRetries() + '\n');
+                + ldapConfiguration.getConnectionTimeout() + '\n' + "readTimeout: " + ldapConfiguration.getReadTimeout());
         }
 
         LDAPConnectionOptions connectionOptions = new LDAPConnectionOptions();
@@ -118,6 +115,7 @@ public class ReadOnlyLDAPUsersDAO implements UsersDAO, Configurable {
         SocketFactory socketFactory = null;
         LDAPConnection ldapConnection = new LDAPConnection(socketFactory, connectionOptions, uri.getHost(), uri.getPort(), ldapConfiguration.getPrincipal(), ldapConfiguration.getCredentials());
         ldapConnectionPool = new LDAPConnectionPool(ldapConnection, 4);
+        ldapConnectionPool.setRetryFailedOperationsDueToInvalidConnections(true);
 
         userExtraFilter = Optional.ofNullable(ldapConfiguration.getFilter())
             .map(Throwing.function(Filter::create).sneakyThrow());
@@ -241,7 +239,7 @@ public class ReadOnlyLDAPUsersDAO implements UsersDAO, Configurable {
         if (!ldapConfiguration.getRestriction().isActivated()
             || userInGroupsMembershipList(result.getParsedDN(), ldapConfiguration.getRestriction().getGroupMembershipLists(ldapConnectionPool))) {
 
-            return new ReadOnlyLDAPUser(name, result.getParsedDN(), ldapConnectionPool, ldapConfiguration);
+            return new ReadOnlyLDAPUser(name, result.getParsedDN(), ldapConnectionPool);
         }
         return null;
     }
@@ -251,37 +249,19 @@ public class ReadOnlyLDAPUsersDAO implements UsersDAO, Configurable {
         Optional<String> userName = Optional.ofNullable(userAttributes.getAttributeValue(ldapConfiguration.getUserIdAttribute()));
         return userName
             .map(Username::of)
-            .map(username -> new ReadOnlyLDAPUser(username, userDN, ldapConnectionPool, ldapConfiguration));
+            .map(username -> new ReadOnlyLDAPUser(username, userDN, ldapConnectionPool));
     }
 
     @Override
     public boolean contains(Username name) throws UsersRepositoryException {
-        try {
-            return Mono.fromCallable(() -> doContains(name))
-                .retryWhen(ldapConfiguration.retrySpec())
-                .block();
-        } catch (Exception e) {
-            if (e.getCause() instanceof UsersRepositoryException) {
-                throw (UsersRepositoryException) e.getCause();
-            }
-            throw new UsersRepositoryException("Unable to check user existence from ldap", e);
-        }
-    }
-
-    private boolean doContains(Username name) throws LDAPException {
-        return doGetUserByName(name).isPresent();
+        return getUserByName(name).isPresent();
     }
 
     @Override
     public int countUsers() throws UsersRepositoryException {
         try {
-            return Mono.fromCallable(() -> Math.toIntExact(doCountUsers()))
-                .retryWhen(ldapConfiguration.retrySpec())
-                .block();
-        } catch (Exception e) {
-            if (e.getCause() instanceof UsersRepositoryException) {
-                throw (UsersRepositoryException) e.getCause();
-            }
+            return Math.toIntExact(doCountUsers());
+        } catch (LDAPException e) {
             throw new UsersRepositoryException("Unable to retrieve user count from ldap", e);
         }
     }
@@ -300,45 +280,28 @@ public class ReadOnlyLDAPUsersDAO implements UsersDAO, Configurable {
     @Override
     public Optional<User> getUserByName(Username name) throws UsersRepositoryException {
         try {
-            return Mono.fromCallable(() -> doGetUserByName(name))
-                .retryWhen(ldapConfiguration.retrySpec())
-                .block();
+            return Optional.ofNullable(searchAndBuildUser(name));
         } catch (Exception e) {
-            if (e.getCause() instanceof UsersRepositoryException) {
-                throw (UsersRepositoryException) e.getCause();
-            }
             throw new UsersRepositoryException("Unable check user existence from ldap", e);
         }
     }
 
-    private Optional<User> doGetUserByName(Username name) throws LDAPException {
-        return Optional.ofNullable(searchAndBuildUser(name));
-    }
-
     @Override
     public Iterator<Username> list() throws UsersRepositoryException {
         try {
-            return Mono.fromCallable(this::doList)
-                .retryWhen(ldapConfiguration.retrySpec())
-                .block();
-        } catch (Exception e) {
-            if (e.getCause() instanceof UsersRepositoryException) {
-                throw (UsersRepositoryException) e.getCause();
+            if (!ldapConfiguration.getRestriction().isActivated()) {
+                return getAllUsernamesFromLDAP().iterator();
             }
+
+            return buildUserCollection(getValidUserDNs())
+                .stream()
+                .map(ReadOnlyLDAPUser::getUserName)
+                .iterator();
+        } catch (LDAPException e) {
             throw new UsersRepositoryException("Unable to list users from ldap", e);
         }
     }
 
-    private Iterator<Username> doList() throws LDAPException {
-        if (!ldapConfiguration.getRestriction().isActivated()) {
-            return getAllUsernamesFromLDAP().iterator();
-        }
-
-        return buildUserCollection(getValidUserDNs())
-            .stream()
-            .map(ReadOnlyLDAPUser::getUserName)
-            .iterator();
-    }
 
     private Collection<DN> getValidUserDNs() throws LDAPException {
         Set<DN> userDNs = getAllUsersDNFromLDAP();
diff --git a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyUsersLDAPRepository.java b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyUsersLDAPRepository.java
index 5a3b44f..32c9232 100644
--- a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyUsersLDAPRepository.java
+++ b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyUsersLDAPRepository.java
@@ -65,10 +65,6 @@ import org.apache.james.user.lib.UsersRepositoryImpl;
  *      userBase=&quot;ou=People,o=myorg.com,ou=system&quot;
  *      userIdAttribute=&quot;uid&quot;
  *      userObjectClass=&quot;inetOrgPerson&quot;
- *      maxRetries=&quot;20&quot;
- *      retryStartInterval=&quot;0&quot;
- *      retryMaxInterval=&quot;30&quot;
- *      retryIntervalScale=&quot;1000&quot;
  *      administratorId=&quot;ldapAdmin&quot;
  *  &lt;/users-store&gt;
  * </pre>
@@ -99,52 +95,6 @@ import org.apache.james.user.lib.UsersRepositoryImpl;
  * <b>poolSize:</b> (optional, default = 4) The maximum number of connection
  * in the pool.</li>
  * <li>
- * <li>
- * <b>maxRetries:</b> (optional, default = 0) The maximum number of times to
- * retry a failed operation. -1 means retry forever.</li>
- * <li>
- * <b>retryStartInterval:</b> (optional, default = 0) The interval in
- * milliseconds to wait before the first retry. If > 0, subsequent retries are
- * made at double the proceeding one up to the <b>retryMaxInterval</b> described
- * below. If = 0, the next retry is 1 and subsequent retries proceed as above.</li>
- * <li>
- * <b>retryMaxInterval:</b> (optional, default = 60) The maximum interval in
- * milliseconds to wait between retries</li>
- * <li>
- * <b>retryIntervalScale:</b> (optional, default = 1000) The amount by which to
- * multiply each retry interval. The default value of 1000 (milliseconds) is 1
- * second, so the default <b>retryMaxInterval</b> of 60 is 60 seconds, or 1
- * minute.
- * </ul>
- * </p>
- * <p>
- * <em>Example Schedules</em>
- * <ul>
- * <li>
- * Retry after 1000 milliseconds, doubling the interval for each retry up to
- * 30000 milliseconds, subsequent retry intervals are 30000 milliseconds until
- * 10 retries have been attempted, after which the <code>Exception</code>
- * causing the fault is thrown:
- * <ul>
- * <li>maxRetries = 10
- * <li>retryStartInterval = 1000
- * <li>retryMaxInterval = 30000
- * <li>retryIntervalScale = 1
- * </ul>
- * <li>
- * Retry immediately, then retry after 1 * 1000 milliseconds, doubling the
- * interval for each retry up to 30 * 1000 milliseconds, subsequent retry
- * intervals are 30 * 1000 milliseconds until 20 retries have been attempted,
- * after which the <code>Exception</code> causing the fault is thrown:
- * <ul>
- * <li>maxRetries = 20
- * <li>retryStartInterval = 0
- * <li>retryMaxInterval = 30
- * <li>retryIntervalScale = 1000
- * </ul>
- * <li>
- * Retry after 5000 milliseconds, subsequent retry intervals are 5000
- * milliseconds.
  * </ul>
  * </p>
  *
diff --git a/server/data/data-ldap/src/test/java/org/apache/james/user/ldap/ReadOnlyUsersLDAPRepositoryTest.java b/server/data/data-ldap/src/test/java/org/apache/james/user/ldap/ReadOnlyUsersLDAPRepositoryTest.java
index 7254907..9638dbf 100644
--- a/server/data/data-ldap/src/test/java/org/apache/james/user/ldap/ReadOnlyUsersLDAPRepositoryTest.java
+++ b/server/data/data-ldap/src/test/java/org/apache/james/user/ldap/ReadOnlyUsersLDAPRepositoryTest.java
@@ -392,10 +392,6 @@ class ReadOnlyUsersLDAPRepositoryTest {
         configuration.addProperty("[@credentials]", ADMIN_PASSWORD);
         configuration.addProperty("[@userBase]", "ou=people,dc=james,dc=org");
         configuration.addProperty("[@userObjectClass]", "inetOrgPerson");
-        configuration.addProperty("[@maxRetries]", "1");
-        configuration.addProperty("[@retryStartInterval]", "0");
-        configuration.addProperty("[@retryMaxInterval]", "2");
-        configuration.addProperty("[@retryIntervalScale]", "100");
         configuration.addProperty("[@connectionTimeout]", "100");
         configuration.addProperty("[@readTimeout]", "100");
         return configuration;
diff --git a/upgrade-instructions.md b/upgrade-instructions.md
index bb7049d..1969894 100644
--- a/upgrade-instructions.md
+++ b/upgrade-instructions.md
@@ -33,6 +33,8 @@ As part of this migration the following change took place:
 
  - `useConnectionPool` : Removed. UnboundId implementation relies on a pool by default.
  - `poolSize` : Added. Allow controlling the count of connection in the pool.
+ - Retries had been removed. Invalid connections errors are retries. THe following parameters were removed:
+ `maxRetries`, `retryStartInterval`, `retryMaxInterval`, `retryIntervalScale`. Those values will be ignored.
  
 The "group restriction" feature should furthermore be considered experimental, its usage is discouraged.
 

---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[james-project] 08/18: JAMES-3594 Use Filter instead of search templates

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 0af05751acbf015c44d937c749a0c90bffce056e
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Wed Jun 9 09:11:09 2021 +0700

    JAMES-3594 Use Filter instead of search templates
---
 server/data/data-ldap/pom.xml                        |  4 ----
 .../apache/james/user/ldap/ReadOnlyLDAPUsersDAO.java | 20 +++++++++++---------
 2 files changed, 11 insertions(+), 13 deletions(-)

diff --git a/server/data/data-ldap/pom.xml b/server/data/data-ldap/pom.xml
index aec06a1..a2e2228 100644
--- a/server/data/data-ldap/pom.xml
+++ b/server/data/data-ldap/pom.xml
@@ -82,10 +82,6 @@
             <artifactId>commons-configuration2</artifactId>
         </dependency>
         <dependency>
-            <groupId>org.apache.directory.api</groupId>
-            <artifactId>api-ldap-model</artifactId>
-        </dependency>
-        <dependency>
             <groupId>org.mockito</groupId>
             <artifactId>mockito-core</artifactId>
             <scope>test</scope>
diff --git a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUsersDAO.java b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUsersDAO.java
index d400aed..772d410 100644
--- a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUsersDAO.java
+++ b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUsersDAO.java
@@ -61,7 +61,6 @@ public class ReadOnlyLDAPUsersDAO implements UsersDAO, Configurable {
     private static final Logger LOGGER = LoggerFactory.getLogger(ReadOnlyLDAPUsersDAO.class);
 
     private LdapRepositoryConfiguration ldapConfiguration;
-    private String filterTemplate;
     private LDAPConnectionPool ldapConnectionPool;
 
     @Inject
@@ -103,7 +102,6 @@ public class ReadOnlyLDAPUsersDAO implements UsersDAO, Configurable {
                 + ldapConfiguration.getConnectionTimeout() + '\n' + "readTimeout: " + ldapConfiguration.getReadTimeout()
                 + '\n' + "maxRetries: " + ldapConfiguration.getMaxRetries() + '\n');
         }
-        filterTemplate = "(&({0}={1})(objectClass={2})" + StringUtils.defaultString(ldapConfiguration.getFilter(), "") + ")";
 
         LDAPConnectionOptions connectionOptions = new LDAPConnectionOptions();
         connectionOptions.setConnectTimeoutMillis(ldapConfiguration.getConnectionTimeout());
@@ -120,6 +118,16 @@ public class ReadOnlyLDAPUsersDAO implements UsersDAO, Configurable {
         ldapConnectionPool.close();
     }
 
+    private Filter createFilter(String username) {
+        return Optional.ofNullable(ldapConfiguration.getFilter())
+            .map(Throwing.function(userFilter -> Filter.createANDFilter(
+                Filter.createEqualityFilter("objectClass", ldapConfiguration.getUserObjectClass()),
+                Filter.createEqualityFilter(ldapConfiguration.getUserIdAttribute(), username),
+                Filter.create(userFilter))))
+            .orElseGet(() -> Filter.createANDFilter(
+                Filter.createEqualityFilter("objectClass", ldapConfiguration.getUserObjectClass()),
+                Filter.createEqualityFilter(ldapConfiguration.getUserIdAttribute(), username)));
+    }
 
     /**
      * Indicates if the user with the specified DN can be found in the group
@@ -192,15 +200,9 @@ public class ReadOnlyLDAPUsersDAO implements UsersDAO, Configurable {
     private ReadOnlyLDAPUser searchAndBuildUser(Username name) throws LDAPException {
         LDAPConnection connection = ldapConnectionPool.getConnection();
         try {
-            String sanitizedFilter = FilterEncoder.format(
-                filterTemplate,
-                ldapConfiguration.getUserIdAttribute(),
-                name.asString(),
-                ldapConfiguration.getUserObjectClass());
-
             SearchResult searchResult = connection.search(ldapConfiguration.getUserBase(),
                 SearchScope.SUB,
-                sanitizedFilter,
+                createFilter(name.asString()),
                 ldapConfiguration.getUserIdAttribute());
 
             SearchResultEntry result = searchResult.getSearchEntries()

---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[james-project] 02/18: JAMES-3594 Implement group restrictions on top of UnboundID

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 8d04141f7cc4ce6c692eacc35c2317ab354b9512
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Sun Jun 6 18:19:56 2021 +0700

    JAMES-3594 Implement group restrictions on top of UnboundID
---
 .../user/ldap/ReadOnlyLDAPGroupRestriction.java    | 33 +++++-------
 .../james/user/ldap/ReadOnlyLDAPUsersDAO.java      | 60 ++++++++++------------
 2 files changed, 39 insertions(+), 54 deletions(-)

diff --git a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPGroupRestriction.java b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPGroupRestriction.java
index 3d3a3d5..9123f65 100644
--- a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPGroupRestriction.java
+++ b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPGroupRestriction.java
@@ -19,21 +19,21 @@
 package org.apache.james.user.ldap;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
-import javax.naming.NamingEnumeration;
-import javax.naming.NamingException;
-import javax.naming.directory.Attribute;
-import javax.naming.directory.Attributes;
-import javax.naming.ldap.LdapContext;
-
 import org.apache.commons.configuration2.HierarchicalConfiguration;
 import org.apache.commons.configuration2.tree.ImmutableNode;
 
+import com.github.steveash.guavate.Guavate;
+import com.unboundid.ldap.sdk.LDAPConnection;
+import com.unboundid.ldap.sdk.LDAPException;
+import com.unboundid.ldap.sdk.SearchResultEntry;
+
 /**
  * <p>
  * Encapsulates the information required to restrict users to LDAP groups or
@@ -112,13 +112,12 @@ public class ReadOnlyLDAPGroupRestriction {
      * <code>groupDN</code> is associated to a list of <code>userDNs</code>.
      *
      * @return Returns a map of groupDNs to userDN lists.
-     * @throws NamingException Propagated from underlying LDAP communication layer.
      */
-    protected Map<String, Collection<String>> getGroupMembershipLists(LdapContext ldapContext) throws NamingException {
+    protected Map<String, Collection<String>> getGroupMembershipLists(LDAPConnection connection) throws LDAPException {
         Map<String, Collection<String>> result = new HashMap<>();
 
         for (String groupDN : groupDNs) {
-            result.put(groupDN, extractMembers(ldapContext.getAttributes(groupDN)));
+            result.put(groupDN, extractMembers(connection.getEntry(groupDN)));
         }
 
         return result;
@@ -130,20 +129,12 @@ public class ReadOnlyLDAPGroupRestriction {
      * attribute, with name equivalent to the field value
      * {@link #memberAttribute}, from the attributes collection.
      *
-     * @param groupAttributes The attributes taken from the group's LDAP context.
      * @return A collection of distinguished-names for the users belonging to
      *         the group with the specified attributes.
-     * @throws NamingException Propagated from underlying LDAP communication layer.
      */
-    private Collection<String> extractMembers(Attributes groupAttributes) throws NamingException {
-        Collection<String> result = new ArrayList<>();
-        Attribute members = groupAttributes.get(memberAttribute);
-        NamingEnumeration<?> memberDNs = members.getAll();
-
-        while (memberDNs.hasMore()) {
-            result.add(memberDNs.next().toString());
-        }
-
-        return result;
+    private Collection<String> extractMembers(SearchResultEntry entry) {
+        com.unboundid.ldap.sdk.Attribute members = entry.getAttribute(memberAttribute);
+        return Arrays.stream(members.getValues())
+            .collect(Guavate.toImmutableList());
     }
 }
diff --git a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUsersDAO.java b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUsersDAO.java
index fb450d0..3d406a1 100644
--- a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUsersDAO.java
+++ b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUsersDAO.java
@@ -207,28 +207,23 @@ public class ReadOnlyLDAPUsersDAO implements UsersDAO, Configurable {
                 sanitizedFilter,
                 ldapConfiguration.getUserIdAttribute());
 
-            return searchResult.getSearchEntries()
+            SearchResultEntry result = searchResult.getSearchEntries()
                 .stream()
-                .map(entry -> new ReadOnlyLDAPUser(
-                    Username.of(entry.getAttribute(ldapConfiguration.getUserIdAttribute()).getName()),
-                    entry.getDN(),
-                    ldapConnectionPool))
                 .findFirst()
                 .orElse(null);
-        } finally {
-            ldapConnectionPool.releaseConnection(connection);
-        }
+            if (result == null) {
+                return null;
+            }
 
-        /*
-        TODO implement restrictions
+            if (!ldapConfiguration.getRestriction().isActivated()
+                || userInGroupsMembershipList(result.getDN(), ldapConfiguration.getRestriction().getGroupMembershipLists(connection))) {
 
-        if (!ldapConfiguration.getRestriction().isActivated()
-            || userInGroupsMembershipList(r.getNameInNamespace(), ldapConfiguration.getRestriction().getGroupMembershipLists(ldapContext))) {
-            return new ReadOnlyLDAPUser(Username.of(userName.get().toString()), r.getNameInNamespace(), ldapContext);
+                return new ReadOnlyLDAPUser(name, result.getDN(), ldapConnectionPool);
+            }
+            return null;
+        } finally {
+            ldapConnectionPool.releaseConnection(connection);
         }
-
-        return null;
-        */
     }
 
     private Optional<ReadOnlyLDAPUser> buildUser(String userDN) throws LDAPException {
@@ -285,31 +280,30 @@ public class ReadOnlyLDAPUsersDAO implements UsersDAO, Configurable {
     }
 
     private Collection<String> getValidUsers() throws LDAPException {
-        return getAllUsersFromLDAP();
-
-        /*
-        TODO Implement restrictions
-         */
-        /*
+        Set<String> userDNs = getAllUsersFromLDAP();
         Collection<String> validUserDNs;
         if (ldapConfiguration.getRestriction().isActivated()) {
-            Map<String, Collection<String>> groupMembershipList = ldapConfiguration.getRestriction()
-                    .getGroupMembershipLists(ldapContext);
-            validUserDNs = new ArrayList<>();
-
-            Iterator<String> userDNIterator = userDNs.iterator();
-            String userDN;
-            while (userDNIterator.hasNext()) {
-                userDN = userDNIterator.next();
-                if (userInGroupsMembershipList(userDN, groupMembershipList)) {
-                    validUserDNs.add(userDN);
+            final LDAPConnection connection = ldapConnectionPool.getConnection();
+            try {
+                Map<String, Collection<String>> groupMembershipList = ldapConfiguration.getRestriction()
+                    .getGroupMembershipLists(connection);
+                validUserDNs = new ArrayList<>();
+
+                Iterator<String> userDNIterator = userDNs.iterator();
+                String userDN;
+                while (userDNIterator.hasNext()) {
+                    userDN = userDNIterator.next();
+                    if (userInGroupsMembershipList(userDN, groupMembershipList)) {
+                        validUserDNs.add(userDN);
+                    }
                 }
+            } finally {
+                ldapConnectionPool.releaseConnection(connection);
             }
         } else {
             validUserDNs = userDNs;
         }
         return validUserDNs;
-         */
     }
 
     @Override

---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[james-project] 10/18: JAMES-3594 Implement ReadOnlyLDAPUsersDAO with UnboundID library

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 5d214eafac717b9869935621f079079db3a5b383
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Wed Jun 9 08:36:24 2021 +0700

    JAMES-3594 Implement ReadOnlyLDAPUsersDAO with UnboundID library
---
 .../apache/james/user/ldap/ReadOnlyLDAPUser.java   | 11 ++++---
 .../james/user/ldap/ReadOnlyLDAPUsersDAO.java      | 35 ++++++++++++++--------
 2 files changed, 28 insertions(+), 18 deletions(-)

diff --git a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUser.java b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUser.java
index 09ecc6c..5baaf02 100644
--- a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUser.java
+++ b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUser.java
@@ -47,7 +47,6 @@ import reactor.core.publisher.Mono;
  * 
  */
 public class ReadOnlyLDAPUser implements User, Serializable {
-    private static final long serialVersionUID = -5201235065842464014L;
     public static final Logger LOGGER = LoggerFactory.getLogger(ReadOnlyLDAPUser.class);
 
     /**
@@ -76,17 +75,17 @@ public class ReadOnlyLDAPUser implements User, Serializable {
     /**
      * Constructs an instance for the given user-details, and which will
      * authenticate against the given host.
-     *  @param userName
+     * @param userName
      *            The user-identifier/name. This is the value with which the
      *            field  will be initialised, and which will be
      *            returned by invoking {@link #getUserName()}.
      * @param userDN
      *            The distinguished (unique-key) of the user details as stored
      * @param connectionPool
- *            The connectionPool for the LDAP server on which the user details are held.
- *            This is also the host against which the user will be
- *            authenticated, when {@link #verifyPassword(String)} is
- *            invoked.
+     *            The connectionPool for the LDAP server on which the user details are held.
+     *            This is also the host against which the user will be
+     *            authenticated, when {@link #verifyPassword(String)} is
+     *            invoked.
      * @param configuration
      */
     public ReadOnlyLDAPUser(Username userName, String userDN, LDAPConnectionPool connectionPool, LdapRepositoryConfiguration configuration) {
diff --git a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUsersDAO.java b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUsersDAO.java
index 772d410..9a4d707 100644
--- a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUsersDAO.java
+++ b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUsersDAO.java
@@ -35,8 +35,6 @@ import javax.net.SocketFactory;
 import org.apache.commons.configuration2.HierarchicalConfiguration;
 import org.apache.commons.configuration2.ex.ConfigurationException;
 import org.apache.commons.configuration2.tree.ImmutableNode;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.directory.api.ldap.model.filter.FilterEncoder;
 import org.apache.james.core.Username;
 import org.apache.james.lifecycle.api.Configurable;
 import org.apache.james.user.api.UsersRepositoryException;
@@ -47,10 +45,13 @@ import org.slf4j.LoggerFactory;
 
 import com.github.fge.lambdas.Throwing;
 import com.github.steveash.guavate.Guavate;
+import com.unboundid.ldap.sdk.Entry;
+import com.unboundid.ldap.sdk.Filter;
 import com.unboundid.ldap.sdk.LDAPConnection;
 import com.unboundid.ldap.sdk.LDAPConnectionOptions;
 import com.unboundid.ldap.sdk.LDAPConnectionPool;
 import com.unboundid.ldap.sdk.LDAPException;
+import com.unboundid.ldap.sdk.SearchRequest;
 import com.unboundid.ldap.sdk.SearchResult;
 import com.unboundid.ldap.sdk.SearchResultEntry;
 import com.unboundid.ldap.sdk.SearchScope;
@@ -119,14 +120,21 @@ public class ReadOnlyLDAPUsersDAO implements UsersDAO, Configurable {
     }
 
     private Filter createFilter(String username) {
+        Filter specificUserFilter = Filter.createEqualityFilter(ldapConfiguration.getUserIdAttribute(), username);
         return Optional.ofNullable(ldapConfiguration.getFilter())
-            .map(Throwing.function(userFilter -> Filter.createANDFilter(
-                Filter.createEqualityFilter("objectClass", ldapConfiguration.getUserObjectClass()),
-                Filter.createEqualityFilter(ldapConfiguration.getUserIdAttribute(), username),
-                Filter.create(userFilter))))
-            .orElseGet(() -> Filter.createANDFilter(
-                Filter.createEqualityFilter("objectClass", ldapConfiguration.getUserObjectClass()),
-                Filter.createEqualityFilter(ldapConfiguration.getUserIdAttribute(), username)));
+            .map(Throwing.function(userFilter ->
+                Filter.createANDFilter(objectClassFilter(), specificUserFilter, Filter.create(userFilter))))
+            .orElseGet(() -> Filter.createANDFilter(objectClassFilter(), specificUserFilter));
+    }
+
+    private Filter objectClassFilter() {
+        return Filter.createEqualityFilter("objectClass", ldapConfiguration.getUserObjectClass());
+    }
+
+    private Filter createFilter() {
+        return Optional.ofNullable(ldapConfiguration.getFilter())
+            .map(Throwing.function(userFilter -> Filter.createANDFilter(objectClassFilter(), Filter.create(userFilter))))
+            .orElseGet(this::objectClassFilter);
     }
 
     /**
@@ -162,13 +170,16 @@ public class ReadOnlyLDAPUsersDAO implements UsersDAO, Configurable {
     }
 
     private Set<String> getAllUsersFromLDAP() throws LDAPException {
-        SearchResult searchResult = ldapConnectionPool.search(ldapConfiguration.getUserBase(),
+        SearchRequest searchRequest = new SearchRequest(ldapConfiguration.getUserBase(),
             SearchScope.SUB,
-            filterTemplate);
+            createFilter(),
+            SearchRequest.NO_ATTRIBUTES);
+
+        SearchResult searchResult = ldapConnectionPool.search(searchRequest);
 
         return searchResult.getSearchEntries()
             .stream()
-            .map(entry -> entry.getObjectClassAttribute().getName())
+            .map(Entry::getDN)
             .collect(Guavate.toImmutableSet());
     }
 

---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[james-project] 05/18: JAMES-3594 Fix ReadOnlyUsersLDAPRepository JavaDoc

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 8be22b329550a57d1ef3a0ebec097d0fab15c9c4
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Mon Jun 7 08:27:58 2021 +0700

    JAMES-3594 Fix ReadOnlyUsersLDAPRepository JavaDoc
---
 .../user/ldap/ReadOnlyUsersLDAPRepository.java     | 25 ++++------------------
 1 file changed, 4 insertions(+), 21 deletions(-)

diff --git a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyUsersLDAPRepository.java b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyUsersLDAPRepository.java
index 2938298..78faa16 100644
--- a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyUsersLDAPRepository.java
+++ b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyUsersLDAPRepository.java
@@ -139,13 +139,7 @@ import org.apache.james.user.lib.UsersRepositoryImpl;
  * </ul>
  * <li>
  * Retry after 5000 milliseconds, subsequent retry intervals are 5000
- * milliseconds. Retry forever:
- * <ul>
- * <li>maxRetries = -1
- * <li>retryStartInterval = 5000
- * <li>retryMaxInterval = 5000
- * <li>retryIntervalScale = 1
- * </ul>
+ * milliseconds.
  * </ul>
  * </p>
  *
@@ -175,22 +169,11 @@ import org.apache.james.user.lib.UsersRepositoryImpl;
  * </p>
  *
  * <p>
- * The following parameters may be used to adjust the underlying
- * <code>com.sun.jndi.ldap.LdapCtxFactory</code>. See <a href=
- * "http://docs.oracle.com/javase/1.5.0/docs/guide/jndi/jndi-ldap.html#SPIPROPS"
- * > LDAP Naming Service Provider for the Java Naming and Directory InterfaceTM
- * (JNDI) : Provider-specific Properties</a> for details.
+ * The following parameters may be used to adjust the underlying socket settings:
  * <ul>
+ * <b>connectionTimeout:</b> (optional) Sets the connection timeout on the underlying  to the specified integer value
  * <li>
- * <b>useConnectionPool:</b> (optional, default = true) Sets property
- * <code>com.sun.jndi.ldap.connect.pool</code> to the specified boolean value
- * <li>
- * <b>connectionTimeout:</b> (optional) Sets property
- * <code>com.sun.jndi.ldap.connect.timeout</code> to the specified integer value
- * <li>
- * <b>readTimeout:</b> (optional) Sets property
- * <code>com.sun.jndi.ldap.read.timeout</code> to the specified integer value.
- * Applicable to Java 6 and above.
+ * <b>readTimeout:</b> (optional) Sets property the read timeout to the specified integer value.
  * <li>
  * <b>administratorId:</b> (optional) User identifier of the administrator user.
  * The administrator user is allowed to authenticate as other users.

---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[james-project] 11/18: JAMES-3594 LDAP user listing: avoid extra requests for each users

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit bfd78ca918f98f613452d948ba672f724ad4ee67
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Wed Jun 9 11:21:11 2021 +0700

    JAMES-3594 LDAP user listing: avoid extra requests for each users
    
    Because we needed a conversion DN <-> username.
---
 .../james/user/ldap/ReadOnlyLDAPUsersDAO.java      | 25 ++++++++++++++++++++--
 1 file changed, 23 insertions(+), 2 deletions(-)

diff --git a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUsersDAO.java b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUsersDAO.java
index 9a4d707..f1c1819 100644
--- a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUsersDAO.java
+++ b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUsersDAO.java
@@ -27,6 +27,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
+import java.util.stream.Stream;
 
 import javax.annotation.PreDestroy;
 import javax.inject.Inject;
@@ -45,6 +46,7 @@ import org.slf4j.LoggerFactory;
 
 import com.github.fge.lambdas.Throwing;
 import com.github.steveash.guavate.Guavate;
+import com.unboundid.ldap.sdk.Attribute;
 import com.unboundid.ldap.sdk.Entry;
 import com.unboundid.ldap.sdk.Filter;
 import com.unboundid.ldap.sdk.LDAPConnection;
@@ -169,7 +171,7 @@ public class ReadOnlyLDAPUsersDAO implements UsersDAO, Configurable {
         return result;
     }
 
-    private Set<String> getAllUsersFromLDAP() throws LDAPException {
+    private Set<String> getAllUsersDNFromLDAP() throws LDAPException {
         SearchRequest searchRequest = new SearchRequest(ldapConfiguration.getUserBase(),
             SearchScope.SUB,
             createFilter(),
@@ -183,6 +185,21 @@ public class ReadOnlyLDAPUsersDAO implements UsersDAO, Configurable {
             .collect(Guavate.toImmutableSet());
     }
 
+    private Stream<Username> getAllUsernamesFromLDAP() throws LDAPException {
+        SearchRequest searchRequest = new SearchRequest(ldapConfiguration.getUserBase(),
+            SearchScope.SUB,
+            createFilter(),
+            ldapConfiguration.getUserIdAttribute());
+
+        SearchResult searchResult = ldapConnectionPool.search(searchRequest);
+
+        return searchResult.getSearchEntries()
+            .stream()
+            .flatMap(entry -> Optional.ofNullable(entry.getAttribute(ldapConfiguration.getUserIdAttribute())).stream())
+            .map(Attribute::getValue)
+            .map(Username::of);
+    }
+
     /**
      * Extract the user attributes for the given collection of userDNs, and
      * encapsulates the user list as a collection of {@link ReadOnlyLDAPUser}s.
@@ -315,6 +332,10 @@ public class ReadOnlyLDAPUsersDAO implements UsersDAO, Configurable {
     }
 
     private Iterator<Username> doList() throws LDAPException {
+        if (!ldapConfiguration.getRestriction().isActivated()) {
+            return getAllUsernamesFromLDAP().iterator();
+        }
+
         return buildUserCollection(getValidUsers())
             .stream()
             .map(ReadOnlyLDAPUser::getUserName)
@@ -322,7 +343,7 @@ public class ReadOnlyLDAPUsersDAO implements UsersDAO, Configurable {
     }
 
     private Collection<String> getValidUsers() throws LDAPException {
-        Set<String> userDNs = getAllUsersFromLDAP();
+        Set<String> userDNs = getAllUsersDNFromLDAP();
         Collection<String> validUserDNs;
         if (ldapConfiguration.getRestriction().isActivated()) {
             final LDAPConnection connection = ldapConnectionPool.getConnection();

---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[james-project] 06/18: JAMES-3594 Document group/role based access restrictions as experimental

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit e483dc704313f11d59fe6fa7908548051198dbf6
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Tue Jun 8 09:59:34 2021 +0700

    JAMES-3594 Document group/role based access restrictions as experimental
---
 .../org/apache/james/user/ldap/ReadOnlyUsersLDAPRepository.java     | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyUsersLDAPRepository.java b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyUsersLDAPRepository.java
index 78faa16..2f8e309 100644
--- a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyUsersLDAPRepository.java
+++ b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyUsersLDAPRepository.java
@@ -36,7 +36,8 @@ import org.apache.james.user.lib.UsersRepositoryImpl;
  * This repository implementation serves as a bridge between Apache James and
  * LDAP. It allows James to authenticate users against an LDAP compliant server
  * such as Apache DS or Microsoft AD. It also enables role/group based access
- * restriction based on LDAP groups.
+ * restriction based on LDAP groups (role/group based access are experimental
+ * and untested, contributions welcomed).
  * </p>
  * <p>
  * It is intended for organisations that already have a user-authentication and
@@ -167,6 +168,9 @@ import org.apache.james.user.lib.UsersRepositoryImpl;
  * the &quot;&lt;restriction&gt;&quot; sections.</li>
  * </ul>
  * </p>
+ * <p><b>WARNING</b>: group/role based access restrictions is currently untested and should
+ * be considered experimental. Use at your own risks. Contributions to strengthen that part
+ * of the code base are welcomed.</p>
  *
  * <p>
  * The following parameters may be used to adjust the underlying socket settings:

---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[james-project] 13/18: JAMES-3594 LDAP user counting: avoid extra requests for each users

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 299a110536428a0a28674a5781459622c0b90997
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Thu Jun 10 10:51:17 2021 +0700

    JAMES-3594 LDAP user counting: avoid extra requests for each users
---
 .../org/apache/james/user/ldap/ReadOnlyLDAPUsersDAO.java     | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

diff --git a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUsersDAO.java b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUsersDAO.java
index 81b8375..7a2301b 100644
--- a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUsersDAO.java
+++ b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUsersDAO.java
@@ -276,7 +276,7 @@ public class ReadOnlyLDAPUsersDAO implements UsersDAO, Configurable {
     @Override
     public int countUsers() throws UsersRepositoryException {
         try {
-            return Mono.fromCallable(() -> doCountUsers())
+            return Mono.fromCallable(() -> Math.toIntExact(doCountUsers()))
                 .retryWhen(ldapConfiguration.retrySpec())
                 .block();
         } catch (Exception e) {
@@ -287,11 +287,15 @@ public class ReadOnlyLDAPUsersDAO implements UsersDAO, Configurable {
         }
     }
 
-    private int doCountUsers() throws LDAPException {
-        return Math.toIntExact(getValidUsers().stream()
+    private long doCountUsers() throws LDAPException {
+        if (!ldapConfiguration.getRestriction().isActivated()) {
+            return getAllUsernamesFromLDAP().count();
+        }
+
+        return getValidUsers().stream()
             .map(Throwing.function(this::buildUser).sneakyThrow())
             .flatMap(Optional::stream)
-            .count());
+            .count();
     }
 
     @Override

---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[james-project] 15/18: JAMES-3594 Validate filters at ReadOnlyLDAPUsersDAO initialization

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 15a86bf58ff72c4269b8a8af9d646c2ec6ff2933
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Thu Jun 10 11:17:49 2021 +0700

    JAMES-3594 Validate filters at ReadOnlyLDAPUsersDAO initialization
---
 .../james/user/ldap/ReadOnlyLDAPUsersDAO.java      | 30 ++++++++++------------
 .../user/ldap/ReadOnlyUsersLDAPRepositoryTest.java | 21 +++++++++++++++
 2 files changed, 35 insertions(+), 16 deletions(-)

diff --git a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUsersDAO.java b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUsersDAO.java
index 3839126..6901ab4 100644
--- a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUsersDAO.java
+++ b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUsersDAO.java
@@ -66,6 +66,9 @@ public class ReadOnlyLDAPUsersDAO implements UsersDAO, Configurable {
 
     private LdapRepositoryConfiguration ldapConfiguration;
     private LDAPConnectionPool ldapConnectionPool;
+    private Optional<Filter> userExtraFilter;
+    private Filter objectClassFilter;
+    private Filter listingFilter;
 
     @Inject
     public ReadOnlyLDAPUsersDAO() {
@@ -115,6 +118,12 @@ public class ReadOnlyLDAPUsersDAO implements UsersDAO, Configurable {
         SocketFactory socketFactory = null;
         LDAPConnection ldapConnection = new LDAPConnection(socketFactory, connectionOptions, uri.getHost(), uri.getPort(), ldapConfiguration.getPrincipal(), ldapConfiguration.getCredentials());
         ldapConnectionPool = new LDAPConnectionPool(ldapConnection, 4);
+
+        userExtraFilter = Optional.ofNullable(ldapConfiguration.getFilter())
+            .map(Throwing.function(Filter::create).sneakyThrow());
+        objectClassFilter = Filter.createEqualityFilter("objectClass", ldapConfiguration.getUserObjectClass());
+        listingFilter = userExtraFilter.map(extraFilter -> Filter.createANDFilter(objectClassFilter, extraFilter))
+            .orElse(objectClassFilter);
     }
 
     @PreDestroy
@@ -124,20 +133,9 @@ public class ReadOnlyLDAPUsersDAO implements UsersDAO, Configurable {
 
     private Filter createFilter(String username) {
         Filter specificUserFilter = Filter.createEqualityFilter(ldapConfiguration.getUserIdAttribute(), username);
-        return Optional.ofNullable(ldapConfiguration.getFilter())
-            .map(Throwing.function(userFilter ->
-                Filter.createANDFilter(objectClassFilter(), specificUserFilter, Filter.create(userFilter))))
-            .orElseGet(() -> Filter.createANDFilter(objectClassFilter(), specificUserFilter));
-    }
-
-    private Filter objectClassFilter() {
-        return Filter.createEqualityFilter("objectClass", ldapConfiguration.getUserObjectClass());
-    }
-
-    private Filter createFilter() {
-        return Optional.ofNullable(ldapConfiguration.getFilter())
-            .map(Throwing.function(userFilter -> Filter.createANDFilter(objectClassFilter(), Filter.create(userFilter))))
-            .orElseGet(this::objectClassFilter);
+        return userExtraFilter
+            .map(extraFilter -> Filter.createANDFilter(objectClassFilter, specificUserFilter, extraFilter))
+            .orElseGet(() -> Filter.createANDFilter(objectClassFilter, specificUserFilter));
     }
 
     /**
@@ -175,7 +173,7 @@ public class ReadOnlyLDAPUsersDAO implements UsersDAO, Configurable {
     private Set<DN> getAllUsersDNFromLDAP() throws LDAPException {
         SearchRequest searchRequest = new SearchRequest(ldapConfiguration.getUserBase(),
             SearchScope.SUB,
-            createFilter(),
+            listingFilter,
             SearchRequest.NO_ATTRIBUTES);
 
         SearchResult searchResult = ldapConnectionPool.search(searchRequest);
@@ -189,7 +187,7 @@ public class ReadOnlyLDAPUsersDAO implements UsersDAO, Configurable {
     private Stream<Username> getAllUsernamesFromLDAP() throws LDAPException {
         SearchRequest searchRequest = new SearchRequest(ldapConfiguration.getUserBase(),
             SearchScope.SUB,
-            createFilter(),
+            listingFilter,
             ldapConfiguration.getUserIdAttribute());
 
         SearchResult searchResult = ldapConnectionPool.search(searchRequest);
diff --git a/server/data/data-ldap/src/test/java/org/apache/james/user/ldap/ReadOnlyUsersLDAPRepositoryTest.java b/server/data/data-ldap/src/test/java/org/apache/james/user/ldap/ReadOnlyUsersLDAPRepositoryTest.java
index db551cc..047ce35 100644
--- a/server/data/data-ldap/src/test/java/org/apache/james/user/ldap/ReadOnlyUsersLDAPRepositoryTest.java
+++ b/server/data/data-ldap/src/test/java/org/apache/james/user/ldap/ReadOnlyUsersLDAPRepositoryTest.java
@@ -48,6 +48,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.google.common.collect.ImmutableList;
+import com.unboundid.ldap.sdk.LDAPException;
 
 class ReadOnlyUsersLDAPRepositoryTest {
 
@@ -72,6 +73,26 @@ class ReadOnlyUsersLDAPRepositoryTest {
         ldapContainer.stop();
     }
 
+    @Test
+    void shouldNotStartWithInvalidFilter() throws Exception {
+        PropertyListConfiguration configuration = new PropertyListConfiguration();
+        configuration.addProperty("[@ldapHost]", ldapContainer.getLdapHost());
+        configuration.addProperty("[@principal]", "cn=admin,dc=james,dc=org");
+        configuration.addProperty("[@credentials]", ADMIN_PASSWORD);
+        configuration.addProperty("[@userBase]", "ou=people,dc=james,dc=org");
+        configuration.addProperty("[@userObjectClass]", "inetOrgPerson");
+        configuration.addProperty("[@userIdAttribute]", "uid");
+        configuration.addProperty("[@administratorId]", ADMIN_LOCAL_PART);
+
+        configuration.addProperty("[@filter]", "INVALID!!!");
+
+        ReadOnlyUsersLDAPRepository usersLDAPRepository = new ReadOnlyUsersLDAPRepository(new SimpleDomainList());
+        usersLDAPRepository.configure(configuration);
+
+        assertThatThrownBy(usersLDAPRepository::init)
+            .isInstanceOf(LDAPException.class);
+    }
+
     @Nested
     class WhenEnableVirtualHosting implements UsersRepositoryContract.WithVirtualHostingReadOnlyContract {
         @RegisterExtension

---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[james-project] 01/18: JAMES-3594 Implement ReadOnlyLDAPUsersDAO with UnboundID library

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 393c65a6e5196b8b167e679784957c290c7c976f
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Sun Jun 6 13:13:53 2021 +0700

    JAMES-3594 Implement ReadOnlyLDAPUsersDAO with UnboundID library
---
 pom.xml                                            |   6 +-
 server/data/data-ldap/pom.xml                      |   4 +
 .../user/ldap/LdapRepositoryConfiguration.java     |  22 +-
 .../apache/james/user/ldap/ReadOnlyLDAPUser.java   |  76 ++--
 .../james/user/ldap/ReadOnlyLDAPUsersDAO.java      | 266 ++++-------
 .../apache/james/user/ldap/api/LdapConstants.java  |  32 --
 .../user/ldap/retry/DoublingRetrySchedule.java     |  99 ----
 .../user/ldap/retry/ExceptionRetryHandler.java     | 131 ------
 .../ldap/retry/api/ExceptionRetryingProxy.java     |  49 --
 .../james/user/ldap/retry/api/RetryHandler.java    |  52 ---
 .../james/user/ldap/retry/api/RetrySchedule.java   |  35 --
 .../ldap/retry/naming/LoggingRetryHandler.java     |  54 ---
 .../retry/naming/NamingExceptionRetryHandler.java  |  72 ---
 .../user/ldap/retry/naming/RetryingContext.java    | 497 ---------------------
 .../retry/naming/directory/RetryingDirContext.java | 407 -----------------
 .../retry/naming/ldap/RetryingLdapContext.java     | 129 ------
 .../user/ldap/retry/DoublingRetryScheduleTest.java |  71 ---
 .../user/ldap/retry/ExceptionRetryHandlerTest.java | 153 -------
 .../naming/NamingExceptionRetryHandlerTest.java    |  92 ----
 19 files changed, 132 insertions(+), 2115 deletions(-)

diff --git a/pom.xml b/pom.xml
index f7bc55a..21e035a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -2105,7 +2105,6 @@
                 <artifactId>metrics-elasticsearch-reporter</artifactId>
                 <version>${es-reporter.version}</version>
             </dependency>
-
             <dependency>
                 <groupId>com.rabbitmq</groupId>
                 <artifactId>amqp-client</artifactId>
@@ -2153,6 +2152,11 @@
                 <version>2.9.2</version>
             </dependency>
             <dependency>
+                <groupId>com.unboundid</groupId>
+                <artifactId>unboundid-ldapsdk</artifactId>
+                <version>6.0.0</version>
+            </dependency>
+            <dependency>
                 <groupId>commons-beanutils</groupId>
                 <artifactId>commons-beanutils</artifactId>
                 <version>1.9.4</version>
diff --git a/server/data/data-ldap/pom.xml b/server/data/data-ldap/pom.xml
index 88492e1..aec06a1 100644
--- a/server/data/data-ldap/pom.xml
+++ b/server/data/data-ldap/pom.xml
@@ -70,6 +70,10 @@
             <artifactId>guavate</artifactId>
         </dependency>
         <dependency>
+            <groupId>com.unboundid</groupId>
+            <artifactId>unboundid-ldapsdk</artifactId>
+        </dependency>
+        <dependency>
             <groupId>javax.annotation</groupId>
             <artifactId>javax.annotation-api</artifactId>
         </dependency>
diff --git a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/LdapRepositoryConfiguration.java b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/LdapRepositoryConfiguration.java
index f526cd2..efc79ed 100644
--- a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/LdapRepositoryConfiguration.java
+++ b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/LdapRepositoryConfiguration.java
@@ -32,9 +32,8 @@ import com.google.common.base.Preconditions;
 public class LdapRepositoryConfiguration {
     public static final String SUPPORTS_VIRTUAL_HOSTING = "supportsVirtualHosting";
 
-    private static final boolean NO_CONNECTION_POOL = false;
-    private static final int NO_CONNECTION_TIMEOUT = -1;
-    private static final int NO_READ_TIME_OUT = -1;
+    private static final int NO_CONNECTION_TIMEOUT = 0;
+    private static final int NO_READ_TIME_OUT = 0;
     private static final boolean ENABLE_VIRTUAL_HOSTING = true;
     private static final ReadOnlyLDAPGroupRestriction NO_RESTRICTION = new ReadOnlyLDAPGroupRestriction(null);
     private static final String NO_FILTER = null;
@@ -134,7 +133,6 @@ public class LdapRepositoryConfiguration {
                 userBase.get(),
                 userIdAttribute.get(),
                 userObjectClass.get(),
-                NO_CONNECTION_POOL,
                 NO_CONNECTION_TIMEOUT,
                 NO_READ_TIME_OUT,
                 maxRetries.get(),
@@ -160,7 +158,6 @@ public class LdapRepositoryConfiguration {
         String userIdAttribute = configuration.getString("[@userIdAttribute]");
         String userObjectClass = configuration.getString("[@userObjectClass]");
         // Default is to use connection pooling
-        boolean useConnectionPool = configuration.getBoolean("[@useConnectionPool]", NO_CONNECTION_POOL);
         int connectionTimeout = configuration.getInt("[@connectionTimeout]", NO_CONNECTION_TIMEOUT);
         int readTimeout = configuration.getInt("[@readTimeout]", NO_READ_TIME_OUT);
         // Default maximum retries is 1, which allows an alternate connection to
@@ -193,7 +190,6 @@ public class LdapRepositoryConfiguration {
             userBase,
             userIdAttribute,
             userObjectClass,
-            useConnectionPool,
             connectionTimeout,
             readTimeout,
             maxRetries,
@@ -251,9 +247,6 @@ public class LdapRepositoryConfiguration {
      */
     private final String userObjectClass;
 
-    // Use a connection pool. Default is true.
-    private final boolean useConnectionPool;
-
     // The connection timeout in milliseconds.
     // A value of less than or equal to zero means to use the network protocol's
     // (i.e., TCP's) timeout value.
@@ -290,7 +283,7 @@ public class LdapRepositoryConfiguration {
     private final Optional<Username> administratorId;
 
     private LdapRepositoryConfiguration(String ldapHost, String principal, String credentials, String userBase, String userIdAttribute,
-                                       String userObjectClass, boolean useConnectionPool, int connectionTimeout, int readTimeout,
+                                       String userObjectClass, int connectionTimeout, int readTimeout,
                                        int maxRetries, boolean supportsVirtualHosting, long retryStartInterval, long retryMaxInterval,
                                        int scale, ReadOnlyLDAPGroupRestriction restriction, String filter,
                                        Optional<String> administratorId) throws ConfigurationException {
@@ -300,7 +293,6 @@ public class LdapRepositoryConfiguration {
         this.userBase = userBase;
         this.userIdAttribute = userIdAttribute;
         this.userObjectClass = userObjectClass;
-        this.useConnectionPool = useConnectionPool;
         this.connectionTimeout = connectionTimeout;
         this.readTimeout = readTimeout;
         this.maxRetries = maxRetries;
@@ -351,9 +343,6 @@ public class LdapRepositoryConfiguration {
         return userObjectClass;
     }
 
-    public boolean useConnectionPool() {
-        return useConnectionPool;
-    }
 
     public int getConnectionTimeout() {
         return connectionTimeout;
@@ -400,8 +389,7 @@ public class LdapRepositoryConfiguration {
         if (o instanceof LdapRepositoryConfiguration) {
             LdapRepositoryConfiguration that = (LdapRepositoryConfiguration) o;
 
-            return Objects.equals(this.useConnectionPool, that.useConnectionPool)
-                && Objects.equals(this.connectionTimeout, that.connectionTimeout)
+            return Objects.equals(this.connectionTimeout, that.connectionTimeout)
                 && Objects.equals(this.readTimeout, that.readTimeout)
                 && Objects.equals(this.maxRetries, that.maxRetries)
                 && Objects.equals(this.supportsVirtualHosting, that.supportsVirtualHosting)
@@ -423,7 +411,7 @@ public class LdapRepositoryConfiguration {
 
     @Override
     public final int hashCode() {
-        return Objects.hash(ldapHost, principal, credentials, userBase, userIdAttribute, userObjectClass, useConnectionPool,
+        return Objects.hash(ldapHost, principal, credentials, userBase, userIdAttribute, userObjectClass,
             connectionTimeout, readTimeout, maxRetries, supportsVirtualHosting, retryStartInterval, retryMaxInterval, scale,
             restriction, filter, administratorId);
     }
diff --git a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUser.java b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUser.java
index 6946fa6..6125fb9 100644
--- a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUser.java
+++ b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUser.java
@@ -21,13 +21,14 @@ package org.apache.james.user.ldap;
 
 import java.io.Serializable;
 
-import javax.naming.Context;
-import javax.naming.NamingException;
-import javax.naming.ldap.LdapContext;
-
 import org.apache.james.core.Username;
 import org.apache.james.user.api.model.User;
-import org.apache.james.user.ldap.api.LdapConstants;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.unboundid.ldap.sdk.BindResult;
+import com.unboundid.ldap.sdk.LDAPConnectionPool;
+import com.unboundid.ldap.sdk.LDAPException;
 
 /**
  * Encapsulates the details of a user as taken from an LDAP compliant directory.
@@ -38,14 +39,13 @@ import org.apache.james.user.ldap.api.LdapConstants;
  * means of authenticating the user against the LDAP server. Consequently
  * invocations of the contract method {@link User#setPassword(String)} always
  * returns <code>false</code>.
- * 
- * @see SimpleLDAPConnection
+ *
  * @see ReadOnlyUsersLDAPRepository
  * 
  */
 public class ReadOnlyLDAPUser implements User, Serializable {
-    // private static final long serialVersionUID = -6712066073820393235L; 
-    private static final long serialVersionUID = -5201235065842464013L;
+    private static final long serialVersionUID = -5201235065842464014L;
+    public static final Logger LOGGER = LoggerFactory.getLogger(ReadOnlyLDAPUser.class);
 
     /**
      * The user's identifier or name. This is the value that is returned by the
@@ -55,31 +55,28 @@ public class ReadOnlyLDAPUser implements User, Serializable {
      * <code>&quot;myorg.com&quot;</code>, the user's email address will be
      * <code>&quot;john.bold&#64;myorg.com&quot;</code>.
      */
-    private Username userName;
+    private final Username userName;
 
     /**
      * The distinguished name of the user-record in the LDAP directory.
      */
-    private String userDN;
+    private final String userDN;
 
     /**
      * The context for the LDAP server from which to retrieve the
      * user's details.
      */
-    private LdapContext ldapContext = null;
-
-    /**
-     * Creates a new instance of ReadOnlyLDAPUser.
-     *
-     */
-    private ReadOnlyLDAPUser() {
-        super();
-    }
+    private final LDAPConnectionPool connectionPool;
 
     /**
      * Constructs an instance for the given user-details, and which will
      * authenticate against the given host.
-     * 
+     *
+     * @param connectionPool
+     *            The connectionPool for the LDAP server on which the user details are held.
+     *            This is also the host against which the user will be
+     *            authenticated, when {@link #verifyPassword(String)} is
+     *            invoked.
      * @param userName
      *            The user-identifier/name. This is the value with which the
      *            field  will be initialised, and which will be
@@ -87,18 +84,11 @@ public class ReadOnlyLDAPUser implements User, Serializable {
      * @param userDN
      *            The distinguished (unique-key) of the user details as stored
      *            on the LDAP directory.
-     * @param ldapContext
-     *            The context for the LDAP server on which the user details are held.
-     *            This is also the host against which the user will be
-     *            authenticated, when {@link #verifyPassword(String)} is
-     *            invoked.
-     * @throws NamingException 
      */
-    public ReadOnlyLDAPUser(Username userName, String userDN, LdapContext ldapContext) {
-        this();
+    public ReadOnlyLDAPUser(Username userName, String userDN, LDAPConnectionPool connectionPool) {
         this.userName = userName;
         this.userDN = userDN;
-        this.ldapContext = ldapContext;
+        this.connectionPool = connectionPool;
     }
 
     /**
@@ -139,27 +129,13 @@ public class ReadOnlyLDAPUser implements User, Serializable {
      */
     @Override
     public boolean verifyPassword(String password) {
-        boolean result = false;
-        LdapContext ldapContext = null;
         try {
-            ldapContext = this.ldapContext.newInstance(null);
-            ldapContext.addToEnvironment(Context.SECURITY_AUTHENTICATION,
-                    LdapConstants.SECURITY_AUTHENTICATION_SIMPLE);
-            ldapContext.addToEnvironment(Context.SECURITY_PRINCIPAL, userDN);
-            ldapContext.addToEnvironment(Context.SECURITY_CREDENTIALS, password);
-            ldapContext.reconnect(null);
-            result = true;
-        } catch (NamingException exception) {
-            // no-op
-        } finally {
-            if (null != ldapContext) {
-                try {
-                    ldapContext.close();
-                } catch (NamingException ex) {
-                    // no-op
-                }
-            }
+            BindResult bindResult = connectionPool.bindAndRevertAuthentication(userDN, password);
+            return bindResult.getResultCode()
+                .intValue() == 0;
+        } catch (LDAPException e) {
+            LOGGER.error("Unexpected error upon authentication", e);
+            return false;
         }
-        return result;
     }
 }
diff --git a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUsersDAO.java b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUsersDAO.java
index 678a96a..fb450d0 100644
--- a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUsersDAO.java
+++ b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUsersDAO.java
@@ -19,26 +19,18 @@
 
 package org.apache.james.user.ldap;
 
+import java.net.URI;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
-import java.util.Properties;
 import java.util.Set;
 
+import javax.annotation.PreDestroy;
 import javax.inject.Inject;
-import javax.naming.Context;
-import javax.naming.NamingEnumeration;
-import javax.naming.NamingException;
-import javax.naming.directory.Attribute;
-import javax.naming.directory.Attributes;
-import javax.naming.directory.SearchControls;
-import javax.naming.directory.SearchResult;
-import javax.naming.ldap.InitialLdapContext;
-import javax.naming.ldap.LdapContext;
+import javax.net.SocketFactory;
 
 import org.apache.commons.configuration2.HierarchicalConfiguration;
 import org.apache.commons.configuration2.ex.ConfigurationException;
@@ -49,39 +41,26 @@ import org.apache.james.core.Username;
 import org.apache.james.lifecycle.api.Configurable;
 import org.apache.james.user.api.UsersRepositoryException;
 import org.apache.james.user.api.model.User;
-import org.apache.james.user.ldap.api.LdapConstants;
-import org.apache.james.user.ldap.retry.DoublingRetrySchedule;
-import org.apache.james.user.ldap.retry.api.RetrySchedule;
-import org.apache.james.user.ldap.retry.naming.ldap.RetryingLdapContext;
 import org.apache.james.user.lib.UsersDAO;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.github.fge.lambdas.Throwing;
 import com.github.steveash.guavate.Guavate;
-import com.google.common.base.Strings;
+import com.unboundid.ldap.sdk.LDAPConnection;
+import com.unboundid.ldap.sdk.LDAPConnectionOptions;
+import com.unboundid.ldap.sdk.LDAPConnectionPool;
+import com.unboundid.ldap.sdk.LDAPException;
+import com.unboundid.ldap.sdk.SearchResult;
+import com.unboundid.ldap.sdk.SearchResultEntry;
+import com.unboundid.ldap.sdk.SearchScope;
 
 public class ReadOnlyLDAPUsersDAO implements UsersDAO, Configurable {
     private static final Logger LOGGER = LoggerFactory.getLogger(ReadOnlyLDAPUsersDAO.class);
 
-    // The name of the factory class which creates the initial context
-    // for the LDAP service provider
-    private static final String INITIAL_CONTEXT_FACTORY = "com.sun.jndi.ldap.LdapCtxFactory";
-
-    private static final String PROPERTY_NAME_CONNECTION_POOL = "com.sun.jndi.ldap.connect.pool";
-    private static final String PROPERTY_NAME_CONNECT_TIMEOUT = "com.sun.jndi.ldap.connect.timeout";
-    private static final String PROPERTY_NAME_READ_TIMEOUT = "com.sun.jndi.ldap.read.timeout";
-
-    /**
-     * The context for the LDAP server. This is the connection that is built
-     * from the configuration attributes &quot;ldapHost&quot;,
-     * &quot;principal&quot; and &quot;credentials&quot;.
-     */
-    private LdapContext ldapContext;
-    // The schedule for retry attempts
-    private RetrySchedule schedule = null;
-
     private LdapRepositoryConfiguration ldapConfiguration;
+    private String filterTemplate;
+    private LDAPConnectionPool ldapConnectionPool;
 
     @Inject
     public ReadOnlyLDAPUsersDAO() {
@@ -91,8 +70,6 @@ public class ReadOnlyLDAPUsersDAO implements UsersDAO, Configurable {
     /**
      * Extracts the parameters required by the repository instance from the
      * James server configuration data. The fields extracted include
-     * {@link LdapRepositoryConfiguration#ldapHost}, {@link LdapRepositoryConfiguration#userIdAttribute}, {@link LdapRepositoryConfiguration#userBase},
-     * {@link LdapRepositoryConfiguration#principal}, {@link LdapRepositoryConfiguration#credentials} and {@link LdapRepositoryConfiguration#restriction}.
      *
      * @param configuration
      *            An encapsulation of the James server configuration data.
@@ -104,11 +81,6 @@ public class ReadOnlyLDAPUsersDAO implements UsersDAO, Configurable {
 
     public void configure(LdapRepositoryConfiguration configuration) {
         ldapConfiguration = configuration;
-
-        schedule = new DoublingRetrySchedule(
-            configuration.getRetryStartInterval(),
-            configuration.getRetryMaxInterval(),
-            configuration.getScale());
     }
 
     /**
@@ -120,63 +92,34 @@ public class ReadOnlyLDAPUsersDAO implements UsersDAO, Configurable {
      *             specified LDAP host.
      */
     public void init() throws Exception {
+
         if (LOGGER.isDebugEnabled()) {
             LOGGER.debug(this.getClass().getName() + ".init()" + '\n' + "LDAP host: " + ldapConfiguration.getLdapHost()
                 + '\n' + "User baseDN: " + ldapConfiguration.getUserBase() + '\n' + "userIdAttribute: "
                 + ldapConfiguration.getUserIdAttribute() + '\n' + "Group restriction: " + ldapConfiguration.getRestriction()
-                + '\n' + "UseConnectionPool: " + ldapConfiguration.useConnectionPool() + '\n' + "connectionTimeout: "
+                + '\n' + "connectionTimeout: "
                 + ldapConfiguration.getConnectionTimeout() + '\n' + "readTimeout: " + ldapConfiguration.getReadTimeout()
-                + '\n' + "retrySchedule: " + schedule + '\n' + "maxRetries: " + ldapConfiguration.getMaxRetries() + '\n');
+                + '\n' + "maxRetries: " + ldapConfiguration.getMaxRetries() + '\n');
         }
-        // Setup the initial LDAP context
-        updateLdapContext();
-    }
-
-    protected void updateLdapContext() throws NamingException {
-        ldapContext = computeLdapContext();
-    }
+        filterTemplate = "(&({0}={1})(objectClass={2})" + StringUtils.defaultString(ldapConfiguration.getFilter(), "") + ")";
 
-    /**
-     * Answers a new LDAP/JNDI context using the specified user credentials.
-     *
-     * @return an LDAP directory context
-     * @throws NamingException
-     *             Propagated from underlying LDAP communication API.
-     */
-    protected LdapContext computeLdapContext() throws NamingException {
-        return new RetryingLdapContext(schedule, ldapConfiguration.getMaxRetries()) {
+        LDAPConnectionOptions connectionOptions = new LDAPConnectionOptions();
+        connectionOptions.setConnectTimeoutMillis(ldapConfiguration.getConnectionTimeout());
+        connectionOptions.setResponseTimeoutMillis(ldapConfiguration.getReadTimeout());
 
-            @Override
-            public Context newDelegate() throws NamingException {
-                return new InitialLdapContext(getContextEnvironment(), null);
-            }
-        };
+        URI uri = new URI(ldapConfiguration.getLdapHost());
+        SocketFactory socketFactory = null;
+        LDAPConnection ldapConnection = new LDAPConnection(socketFactory, connectionOptions, uri.getHost(), uri.getPort(), ldapConfiguration.getPrincipal(), ldapConfiguration.getCredentials());
+        ldapConnectionPool = new LDAPConnectionPool(ldapConnection, 4);
+        // TODO implement retries
     }
 
-    protected Properties getContextEnvironment() {
-        Properties props = new Properties();
-        props.put(Context.INITIAL_CONTEXT_FACTORY, INITIAL_CONTEXT_FACTORY);
-        props.put(Context.PROVIDER_URL, Optional.ofNullable(ldapConfiguration.getLdapHost())
-            .orElse(""));
-        if (Strings.isNullOrEmpty(ldapConfiguration.getCredentials())) {
-            props.put(Context.SECURITY_AUTHENTICATION, LdapConstants.SECURITY_AUTHENTICATION_NONE);
-        } else {
-            props.put(Context.SECURITY_AUTHENTICATION, LdapConstants.SECURITY_AUTHENTICATION_SIMPLE);
-            props.put(Context.SECURITY_PRINCIPAL, Optional.ofNullable(ldapConfiguration.getPrincipal())
-                .orElse(""));
-            props.put(Context.SECURITY_CREDENTIALS, ldapConfiguration.getCredentials());
-        }
-        // The following properties are specific to com.sun.jndi.ldap.LdapCtxFactory
-        props.put(PROPERTY_NAME_CONNECTION_POOL, String.valueOf(ldapConfiguration.useConnectionPool()));
-        if (ldapConfiguration.getConnectionTimeout() > -1) {
-            props.put(PROPERTY_NAME_CONNECT_TIMEOUT, String.valueOf(ldapConfiguration.getConnectionTimeout()));
-        }
-        if (ldapConfiguration.getReadTimeout() > -1) {
-            props.put(PROPERTY_NAME_READ_TIMEOUT, Integer.toString(ldapConfiguration.getReadTimeout()));
-        }
-        return props;
+    @PreDestroy
+    void dispose() {
+        ldapConnectionPool.close();
     }
 
+
     /**
      * Indicates if the user with the specified DN can be found in the group
      * membership map&#45;as encapsulated by the specified parameter map.
@@ -209,29 +152,20 @@ public class ReadOnlyLDAPUsersDAO implements UsersDAO, Configurable {
         return result;
     }
 
-    /**
-     * Gets all the user entities taken from the LDAP server, as taken from the
-     * search-context given by the value of the attribute {@link LdapRepositoryConfiguration#userBase}.
-     *
-     * @return A set containing all the relevant users found in the LDAP
-     *         directory.
-     * @throws NamingException
-     *             Propagated from the LDAP communication layer.
-     */
-    private Set<String> getAllUsersFromLDAP() throws NamingException {
-        Set<String> result = new HashSet<>();
-
-        SearchControls sc = new SearchControls();
-        sc.setSearchScope(SearchControls.SUBTREE_SCOPE);
-        sc.setReturningAttributes(new String[] { "distinguishedName" });
-        NamingEnumeration<SearchResult> sr = ldapContext.search(ldapConfiguration.getUserBase(), "(objectClass="
-                + ldapConfiguration.getUserObjectClass() + ")", sc);
-        while (sr.hasMore()) {
-            SearchResult r = sr.next();
-            result.add(r.getNameInNamespace());
-        }
+    private Set<String> getAllUsersFromLDAP() throws LDAPException {
+        LDAPConnection connection = ldapConnectionPool.getConnection();
+        try {
+            SearchResult searchResult = connection.search(ldapConfiguration.getUserBase(),
+                SearchScope.SUB,
+                filterTemplate);
 
-        return result;
+            return searchResult.getSearchEntries()
+                .stream()
+                .map(entry -> entry.getObjectClassAttribute().getName())
+                .collect(Guavate.toImmutableSet());
+        } finally {
+            ldapConnectionPool.releaseConnection(connection);
+        }
     }
 
     /**
@@ -245,11 +179,10 @@ public class ReadOnlyLDAPUsersDAO implements UsersDAO, Configurable {
      *            is to be extracted from the LDAP repository.
      * @return A collection of {@link ReadOnlyLDAPUser}s as taken from the LDAP
      *         server.
-     * @throws NamingException
+     * @throws LDAPException
      *             Propagated from the underlying LDAP communication layer.
      */
-    private Collection<ReadOnlyLDAPUser> buildUserCollection(Collection<String> userDNs)
-            throws NamingException {
+    private Collection<ReadOnlyLDAPUser> buildUserCollection(Collection<String> userDNs) throws LDAPException {
         List<ReadOnlyLDAPUser> results = new ArrayList<>();
 
         for (String userDN : userDNs) {
@@ -260,42 +193,34 @@ public class ReadOnlyLDAPUsersDAO implements UsersDAO, Configurable {
         return results;
     }
 
-    /**
-     * For a given name, this method makes ldap search in userBase with filter {@link LdapRepositoryConfiguration#userIdAttribute}=name
-     * and objectClass={@link LdapRepositoryConfiguration#userObjectClass} and builds {@link User} based on search result.
-     *
-     * @param name
-     *            The userId which should be value of the field {@link LdapRepositoryConfiguration#userIdAttribute}
-     * @return A {@link ReadOnlyLDAPUser} instance which is initialized with the
-     *         userId of this user and ldap connection information with which
-     *         the user was searched. Return null if such a user was not found.
-     * @throws NamingException
-     *             Propagated by the underlying LDAP communication layer.
-     */
-    private ReadOnlyLDAPUser searchAndBuildUser(Username name) throws NamingException {
-        SearchControls sc = new SearchControls();
-        sc.setSearchScope(SearchControls.SUBTREE_SCOPE);
-        sc.setReturningAttributes(new String[] { ldapConfiguration.getUserIdAttribute() });
-        sc.setCountLimit(1);
-
-        String filterTemplate = "(&({0}={1})(objectClass={2})" +
-            StringUtils.defaultString(ldapConfiguration.getFilter(), "") +
-            ")";
-
-        String sanitizedFilter = FilterEncoder.format(
-            filterTemplate,
-            ldapConfiguration.getUserIdAttribute(),
-            name.asString(),
-            ldapConfiguration.getUserObjectClass());
-
-        NamingEnumeration<SearchResult> sr = ldapContext.search(ldapConfiguration.getUserBase(), sanitizedFilter, sc);
-
-        if (!sr.hasMore()) {
-            return null;
+    private ReadOnlyLDAPUser searchAndBuildUser(Username name) throws LDAPException {
+        LDAPConnection connection = ldapConnectionPool.getConnection();
+        try {
+            String sanitizedFilter = FilterEncoder.format(
+                filterTemplate,
+                ldapConfiguration.getUserIdAttribute(),
+                name.asString(),
+                ldapConfiguration.getUserObjectClass());
+
+            SearchResult searchResult = connection.search(ldapConfiguration.getUserBase(),
+                SearchScope.SUB,
+                sanitizedFilter,
+                ldapConfiguration.getUserIdAttribute());
+
+            return searchResult.getSearchEntries()
+                .stream()
+                .map(entry -> new ReadOnlyLDAPUser(
+                    Username.of(entry.getAttribute(ldapConfiguration.getUserIdAttribute()).getName()),
+                    entry.getDN(),
+                    ldapConnectionPool))
+                .findFirst()
+                .orElse(null);
+        } finally {
+            ldapConnectionPool.releaseConnection(connection);
         }
 
-        SearchResult r = sr.next();
-        Attribute userName = r.getAttributes().get(ldapConfiguration.getUserIdAttribute());
+        /*
+        TODO implement restrictions
 
         if (!ldapConfiguration.getRestriction().isActivated()
             || userInGroupsMembershipList(r.getNameInNamespace(), ldapConfiguration.getRestriction().getGroupMembershipLists(ldapContext))) {
@@ -303,31 +228,20 @@ public class ReadOnlyLDAPUsersDAO implements UsersDAO, Configurable {
         }
 
         return null;
+        */
     }
 
-    /**
-     * Given a userDN, this method retrieves the user attributes from the LDAP
-     * server, so as to extract the items that are of interest to James.
-     * Specifically it extracts the userId, which is extracted from the LDAP
-     * attribute whose name is given by the value of the field
-     * {@link LdapRepositoryConfiguration#userIdAttribute}.
-     *
-     * @param userDN
-     *            The distinguished-name of the user whose details are to be
-     *            extracted from the LDAP repository.
-     * @return A {@link ReadOnlyLDAPUser} instance which is initialized with the
-     *         userId of this user and ldap connection information with which
-     *         the userDN and attributes were obtained.
-     * @throws NamingException
-     *             Propagated by the underlying LDAP communication layer.
-     */
-    private Optional<ReadOnlyLDAPUser> buildUser(String userDN) throws NamingException {
-      Attributes userAttributes = ldapContext.getAttributes(userDN);
-      Optional<Attribute> userName = Optional.ofNullable(userAttributes.get(ldapConfiguration.getUserIdAttribute()));
-      return userName
-          .map(Throwing.<Attribute, String>function(u -> u.get().toString()).sneakyThrow())
-          .map(Username::of)
-          .map(username -> new ReadOnlyLDAPUser(username, userDN, ldapContext));
+    private Optional<ReadOnlyLDAPUser> buildUser(String userDN) throws LDAPException {
+        LDAPConnection connection = ldapConnectionPool.getConnection();
+        try {
+            SearchResultEntry userAttributes = connection.getEntry(userDN);
+            Optional<String> userName = Optional.ofNullable(userAttributes.getAttributeValue(ldapConfiguration.getUserIdAttribute()));
+            return userName
+                .map(Username::of)
+                .map(username -> new ReadOnlyLDAPUser(username, userDN, ldapConnectionPool));
+        } finally {
+            ldapConnectionPool.releaseConnection(connection);
+        }
     }
 
     @Override
@@ -342,7 +256,7 @@ public class ReadOnlyLDAPUsersDAO implements UsersDAO, Configurable {
                 .map(Throwing.function(this::buildUser).sneakyThrow())
                 .flatMap(Optional::stream)
                 .count());
-        } catch (NamingException e) {
+        } catch (LDAPException e) {
             throw new UsersRepositoryException("Unable to retrieve user count from ldap", e);
         }
     }
@@ -351,7 +265,7 @@ public class ReadOnlyLDAPUsersDAO implements UsersDAO, Configurable {
     public Optional<User> getUserByName(Username name) throws UsersRepositoryException {
         try {
           return Optional.ofNullable(searchAndBuildUser(name));
-        } catch (NamingException e) {
+        } catch (LDAPException e) {
             throw new UsersRepositoryException("Unable to retrieve user from ldap", e);
         }
     }
@@ -362,19 +276,22 @@ public class ReadOnlyLDAPUsersDAO implements UsersDAO, Configurable {
             return buildUserCollection(getValidUsers())
                 .stream()
                 .map(ReadOnlyLDAPUser::getUserName)
-                .collect(Guavate.toImmutableList())
                 .iterator();
-        } catch (NamingException namingException) {
+        } catch (LDAPException namingException) {
             throw new UsersRepositoryException(
                     "Unable to retrieve users list from LDAP due to unknown naming error.",
                     namingException);
         }
     }
 
-    private Collection<String> getValidUsers() throws NamingException {
-        Set<String> userDNs = getAllUsersFromLDAP();
-        Collection<String> validUserDNs;
+    private Collection<String> getValidUsers() throws LDAPException {
+        return getAllUsersFromLDAP();
 
+        /*
+        TODO Implement restrictions
+         */
+        /*
+        Collection<String> validUserDNs;
         if (ldapConfiguration.getRestriction().isActivated()) {
             Map<String, Collection<String>> groupMembershipList = ldapConfiguration.getRestriction()
                     .getGroupMembershipLists(ldapContext);
@@ -392,6 +309,7 @@ public class ReadOnlyLDAPUsersDAO implements UsersDAO, Configurable {
             validUserDNs = userDNs;
         }
         return validUserDNs;
+         */
     }
 
     @Override
diff --git a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/api/LdapConstants.java b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/api/LdapConstants.java
deleted file mode 100644
index 1227bca..0000000
--- a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/api/LdapConstants.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- *   Licensed to the Apache Software Foundation (ASF) under one
- *   or more contributor license agreements.  See the NOTICE file
- *   distributed with this work for additional information
- *   regarding copyright ownership.  The ASF licenses this file
- *   to you under the Apache License, Version 2.0 (the
- *   "License"); you may not use this file except in compliance
- *   with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- *   Unless required by applicable law or agreed to in writing,
- *   software distributed under the License is distributed on an
- *   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *   KIND, either express or implied.  See the License for the
- *   specific language governing permissions and limitations
- *   under the License.
- *
- */
-
-package org.apache.james.user.ldap.api;
-
-/**
- * <code>LdapConstants</code>
- */
-public interface LdapConstants {
-
-    // The authentication mechanisms for the provider to use
-    String SECURITY_AUTHENTICATION_NONE = "none";
-    String SECURITY_AUTHENTICATION_SIMPLE = "simple";
-
-}
diff --git a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/retry/DoublingRetrySchedule.java b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/retry/DoublingRetrySchedule.java
deleted file mode 100644
index f9e435e..0000000
--- a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/retry/DoublingRetrySchedule.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- *   Licensed to the Apache Software Foundation (ASF) under one
- *   or more contributor license agreements.  See the NOTICE file
- *   distributed with this work for additional information
- *   regarding copyright ownership.  The ASF licenses this file
- *   to you under the Apache License, Version 2.0 (the
- *   "License"); you may not use this file except in compliance
- *   with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- *   Unless required by applicable law or agreed to in writing,
- *   software distributed under the License is distributed on an
- *   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *   KIND, either express or implied.  See the License for the
- *   specific language governing permissions and limitations
- *   under the License.
- *
- */
-
-package org.apache.james.user.ldap.retry;
-
-import org.apache.james.user.ldap.retry.api.RetrySchedule;
-
-
-/**
- * <code>DoublingRetrySchedule</code> is an implementation of <code>RetrySchedule</code> that
- * returns the lesser of an interval that is double the proceeding interval and the maximum interval.
- * 
- * <p>When the initial interval is 0, the next interval is 1.
- * 
- * <p>A multiplier can be specified to scale the results.
- */
-/**
- * <code>DoublingRetrySchedule</code>
- */
-public class DoublingRetrySchedule implements RetrySchedule {
-    
-    private long startInterval = 0;
-    private long maxInterval = 0;
-    private long multiplier = 1;
-
-    /**
-     * Creates a new instance of DoublingRetrySchedule.
-     *
-     */
-    private DoublingRetrySchedule() {
-    }
-    
-    /**
-     * Creates a new instance of DoublingRetrySchedule.
-     *
-     * @param startInterval
-     *      The interval for an index of 0
-     * @param maxInterval
-     *      The maximum interval for any index
-     */
-    public DoublingRetrySchedule(long startInterval, long maxInterval) {
-        this(startInterval, maxInterval, 1);
-    }
-    
-    /**
-     * Creates a new instance of DoublingRetrySchedule.
-     *
-     * @param startInterval
-     *      The interval for an index of 0
-     * @param maxInterval
-     *      The maximum interval for any index
-     * @param multiplier
-     *      The multiplier to apply to the result
-     */
-    public DoublingRetrySchedule(long startInterval, long maxInterval, int multiplier) {
-        this();
-        this.startInterval = Math.max(0, startInterval);
-        this.maxInterval = Math.max(0, maxInterval);
-        this.multiplier = Math.max(1, multiplier);
-    }
-
-    @Override
-    public long getInterval(int index) {
-        if (startInterval > 0) {
-            return getInterval(index, startInterval);
-        }
-        return index == 0 ? 0 : getInterval(index - 1, 1);
-    }
-    
-    private long getInterval(int index, long startInterval) {
-        return multiplier * Math.min((long) (startInterval * Math.pow(2, index)), maxInterval);
-    }    
-
-    @Override
-    public String toString() {
-        StringBuilder builder = new StringBuilder();
-        builder.append("DoublingRetrySchedule [startInterval=").append(startInterval).append(
-                ", maxInterval=").append(maxInterval).append(", multiplier=").append(multiplier).append("]");
-        return builder.toString();
-    }
-
-}
diff --git a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/retry/ExceptionRetryHandler.java b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/retry/ExceptionRetryHandler.java
deleted file mode 100644
index 218048b..0000000
--- a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/retry/ExceptionRetryHandler.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- *   Licensed to the Apache Software Foundation (ASF) under one
- *   or more contributor license agreements.  See the NOTICE file
- *   distributed with this work for additional information
- *   regarding copyright ownership.  The ASF licenses this file
- *   to you under the Apache License, Version 2.0 (the
- *   "License"); you may not use this file except in compliance
- *   with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- *   Unless required by applicable law or agreed to in writing,
- *   software distributed under the License is distributed on an
- *   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *   KIND, either express or implied.  See the License for the
- *   specific language governing permissions and limitations
- *   under the License.
- *
- */
-
-package org.apache.james.user.ldap.retry;
-
-import org.apache.james.user.ldap.retry.api.ExceptionRetryingProxy;
-import org.apache.james.user.ldap.retry.api.RetryHandler;
-import org.apache.james.user.ldap.retry.api.RetrySchedule;
-
-/**
- * Abstract class <code>ExceptionRetryHandler</code> retries the behaviour defined in abstract method 
- * <code>operation()</code> implemented in a concrete subclass when nominated subclasses of <code>Exception</code>
- * are thrown. The intervals between retries are defined by a <code>RetrySchedule</code>.
- * <p>
- * Concrete subclasses are proxies that forward work via the <code>operation()</code> to a delegate
- * instance for which retry behaviour is required. Both the proxy and the delegate implement the
- * same interfaces. 
- * <p>
- * The method stubs required to perform the proxy call via <code>operation()</code> may be generated by many means,
- * including explicit code, a <code>DynamicProxy</code> and compile time aspect injection.
- *
- * @see org.apache.james.user.ldap.retry.naming.RetryingContext
- */
-public abstract class ExceptionRetryHandler implements RetryHandler {
-
-    private Class<?>[] exceptionClasses = null;
-    
-    private ExceptionRetryingProxy proxy = null;
-    private RetrySchedule schedule;
-    private int maxRetries = 0;
-
-        /**
-         * Creates a new instance of ExceptionRetryHandler.
-         *
-         */
-        private ExceptionRetryHandler() {
-            super();
-        }
-
-
-        /**
-         * Creates a new instance of ExceptionRetryHandler.
-         *
-         * @param exceptionClasses
-         * @param proxy
-         * @param maxRetries
-         */
-        public ExceptionRetryHandler(Class<?>[] exceptionClasses, ExceptionRetryingProxy proxy, RetrySchedule schedule, int maxRetries) {
-            this();
-            this.exceptionClasses = exceptionClasses;
-            this.proxy = proxy;
-            this.schedule = schedule;
-            this.maxRetries = maxRetries;
-        }
-
-        @Override
-        public Object perform() throws Exception {
-            boolean success = false;
-            Object result = null;
-            int retryCount = 0;
-            while (!success) {
-                try {
-                    if (retryCount > 0) {
-                        proxy.resetDelegate();
-                    }
-                    result = operation();
-                    success = true;
-
-                } catch (Exception ex) {
-                    if (retryCount >= maxRetries || !isRetryable(ex)) {
-                        throw ex;
-                    }
-                    postFailure(ex, retryCount);
-                    try {
-                        Thread.sleep(getRetryInterval(retryCount));
-                    } catch (InterruptedException ex1) {
-                        // no-op
-                    }
-                    retryCount = maxRetries < 0 ? maxRetries
-                            : retryCount + 1;
-                }
-            }
-            return result;
-        }
-        
-        /**
-         * @param ex
-         * The <code>Throwable</code> to test
-         * 
-         * @return true if the array of exception classes contains <strong>ex</strong>
-         */
-        private boolean isRetryable(Throwable ex) {
-            boolean isRetryable = false;
-            for (int i = 0; !isRetryable && i < exceptionClasses.length; i++) {
-                isRetryable = exceptionClasses[i].isInstance(ex);
-            }
-            return isRetryable;
-        }
-
-        @Override
-        public void postFailure(Exception ex, int retryCount) {
-            // no-op
-        }        
-
-        @Override
-        public abstract Object operation() throws Exception;
-        
-        /**
-         * @return the retryInterval
-         */
-        public long getRetryInterval(int retryCount) {
-            return schedule.getInterval(retryCount);
-        }
-}
diff --git a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/retry/api/ExceptionRetryingProxy.java b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/retry/api/ExceptionRetryingProxy.java
deleted file mode 100644
index fb41fe9..0000000
--- a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/retry/api/ExceptionRetryingProxy.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- *   Licensed to the Apache Software Foundation (ASF) under one
- *   or more contributor license agreements.  See the NOTICE file
- *   distributed with this work for additional information
- *   regarding copyright ownership.  The ASF licenses this file
- *   to you under the Apache License, Version 2.0 (the
- *   "License"); you may not use this file except in compliance
- *   with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- *   Unless required by applicable law or agreed to in writing,
- *   software distributed under the License is distributed on an
- *   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *   KIND, either express or implied.  See the License for the
- *   specific language governing permissions and limitations
- *   under the License.
- *
- */
-
-package org.apache.james.user.ldap.retry.api;
-
-
-/**
- * <code>ExceptionRetryingProxy</code> defines the behaviour for a
- * proxy that can retry <codeException</code> and its subclasses.
- */
-public interface ExceptionRetryingProxy {
-    /**
-     * @return a new instance that the proxy delegates to
-     * @throws Exception
-     */
-    Object newDelegate() throws Exception;
-    
-    /**
-     * @return the current instance of the proxy delegate
-     * @throws Exception
-     */
-    Object getDelegate();
-    
-    /**
-     * Resets the delegate instance to a state from which it can perform the 
-     * operations delegated to it.
-     *
-     * @throws Exception
-     */
-    void resetDelegate() throws Exception;
-
-}
diff --git a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/retry/api/RetryHandler.java b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/retry/api/RetryHandler.java
deleted file mode 100644
index 77f7e56..0000000
--- a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/retry/api/RetryHandler.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- *   Licensed to the Apache Software Foundation (ASF) under one
- *   or more contributor license agreements.  See the NOTICE file
- *   distributed with this work for additional information
- *   regarding copyright ownership.  The ASF licenses this file
- *   to you under the Apache License, Version 2.0 (the
- *   "License"); you may not use this file except in compliance
- *   with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- *   Unless required by applicable law or agreed to in writing,
- *   software distributed under the License is distributed on an
- *   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *   KIND, either express or implied.  See the License for the
- *   specific language governing permissions and limitations
- *   under the License.
- *
- */
-
-package org.apache.james.user.ldap.retry.api;
-
-
-/**
- * <code>RetryHandler</code>
- */
-public interface RetryHandler {
-
-    /**
-     * @return the result of invoking an operation
-     * @throws Exception
-     */
-    Object perform() throws Exception;
-
-    /**
-     * A hook invoked each time an operation fails if a retry is scheduled
-     *
-     * @param ex
-     *      The <code>Exception</code> thrown when the operation was invoked
-     * @param retryCount
-     *      The number of times  
-     */
-    void postFailure(Exception ex, int retryCount);
-
-    /**
-     * Encapsulates desired behaviour
-     * @return The result of performing the behaviour
-     * @throws Exception
-     */
-    Object operation() throws Exception;
-
-}
\ No newline at end of file
diff --git a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/retry/api/RetrySchedule.java b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/retry/api/RetrySchedule.java
deleted file mode 100644
index 6607925..0000000
--- a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/retry/api/RetrySchedule.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- *   Licensed to the Apache Software Foundation (ASF) under one
- *   or more contributor license agreements.  See the NOTICE file
- *   distributed with this work for additional information
- *   regarding copyright ownership.  The ASF licenses this file
- *   to you under the Apache License, Version 2.0 (the
- *   "License"); you may not use this file except in compliance
- *   with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- *   Unless required by applicable law or agreed to in writing,
- *   software distributed under the License is distributed on an
- *   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *   KIND, either express or implied.  See the License for the
- *   specific language governing permissions and limitations
- *   under the License.
- *
- */
-
-package org.apache.james.user.ldap.retry.api;
-
-/**
- * <code>RetrySchedule</code>
- */
-public interface RetrySchedule {
-    /**
-     * Returns the interval time in milliseconds at the specified zero based index.
-     *
-     * @param index
-     * @return
-     */
-    long getInterval(int index);
-
-}
diff --git a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/retry/naming/LoggingRetryHandler.java b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/retry/naming/LoggingRetryHandler.java
deleted file mode 100644
index e9af6f4..0000000
--- a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/retry/naming/LoggingRetryHandler.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- *   Licensed to the Apache Software Foundation (ASF) under one
- *   or more contributor license agreements.  See the NOTICE file
- *   distributed with this work for additional information
- *   regarding copyright ownership.  The ASF licenses this file
- *   to you under the Apache License, Version 2.0 (the
- *   "License"); you may not use this file except in compliance
- *   with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- *   Unless required by applicable law or agreed to in writing,
- *   software distributed under the License is distributed on an
- *   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *   KIND, either express or implied.  See the License for the
- *   specific language governing permissions and limitations
- *   under the License.
- *
- */
-
-package org.apache.james.user.ldap.retry.naming;
-
-import javax.naming.NamingException;
-
-import org.apache.james.user.ldap.retry.api.ExceptionRetryingProxy;
-import org.apache.james.user.ldap.retry.api.RetrySchedule;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Abstract class <code>LoggingRetryHandler</code> implements logging of failures 
- */
-public abstract class LoggingRetryHandler extends NamingExceptionRetryHandler {
-    private static final Logger LOGGER = LoggerFactory.getLogger(LoggingRetryHandler.class);
-
-    /**
-     * Creates a new instance of LoggingRetryHandler.
-     *
-     * @param exceptionClasses
-     * @param proxy
-     * @param maxRetries
-     */
-    public LoggingRetryHandler(Class<?>[] exceptionClasses, ExceptionRetryingProxy proxy,
-                               RetrySchedule schedule, int maxRetries) {
-        super(exceptionClasses, proxy, schedule, maxRetries);
-    }
-
-    @Override
-    public void postFailure(NamingException ex, int retryCount) {
-        super.postFailure(ex, retryCount);
-        LOGGER.info("Retry failure. Retrying in {} seconds", getRetryInterval(retryCount) / 1000, ex);
-    }
-
-}
diff --git a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/retry/naming/NamingExceptionRetryHandler.java b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/retry/naming/NamingExceptionRetryHandler.java
deleted file mode 100644
index 368b5d2..0000000
--- a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/retry/naming/NamingExceptionRetryHandler.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- *   Licensed to the Apache Software Foundation (ASF) under one
- *   or more contributor license agreements.  See the NOTICE file
- *   distributed with this work for additional information
- *   regarding copyright ownership.  The ASF licenses this file
- *   to you under the Apache License, Version 2.0 (the
- *   "License"); you may not use this file except in compliance
- *   with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- *   Unless required by applicable law or agreed to in writing,
- *   software distributed under the License is distributed on an
- *   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *   KIND, either express or implied.  See the License for the
- *   specific language governing permissions and limitations
- *   under the License.
- *
- */
-
-package org.apache.james.user.ldap.retry.naming;
-
-import javax.naming.NamingException;
-
-import org.apache.james.user.ldap.retry.ExceptionRetryHandler;
-import org.apache.james.user.ldap.retry.api.ExceptionRetryingProxy;
-import org.apache.james.user.ldap.retry.api.RetrySchedule;
-
-/**
- * Abstract class <code>NamingExceptionRetryHandler</code> narrows the set of Exceptions throwable 
- * by <code>perform</code> to <code>NamingException</code> and its subclasses.
- * <p><code>RuntimeException</code>s are <strong>not</strong> retried.
- * 
- * @see org.apache.james.user.ldap.retry.ExceptionRetryHandler
- * 
- */
-public abstract class NamingExceptionRetryHandler extends ExceptionRetryHandler {
-
-    /**
-     * Creates a new instance of NamingExceptionRetryHandler.
-     *
-     * @param exceptionClasses
-     * @param proxy
-     * @param schedule
-     * @param maxRetries
-     */
-    public NamingExceptionRetryHandler(Class<?>[] exceptionClasses, ExceptionRetryingProxy proxy,
-            RetrySchedule schedule, int maxRetries) {
-        super(exceptionClasses, proxy, schedule, maxRetries);
-    }
-
-    @Override
-    public Object perform() throws NamingException {
-        try {
-            return super.perform();
-        } catch (RuntimeException ex) {
-            throw ex;
-        } catch (Exception ex) {
-            // Should only ever be a NamingException
-            throw ((NamingException) ex);
-        }
-    }
-
-    @Override
-    public void postFailure(Exception ex, int retryCount) {
-        postFailure(((NamingException) ex), retryCount);        
-    }
-
-    public void postFailure(NamingException ex, int retryCount) {
-        // no-op
-    }
-}
diff --git a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/retry/naming/RetryingContext.java b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/retry/naming/RetryingContext.java
deleted file mode 100644
index 65bbd1a..0000000
--- a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/retry/naming/RetryingContext.java
+++ /dev/null
@@ -1,497 +0,0 @@
-/*
- *   Licensed to the Apache Software Foundation (ASF) under one
- *   or more contributor license agreements.  See the NOTICE file
- *   distributed with this work for additional information
- *   regarding copyright ownership.  The ASF licenses this file
- *   to you under the Apache License, Version 2.0 (the
- *   "License"); you may not use this file except in compliance
- *   with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- *   Unless required by applicable law or agreed to in writing,
- *   software distributed under the License is distributed on an
- *   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *   KIND, either express or implied.  See the License for the
- *   specific language governing permissions and limitations
- *   under the License.
- *
- */
-
-package org.apache.james.user.ldap.retry.naming;
-
-import java.util.Hashtable;
-
-import javax.naming.Binding;
-import javax.naming.CommunicationException;
-import javax.naming.Context;
-import javax.naming.Name;
-import javax.naming.NameClassPair;
-import javax.naming.NameParser;
-import javax.naming.NamingEnumeration;
-import javax.naming.NamingException;
-import javax.naming.NoInitialContextException;
-import javax.naming.ServiceUnavailableException;
-
-import org.apache.james.user.ldap.retry.ExceptionRetryHandler;
-import org.apache.james.user.ldap.retry.api.ExceptionRetryingProxy;
-import org.apache.james.user.ldap.retry.api.RetrySchedule;
-
-/**
- * <code>RetryingContext</code> retries the methods defined by <code>javax.naming.Context</code>
- * according to the specified schedule. 
- * 
- * @see ExceptionRetryHandler
- * @see org.apache.james.user.ldap.retry.api.ExceptionRetryingProxy
- * @see javax.naming.Context
- */
-public abstract class RetryingContext implements Context, ExceptionRetryingProxy {
-
-    public static final Class<?>[] DEFAULT_EXCEPTION_CLASSES = new Class<?>[] {
-            CommunicationException.class,
-            ServiceUnavailableException.class,
-            NoInitialContextException.class };
-
-    private Context delegate = null;
-    private RetrySchedule schedule = null;
-    private int maxRetries = 0;
-
-    /**
-     * Creates a new instance of RetryingContext.
-     * 
-     * @throws NamingException
-     * 
-     */
-    private RetryingContext() {
-        super();
-    }
-    
-    /**
-     * Creates a new instance of RetryingContext using the default exception
-     * classes thrown when an external interruption to services on which we
-     * depend occurs.
-     * 
-     * @param schedule
-     * @param maxRetries
-     * @throws NamingException
-     */
-    public RetryingContext(RetrySchedule schedule, int maxRetries)
-            throws NamingException {
-        this(DEFAULT_EXCEPTION_CLASSES, schedule, maxRetries);
-    }
-
-    /**
-     * Creates a new instance of RetryingContext.
-     *
-     * @param exceptionClasses
-     * @param schedule
-     * @param maxRetries
-     * @throws NamingException
-     */
-    public RetryingContext(Class<?>[] exceptionClasses, RetrySchedule schedule, int maxRetries)
-            throws NamingException {
-        this();
-        this.schedule = schedule;
-        this.maxRetries = maxRetries;
-        this.delegate = (Context) new LoggingRetryHandler(exceptionClasses, this,
-                this.schedule, this.maxRetries) {
-
-            @Override
-            public Object operation() throws Exception {
-                return newDelegate();
-            }
-        }.perform();
-    }
-
-    @Override
-    public Object addToEnvironment(final String propName, final Object propVal)
-            throws NamingException {
-        return new LoggingRetryHandler(DEFAULT_EXCEPTION_CLASSES, this, schedule, maxRetries
-        ) {
-
-            @Override
-            public Object operation() throws NamingException {
-                return getDelegate().addToEnvironment(propName, propVal);
-            }
-        }.perform();
-    }
-
-    @Override
-    public void bind(final Name name, final Object obj) throws NamingException {
-        new LoggingRetryHandler(DEFAULT_EXCEPTION_CLASSES, this, schedule, maxRetries
-        ) {
-
-            @Override
-            public Object operation() throws NamingException {
-                getDelegate().bind(name, obj);
-                return null;
-            }
-        }.perform();
-    }
-
-    @Override
-    public void bind(final String name, final Object obj) throws NamingException {
-        new LoggingRetryHandler(DEFAULT_EXCEPTION_CLASSES, this, schedule, maxRetries
-        ) {
-
-            @Override
-            public Object operation() throws NamingException {
-                getDelegate().bind(name, obj);
-                return null;
-            }
-        }.perform();
-    }
-
-    @Override
-    public void close() throws NamingException {
-        new LoggingRetryHandler(DEFAULT_EXCEPTION_CLASSES, this, schedule, maxRetries
-        ) {
-
-            @Override
-            public Object operation() throws NamingException {
-                getDelegate().close();
-                return null;
-            }
-        }.perform();
-    }
-
-    @Override
-    public Name composeName(final Name name, final Name prefix) throws NamingException {
-        return (Name) new LoggingRetryHandler(DEFAULT_EXCEPTION_CLASSES, this, schedule, maxRetries
-        ) {
-
-            @Override
-            public Object operation() throws NamingException {
-                return getDelegate().composeName(name, prefix);
-            }
-        }.perform();
-    }
-
-    @Override
-    public String composeName(final String name, final String prefix) throws NamingException {
-        return (String) new LoggingRetryHandler(DEFAULT_EXCEPTION_CLASSES, this, schedule, maxRetries
-        ) {
-
-            @Override
-            public Object operation() throws NamingException {
-                return getDelegate().composeName(name, prefix);
-            }
-        }.perform();
-    }
-
-    @Override
-    public Context createSubcontext(final Name name) throws NamingException {
-        final Context context = getDelegate();
-        return new RetryingContext(getSchedule(), getMaxRetries()) {
-
-            @Override
-            public Context newDelegate() throws NamingException {
-                return context.createSubcontext(name);
-            }
-        };
-    }
-
-    @Override
-    public Context createSubcontext(final String name) throws NamingException {
-        final Context context = getDelegate();
-        return new RetryingContext(getSchedule(), getMaxRetries()) {
-
-            @Override
-            public Context newDelegate() throws NamingException {
-                return context.createSubcontext(name);
-            }
-        };
-    }
-
-    @Override
-    public void destroySubcontext(final Name name) throws NamingException {
-        new LoggingRetryHandler(DEFAULT_EXCEPTION_CLASSES, this, schedule, maxRetries
-        ) {
-
-            @Override
-            public Object operation() throws NamingException {
-                getDelegate().destroySubcontext(name);
-                return null;
-            }
-        }.perform();
-    }
-
-    @Override
-    public void destroySubcontext(final String name) throws NamingException {
-        new LoggingRetryHandler(DEFAULT_EXCEPTION_CLASSES, this, schedule, maxRetries
-        ) {
-
-            @Override
-            public Object operation() throws NamingException {
-                getDelegate().destroySubcontext(name);
-                return null;
-            }
-        }.perform();
-    }
-
-    @Override
-    public Hashtable<?, ?> getEnvironment() throws NamingException {
-        return (Hashtable<?, ?>) new LoggingRetryHandler(DEFAULT_EXCEPTION_CLASSES, this,
-                schedule, maxRetries) {
-
-            @Override
-            public Object operation() throws NamingException {
-                return getDelegate().getEnvironment();
-            }
-        }.perform();
-    }
-
-    @Override
-    public String getNameInNamespace() throws NamingException {
-        return (String) new LoggingRetryHandler(DEFAULT_EXCEPTION_CLASSES, this, schedule, maxRetries
-        ) {
-
-            @Override
-            public Object operation() throws NamingException {
-                return getDelegate().getNameInNamespace();
-            }
-        }.perform();
-    }
-
-    @Override
-    public NameParser getNameParser(final Name name) throws NamingException {
-        return (NameParser) new LoggingRetryHandler(DEFAULT_EXCEPTION_CLASSES, this, schedule, maxRetries
-        ) {
-
-            @Override
-            public Object operation() throws NamingException {
-                return getDelegate().getNameParser(name);
-            }
-        }.perform();
-    }
-
-    @Override
-    public NameParser getNameParser(final String name) throws NamingException {
-        return (NameParser) new LoggingRetryHandler(DEFAULT_EXCEPTION_CLASSES, this, schedule, maxRetries
-        ) {
-
-            @Override
-            public Object operation() throws NamingException {
-                return getDelegate().getNameParser(name);
-            }
-        }.perform();
-    }
-
-    @SuppressWarnings("unchecked")
-    @Override
-    public NamingEnumeration<NameClassPair> list(final Name name) throws NamingException {
-        return (NamingEnumeration<NameClassPair>) new LoggingRetryHandler(
-                DEFAULT_EXCEPTION_CLASSES, this, schedule, maxRetries) {
-
-            @Override
-            public Object operation() throws NamingException {
-                return getDelegate().list(name);
-            }
-        }.perform();
-    }
-
-    @SuppressWarnings("unchecked")
-    @Override
-    public NamingEnumeration<NameClassPair> list(final String name) throws NamingException {
-        return (NamingEnumeration<NameClassPair>) new LoggingRetryHandler(
-                DEFAULT_EXCEPTION_CLASSES, this, schedule, maxRetries) {
-
-            @Override
-            public Object operation() throws NamingException {
-                return getDelegate().list(name);
-            }
-        }.perform();
-    }
-
-    @SuppressWarnings("unchecked")
-    @Override
-    public NamingEnumeration<Binding> listBindings(final Name name) throws NamingException {
-        return (NamingEnumeration<Binding>) new LoggingRetryHandler(DEFAULT_EXCEPTION_CLASSES,
-                this, schedule, maxRetries) {
-
-            @Override
-            public Object operation() throws NamingException {
-                return getDelegate().listBindings(name);
-            }
-        }.perform();
-    }
-
-    @SuppressWarnings("unchecked")
-    @Override
-    public NamingEnumeration<Binding> listBindings(final String name) throws NamingException {
-        return (NamingEnumeration<Binding>) new LoggingRetryHandler(DEFAULT_EXCEPTION_CLASSES,
-                this, schedule, maxRetries) {
-
-            @Override
-            public Object operation() throws NamingException {
-                return getDelegate().listBindings(name);
-            }
-        }.perform();
-    }
-
-    @Override
-    public Object lookup(final Name name) throws NamingException {
-        return new LoggingRetryHandler(DEFAULT_EXCEPTION_CLASSES, this, schedule, maxRetries
-        ) {
-
-            @Override
-            public Object operation() throws NamingException {
-                return getDelegate().lookup(name);
-            }
-        }.perform();
-    }
-
-    @Override
-    public Object lookup(final String name) throws NamingException {
-        return new LoggingRetryHandler(DEFAULT_EXCEPTION_CLASSES, this, schedule, maxRetries
-        ) {
-
-            @Override
-            public Object operation() throws NamingException {
-                return getDelegate().lookup(name);
-            }
-        }.perform();
-    }
-
-    @Override
-    public Object lookupLink(final Name name) throws NamingException {
-        return new LoggingRetryHandler(DEFAULT_EXCEPTION_CLASSES, this, schedule, maxRetries
-        ) {
-
-            @Override
-            public Object operation() throws NamingException {
-                return getDelegate().lookupLink(name);
-            }
-        }.perform();
-    }
-
-    @Override
-    public Object lookupLink(final String name) throws NamingException {
-        return new LoggingRetryHandler(DEFAULT_EXCEPTION_CLASSES, this, schedule, maxRetries
-        ) {
-
-            @Override
-            public Object operation() throws NamingException {
-                return getDelegate().lookupLink(name);
-            }
-        }.perform();
-    }
-
-    @Override
-    public void rebind(final Name name, final Object obj) throws NamingException {
-        new LoggingRetryHandler(DEFAULT_EXCEPTION_CLASSES, this, schedule, maxRetries
-        ) {
-
-            @Override
-            public Object operation() throws NamingException {
-                getDelegate().rebind(name, obj);
-                return null;
-            }
-        }.perform();
-    }
-
-    @Override
-    public void rebind(final String name, final Object obj) throws NamingException {
-        new LoggingRetryHandler(DEFAULT_EXCEPTION_CLASSES, this, schedule, maxRetries
-        ) {
-
-            @Override
-            public Object operation() throws NamingException {
-                getDelegate().rebind(name, obj);
-                return null;
-            }
-        }.perform();
-    }
-
-    @Override
-    public Object removeFromEnvironment(final String propName) throws NamingException {
-        return new LoggingRetryHandler(DEFAULT_EXCEPTION_CLASSES, this, schedule, maxRetries
-        ) {
-
-            @Override
-            public Object operation() throws NamingException {
-                return getDelegate().removeFromEnvironment(propName);
-            }
-        }.perform();
-    }
-
-    @Override
-    public void rename(final Name oldName, final Name newName) throws NamingException {
-        new LoggingRetryHandler(DEFAULT_EXCEPTION_CLASSES, this, schedule, maxRetries
-        ) {
-
-            @Override
-            public Object operation() throws NamingException {
-                getDelegate().rename(oldName, newName);
-                return null;
-            }
-        }.perform();
-    }
-
-    @Override
-    public void rename(final String oldName, final String newName) throws NamingException {
-        new LoggingRetryHandler(DEFAULT_EXCEPTION_CLASSES, this, schedule, maxRetries
-        ) {
-
-            @Override
-            public Object operation() throws NamingException {
-                getDelegate().rename(oldName, newName);
-                return null;
-            }
-        }.perform();
-    }
-
-    @Override
-    public void unbind(final Name name) throws NamingException {
-        new LoggingRetryHandler(DEFAULT_EXCEPTION_CLASSES, this, schedule, maxRetries
-        ) {
-
-            @Override
-            public Object operation() throws NamingException {
-                getDelegate().unbind(name);
-                return null;
-            }
-        }.perform();
-    }
-
-    @Override
-    public void unbind(final String name) throws NamingException {
-        new LoggingRetryHandler(DEFAULT_EXCEPTION_CLASSES, this, schedule, maxRetries) {
-
-            @Override
-            public Object operation() throws NamingException {
-                getDelegate().unbind(name);
-                return null;
-            }
-        }.perform();
-    }
-
-    @Override
-    public Context getDelegate() {
-        return delegate;
-    }
-    
-    
-    @Override
-    public void resetDelegate() throws Exception {
-        if (null != delegate) {
-            delegate.close();
-        }
-        delegate = (Context)newDelegate();
-    }
-   
-    /**
-     * @return the schedule
-     */
-    public RetrySchedule getSchedule() {
-        return schedule;
-    }
-    
-    /**
-     * @return the maxRetries
-     */
-    public int getMaxRetries() {
-        return maxRetries;
-    }
-
-}
diff --git a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/retry/naming/directory/RetryingDirContext.java b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/retry/naming/directory/RetryingDirContext.java
deleted file mode 100644
index 72c1679..0000000
--- a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/retry/naming/directory/RetryingDirContext.java
+++ /dev/null
@@ -1,407 +0,0 @@
-/*
- *   Licensed to the Apache Software Foundation (ASF) under one
- *   or more contributor license agreements.  See the NOTICE file
- *   distributed with this work for additional information
- *   regarding copyright ownership.  The ASF licenses this file
- *   to you under the Apache License, Version 2.0 (the
- *   "License"); you may not use this file except in compliance
- *   with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- *   Unless required by applicable law or agreed to in writing,
- *   software distributed under the License is distributed on an
- *   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *   KIND, either express or implied.  See the License for the
- *   specific language governing permissions and limitations
- *   under the License.
- *
- */
-
-package org.apache.james.user.ldap.retry.naming.directory;
-
-import javax.naming.Context;
-import javax.naming.Name;
-import javax.naming.NamingEnumeration;
-import javax.naming.NamingException;
-import javax.naming.directory.Attributes;
-import javax.naming.directory.DirContext;
-import javax.naming.directory.ModificationItem;
-import javax.naming.directory.SearchControls;
-import javax.naming.directory.SearchResult;
-
-import org.apache.james.user.ldap.retry.ExceptionRetryHandler;
-import org.apache.james.user.ldap.retry.api.RetrySchedule;
-import org.apache.james.user.ldap.retry.naming.LoggingRetryHandler;
-import org.apache.james.user.ldap.retry.naming.RetryingContext;
-
-/**
- * <code>RetryingDirContext</code> retries the methods defined by <code>javax.naming.directory.DirContext</code>
- * according to the specified schedule. 
- * 
- * @see ExceptionRetryHandler
- * @see org.apache.james.user.ldap.retry.api.ExceptionRetryingProxy
- * @see javax.naming.directory.DirContext
- */
-public abstract class RetryingDirContext extends RetryingContext implements DirContext {
-
-
-    /**
-     * Creates a new instance of RetryingDirContext.
-     *
-     * @param schedule
-     * @param maxRetries
-     * @throws NamingException
-     */
-    public RetryingDirContext(RetrySchedule schedule, int maxRetries)
-            throws NamingException {
-        super(schedule, maxRetries);
-    }
-
-    @Override
-    public void bind(final Name name, final Object obj, final Attributes attrs)
-            throws NamingException {
-        new LoggingRetryHandler(DEFAULT_EXCEPTION_CLASSES, this, getSchedule(), getMaxRetries()) {
-
-            @Override
-            public Object operation() throws NamingException {
-                ((DirContext) getDelegate()).bind(name, obj, attrs);
-                return null;
-            }
-        }.perform();
-    }
-
-    @Override
-    public void bind(final String name, final Object obj, final Attributes attrs)
-            throws NamingException {
-        new LoggingRetryHandler(DEFAULT_EXCEPTION_CLASSES, this, getSchedule(), getMaxRetries()) {
-
-            @Override
-            public Object operation() throws NamingException {
-                ((DirContext) getDelegate()).bind(name, obj, attrs);
-                return null;
-            }
-        }.perform();
-    }
-
-    @Override
-    public DirContext createSubcontext(final Name name, final Attributes attrs)
-            throws NamingException {
-        return (DirContext) new LoggingRetryHandler(DEFAULT_EXCEPTION_CLASSES, this,
-                getSchedule(), getMaxRetries()) {
-
-            @Override
-            public Object operation() throws NamingException {
-                return ((DirContext) getDelegate()).createSubcontext(name, attrs);
-            }
-        }.perform();
-    }
-
-    @Override
-    public DirContext createSubcontext(final String name, final Attributes attrs)
-            throws NamingException {
-        return (DirContext) new LoggingRetryHandler(DEFAULT_EXCEPTION_CLASSES, this,
-                getSchedule(), getMaxRetries()) {
-
-            @Override
-            public Object operation() throws NamingException {
-                return ((DirContext) getDelegate()).createSubcontext(name, attrs);
-            }
-        }.perform();
-    }
-
-    @Override
-    public Attributes getAttributes(final Name name) throws NamingException {
-        return (Attributes) new LoggingRetryHandler(DEFAULT_EXCEPTION_CLASSES, this,
-                getSchedule(), getMaxRetries()) {
-
-            @Override
-            public Object operation() throws NamingException {
-                return ((DirContext) getDelegate()).getAttributes(name);
-            }
-        }.perform();
-    }
-
-    @Override
-    public Attributes getAttributes(final String name) throws NamingException {
-        return (Attributes) new LoggingRetryHandler(DEFAULT_EXCEPTION_CLASSES, this,
-                getSchedule(), getMaxRetries()) {
-
-            @Override
-            public Object operation() throws NamingException {
-                return ((DirContext) getDelegate()).getAttributes(name);
-            }
-        }.perform();
-    }
-
-    @Override
-    public Attributes getAttributes(final Name name, final String[] attrIds) throws NamingException {
-        return (Attributes) new LoggingRetryHandler(DEFAULT_EXCEPTION_CLASSES, this,
-                getSchedule(), getMaxRetries()) {
-
-            @Override
-            public Object operation() throws NamingException {
-                return ((DirContext) getDelegate()).getAttributes(name, attrIds);
-            }
-        }.perform();
-    }
-
-    @Override
-    public Attributes getAttributes(final String name, final String[] attrIds)
-            throws NamingException {
-        return (Attributes) new LoggingRetryHandler(DEFAULT_EXCEPTION_CLASSES, this,
-                getSchedule(), getMaxRetries()) {
-
-            @Override
-            public Object operation() throws NamingException {
-                return ((DirContext) getDelegate()).getAttributes(name, attrIds);
-            }
-        }.perform();
-    }
-
-    @Override
-    public DirContext getSchema(final Name name) throws NamingException {
-        final Context context = getDelegate();
-        return new RetryingDirContext(getSchedule(), getMaxRetries()) {
-
-            @Override
-            public DirContext newDelegate() throws NamingException {
-                return ((DirContext) context).getSchema(name);
-            }
-        };
-    }
-
-    @Override
-    public DirContext getSchema(final String name) throws NamingException {
-        final Context context = getDelegate();
-        return new RetryingDirContext(getSchedule(), getMaxRetries()) {
-
-            @Override
-            public DirContext newDelegate() throws NamingException {
-                return ((DirContext) context).getSchema(name);
-            }
-        };
-    }
-
-    @Override
-    public DirContext getSchemaClassDefinition(final Name name) throws NamingException {
-        final Context context = getDelegate();
-        return new RetryingDirContext(getSchedule(), getMaxRetries()) {
-
-            @Override
-            public DirContext newDelegate() throws NamingException {
-                return ((DirContext) context).getSchemaClassDefinition(name);
-            }
-        };
-    }
-
-    @Override
-    public DirContext getSchemaClassDefinition(final String name) throws NamingException {
-        final Context context = getDelegate();
-        return new RetryingDirContext(getSchedule(), getMaxRetries()) {
-
-            @Override
-            public DirContext newDelegate() throws NamingException {
-                return ((DirContext) context).getSchemaClassDefinition(name);
-            }
-        };
-    }
-
-    @Override
-    public void modifyAttributes(final Name name, final ModificationItem[] mods)
-            throws NamingException {
-        new LoggingRetryHandler(DEFAULT_EXCEPTION_CLASSES, this, getSchedule(), getMaxRetries()) {
-
-            @Override
-            public Object operation() throws NamingException {
-                ((DirContext) getDelegate()).modifyAttributes(name, mods);
-                return null;
-            }
-        }.perform();
-    }
-
-    @Override
-    public void modifyAttributes(final String name, final ModificationItem[] mods)
-            throws NamingException {
-        new LoggingRetryHandler(DEFAULT_EXCEPTION_CLASSES, this, getSchedule(), getMaxRetries()) {
-
-            @Override
-            public Object operation() throws NamingException {
-                ((DirContext) getDelegate()).modifyAttributes(name, mods);
-                return null;
-            }
-        }.perform();
-    }
-
-    @Override
-    public void modifyAttributes(final Name name, final int modOp, final Attributes attrs)
-            throws NamingException {
-        new LoggingRetryHandler(DEFAULT_EXCEPTION_CLASSES, this, getSchedule(), getMaxRetries()) {
-
-            @Override
-            public Object operation() throws NamingException {
-                ((DirContext) getDelegate()).modifyAttributes(name, modOp, attrs);
-                return null;
-            }
-        }.perform();
-    }
-
-    @Override
-    public void modifyAttributes(final String name, final int modOp, final Attributes attrs)
-            throws NamingException {
-        new LoggingRetryHandler(DEFAULT_EXCEPTION_CLASSES, this, getSchedule(), getMaxRetries()) {
-
-            @Override
-            public Object operation() throws NamingException {
-                ((DirContext) getDelegate()).modifyAttributes(name, modOp, attrs);
-                return null;
-            }
-        }.perform();
-    }
-
-    @Override
-    public void rebind(final Name name, final Object obj, final Attributes attrs)
-            throws NamingException {
-        new LoggingRetryHandler(DEFAULT_EXCEPTION_CLASSES, this, getSchedule(), getMaxRetries()) {
-
-            @Override
-            public Object operation() throws NamingException {
-                ((DirContext) getDelegate()).rebind(name, obj, attrs);
-                return null;
-            }
-        }.perform();
-    }
-
-    @Override
-    public void rebind(final String name, final Object obj, final Attributes attrs)
-            throws NamingException {
-        new LoggingRetryHandler(DEFAULT_EXCEPTION_CLASSES, this, getSchedule(), getMaxRetries()) {
-
-            @Override
-            public Object operation() throws NamingException {
-                ((DirContext) getDelegate()).rebind(name, obj, attrs);
-                return null;
-            }
-        }.perform();
-    }
-
-    @SuppressWarnings("unchecked")
-    @Override
-    public NamingEnumeration<SearchResult> search(final Name name,
-            final Attributes matchingAttributes)
-            throws NamingException {
-        return (NamingEnumeration<SearchResult>) new LoggingRetryHandler(
-                DEFAULT_EXCEPTION_CLASSES, this, getSchedule(), getMaxRetries()) {
-
-            @Override
-            public Object operation() throws NamingException {
-                return ((DirContext) getDelegate()).search(name, matchingAttributes);
-            }
-        }.perform();
-    }
-
-    @SuppressWarnings("unchecked")
-    @Override
-    public NamingEnumeration<SearchResult> search(final String name,
-            final Attributes matchingAttributes)
-            throws NamingException {
-        return (NamingEnumeration<SearchResult>) new LoggingRetryHandler(
-                DEFAULT_EXCEPTION_CLASSES, this, getSchedule(), getMaxRetries()) {
-
-            @Override
-            public Object operation() throws NamingException {
-                return ((DirContext) getDelegate()).search(name, matchingAttributes);
-            }
-        }.perform();
-    }
-
-    @SuppressWarnings("unchecked")
-    @Override
-    public NamingEnumeration<SearchResult> search(final Name name,
-            final Attributes matchingAttributes,
-            String[] attributesToReturn) throws NamingException {
-        return (NamingEnumeration<SearchResult>) new LoggingRetryHandler(
-                DEFAULT_EXCEPTION_CLASSES, this, getSchedule(), getMaxRetries()) {
-
-            @Override
-            public Object operation() throws NamingException {
-                return ((DirContext) getDelegate()).search(name, matchingAttributes);
-            }
-        }.perform();
-    }
-
-    @SuppressWarnings("unchecked")
-    @Override
-    public NamingEnumeration<SearchResult> search(final String name,
-            final Attributes matchingAttributes,
-            final String[] attributesToReturn) throws NamingException {
-        return (NamingEnumeration<SearchResult>) new LoggingRetryHandler(
-                DEFAULT_EXCEPTION_CLASSES, this, getSchedule(), getMaxRetries()) {
-
-            @Override
-            public Object operation() throws NamingException {
-                return ((DirContext) getDelegate()).search(name, matchingAttributes,
-                        attributesToReturn);
-            }
-        }.perform();
-    }
-
-    @SuppressWarnings("unchecked")
-    @Override
-    public NamingEnumeration<SearchResult> search(final Name name, final String filter,
-            final SearchControls cons)
-            throws NamingException {
-        return (NamingEnumeration<SearchResult>) new LoggingRetryHandler(
-                DEFAULT_EXCEPTION_CLASSES, this, getSchedule(), getMaxRetries()) {
-
-            @Override
-            public Object operation() throws NamingException {
-                return ((DirContext) getDelegate()).search(name, filter, cons);
-            }
-        }.perform();
-    }
-
-    @SuppressWarnings("unchecked")
-    @Override
-    public NamingEnumeration<SearchResult> search(final String name, final String filter,
-            final SearchControls cons)
-            throws NamingException {
-        return (NamingEnumeration<SearchResult>) new LoggingRetryHandler(
-                DEFAULT_EXCEPTION_CLASSES, this, getSchedule(), getMaxRetries()) {
-
-            @Override
-            public Object operation() throws NamingException {
-                return ((DirContext) getDelegate()).search(name, filter, cons);
-            }
-        }.perform();
-    }
-
-    @SuppressWarnings("unchecked")
-    @Override
-    public NamingEnumeration<SearchResult> search(final Name name, final String filterExpr,
-            final Object[] filterArgs, final SearchControls cons) throws NamingException {
-        return (NamingEnumeration<SearchResult>) new LoggingRetryHandler(
-                DEFAULT_EXCEPTION_CLASSES, this, getSchedule(), getMaxRetries()) {
-
-            @Override
-            public Object operation() throws NamingException {
-                return ((DirContext) getDelegate()).search(name, filterExpr, filterArgs, cons);
-            }
-        }.perform();
-    }
-
-    @SuppressWarnings("unchecked")
-    @Override
-    public NamingEnumeration<SearchResult> search(final String name, final String filterExpr,
-            final Object[] filterArgs, final SearchControls cons) throws NamingException {
-        return (NamingEnumeration<SearchResult>) new LoggingRetryHandler(
-                DEFAULT_EXCEPTION_CLASSES, this, getSchedule(), getMaxRetries()) {
-
-            @Override
-            public Object operation() throws NamingException {
-                return ((DirContext) getDelegate()).search(name, filterExpr, filterArgs, cons);
-            }
-        }.perform();
-    }
-
-}
diff --git a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/retry/naming/ldap/RetryingLdapContext.java b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/retry/naming/ldap/RetryingLdapContext.java
deleted file mode 100644
index 0355c09..0000000
--- a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/retry/naming/ldap/RetryingLdapContext.java
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- *   Licensed to the Apache Software Foundation (ASF) under one
- *   or more contributor license agreements.  See the NOTICE file
- *   distributed with this work for additional information
- *   regarding copyright ownership.  The ASF licenses this file
- *   to you under the Apache License, Version 2.0 (the
- *   "License"); you may not use this file except in compliance
- *   with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- *   Unless required by applicable law or agreed to in writing,
- *   software distributed under the License is distributed on an
- *   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *   KIND, either express or implied.  See the License for the
- *   specific language governing permissions and limitations
- *   under the License.
- *
- */
-
-package org.apache.james.user.ldap.retry.naming.ldap;
-
-import javax.naming.Context;
-import javax.naming.NamingException;
-import javax.naming.ldap.Control;
-import javax.naming.ldap.ExtendedRequest;
-import javax.naming.ldap.ExtendedResponse;
-import javax.naming.ldap.LdapContext;
-
-import org.apache.james.user.ldap.retry.ExceptionRetryHandler;
-import org.apache.james.user.ldap.retry.api.RetrySchedule;
-import org.apache.james.user.ldap.retry.naming.LoggingRetryHandler;
-import org.apache.james.user.ldap.retry.naming.directory.RetryingDirContext;
-
-/**
- * <code>RetryingLdapContext</code> retries the methods defined by <code>javax.naming.ldap.LdapContext</code>
- * according to the specified schedule. 
- * 
- * @see ExceptionRetryHandler
- * @see org.apache.james.user.ldap.retry.api.ExceptionRetryingProxy
- * @see javax.naming.ldap.LdapContext
- */
-public abstract class RetryingLdapContext extends RetryingDirContext implements LdapContext {
-   
-    /**
-     * Creates a new instance of RetryingLdapContext.
-     *
-     * @param maxRetries
-     * @throws NamingException
-     */
-    public RetryingLdapContext(RetrySchedule schedule, int maxRetries) throws NamingException {
-        super(schedule, maxRetries);
-    }
-
-    @Override
-    public ExtendedResponse extendedOperation(final ExtendedRequest request) throws NamingException {
-        return (ExtendedResponse) new LoggingRetryHandler(DEFAULT_EXCEPTION_CLASSES, this, getSchedule(), getMaxRetries()) {
-                @Override
-                public Object operation() throws NamingException {
-                    return ((LdapContext) getDelegate()).extendedOperation(request);
-                }
-            }.perform();
-    }
-
-    @Override
-    public Control[] getConnectControls() throws NamingException {
-        return (Control[]) new LoggingRetryHandler(DEFAULT_EXCEPTION_CLASSES, this, getSchedule(), getMaxRetries()) {
-                @Override
-                public Object operation() throws NamingException {
-                    return ((LdapContext) getDelegate()).getConnectControls();
-                }
-            }.perform();
-    }
-
-    @Override
-    public Control[] getRequestControls() throws NamingException {
-        return (Control[]) new LoggingRetryHandler(DEFAULT_EXCEPTION_CLASSES, this, getSchedule(), getMaxRetries()) {
-                @Override
-                public Object operation() throws NamingException {
-                    return ((LdapContext) getDelegate()).getRequestControls();
-                }
-            }.perform();
-    }
-
-    @Override
-    public Control[] getResponseControls() throws NamingException {
-        return (Control[]) new LoggingRetryHandler(DEFAULT_EXCEPTION_CLASSES, this, getSchedule(), getMaxRetries()) {
-                @Override
-                public Object operation() throws NamingException {
-                    return ((LdapContext) getDelegate()).getResponseControls();
-                }
-            }.perform();
-    }
-
-    @Override
-    public LdapContext newInstance(final Control[] requestControls) throws NamingException {
-        final Context context = getDelegate();
-        return new RetryingLdapContext(getSchedule(), getMaxRetries()) {
-
-            @Override
-            public Context newDelegate() throws NamingException {
-                return ((LdapContext) context).newInstance(requestControls);
-            }
-        };
-    }
-
-    @Override
-    public void reconnect(final Control[] connCtls) throws NamingException {
-        new LoggingRetryHandler(DEFAULT_EXCEPTION_CLASSES, this, getSchedule(), getMaxRetries()) {
-                @Override
-                public Object operation() throws NamingException {
-                    ((LdapContext) getDelegate()).reconnect(connCtls);
-                    return null;
-                }
-            }.perform();
-    }
-
-    @Override
-    public void setRequestControls(final Control[] requestControls) throws NamingException {
-        new LoggingRetryHandler(DEFAULT_EXCEPTION_CLASSES, this, getSchedule(), getMaxRetries()) {
-                @Override
-                public Object operation() throws NamingException {
-                    ((LdapContext) getDelegate()).setRequestControls(requestControls);
-                    return null;
-                }
-            }.perform();
-    }
-
-}
diff --git a/server/data/data-ldap/src/test/java/org/apache/james/user/ldap/retry/DoublingRetryScheduleTest.java b/server/data/data-ldap/src/test/java/org/apache/james/user/ldap/retry/DoublingRetryScheduleTest.java
deleted file mode 100644
index 7ec732c..0000000
--- a/server/data/data-ldap/src/test/java/org/apache/james/user/ldap/retry/DoublingRetryScheduleTest.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- *   Licensed to the Apache Software Foundation (ASF) under one
- *   or more contributor license agreements.  See the NOTICE file
- *   distributed with this work for additional information
- *   regarding copyright ownership.  The ASF licenses this file
- *   to you under the Apache License, Version 2.0 (the
- *   "License"); you may not use this file except in compliance
- *   with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- *   Unless required by applicable law or agreed to in writing,
- *   software distributed under the License is distributed on an
- *   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *   KIND, either express or implied.  See the License for the
- *   specific language governing permissions and limitations
- *   under the License.
- *
- */
-package org.apache.james.user.ldap.retry;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-import org.apache.james.user.ldap.retry.api.RetrySchedule;
-import org.junit.jupiter.api.Test;
-
-class DoublingRetryScheduleTest {
-
-    @Test
-    void testDoublingRetrySchedule() {
-        assertThat(RetrySchedule.class.isAssignableFrom(DoublingRetrySchedule.class)).isTrue();
-        assertThat(new DoublingRetrySchedule(0, 0).getInterval(0)).isEqualTo(0);
-        assertThat(new DoublingRetrySchedule(-1, -1).getInterval(0)).isEqualTo(0);
-        assertThat(new DoublingRetrySchedule(-1, 0).getInterval(0)).isEqualTo(0);
-        assertThat(new DoublingRetrySchedule(0, -1).getInterval(0)).isEqualTo(0);
-    }
-
-    @Test
-    void testGetInterval() {
-        assertThat(new DoublingRetrySchedule(0, 8).getInterval(0)).isEqualTo(0);
-        assertThat(new DoublingRetrySchedule(0, 8).getInterval(1)).isEqualTo(1);
-        assertThat(new DoublingRetrySchedule(0, 8).getInterval(2)).isEqualTo(2);
-        assertThat(new DoublingRetrySchedule(0, 8).getInterval(3)).isEqualTo(4);
-        assertThat(new DoublingRetrySchedule(0, 8).getInterval(4)).isEqualTo(8);
-        assertThat(new DoublingRetrySchedule(0, 8).getInterval(5)).isEqualTo(8);
-
-        assertThat(new DoublingRetrySchedule(1, 8).getInterval(0)).isEqualTo(1);
-        assertThat(new DoublingRetrySchedule(1, 8).getInterval(1)).isEqualTo(2);
-        assertThat(new DoublingRetrySchedule(1, 8).getInterval(2)).isEqualTo(4);
-        assertThat(new DoublingRetrySchedule(1, 8).getInterval(3)).isEqualTo(8);
-        assertThat(new DoublingRetrySchedule(1, 8).getInterval(4)).isEqualTo(8);
-
-        assertThat(new DoublingRetrySchedule(3, 12).getInterval(0)).isEqualTo(3);
-        assertThat(new DoublingRetrySchedule(3, 12).getInterval(1)).isEqualTo(6);
-        assertThat(new DoublingRetrySchedule(3, 12).getInterval(2)).isEqualTo(12);
-        assertThat(new DoublingRetrySchedule(3, 12).getInterval(3)).isEqualTo(12);
-
-        assertThat(new DoublingRetrySchedule(0, 8, 1000).getInterval(0)).isEqualTo(0);
-        assertThat(new DoublingRetrySchedule(0, 8, 1000).getInterval(1)).isEqualTo(1000);
-        assertThat(new DoublingRetrySchedule(0, 8, 1000).getInterval(2)).isEqualTo(2000);
-        assertThat(new DoublingRetrySchedule(0, 8, 1000).getInterval(3)).isEqualTo(4000);
-        assertThat(new DoublingRetrySchedule(0, 8, 1000).getInterval(4)).isEqualTo(8000);
-        assertThat(new DoublingRetrySchedule(0, 8, 1000).getInterval(5)).isEqualTo(8000);
-    }
-
-    @Test
-    void testToString() {
-        assertThat(new DoublingRetrySchedule(0, 1).toString())
-            .isEqualTo("DoublingRetrySchedule [startInterval=0, maxInterval=1, multiplier=1]");
-    }
-}
diff --git a/server/data/data-ldap/src/test/java/org/apache/james/user/ldap/retry/ExceptionRetryHandlerTest.java b/server/data/data-ldap/src/test/java/org/apache/james/user/ldap/retry/ExceptionRetryHandlerTest.java
deleted file mode 100644
index f3d921d..0000000
--- a/server/data/data-ldap/src/test/java/org/apache/james/user/ldap/retry/ExceptionRetryHandlerTest.java
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- *   Licensed to the Apache Software Foundation (ASF) under one
- *   or more contributor license agreements.  See the NOTICE file
- *   distributed with this work for additional information
- *   regarding copyright ownership.  The ASF licenses this file
- *   to you under the Apache License, Version 2.0 (the
- *   "License"); you may not use this file except in compliance
- *   with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- *   Unless required by applicable law or agreed to in writing,
- *   software distributed under the License is distributed on an
- *   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *   KIND, either express or implied.  See the License for the
- *   specific language governing permissions and limitations
- *   under the License.
- *
- */
-package org.apache.james.user.ldap.retry;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.naming.Context;
-
-import org.apache.james.user.ldap.retry.api.ExceptionRetryingProxy;
-import org.apache.james.user.ldap.retry.api.RetryHandler;
-import org.apache.james.user.ldap.retry.api.RetrySchedule;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-
-class ExceptionRetryHandlerTest {
-    private static class TestRetryingProxy implements ExceptionRetryingProxy {
-
-        @Override
-        public Context getDelegate() {
-            return null;
-        }
-
-        @Override
-        public Context newDelegate() {
-            return null;
-        }
-
-        @Override
-        public void resetDelegate() {
-        }
-    }
-
-    private Class<?>[] exceptionClasses = null;
-    private ExceptionRetryingProxy proxy = null;
-    private RetrySchedule schedule = null;
-
-    @BeforeEach
-    void setUp() {
-        exceptionClasses = new Class<?>[]{Exception.class};
-        proxy = new TestRetryingProxy();
-        schedule = i -> i;
-    }
-
-    @Test
-    void testExceptionRetryHandler() {
-        assertThat(RetryHandler.class.isAssignableFrom(new ExceptionRetryHandler(
-            exceptionClasses, proxy, schedule, 0) {
-
-            @Override
-            public Object operation() {
-                return null;
-            }
-        }.getClass())).isTrue();
-    }
-
-    @Test
-    void testPerform() throws Exception {
-        Object result = new ExceptionRetryHandler(
-            exceptionClasses, proxy, schedule, 0) {
-
-            @Override
-            public Object operation() {
-                return "Hi!";
-            }
-        }.perform();
-        assertThat(result).isEqualTo("Hi!");
-
-        try {
-            new ExceptionRetryHandler(
-                exceptionClasses, proxy, schedule, 0) {
-
-                @Override
-                public Object operation() throws Exception {
-                    throw new Exception();
-                }
-            }.perform();
-        } catch (Exception ex) {
-            // no-op
-        }
-        assertThat(result).isEqualTo("Hi!");
-    }
-
-    @Test
-    void testPostFailure() {
-        final List<Exception> results = new ArrayList<>();
-        RetryHandler handler = new ExceptionRetryHandler(
-            exceptionClasses, proxy, schedule, 7) {
-
-            @Override
-            public void postFailure(Exception ex, int retryCount) {
-                super.postFailure(ex, retryCount);
-                results.add(ex);
-            }
-
-            @Override
-            public Object operation() throws Exception {
-                throw new Exception();
-            }
-        };
-        try {
-            handler.perform();
-        } catch (Exception ex) {
-            // no-op
-        }
-        assertThat(results.size()).isEqualTo(7);
-    }
-
-    @Test
-    void testOperation() throws Exception {
-        RetryHandler handler = new ExceptionRetryHandler(
-            exceptionClasses, proxy, schedule, 0) {
-
-            @Override
-            public Object operation() {
-                return "Hi!";
-            }
-        };
-        assertThat(handler.operation()).isEqualTo("Hi!");
-    }
-
-    @Test
-    void testGetRetryInterval() {
-        ExceptionRetryHandler handler = new ExceptionRetryHandler(
-            exceptionClasses, proxy, schedule, 0) {
-
-            @Override
-            public Object operation() {
-                return null;
-            }
-        };
-        assertThat(handler.getRetryInterval(8)).isEqualTo(8);
-    }
-}
diff --git a/server/data/data-ldap/src/test/java/org/apache/james/user/ldap/retry/naming/NamingExceptionRetryHandlerTest.java b/server/data/data-ldap/src/test/java/org/apache/james/user/ldap/retry/naming/NamingExceptionRetryHandlerTest.java
deleted file mode 100644
index 59c8fb8..0000000
--- a/server/data/data-ldap/src/test/java/org/apache/james/user/ldap/retry/naming/NamingExceptionRetryHandlerTest.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- *   Licensed to the Apache Software Foundation (ASF) under one
- *   or more contributor license agreements.  See the NOTICE file
- *   distributed with this work for additional information
- *   regarding copyright ownership.  The ASF licenses this file
- *   to you under the Apache License, Version 2.0 (the
- *   "License"); you may not use this file except in compliance
- *   with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- *   Unless required by applicable law or agreed to in writing,
- *   software distributed under the License is distributed on an
- *   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *   KIND, either express or implied.  See the License for the
- *   specific language governing permissions and limitations
- *   under the License.
- *
- */
-package org.apache.james.user.ldap.retry.naming;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-import javax.naming.Context;
-import javax.naming.NamingException;
-
-import org.apache.james.user.ldap.retry.api.ExceptionRetryingProxy;
-import org.apache.james.user.ldap.retry.api.RetryHandler;
-import org.apache.james.user.ldap.retry.api.RetrySchedule;
-import org.junit.jupiter.api.Test;
-
-class NamingExceptionRetryHandlerTest {
-
-    private static class TestRetryingProxy implements ExceptionRetryingProxy {
-        @Override
-        public Context getDelegate() {
-            return null;
-        }
-
-        @Override
-        public Context newDelegate() {
-            return null;
-        }
-
-        @Override
-        public void resetDelegate() {
-        }
-    }
-
-    private static final Class<?>[] exceptionClasses = new Class<?>[]{NamingException.class};
-    private static final ExceptionRetryingProxy proxy = new TestRetryingProxy();
-    private static final RetrySchedule schedule = i -> i;
-
-    @Test
-    void testExceptionRetryHandler() {
-        assertThat(RetryHandler.class.isAssignableFrom(new NamingExceptionRetryHandler(
-            exceptionClasses, proxy, schedule, 0) {
-
-            @Override
-            public Object operation() {
-                return null;
-            }
-        }.getClass())).isTrue();
-    }
-
-    @Test
-    void testPerform() throws NamingException {
-        Object result = new NamingExceptionRetryHandler(
-            exceptionClasses, proxy, schedule, 0) {
-
-            @Override
-            public Object operation() {
-                return "Hi!";
-            }
-        }.perform();
-        assertThat(result).isEqualTo("Hi!");
-
-        try {
-            new NamingExceptionRetryHandler(
-                exceptionClasses, proxy, schedule, 0) {
-
-                @Override
-                public Object operation() throws Exception {
-                    throw new NamingException();
-                }
-            }.perform();
-        } catch (NamingException ex) {
-            // no-op
-        }
-        assertThat(result).isEqualTo("Hi!");
-    }
-}

---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


[james-project] 14/18: JAMES-3594 Strong typing for DN

Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 6b91bb487e23045a8a9a02221ae9165fa7348f3d
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Thu Jun 10 11:00:49 2021 +0700

    JAMES-3594 Strong typing for DN
---
 .../user/ldap/ReadOnlyLDAPGroupRestriction.java    | 10 +++--
 .../apache/james/user/ldap/ReadOnlyLDAPUser.java   |  7 ++--
 .../james/user/ldap/ReadOnlyLDAPUsersDAO.java      | 45 +++++++++++-----------
 3 files changed, 33 insertions(+), 29 deletions(-)

diff --git a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPGroupRestriction.java b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPGroupRestriction.java
index d9023b7..25af9ed 100644
--- a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPGroupRestriction.java
+++ b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPGroupRestriction.java
@@ -29,8 +29,9 @@ import java.util.Map;
 import org.apache.commons.configuration2.HierarchicalConfiguration;
 import org.apache.commons.configuration2.tree.ImmutableNode;
 
+import com.github.fge.lambdas.Throwing;
 import com.github.steveash.guavate.Guavate;
-import com.unboundid.ldap.sdk.LDAPConnection;
+import com.unboundid.ldap.sdk.DN;
 import com.unboundid.ldap.sdk.LDAPConnectionPool;
 import com.unboundid.ldap.sdk.LDAPException;
 import com.unboundid.ldap.sdk.SearchResultEntry;
@@ -114,8 +115,8 @@ public class ReadOnlyLDAPGroupRestriction {
      *
      * @return Returns a map of groupDNs to userDN lists.
      */
-    protected Map<String, Collection<String>> getGroupMembershipLists(LDAPConnectionPool connection) throws LDAPException {
-        Map<String, Collection<String>> result = new HashMap<>();
+    protected Map<String, Collection<DN>> getGroupMembershipLists(LDAPConnectionPool connection) throws LDAPException {
+        Map<String, Collection<DN>> result = new HashMap<>();
 
         for (String groupDN : groupDNs) {
             result.put(groupDN, extractMembers(connection.getEntry(groupDN)));
@@ -133,9 +134,10 @@ public class ReadOnlyLDAPGroupRestriction {
      * @return A collection of distinguished-names for the users belonging to
      *         the group with the specified attributes.
      */
-    private Collection<String> extractMembers(SearchResultEntry entry) {
+    private Collection<DN> extractMembers(SearchResultEntry entry) {
         com.unboundid.ldap.sdk.Attribute members = entry.getAttribute(memberAttribute);
         return Arrays.stream(members.getValues())
+            .map(Throwing.function(DN::new))
             .collect(Guavate.toImmutableList());
     }
 }
diff --git a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUser.java b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUser.java
index 5baaf02..1bc7147 100644
--- a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUser.java
+++ b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUser.java
@@ -27,6 +27,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.unboundid.ldap.sdk.BindResult;
+import com.unboundid.ldap.sdk.DN;
 import com.unboundid.ldap.sdk.LDAPConnectionPool;
 import com.unboundid.ldap.sdk.LDAPException;
 import com.unboundid.ldap.sdk.ResultCode;
@@ -62,7 +63,7 @@ public class ReadOnlyLDAPUser implements User, Serializable {
     /**
      * The distinguished name of the user-record in the LDAP directory.
      */
-    private final String userDN;
+    private final DN userDN;
 
     /**
      * The context for the LDAP server from which to retrieve the
@@ -88,7 +89,7 @@ public class ReadOnlyLDAPUser implements User, Serializable {
      *            invoked.
      * @param configuration
      */
-    public ReadOnlyLDAPUser(Username userName, String userDN, LDAPConnectionPool connectionPool, LdapRepositoryConfiguration configuration) {
+    public ReadOnlyLDAPUser(Username userName, DN userDN, LDAPConnectionPool connectionPool, LdapRepositoryConfiguration configuration) {
         this.userName = userName;
         this.userDN = userDN;
         this.connectionPool = connectionPool;
@@ -144,7 +145,7 @@ public class ReadOnlyLDAPUser implements User, Serializable {
     }
 
     private boolean doVerifyPassword(String password) throws LDAPException {
-        BindResult bindResult = connectionPool.bindAndRevertAuthentication(userDN, password);
+        BindResult bindResult = connectionPool.bindAndRevertAuthentication(userDN.toString(), password);
         return bindResult.getResultCode() == ResultCode.SUCCESS;
     }
 }
diff --git a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUsersDAO.java b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUsersDAO.java
index 7a2301b..3839126 100644
--- a/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUsersDAO.java
+++ b/server/data/data-ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUsersDAO.java
@@ -47,6 +47,7 @@ import org.slf4j.LoggerFactory;
 import com.github.fge.lambdas.Throwing;
 import com.github.steveash.guavate.Guavate;
 import com.unboundid.ldap.sdk.Attribute;
+import com.unboundid.ldap.sdk.DN;
 import com.unboundid.ldap.sdk.Entry;
 import com.unboundid.ldap.sdk.Filter;
 import com.unboundid.ldap.sdk.LDAPConnection;
@@ -156,22 +157,22 @@ public class ReadOnlyLDAPUsersDAO implements UsersDAO, Configurable {
      *         least one group in the parameter map, and <code>False</code>
      *         otherwise.
      */
-    private boolean userInGroupsMembershipList(String userDN,
-            Map<String, Collection<String>> groupMembershipList) {
+    private boolean userInGroupsMembershipList(DN userDN,
+            Map<String, Collection<DN>> groupMembershipList) {
         boolean result = false;
 
-        Collection<Collection<String>> memberLists = groupMembershipList.values();
-        Iterator<Collection<String>> memberListsIterator = memberLists.iterator();
+        Collection<Collection<DN>> memberLists = groupMembershipList.values();
+        Iterator<Collection<DN>> memberListsIterator = memberLists.iterator();
 
         while (memberListsIterator.hasNext() && !result) {
-            Collection<String> groupMembers = memberListsIterator.next();
+            Collection<DN> groupMembers = memberListsIterator.next();
             result = groupMembers.contains(userDN);
         }
 
         return result;
     }
 
-    private Set<String> getAllUsersDNFromLDAP() throws LDAPException {
+    private Set<DN> getAllUsersDNFromLDAP() throws LDAPException {
         SearchRequest searchRequest = new SearchRequest(ldapConfiguration.getUserBase(),
             SearchScope.SUB,
             createFilter(),
@@ -181,7 +182,7 @@ public class ReadOnlyLDAPUsersDAO implements UsersDAO, Configurable {
 
         return searchResult.getSearchEntries()
             .stream()
-            .map(Entry::getDN)
+            .map(Throwing.function(Entry::getParsedDN))
             .collect(Guavate.toImmutableSet());
     }
 
@@ -204,7 +205,7 @@ public class ReadOnlyLDAPUsersDAO implements UsersDAO, Configurable {
      * Extract the user attributes for the given collection of userDNs, and
      * encapsulates the user list as a collection of {@link ReadOnlyLDAPUser}s.
      * This method delegates the extraction of a single user's details to the
-     * method {@link #buildUser(String)}.
+     * method {@link #buildUser(DN)}.
      *
      * @param userDNs
      *            The distinguished-names (DNs) of the users whose information
@@ -214,10 +215,10 @@ public class ReadOnlyLDAPUsersDAO implements UsersDAO, Configurable {
      * @throws LDAPException
      *             Propagated from the underlying LDAP communication layer.
      */
-    private Collection<ReadOnlyLDAPUser> buildUserCollection(Collection<String> userDNs) throws LDAPException {
+    private Collection<ReadOnlyLDAPUser> buildUserCollection(Collection<DN> userDNs) throws LDAPException {
         List<ReadOnlyLDAPUser> results = new ArrayList<>();
 
-        for (String userDN : userDNs) {
+        for (DN userDN : userDNs) {
             Optional<ReadOnlyLDAPUser> user = buildUser(userDN);
             user.ifPresent(results::add);
         }
@@ -240,15 +241,15 @@ public class ReadOnlyLDAPUsersDAO implements UsersDAO, Configurable {
         }
 
         if (!ldapConfiguration.getRestriction().isActivated()
-            || userInGroupsMembershipList(result.getDN(), ldapConfiguration.getRestriction().getGroupMembershipLists(ldapConnectionPool))) {
+            || userInGroupsMembershipList(result.getParsedDN(), ldapConfiguration.getRestriction().getGroupMembershipLists(ldapConnectionPool))) {
 
-            return new ReadOnlyLDAPUser(name, result.getDN(), ldapConnectionPool, ldapConfiguration);
+            return new ReadOnlyLDAPUser(name, result.getParsedDN(), ldapConnectionPool, ldapConfiguration);
         }
         return null;
     }
 
-    private Optional<ReadOnlyLDAPUser> buildUser(String userDN) throws LDAPException {
-        SearchResultEntry userAttributes = ldapConnectionPool.getEntry(userDN);
+    private Optional<ReadOnlyLDAPUser> buildUser(DN userDN) throws LDAPException {
+        SearchResultEntry userAttributes = ldapConnectionPool.getEntry(userDN.toString());
         Optional<String> userName = Optional.ofNullable(userAttributes.getAttributeValue(ldapConfiguration.getUserIdAttribute()));
         return userName
             .map(Username::of)
@@ -292,7 +293,7 @@ public class ReadOnlyLDAPUsersDAO implements UsersDAO, Configurable {
             return getAllUsernamesFromLDAP().count();
         }
 
-        return getValidUsers().stream()
+        return getValidUserDNs().stream()
             .map(Throwing.function(this::buildUser).sneakyThrow())
             .flatMap(Optional::stream)
             .count();
@@ -335,22 +336,22 @@ public class ReadOnlyLDAPUsersDAO implements UsersDAO, Configurable {
             return getAllUsernamesFromLDAP().iterator();
         }
 
-        return buildUserCollection(getValidUsers())
+        return buildUserCollection(getValidUserDNs())
             .stream()
             .map(ReadOnlyLDAPUser::getUserName)
             .iterator();
     }
 
-    private Collection<String> getValidUsers() throws LDAPException {
-        Set<String> userDNs = getAllUsersDNFromLDAP();
-        Collection<String> validUserDNs;
+    private Collection<DN> getValidUserDNs() throws LDAPException {
+        Set<DN> userDNs = getAllUsersDNFromLDAP();
+        Collection<DN> validUserDNs;
         if (ldapConfiguration.getRestriction().isActivated()) {
-            Map<String, Collection<String>> groupMembershipList = ldapConfiguration.getRestriction()
+            Map<String, Collection<DN>> groupMembershipList = ldapConfiguration.getRestriction()
                 .getGroupMembershipLists(ldapConnectionPool);
             validUserDNs = new ArrayList<>();
 
-            Iterator<String> userDNIterator = userDNs.iterator();
-            String userDN;
+            Iterator<DN> userDNIterator = userDNs.iterator();
+            DN userDN;
             while (userDNIterator.hasNext()) {
                 userDN = userDNIterator.next();
                 if (userInGroupsMembershipList(userDN, groupMembershipList)) {

---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org