Search code examples
javasocketsldapjndiconnection-pooling

Pooling LDAP connections with custom socket factory


My Java web application is using an LDAP backend for authentication / authorization. I am currently using a custom socket factory, and want to enable connection pooling for performance reasons.

The confusion starts.

According to the standard JNDI tutorials here, A Context instance cannot use a pooled connection if it has its "java.naming.ldap.factory.socket" property set to a custom socket factory class.

However, according to the official (Java 6) JNDI documentation here, Pooling of connections from a custom socket factory is allowed when java.naming.ldap.factory.socket environment property is set. For the custom socket factory to be pooled the socket factory class must implement the Comparator interface.

Encouraging. What I have done so far:

  • my custom socketfactory implements Comparator<SocketFactory> with a simple comparison of host+port
  • when creating my LdapContext, I do env.put("java.naming.ldap.factory.socket", "portal.ldap.util.PortalSocketFactory") and env.put("com.sun.jndi.ldap.connect.pool", "true")
  • my Tomcat server is started with -Dcom.sun.jndi.ldap.connect.pool.initsize=10 -Dcom.sun.jndi.ldap.connect.pool.maxsize=20 -Dcom.sun.jndi.ldap.connect.pool.prefsize=10 -Dcom.sun.jndi.ldap.connect.pool.timeout=300000 -Dcom.sun.jndi.ldap.connect.pool.protocol="plain ssl" -Dcom.sun.jndi.ldap.connect.pool.debug="all"

Next, a test. I connect to the application, it connects to the LDAP server, and behold - a connection pool is created. Clearly, my custom socketfactory is accepted:

com.sun.jndi.ldap.pool.Pool@26cd2192 {}.get(): 172.17.2.91:636:ssl::portal.ldap.util.PortalSocketFactory:cn=PortalProxy,ou=sa,o=config
com.sun.jndi.ldap.pool.Pool@26cd2192 {}.size: 0
com.sun.jndi.ldap.pool.Pool@26cd2192 {}.get(): creating new connections list for 172.17.2.91:636:ssl::portal.ldap.util.PortalSocketFactory:cn=PortalProxy,ou=sa,o=config
[email protected] size=10; size: 0
[email protected] size=20; size: 0
[email protected] size=10; size: 0
Create com.sun.jndi.ldap.LdapClient@127e942f[172.17.2.91:636]
<snip>
Create com.sun.jndi.ldap.LdapClient@3c7038b9[172.17.2.91:636]
com.sun.jndi.ldap.pool.Pool@26cd2192 {172.17.2.91:636:ssl::portal.ldap.util.PortalSocketFactory:cn=PortalProxy,ou=sa,o=config=com.sun.jndi.ldap.pool.ConnectionsRef@6b9c18ae}.get(): size after: 1
[email protected](): before; size: 10
ConnectionDesc.tryUse() com.sun.jndi.ldap.LdapClient@127e942f idle
[email protected](): use com.sun.jndi.ldap.LdapClient@127e942f; size: 10
Use com.sun.jndi.ldap.LdapClient@127e942f
[email protected](): after; size: 10

BUT you guessed it, something's rotten. Now, every time the application connects, a new pool is instantiated! I can see that the connections are actually pooled, because after the configured 300 seconds, they expire and are removed from the pool:

ConnectionDesc.expire(): not expired com.sun.jndi.ldap.LdapClient@e6c7a64 idle
ConnectionDesc.expire(): expired com.sun.jndi.ldap.LdapClient@39579371 idle
[email protected](): removing com.sun.jndi.ldap.LdapClient@39579371 expired; size: 10
Expired com.sun.jndi.ldap.LdapClient@39579371 expired
ConnectionDesc.expire(): expired com.sun.jndi.ldap.LdapClient@2ada52a1 idle
[email protected](): removing com.sun.jndi.ldap.LdapClient@2ada52a1 expired; size: 9
Expired com.sun.jndi.ldap.LdapClient@2ada52a1 expired
<and so on>

Help! Am I missing some small detail? Has anyone succeeded in getting this to work?


Solution

  • If you have a look at the actual code that does the comparison you'll see that it's definitely a bug. It expects Comparator<String> rather than Comparator<SocketFactory>. classnames of the factories are being passed, not the factories themselves. At a runtime ClassCastException is swallowed and false is returned. That's why you cannot see your "debug println()" from the compare method -- it's never executed.