You are viewing a plain text version of this content. The canonical link for it is here.
Posted to server-dev@james.apache.org by sb...@apache.org on 2011/12/12 13:04:11 UTC

svn commit: r1213200 - in /james/server/trunk/ldap: ./ src/main/java/org/apache/james/user/ldap/ src/main/java/org/apache/james/user/ldap/api/ src/test/ src/test/java/

Author: sbrewin
Date: Mon Dec 12 12:04:10 2011
New Revision: 1213200

URL: http://svn.apache.org/viewvc?rev=1213200&view=rev
Log:
JAMES-1352 Implemented a retry mechanism for recoverable JNDI LDAP operations

Added:
    james/server/trunk/ldap/narative.txt   (with props)
    james/server/trunk/ldap/src/main/java/org/apache/james/user/ldap/api/
    james/server/trunk/ldap/src/main/java/org/apache/james/user/ldap/api/LdapConstants.java   (with props)
    james/server/trunk/ldap/src/test/
    james/server/trunk/ldap/src/test/java/
Removed:
    james/server/trunk/ldap/src/main/java/org/apache/james/user/ldap/SimpleLDAPConnection.java
Modified:
    james/server/trunk/ldap/pom.xml
    james/server/trunk/ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPGroupRestriction.java
    james/server/trunk/ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUser.java
    james/server/trunk/ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyUsersLDAPRepository.java

Added: james/server/trunk/ldap/narative.txt
URL: http://svn.apache.org/viewvc/james/server/trunk/ldap/narative.txt?rev=1213200&view=auto
==============================================================================
--- james/server/trunk/ldap/narative.txt (added)
+++ james/server/trunk/ldap/narative.txt Mon Dec 12 12:04:10 2011
@@ -0,0 +1,3 @@
+Interestingly, while researching what other applications do I have found scant support for a fully fledeged LDAP retry mechanism. This isn't to say that such a thing is bad, but it does demonstrate that most organisations get by without it.
+In robust deployment architectures, should an LDAP server instance fail there will be others to take up the slack. This is probably why the Tomcat JNDIRealm, which uses LDAP in a similar way to James, retries just once. It will get a connection to an alternative and be happily on its way. Pragmatically, should this second attempt fail most likey many other services have also failed. Operationally, the only way forward is to identify the critical service that has failed, fix and restart it and then restart all of the services that depend on it.
+This does not detract from the need for a retry mechanism for LDAP via JNDI...
\ No newline at end of file

Propchange: james/server/trunk/ldap/narative.txt
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: james/server/trunk/ldap/narative.txt
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: james/server/trunk/ldap/pom.xml
URL: http://svn.apache.org/viewvc/james/server/trunk/ldap/pom.xml?rev=1213200&r1=1213199&r2=1213200&view=diff
==============================================================================
--- james/server/trunk/ldap/pom.xml (original)
+++ james/server/trunk/ldap/pom.xml Mon Dec 12 12:04:10 2011
@@ -55,6 +55,10 @@
             <artifactId>james-server-data-api</artifactId>
         </dependency>
         <dependency>
+            <groupId>org.apache.james</groupId>
+            <artifactId>james-server-util</artifactId>
+        </dependency>
+        <dependency>
             <groupId>commons-configuration</groupId>
             <artifactId>commons-configuration</artifactId>
         </dependency>
@@ -66,6 +70,11 @@
             <groupId>org.apache.geronimo.specs</groupId>
             <artifactId>geronimo-annotation_1.1_spec</artifactId>
         </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <build>

Modified: james/server/trunk/ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPGroupRestriction.java
URL: http://svn.apache.org/viewvc/james/server/trunk/ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPGroupRestriction.java?rev=1213200&r1=1213199&r2=1213200&view=diff
==============================================================================
--- james/server/trunk/ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPGroupRestriction.java (original)
+++ james/server/trunk/ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPGroupRestriction.java Mon Dec 12 12:04:10 2011
@@ -29,6 +29,7 @@ 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.configuration.HierarchicalConfiguration;
 
@@ -122,7 +123,7 @@ public class ReadOnlyLDAPGroupRestrictio
      * @throws NamingException
      *             Propagated from underlying LDAP communication layer.
      */
-    protected Map<String, Collection<String>> getGroupMembershipLists(SimpleLDAPConnection connection) throws NamingException {
+    protected Map<String, Collection<String>> getGroupMembershipLists(LdapContext ldapContext) throws NamingException {
         Map<String, Collection<String>> result = new HashMap<String, Collection<String>>();
 
         Iterator<String> groupDNsIterator = groupDNs.iterator();
@@ -130,7 +131,7 @@ public class ReadOnlyLDAPGroupRestrictio
         Attributes groupAttributes;
         while (groupDNsIterator.hasNext()) {
             String groupDN = (String) groupDNsIterator.next();
-            groupAttributes = connection.getLdapContext().getAttributes(groupDN);
+            groupAttributes = ldapContext.getAttributes(groupDN);
             result.put(groupDN, extractMembers(groupAttributes));
         }
 

Modified: james/server/trunk/ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUser.java
URL: http://svn.apache.org/viewvc/james/server/trunk/ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUser.java?rev=1213200&r1=1213199&r2=1213200&view=diff
==============================================================================
--- james/server/trunk/ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUser.java (original)
+++ james/server/trunk/ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyLDAPUser.java Mon Dec 12 12:04:10 2011
@@ -21,9 +21,12 @@ 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.user.api.model.User;
+import org.apache.james.user.ldap.api.LdapConstants;
 
 /**
  * Encapsulates the details of a user as taken from an LDAP compliant directory.
@@ -40,7 +43,8 @@ import org.apache.james.user.api.model.U
  * 
  */
 public class ReadOnlyLDAPUser implements User, Serializable {
-    private static final long serialVersionUID = -6712066073820393235L;
+    // private static final long serialVersionUID = -6712066073820393235L; 
+    private static final long serialVersionUID = -5201235065842464013L;
 
     /**
      * The user's identifier or name. This is the value that is returned by the
@@ -50,18 +54,26 @@ public class ReadOnlyLDAPUser implements
      * <code>&quot;myorg.com&quot;</code>, the user's email address will be
      * <code>&quot;john.bold&#64;myorg.com&quot;</code>.
      */
-    private String userName;
+    private String _userName;
 
     /**
      * The distinguished name of the user-record in the LDAP directory.
      */
-    private String userDN;
+    private String _userDN;
 
     /**
-     * The URL for connecting to the LDAP server from which to retrieve the
+     * The context for the LDAP server from which to retrieve the
      * user's details.
      */
-    private String ldapURL;
+    private LdapContext _ldapContext = null;
+
+    /**
+     * Creates a new instance of ReadOnlyLDAPUser.
+     *
+     */
+    private ReadOnlyLDAPUser() {
+        super();
+    }
 
     /**
      * Constructs an instance for the given user-details, and which will
@@ -74,16 +86,18 @@ public class ReadOnlyLDAPUser implements
      * @param userDN
      *            The distinguished (unique-key) of the user details as stored
      *            on the LDAP directory.
-     * @param ldapURL
-     *            The URL of the LDAP server on which the user details are held.
+     * @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(String userName, String userDN, String ldapURL) {
-        this.userName = userName;
-        this.userDN = userDN;
-        this.ldapURL = ldapURL;
+    public ReadOnlyLDAPUser(String userName, String userDN, LdapContext ldapContext) throws NamingException {
+        this();
+        _userName = userName;
+        _userDN = userDN;
+        _ldapContext = ldapContext;
     }
 
     /**
@@ -94,7 +108,7 @@ public class ReadOnlyLDAPUser implements
      * @return The user's identifier or name.
      */
     public String getUserName() {
-        return userName;
+        return _userName;
     }
 
     /**
@@ -111,27 +125,37 @@ public class ReadOnlyLDAPUser implements
 
     /**
      * Verifies that the password supplied is actually the user's password, by
-     * attempting to bind to the LDAP server using the user's username and the
-     * supplied password.
+     * attempting to rebind to a copy of the LDAP server context using the user's 
+     * username and the supplied password.
      * 
      * @param password
      *            The password to validate.
      * @return <code>True</code> if a connection can successfully be established
      *         to the LDAP host using the user's id and the supplied password,
-     *         and <code>False</code> otherwise. <b>Please note</b> that if the
-     *         LDAP server has suffered a crash or failure in between the
-     *         initialisation of the user repository and the invocation of this
-     *         method, the result will still be <code>false</code>.
+     *         and <code>False</code> otherwise.
      */
     public boolean verifyPassword(String password) {
-        boolean result;
+        boolean result = false;
+        LdapContext ldapContext = null;
         try {
-            SimpleLDAPConnection.openLDAPConnection(userDN, password, ldapURL);
+            ldapContext = _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) {
-            result = false;
+            // no-op
+        } finally {
+            if (null != ldapContext) {
+                try {
+                    ldapContext.close();
+                } catch (NamingException ex) {
+                    // no-op
+                }
+            }
         }
         return result;
     }
-
 }

Modified: james/server/trunk/ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyUsersLDAPRepository.java
URL: http://svn.apache.org/viewvc/james/server/trunk/ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyUsersLDAPRepository.java?rev=1213200&r1=1213199&r2=1213200&view=diff
==============================================================================
--- james/server/trunk/ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyUsersLDAPRepository.java (original)
+++ james/server/trunk/ldap/src/main/java/org/apache/james/user/ldap/ReadOnlyUsersLDAPRepository.java Mon Dec 12 12:04:10 2011
@@ -25,15 +25,19 @@ import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Properties;
 import java.util.Set;
 
 import javax.annotation.PostConstruct;
+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 org.apache.commons.configuration.ConfigurationException;
 import org.apache.commons.configuration.HierarchicalConfiguration;
@@ -42,6 +46,11 @@ import org.apache.james.lifecycle.api.Lo
 import org.apache.james.user.api.UsersRepository;
 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.util.retry.DoublingRetrySchedule;
+import org.apache.james.util.retry.api.RetrySchedule;
+import org.apache.james.util.retry.naming.ldap.RetryingLdapContext;
+
 import org.slf4j.Logger;
 
 /**
@@ -75,8 +84,12 @@ import org.slf4j.Logger;
  *      principal=&quot;uid=ldapUser,ou=system&quot;
  *      credentials=&quot;password&quot;
  *      userBase=&quot;ou=People,o=myorg.com,ou=system&quot;
- *      userIdAttribute=&quot;uid&quot;/&gt;
- *      userObjectClass=&quot;inetOrgPerson&quot;/&gt;
+ *      userIdAttribute=&quot;uid&quot;
+ *      userObjectClass=&quot;inetOrgPerson&quot;
+ *      maxRetries=&quot;20&quot;
+ *      retryStartInterval=&quot;0&quot;
+ *      retryMaxInterval=&quot;30&quot;
+ *      retryIntervalScale=&quot;1000&quot;
  *  &lt;/users-store&gt;
  * </pre>
  * 
@@ -86,11 +99,11 @@ import org.slf4j.Logger;
  * <ul>
  * <li><b>ldapHost:</b> The URL of the LDAP server to connect to.</li>
  * <li>
- * <b>principal:</b> (optional) The name (DN) of the user with which to initially bind to
- * the LDAP server.</li>
+ * <b>principal:</b> (optional) The name (DN) of the user with which to
+ * initially bind to the LDAP server.</li>
  * <li>
- * <b>credentials:</b> (optional) The password with which to initially bind to the LDAP
- * server.</li>
+ * <b>credentials:</b> (optional) The password with which to initially bind to
+ * the LDAP server.</li>
  * <li>
  * <b>userBase:</b>The context within which to search for user entities.</li>
  * <li>
@@ -101,6 +114,59 @@ import org.slf4j.Logger;
  * <b>userObjectClass:</b>The objectClass value for user nodes below the
  * userBase. For example &quot;inetOrgPerson&quot; for Apache DS, or
  * &quot;user&quot; for Microsoft Active Directory.</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. Retry forever:
+ * <ul>
+ * <li>maxRetries = -1
+ * <li>retryStartInterval = 5000
+ * <li>retryMaxInterval = 5000
+ * <li>retryIntervalScale = 1
+ * </ul>
  * </ul>
  * </p>
  * 
@@ -117,7 +183,6 @@ import org.slf4j.Logger;
  * &lt;/restriction&gt;
  * </pre>
  * 
- * <br>
  * Its constituent attributes and elements are defined as follows:
  * <ul>
  * <li>
@@ -130,13 +195,39 @@ import org.slf4j.Logger;
  * </ul>
  * </p>
  * 
- * @see SimpleLDAPConnection
+ * <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.
+ * <ul>
+ * <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.
+ * </ul>
+ * 
  * @see ReadOnlyLDAPUser
  * @see ReadOnlyLDAPGroupRestriction
  * 
  */
 public class ReadOnlyUsersLDAPRepository implements UsersRepository, Configurable, LogEnabled {
 
+    // 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 URL of the LDAP server against which users are to be authenticated.
      * Note that users are actually authenticated by binding against the LDAP
@@ -190,15 +281,41 @@ public class ReadOnlyUsersLDAPRepository
     private ReadOnlyLDAPGroupRestriction restriction;
 
     /**
-     * The connection handle to the LDAP server. This is the connection that is
-     * built from the configuration attributes &quot;ldapHost&quot;,
+     * 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 SimpleLDAPConnection ldapConnection;
+    private LdapContext ldapContext;
+
+    // Use a connection pool. Default is true.
+    private boolean useConnectionPool = true;
+
+    // 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.
+    private int connectionTimeout = -1;
+    
+    // The LDAP read timeout in milliseconds.
+    private int readTimeout = -1;
+
+    // The schedule for retry attempts
+    private RetrySchedule schedule = null;
+    
+    // Maximum number of times to retry a connection attempts. Default is no
+    // retries.
+    private int maxRetries = 0;
 
     private Logger log;
 
     /**
+     * Creates a new instance of ReadOnlyUsersLDAPRepository.
+     * 
+     */
+    public ReadOnlyUsersLDAPRepository() {
+        super();
+    }
+
+    /**
      * Extracts the parameters required by the repository instance from the
      * James server configuration data. The fields extracted include
      * {@link #ldapHost}, {@link #userIdAttribute}, {@link #userBase},
@@ -208,14 +325,25 @@ public class ReadOnlyUsersLDAPRepository
      *            An encapsulation of the James server configuration data.
      */
     public void configure(HierarchicalConfiguration configuration) throws ConfigurationException {
-        ldapHost = configuration.getString("[@ldapHost]");
-        // JAMES-1351 - ReadOnlyUsersLDAPRepository principal and credentials parameters should be optional
-        //              Added an empty String as the default
+        ldapHost = configuration.getString("[@ldapHost]", "");
         principal = configuration.getString("[@principal]", "");
         credentials = configuration.getString("[@credentials]", "");
         userBase = configuration.getString("[@userBase]");
         userIdAttribute = configuration.getString("[@userIdAttribute]");
         userObjectClass = configuration.getString("[@userObjectClass]");
+        // Default is to use connection pooling
+        useConnectionPool = configuration.getBoolean("[@useConnectionPool]", true);
+        connectionTimeout = configuration.getInt("[@connectionTimeout]", -1);
+        readTimeout = configuration.getInt("[@readTimeout]", -1);
+        // Default maximum retries is 1, which allows an alternate connection to
+        // be found in a multi-homed environment
+        maxRetries = configuration.getInt("[@maxRetries]", 1);
+        // 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
+        schedule = new DoublingRetrySchedule(retryStartInterval, retryMaxInterval, scale);
 
         HierarchicalConfiguration restrictionConfig = null;
         // Check if we have a restriction we can use
@@ -237,21 +365,101 @@ public class ReadOnlyUsersLDAPRepository
      */
     @PostConstruct
     public void init() throws Exception {
-        StringBuffer logBuffer;
         if (log.isDebugEnabled()) {
-            logBuffer = new StringBuffer(128).append(this.getClass().getName()).append(".initialize()");
-            log.debug(logBuffer.toString());
+            log.debug(new StringBuilder(128).
+                    append(this.getClass().getName()).
+                    append(".init()").
+                    append('\n').
+                    append("LDAP host: ").
+                    append(ldapHost).
+                    append('\n').
+                    append("User baseDN: ").
+                    append(userBase).
+                    append('\n').
+                    append("userIdAttribute: ").
+                    append(userIdAttribute).
+                    append('\n').
+                    append("Group restriction: ").
+                    append(restriction).
+                    append('\n').
+                    append("UseConnectionPool: ").
+                    append(useConnectionPool).
+                    append('\n').
+                    append("connectionTimeout: ").
+                    append(connectionTimeout).
+                    append('\n').
+                    append("readTimeout: ").
+                    append(readTimeout).
+                    append('\n').                    
+                    append("retrySchedule: ").
+                    append(schedule).
+                    append('\n').
+                    append("maxRetries: ").
+                    append(maxRetries).                   
+                    append('\n').
+                    toString());
+        }
+        // Setup the initial LDAP context
+        updateLdapContext();
+    }
 
-            logBuffer = new StringBuffer(256).append("Openning connection to LDAP host: ").append(ldapHost).append(".");
-            log.debug(logBuffer.toString());
+    /**
+     * Answer the LDAP context used to connect with the LDAP server.
+     * 
+     * @return an <code>LdapContext</code>
+     * @throws NamingException
+     */
+    protected LdapContext getLdapContext() throws NamingException {
+        if (null == ldapContext) {
+            updateLdapContext();
         }
+        return ldapContext;
+    }
+    
+    protected void updateLdapContext() throws NamingException {
+        ldapContext = computeLdapContext();
+    }
 
-        ldapConnection = SimpleLDAPConnection.openLDAPConnection(principal, credentials, ldapHost);
+    /**
+     * 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, maxRetries, log) {
 
-        if (log.isDebugEnabled()) {
-            logBuffer = new StringBuffer(256).append("Initialization complete. User baseDN=").append(userBase).append(" ; userIdAttribute=" + userIdAttribute).append("\n\tGroup restriction:" + restriction);
-            log.debug(logBuffer.toString());
-        }
+            @Override
+            public Context newDelegate() throws NamingException {
+                return new InitialLdapContext(getContextEnvironment(), null);
+            }
+        };
+    }
+    
+    protected Properties getContextEnvironment()
+    {
+        final Properties props = new Properties();
+        props.put(Context.INITIAL_CONTEXT_FACTORY, INITIAL_CONTEXT_FACTORY);
+        props.put(Context.PROVIDER_URL, null == ldapHost ? "" : ldapHost);
+        if (null == credentials || credentials.isEmpty()) {
+            props.put(Context.SECURITY_AUTHENTICATION, LdapConstants.SECURITY_AUTHENTICATION_NONE);
+        } else {
+            props.put(Context.SECURITY_AUTHENTICATION, LdapConstants.SECURITY_AUTHENTICATION_SIMPLE);
+            props.put(Context.SECURITY_PRINCIPAL, null == principal ? "" : principal);
+            props.put(Context.SECURITY_CREDENTIALS, credentials);
+        }
+        // The following properties are specific to com.sun.jndi.ldap.LdapCtxFactory
+        props.put(PROPERTY_NAME_CONNECTION_POOL, Boolean.toString(useConnectionPool));
+        if (connectionTimeout > -1)
+        {
+            props.put(PROPERTY_NAME_CONNECT_TIMEOUT, Integer.toString(connectionTimeout));
+        }
+        if (readTimeout > -1)
+        {
+            props.put(PROPERTY_NAME_READ_TIMEOUT, Integer.toString(readTimeout));
+        }        
+        return props;
     }
 
     /**
@@ -271,7 +479,8 @@ public class ReadOnlyUsersLDAPRepository
      *         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(String userDN,
+            Map<String, Collection<String>> groupMembershipList) {
         boolean result = false;
 
         Collection<Collection<String>> memberLists = groupMembershipList.values();
@@ -300,7 +509,8 @@ public class ReadOnlyUsersLDAPRepository
         SearchControls sc = new SearchControls();
         sc.setSearchScope(SearchControls.SUBTREE_SCOPE);
         sc.setReturningAttributes(new String[] { "distinguishedName" });
-        NamingEnumeration<SearchResult> sr = ldapConnection.getLdapContext().search(userBase, "(objectClass=" + userObjectClass + ")", sc);
+        NamingEnumeration<SearchResult> sr = ldapContext.search(userBase, "(objectClass="
+                + userObjectClass + ")", sc);
         while (sr.hasMore()) {
             SearchResult r = sr.next();
             result.add(r.getNameInNamespace());
@@ -323,7 +533,8 @@ public class ReadOnlyUsersLDAPRepository
      * @throws NamingException
      *             Propagated from the underlying LDAP communication layer.
      */
-    private Collection<ReadOnlyLDAPUser> buildUserCollection(Collection<String> userDNs) throws NamingException {
+    private Collection<ReadOnlyLDAPUser> buildUserCollection(Collection<String> userDNs)
+            throws NamingException {
         List<ReadOnlyLDAPUser> results = new ArrayList<ReadOnlyLDAPUser>();
 
         Iterator<String> userDNIterator = userDNs.iterator();
@@ -353,23 +564,29 @@ public class ReadOnlyUsersLDAPRepository
      *             Propagated by the underlying LDAP communication layer.
      */
     private ReadOnlyLDAPUser buildUser(String userDN) throws NamingException {
-      SearchControls sc = new SearchControls();
-      sc.setSearchScope(SearchControls.OBJECT_SCOPE);
-      sc.setReturningAttributes(new String[] {userIdAttribute});
-      sc.setCountLimit(1);
-
-      NamingEnumeration<SearchResult> sr = ldapConnection.getLdapContext().search(userDN, "(objectClass=" + userObjectClass + ")", sc);
-      
-      if (!sr.hasMore())
-          return null;
-
-      Attributes userAttributes = sr.next().getAttributes();
-      Attribute userName = userAttributes.get(userIdAttribute);
-      
-      if (!restriction.isActivated() || userInGroupsMembershipList(userDN, restriction.getGroupMembershipLists(ldapConnection)))
-          return new ReadOnlyLDAPUser(userName.get().toString(), userDN, ldapHost);
-      
-      return null;
+        SearchControls sc = new SearchControls();
+        sc.setSearchScope(SearchControls.OBJECT_SCOPE);
+        sc.setReturningAttributes(new String[] { userIdAttribute });
+        sc.setCountLimit(1);
+
+        StringBuilder builderFilter = new StringBuilder("(objectClass=");
+        builderFilter.append(userObjectClass);
+        builderFilter.append(")");
+        NamingEnumeration<SearchResult> sr = ldapContext.search(userDN, builderFilter.toString(),
+                sc);
+
+        if (!sr.hasMore())
+            return null;
+
+        Attributes userAttributes = sr.next().getAttributes();
+        Attribute userName = userAttributes.get(userIdAttribute);
+
+        if (!restriction.isActivated()
+                || userInGroupsMembershipList(userDN, restriction
+                        .getGroupMembershipLists(ldapContext)))
+            return new ReadOnlyLDAPUser(userName.get().toString(), userDN, ldapContext);
+
+        return null;
     }
 
     /**
@@ -383,8 +600,8 @@ public class ReadOnlyUsersLDAPRepository
     }
 
     /*
-     * TODO
-     * Should this be deprecated? At least the method isn't declared in the interface anymore
+     * TODO Should this be deprecated? At least the method isn't declared in the
+     * interface anymore
      * 
      * @see UsersRepository#containsCaseInsensitive(java.lang.String)
      */
@@ -409,8 +626,8 @@ public class ReadOnlyUsersLDAPRepository
     }
 
     /*
-     * TODO
-     * Should this be deprecated? At least the method isn't declared in the interface anymore
+     * TODO Should this be deprecated? At least the method isn't declared in the
+     * interface anymore
      * 
      * @see UsersRepository#getRealName(java.lang.String)
      */
@@ -427,18 +644,18 @@ public class ReadOnlyUsersLDAPRepository
      * @see UsersRepository#getUserByName(java.lang.String)
      */
     public User getUserByName(String name) throws UsersRepositoryException {
-      try {
-        return buildUser(userIdAttribute + "=" + name + "," + userBase); 
-      } catch (NamingException e) {
-          log.error("Unable to retrieve user from ldap", e);
-          throw new UsersRepositoryException("Unable to retrieve user from ldap", e);
-  
-      }
+        try {
+            return buildUser(userIdAttribute + "=" + name + "," + userBase);
+        } catch (NamingException e) {
+            log.error("Unable to retrieve user from ldap", e);
+            throw new UsersRepositoryException("Unable to retrieve user from ldap", e);
+
+        }
     }
 
     /*
-     * TODO
-     * Should this be deprecated? At least the method isn't declared in the interface anymore
+     * TODO Should this be deprecated? At least the method isn't declared in the
+     * interface anymore
      * 
      * @see UsersRepository#getUserByNameCaseInsensitive(java.lang.String)
      */
@@ -473,7 +690,9 @@ public class ReadOnlyUsersLDAPRepository
                 result.add(userIt.next().getUserName());
             }
         } catch (NamingException namingException) {
-            throw new UsersRepositoryException("Unable to retrieve users list from LDAP due to unknown naming error.", namingException);
+            throw new UsersRepositoryException(
+                    "Unable to retrieve users list from LDAP due to unknown naming error.",
+                    namingException);
         }
 
         return result.iterator();
@@ -484,7 +703,8 @@ public class ReadOnlyUsersLDAPRepository
         Collection<String> validUserDNs;
 
         if (restriction.isActivated()) {
-            Map<String, Collection<String>> groupMembershipList = restriction.getGroupMembershipLists(ldapConnection);
+            Map<String, Collection<String>> groupMembershipList = restriction
+                    .getGroupMembershipLists(ldapContext);
             validUserDNs = new ArrayList<String>();
 
             Iterator<String> userDNIterator = userDNs.iterator();
@@ -505,7 +725,8 @@ public class ReadOnlyUsersLDAPRepository
      */
     public void removeUser(String name) throws UsersRepositoryException {
         log.warn("This user-repository is read-only. Modifications are not permitted.");
-        throw new UsersRepositoryException("This user-repository is read-only. Modifications are not permitted.");
+        throw new UsersRepositoryException(
+                "This user-repository is read-only. Modifications are not permitted.");
 
     }
 
@@ -524,19 +745,18 @@ public class ReadOnlyUsersLDAPRepository
      * @see UsersRepository#addUser(java.lang.String, java.lang.String)
      */
     public void addUser(String username, String password) throws UsersRepositoryException {
-        log.warn("This user-repository is read-only. Modifications are not permitted.");
-        throw new UsersRepositoryException("This user-repository is read-only. Modifications are not permitted.");
+        log.error("This user-repository is read-only. Modifications are not permitted.");
+        throw new UsersRepositoryException(
+                "This user-repository is read-only. Modifications are not permitted.");
     }
 
-    /*
-     * TODO
-     * Should this be deprecated? At least the method isn't declared in the interface anymore
-     * 
+    /**
      * @see UsersRepository#updateUser(org.apache.james.api.user.User)
      */
     public void updateUser(User user) throws UsersRepositoryException {
-        log.warn("This user-repository is read-only. Modifications are not permitted.");
-        throw new UsersRepositoryException("This user-repository is read-only. Modifications are not permitted.");
+        log.error("This user-repository is read-only. Modifications are not permitted.");
+        throw new UsersRepositoryException(
+                "This user-repository is read-only. Modifications are not permitted.");
     }
 
     /**

Added: james/server/trunk/ldap/src/main/java/org/apache/james/user/ldap/api/LdapConstants.java
URL: http://svn.apache.org/viewvc/james/server/trunk/ldap/src/main/java/org/apache/james/user/ldap/api/LdapConstants.java?rev=1213200&view=auto
==============================================================================
--- james/server/trunk/ldap/src/main/java/org/apache/james/user/ldap/api/LdapConstants.java (added)
+++ james/server/trunk/ldap/src/main/java/org/apache/james/user/ldap/api/LdapConstants.java Mon Dec 12 12:04:10 2011
@@ -0,0 +1,32 @@
+/*
+ *   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
+    public static final String SECURITY_AUTHENTICATION_NONE = "none";
+    public static final String SECURITY_AUTHENTICATION_SIMPLE = "simple";
+
+}

Propchange: james/server/trunk/ldap/src/main/java/org/apache/james/user/ldap/api/LdapConstants.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: james/server/trunk/ldap/src/main/java/org/apache/james/user/ldap/api/LdapConstants.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain



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