Reading Objects from an LDAP Directory using Spring

The title of this post is “Reading Objects from an LDAP Directory using Spring”, but I’m going to present a specific example. This example can easily be modified to read any object from an LDAP Directory as a Spring bean.

So, for my example I’m going to use a problem that I had to solve recently. I needed to move some database configuration for several web apps from being defined as a tomcat Resource to being read from an LDAP directory. Why would you want to do this, I hear you ask? Well, imagine you have several Tomcat instances possibly on several different machines. Anytime you need to make any database configuration changes (or even on installation) you have to duplicate the changes on every tomcat installation. By moving some configuration into a centralized location it’s much easier to maintain and administer.

Now, this doesn’t sound like it would be that difficult of a task, but it turns out it wasn’t quite as simple as I thought. The web apps I needed to modify use Spring+Hibernate, so our Spring configuration used to look something like this. 

<bean id="myDataSource" class="org.springframework.jndi.JndiObjectFactoryBean"
      scope="singleton">
  <property name="jndiName" value="/jdbc/MyDatabase" />
  <property name="resourceRef" value="true" />
</bean>

<bean id="sessionFactory"    class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
  <property name="dataSource"  ref="myDataSource" />
  <property name="annotatedClasses">
    <list>
      <value>...</value>
    </list>
  </property>
</bean>

We also had a resource defined in context.xml with all of the database connection information (driver, url, username/password, etc) and a reference to that resource from the web app.

So, instead of having this resource defined in tomcat, we want to read the DataSource from the LDAP Directory. Hibernate is expecting a javax.sql.DataSource. How do we read a javax.sql.DataSource from the LDAP Directory using Spring? We can use Spring’s JndiObjectFactoryBean and JndiTemplate. The template allows us to specify an object factory for constructing objects from JNDI entries. We can use this approach to create an object factory that reads an entry from the LDAP Directory (using JNDI) and constructs a javax.sql.DataSource from it.

public class DataSourceObjectFactory implements DirObjectFactory 
{
    @Override
    public Object getObjectInstance(Object obj, Name name, Context ctx,
            Hashtable<?, ?> env, Attributes attrs) throws Exception 
    {
        if (obj instanceof DirContext) 
        {
            Attribute objectClass = attrs.get("objectClass");
            NamingEnumeration<?> ne = objectClass.getAll();
            while (ne.hasMore()) 
            {
                Object next = ne.next();
                if (next.equals("Database")) 
                {
                    String driver = attrs.get("driver");
                    String scheme = attrs.get("scheme");
                    String server = attrs.get("server");
                    String instance = attrs.get("instance");
                    String database = attrs.get("database");
                    String username = attrs.get("username");
                    String password = attrs.get("password");

                    BasicDataSource ds = new BasicDataSource();
                    ds.setDriverClassName(driver);
                    String url = scheme + "://" + server + "/" + database;
                    if (null != instance)
                    {
                        url += ";instance=" + instance;
                    }
                    if (null != username) 
                    {
                        ds.setUsername(username);
                        ds.setPassword(password);
                    }
                }
            }
        }
        return null;
    }

    @Override
    public Object getObjectInstance(Object obj, Name name, Context ctx,
            Hashtable<?, ?> env) throws Exception 
    {
        return getObjectInstance(obj, name, ctx, env, null);
    }
}

This factory constructs an apache dbcp BasicDataSource (which implements javax.sql.DataSource). The LDAP context will be passed to the factory along with an instance of Attributes which contains the object that was requested. This factory first checks that it is supposed to handle this type of object by checking that the objectClass is a “Database” object (the LDAP schema I’m using was extended to include a custom “Database” object). Once we are sure we should be handling this object we go ahead and construct the DataSource.

Now – how do we use it? Our Spring config file from above is modified like so:

<bean id="myJndiTemplate" class="org.springframework.jndi.JndiTemplate">
  <property name="environment">
    <props>
      <prop key="java.naming.factory.initial">com.sun.jndi.ldap.LdapCtxFactory</prop>
      <prop key="java.naming.provider.url">ldap://localhost:389/dc=myapp,dc=johnhite,dc=com</prop>
      <prop key="java.naming.factory.object">com.johnhite.ldap.jndi.DataSourceObjectFactory</prop>
      <prop key="java.naming.security.authentication">none</prop>
    </props>
  </property>
</bean>

<bean id="myDataSource" 
      class="org.springframework.jndi.JndiObjectFactoryBean" 
      scope="singleton">
  <property name="proxyInterface" value="javax.sql.DataSource" />
  <property name="jndiTemplate" ref="myJndiTemplate" />
  <property name="jndiName" value="cn=MyDatabase, o=Databases" />
</bean>

As  you can see we are still using Springs JndiObjectFactoryBean, but we are using it in conjunction with Springs JndiTemplate in order to specify the LDAP directory location and our ObjectFactory. The jndiName has become the DN (Distinguished Name) of the database entry.

Also notice that we added the proxyInterface. This tells the JndiObjectFactoryBean to return a proxy that implements that interface. In our case we want a javax.sql.DataSource. If we don’t do this hibernate will throw a class cast exception because its expecting a javax.sql.DataSource.

Please Note: This example does not take security into consideration. If you want to use this you must ensure that your LDAP directory is secure.

Leave a Reply

Your email address will not be published. Required fields are marked *