5 minutes with – Spring ldap

Here we are again to speak about another Spring interface for communicating with Ldap protocol.

In this article I’ll show you how use Spring ldap framework with an implementation of Crud controlller.

First, this is not an article about Ldap protocol. In internet you can find a lot of articles that explain, in very good way, what is it and what it isn’t. Anyway, for use it we need to install a ldap service on our machine (otherwise you can use a remote service whether is available).

In the follow example I used OpenDj service installed on local machine. Look at official web site http://www.forgerock.com/opendj.html to find out more information.

Another technology that I used to implement the solution is Spring ldap. Again, you can find information at official web site http://static.springsource.org/spring-ldap/docs/1.3.x/reference/pdf/spring-ldap-reference.pdf and, trust me, it’s very easy to use. Spring ldap is a facilitator for connecting your java application to ldap protocol in the same way as you’re using a Jdbc Template in Spring.

An interesting point is the OdmManager interface. Take a look at it.

public interface OdmManager {  
  
    <T> T read(Class<T> clazz, Name dn);

    void create(Object entry);

    void update(Object entry);

    void delete(Object entry);

    <T> List<T> findAll(Class<T> clazz, Name base, SearchControls searchControls);

    <T> List<T> search(Class<T> clazz, Name base, String filter, SearchControls searchControls);
}

It looks like a crud interface. Doesn’t it?

In my example I used a Object-Directory Mapping (ODM) system. Like Hibernate and other ORM, I mapped my java class with the same model contained into ldap service.  As usual, we can use some annotations like the follows.

@Entry – Class level annotation indicating the objectClass definitions to which the entity maps. (required)
@Id – Indicates the entity DN; the field declaring this attribute must be a derivative of the javax.naming.Name class. (required)
@Attribute – Indicates the mapping of a directory attribute to the object class field.
@Transient – Indicates the field is not persistent and should be ignored by the OdmManager.

My model is

package it.sampleldap;
import java.util.ArrayList;
import java.util.List;
import javax.naming.Name;
import org.springframework.ldap.odm.annotations.Attribute;
import org.springframework.ldap.odm.annotations.Entry;
import org.springframework.ldap.odm.annotations.Id;
@Entry(objectClasses = { "CustomerDAO", "top" })
public class CustomerDAO {
 @Id
 private Name distinguisedName;
 @Attribute(name = "uid")
 private String uid;
 @Attribute(name = "companyName")
 private String companyName;
 @Attribute(name = "address")
 private String address;
 @Attribute(name = "city")
 private String city;
 @Attribute(name = "nation")
 private String nation;
 @Attribute(name = "Password")
 private String password;
 @Attribute(name = "objectClass")
 private List<String> objectClassNames;
 @Override
 public boolean equals(Object obj) {
  CustomerDAO customer = (CustomerDAO) obj;
  if ((this.address.equals(customer.address))
    && (this.city.equals(customer.city))
    && (this.companyName.equals(customer.companyName))
    && (this.nation.equals(customer.nation))
    && (this.password.equals(customer.password)))
   return true;
  else
   return false;
 }
 public String toString() {
  StringBuffer customerDAOStr = new StringBuffer("Customer=[");
  customerDAOStr.append(" uid = " + uid + ",");
  customerDAOStr.append(" companyName = " + companyName + ",");
  customerDAOStr.append(" address = " + address + ",");
  customerDAOStr.append(" city = " + city + ",");
  customerDAOStr.append(" nation = " + nation + ",");
  customerDAOStr.append(" ]");
  return customerDAOStr.toString();
 }
 public CustomerDAO() {
  setObjectClassNames(new ArrayList<String>());
 }
 public String getUid() {
  return uid;
 }
 public void setUid(String uid) {
  this.uid = uid;
 }
 public String getCompanyName() {
  return companyName;
 }
 public void setCompanyName(String companyName) {
  this.companyName = companyName;
 }
 public String getAddress() {
  return address;
 }
 public void setAddress(String address) {
  this.address = address;
 }
 public String getCity() {
  return city;
 }
 public void setCity(String city) {
  this.city = city;
 }
 public String getNation() {
  return nation;
 }
 public void setNation(String nation) {
  this.nation = nation;
 }
 public Name getDistinguisedName() {
  return distinguisedName;
 }
 public void setDistinguisedName(Name distinguisedName) {
  this.distinguisedName = distinguisedName;
 }
 public List<String> getObjectClassNames() {
  return objectClassNames;
 }
 public void setObjectClassNames(List<String> objectClassNames) {
  this.objectClassNames = objectClassNames;
 }
 public String getPassword() {
  return password;
 }
 public void setPassword(String password) {
  this.password = password;
 }
}

Before continuing with the code, we need to configure the ldap service with the object class. As I said before, I used OpenDj service. It’s possibile to configure it with commands script. The follows scripts create the Base Dn, the Organization Unit and the class type CustomerDAO.


Create Base Dn
C:\OpenDJ-2.4.4\bat\dsconfig.bat "set-backend-prop" "--backend-name" "userRoot" "--add" "base-dn:dc=epco,dc=it" "--hostName" "WK216125" "--port" "4444" "--bindDN" "cn=Directory Manager" "--bindPassword" "********" "--trustAll" "--noPropertiesFile" "--no-prompt"

Organization Unit
C:\OpenDJ-2.4.4\bat\ldapmodify.bat "--hostName" "WK216125" "--port" "1389" "--bindDN" "cn=Directory Manager" "--bindPassword" "********" "--noPropertiesFile" "--defaultAdd"
dn: ou=CustomerWareHouse,dc=epco,dc=it
objectclass: top
objectclass: organizationalUnit
ou: CustomerWareHouse

CompanyName
C:\OpenDJ-2.4.4\bat\ldapmodify.bat "-a" "--hostName" "WK216125" "--port" "4444" "--bindDN" "cn=Directory Manager" "--bindPassword" "********" "--trustAll" "--useSSL" "--noPropertiesFile"
dn: cn=schema
changetype: modify
add: attributeTypes
attributeTypes: ( companyName-oid NAME 'companyName' EQUALITY caseIgnoreMatch ORDERING caseIgnoreOrderingMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 USAGE userApplications X-SCHEMA-FILE '99-user.ldif' )

Password
C:\OpenDJ-2.4.4\bat\ldapmodify.bat "-a" "--hostName" "WK216125" "--port" "4444" "--bindDN" "cn=Directory Manager" "--bindPassword" "********" "--trustAll" "--useSSL" "--noPropertiesFile"
dn: cn=schema
changetype: modify
add: attributeTypes
attributeTypes: ( password-oid NAME 'Password' EQUALITY ds-mr-user-password-exact SYNTAX 1.3.6.1.4.1.26027.1.3.1 USAGE userApplications X-SCHEMA-FILE '99-user.ldif' )

Nation
C:\OpenDJ-2.4.4\bat\ldapmodify.bat "-a" "--hostName" "WK216125" "--port" "4444" "--bindDN" "cn=Directory Manager" "--bindPassword" "********" "--trustAll" "--useSSL" "--noPropertiesFile"
dn: cn=schema
changetype: modify
add: attributeTypes
attributeTypes: ( companyName-oid NAME 'nation' EQUALITY caseIgnoreMatch ORDERING caseIgnoreOrderingMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 USAGE userApplications X-SCHEMA-FILE '99-user.ldif' )

City
C:\OpenDJ-2.4.4\bat\ldapmodify.bat "-a" "--hostName" "WK216125" "--port" "4444" "--bindDN" "cn=Directory Manager" "--bindPassword" "********" "--trustAll" "--useSSL" "--noPropertiesFile"
dn: cn=schema
changetype: modify
add: attributeTypes
attributeTypes: ( companyName-oid NAME 'city' EQUALITY caseIgnoreMatch ORDERING caseIgnoreOrderingMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 USAGE userApplications X-SCHEMA-FILE '99-user.ldif' )

Address
C:\OpenDJ-2.4.4\bat\ldapmodify.bat "-a" "--hostName" "WK216125" "--port" "4444" "--bindDN" "cn=Directory Manager" "--bindPassword" "********" "--trustAll" "--useSSL" "--noPropertiesFile"
dn: cn=schema
changetype: modify
add: attributeTypes
attributeTypes: ( companyName-oid NAME 'address' EQUALITY caseIgnoreMatch ORDERING caseIgnoreOrderingMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 USAGE userApplications X-SCHEMA-FILE '99-user.ldif' )

Equivalent command line to add object class 'CustomerDAO':
C:\OpenDJ-2.4.4\bat\ldapmodify.bat "-a" "--hostName" "WK216125" "--port" "4444" "--bindDN" "cn=Directory Manager" "--bindPassword" "********" "--trustAll" "--useSSL" "--noPropertiesFile"
dn: cn=schema
changetype: modify
add: objectClasses
objectClasses: ( customerdao-oid NAME 'CustomerDAO' SUP top STRUCTURAL MUST ( companyName $ uid $ Password ) MAY ( nation $ city $ address ) X-SCHEMA-FILE '99-user.ldif' )

Now, it’s time to configure the object in spring file configuration


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>

 <bean id="fromStringConverter"
  class="org.springframework.ldap.odm.typeconversion.impl.converters.FromStringConverter" />
 <bean id="toStringConverter"
  class="org.springframework.ldap.odm.typeconversion.impl.converters.ToStringConverter" />
 <bean id="converterManager"
  class="org.springframework.ldap.odm.typeconversion.impl.ConverterManagerFactoryBean">
  <property name="converterConfig">
   <set>
    <bean
     class="org.springframework.ldap.odm.typeconversion.impl.ConverterManagerFactoryBean$ConverterConfig">
     <property name="fromClasses">
      <set>
       <value>java.lang.String</value>
      </set>
     </property>
     <property name="toClasses">
      <set>
       <value>java.lang.Byte</value>
       <value>java.lang.Short</value>
       <value>java.lang.Integer</value>
       <value>java.lang.Long</value>
       <value>java.lang.Float</value>
       <value>java.lang.Double</value>
       <value>java.lang.Boolean</value>
      </set>
     </property>
     <property name="converter" ref="fromStringConverter" />
    </bean>
    <bean
     class="org.springframework.ldap.odm.typeconversion.impl.ConverterManagerFactoryBean$ConverterConfig">
     <property name="fromClasses">
      <set>
       <value>java.lang.Byte</value>
       <value>java.lang.Short</value>
       <value>java.lang.Integer</value>
       <value>java.lang.Long</value>
       <value>java.lang.Float</value>
       <value>java.lang.Double</value>
       <value>java.lang.Boolean</value>
      </set>
     </property>
     <property name="toClasses">
      <set>
       <value>java.lang.String</value>
      </set>
     </property>
     <property name="converter" ref="toStringConverter" />
    </bean>
   </set>
  </property>
 </bean>
 <bean id="contextSource">
  <property name="url" value="ldap://localhost:1389" />
  <property name="userDn" value="cn=Directory Manager" />
  <property name="password" value="password" />
 </bean>
 
 <bean id="odmManager">
        <property name="converterManager" ref="converterManager" />
        <property name="contextSource" ref="contextSource" />
        <property name="managedClasses">
            <set>
                <value>it.sampleldap.CustomerDAO</value>
            </set>
        </property>
    </bean>
 <bean id="ldapTemplate">
  <constructor-arg ref="contextSource" />
 </bean>
 <bean id="ldapContact">
  <property name="ldapTemplate" ref="ldapTemplate" />
 </bean>
</beans>

In the last code we can take a notice about contextSource (connection with ldap service) and converterManager. This last is used for mapping the object from ldap to java class.

Finally the unit test file.

package it.sampleldap.test;

import static org.junit.Assert.assertTrue;
import it.sampleldap.CustomerDAO;

import java.util.List;

import javax.naming.directory.SearchControls;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ldap.core.DistinguishedName;
import org.springframework.ldap.odm.core.OdmManager;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/springldap.xml")
public class CustomerLdap {

 @Autowired
 private OdmManager odmManager;

 private static final SearchControls searchControls = new SearchControls(
   SearchControls.SUBTREE_SCOPE, 100, 10000, null, true, false);

 private static String uid = null;
 private static String baseDn = null;
 
 public void prepareTest() {
  uid = "cust" + (int)(Math.random() * 100);
  baseDn = "uid=" + uid + ",ou=CustomerWareHouse,dc=epco,dc=it";
 }

 @Test
 public void testSearch() {
  List<CustomerDAO> customerResults = odmManager.search(
    CustomerDAO.class, new DistinguishedName("dc=epco,dc=it"),
    "uid=*", searchControls);

  int count = 0;

  for (CustomerDAO customer : customerResults) {
   System.out.print("Customer: " + customer.toString() + "\n");
   count++;
  }

  System.out.println("\n" + count);

  assertTrue(count > 0);
 }

 @Test
 public void testCreate() {
  
  prepareTest();
  
  CustomerDAO customerInsert = new CustomerDAO();

  customerInsert.setCompanyName("Company" + uid);
  customerInsert.setAddress("Address" + uid);
  customerInsert.setCity("City" + uid);
  customerInsert.setNation("Nation" + uid);
  customerInsert.setUid(uid);
  customerInsert.setPassword("Password" + uid);
  
  customerInsert.getObjectClassNames().add("top");
  customerInsert.getObjectClassNames().add("CustomerDAO");

  DistinguishedName distinguishedName = new DistinguishedName(baseDn);
  customerInsert.setDistinguisedName(distinguishedName);
  
  odmManager.create(customerInsert);

  CustomerDAO customerRead = odmManager.read(CustomerDAO.class, distinguishedName);
  
  assertTrue(customerInsert.equals(customerRead));

 }
 @Test
 public void testUpdate()
 {  
  DistinguishedName distinguishedName = new DistinguishedName(baseDn);

  CustomerDAO customerEdit = odmManager.read(CustomerDAO.class, distinguishedName);
  
  customerEdit.setNation("EU");
  
  odmManager.update(customerEdit);
 }
 @Test
 public void testDelete() {
  
  CustomerDAO customerDelete = new CustomerDAO();
  
  DistinguishedName distinguishedName = new DistinguishedName(baseDn);
  
  customerDelete.setDistinguisedName(distinguishedName);
  
  customerDelete.getObjectClassNames().add("top");
  customerDelete.getObjectClassNames().add("CustomerDAO");
  
  odmManager.delete(customerDelete);  

  List<CustomerDAO> customerResults = odmManager.search(CustomerDAO.class, new DistinguishedName("dc=epco,dc=it"),"uid=" + uid, searchControls);
  
  assertTrue(customerResults.size() == 0);
 }
}

The tests check up the list, create, update and delete of the users.

Summary

I think the Spring ldap library is a good tool for communicating with ldap protocol. It’s easy to use and I haven’t found any bug. The documentation is not long to read. Obviously there are many other method for accessing at ldap service but I think I’m going to use this library in my next project.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s