Extending Spring LDAP with an iBATIS-style XML Data Mapper

Java Development News:

Extending Spring LDAP with an iBATIS-style XML Data Mapper

By Colin Lu

28 May 2008 | TheServerSide.com

Introduction

If you have ever used Spring-LDAP, you may be impressed by its simple Java API of accessing data in LDAP. Spring-LDAP is built on the pattern of Spring’s LdapTemplate, which relieves user from JNDI programming, LDAP Context managing, and the transaction management. In addition, it provides easier data manipulating using JavaBean and DAO pattern. However, there are two main downsides to this mechanism:

  • First, it requires writing Java code to implement the mappings between JavaBean object and LDAP entry. This leads to the unpleasant situation of tight coupling between your Java application and LDAP schema.
  • Furthermore, it lacks an easy way to manipulate an LDAP hierarchical sub-tree (LDAP entry and its children) with a single JavaBean object.

This article explains how to extend Spring-LDAP with an iBATIS-style XML Data Mapper to access LDAP data through intuitive JavaBean operations.

Example Data Model

Let’s start with an example data model of a company named “Krusty Krab”. Figure1 illustrates the data structure: the company with various departments, each with various employees.

Figure 1. Object Diagram of “Krusty Krab”

Figure 2 shows the hierarchical LDAP entries.

Figure 2. LDAP entries and their hierarchical structure of “Krusty Krab ”

To manipulate the data in LDAP (in Figure2) as JavaBeans (in Figure1), you have to do the followings according to a standard approach of Spring-LDAP.

  • Step1: Design the object model as JavaBeans.
  • Step2: Design the AttributeMapper and/or ContextMapper for each JavaBean class to build the mappings between JavaBean object and the LDAP entry.
  • Step3: Design the DAO object for each JavaBean class to implement the CRUD operations. These operations call methods of Spring LdapTemplate against each mapped LDAP entry.

There are the following challenges with this approach.

  • You have to repeat Step2 for each JavaBean class to build the mappings in Java code. This is error-prone and time consuming. Moreover, whenever the JavaBean or LDAP entry is changed, you have to modify your mapping code as well.
  • You have to repeat Step3 for each JavaBean class to build the DAO object and write code for all CRUD operations. Since the parent DAO may depend on the implementation of the children DAOs, the DAO implementation need to rewrite whenever the object hierarchy is changed. For example, the create() method in CompanyDAO need to call the create() of DepartmentDAO recursively to bind departments into LDAP.

To overcome these challenges, I introduce an iBATIS-style XML Data Mapper. It can help you to design a better LDAP-persistence layer for your Java applications. This XML DataMapper decouples LDAP programming from Java application by moving data mapping logic from Java to XML Map file. With the step-by-step instruction, you will find that the simplicity is the biggest advantage of the XML Data Mapper.

XML Data Mapper Framework

The XML Data Mapper framework will significantly reduce the amount of Java code that you normally need to call Spring-LDAP API.

The following is a high level description of the XML data mapper usage:

  • Provide a JavaBean object as a parameter. The parameter object will be used to set input values in a mapped LDAP operation (e.g. bind, search or modify, etc.).
  • Execute the mapped LDAP operation. This step is where the magic happens. The Data Mapper framework will create an LdapMapClient instance, bind any parameters to the attributes of LDAP entry using the provided parameter object, execute the mapped LDAP operation, and build a result object from the Spring’s AttributeMap or ContextMap.

The system context diagram in Figure3 illustrates the flow as described above:

Figure 3. System Context of LDAP XML Data Mapper

As shown in Figure3, the LDAP XML Data Mapper consists of two main components.

  • Application Specific Artifacts: User-defined JavaBeans, Mapping XML, and LDAP property file.
  • Data Mapper Engine: A set of Java Classes that parse the XML descriptors, bind the runtime input JavaBean object to the mapped LDAP operation, invoke Spring-LDAP API and return the result as a mapped JavaBean object.

Programming with XML Data Mapper

The programming tends to be simple and minimal. To use this XML Data Mapper in your Java application, you need to follow the steps as below:

1. Setup Spring-LDAP and XML Data Mapper Environment

XML Data Mapper requires Spring-LDAP 1.2.1, Spring Framework 2.5.1 and J2SE 1.4.

2. Design Application-Specific Artifacts

  • Design JavaBeans to Model the Data Objects

The class diagram of the Java Beans of the company “Krusty Krab” is depicted in Figure 4:

Figure4. Class Design of “Krusty Krab ”

  • Design the LDAP Map XML File

A user-defined LDAP Map XML file is used to map JavaBean Object to LDAP entry and build mapped LDAP operations. Here is an example of a mapped operation to query all Employee objects under a Department’.

<?xml version="1.0" encoding="utf-8"?>

<!DOCTYPE ldapMap PUBLIC "-//Spring LDAP - XML Data Mapper//DTD Ldap Map 1.0//EN" "ldap-map.dtd">

<ldapMap>

  <parameterMap id="parametermap_list_employees"
          className="com.ldapmapper.sample.dto.CommonSearchDTO"/>

  <resultMap id="resultmap_list_employees"
          className="com.ldapmapper.sample.dto.Employee">
    <attrmap propertyName="employeeId" attributeName="cn"/>
    <attrmap propertyName="name" attributeName="sn"/>
    <attrmap propertyName="title" attributeName="title"/>
    <attrmap propertyName="workPhone" attributeName="telephoneNumber"/>
  </resultMap>

  <search id="list_employees"
          parameterMapId="parametermap_list_employees"
          resultMapId="resultmap_list_employees">
    <base>${base}</base>
    <scope>${scope}</scope>
    <filter>(objectclass=organizationalPerson)</filter>
  </search>

</ldapMap>

From the above sample XML file, you can find the followings from the ‘search’ operation:

  1. The mapped LDAP ‘search’ operation is defined in a ‘search’ element.
  2. This operation has a unique ‘id’ as its identifier.
  3. The ‘parameterMapId’ specifies the input parameter JavaBean object for this operation. From this sample, it expects a ‘CommonSearchDTO’ object as its parameter JavaBean which has properties of ‘base’ and ‘scope’ that will be used to build a valid LDAP search call.
  4. The ‘resultMapId’ specifies the result JavaBean object of this operation. From this sample, it returns a List of ‘Employee’ JavaBean objects.
  • Configure LDAP Connection as Properties

Next step is to configure application properties to let the Data Mapper framework know where to pickup the Map XML file and how to initialize the Spring-LDAP. The sample properties are shown as below:

host=localhost
port=489
sslport=636
managerdn=cn=Directory Manager
password=password
basedn=dc=example,dc=com
ldapMapResource=com/springldap/sample/ldap.map.xml
useTransaction=true

3. Call XML Data Mapper API

The final step is to write java code to invoke the mapped LDAP operations. It is quite easy and simple. Following is the sample code snippet to list all employees of a specified department.

LdapMapClient ldapClient = LdapMapClientFactory.buildLdapMapClient(
          "com/ldapmapper/sample/ldap.properties");
List<Employee> employees = (List)ldapClient.search(
          "list_employees", myEmployeeSearchDTO);

There are only two lines of code to accomplish the invocation of a mapped LDAP ‘search’ operation.

  • Invoke the factory method ‘buildLdapMapClient()’ to read the user-defined property file as input resource and build a LdapMapClient object.
  • Call the ‘search()’ method of the LdapMapClient object to issue a search invocation to LDAP. It returns an ArrayList of ‘Employee’ JavaBean objects.

Look Inside: the XML Data Mapper API

There are two main steps to invoke a mapped LDAP operation.

  • Data Mapper Initialization: the XML Data Mapper system is initialized by user-defined property file to initialize an LdapMapClient bean as illustrated in Figure 5.

Figure 5. Sequence Diagram of LdapMapClient Initialization

  1. The factory method ‘buildLdapMapClient()’ reads user-defined property file, builds the Spring XML for bean wiring and calls Spring XmlBeanFactory to build ldapMapClient object.
  2. Spring XML bean factory generates an LdapMapClient bean instance.
  3. Initialize LdapMapClient by invoking its ’initializeResource()’ method: the user-defined map xml file (i.e. ldap.map.xml ) is parsed to build all mapped LDAP operations in HashMaps.
  4. Return LdapMapClient bean to user.
  • LdapMapClient methods are shown as follows:
public void bind(String id, Object parameterObject) throws DataAccessException;
public void bindList(String id, List parameterObjectList) throws DataAccessException;
public void modify(String id, Object parameterObject) throws DataAccessException;
public void modifyList(String id, List parameterObjectList) throws DataAccessException;
public void rebind(String id, Object parameterObject) throws DataAccessException;
public void unbind(String id, Object parameterObject) throws DataAccessException;
public Object lookup(String id, Object parameterObject) throws DataAccessException;
public Object search(String id, Object parameterObject) throws DataAccessException;
public Object execute(String id, Object parameterObject) throws DataAccessException;

Since the LdapMapClient parses the Map XML file, and binds the JavaBean object to the mapped LDAP operation at runtime, the Spring-LDAP’s AttributeMap or ContextMap are not need for each LDAP invocation.

Advanced Feature: Nested LDAP CRUD Operations Using XML Data Mapper

At this section, I will show you another main advantage of this XML Data Mapper: manipulate nested property element of your JavaBean as nested LDAP operations. From the samples of nested CRUD operations, you will see how to create, search, modify and delete a whole LDAP tree by simply manipulating your local JavaBean object.

Create “Krusty Krab” Company as a sub-tree in LDAP

If you recall the data structure of the JavaBean object, the ‘krustyKrab’ company shown in Figure 1, you will find that it is not a simple JavaBean. It contains not only simple properties (such as name, phone, address, etc.) but also nested properties (such as an Employee object as ‘ceo’, and list of ‘Department’ objects as ‘departments’).

With the nested-operation-mapping, you only need just two-line java code to create the whole sub-tree in LDAP.

1. Define the XML mapping file. The following is the sample mapping file for creating the whole LDAP sub tree.

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE ldapMap PUBLIC "-//Spring LDAP - XML Data Mapper//DTD Ldap Map 1.0//EN" "ldap-map.dtd">
<ldapMap>
 <parameterMap id="parametermap_create_company"
              className="com.springldap.sample.dto.Company">
  <attrmap propertyName="name" attributeName="o"/>
  <attrmap propertyName="address"
               attributeName="registeredAddress"/>
  <attrmap propertyName="phone"
               attributeName="telephoneNumber"/>
  <attrmap propertyName="postalCode"
               attributeName="postalCode"/>
  <attrmap propertyName="description"
               attributeName="description"/>
  <actionmap propertyName="ceo" actionType="bind"
               id="create_employee"/>
  <actionmap propertyName="departments"
               actionType="bindlist" id="create_department"/>
 </parameterMap>

 <bind id="create_company"
             parameterMapId="parametermap_create_company">
  <objectClass>organization</objectClass>
  <dn>${dn}</dn>
 </bind>

...
</ ldapMap>

As shown in the sample Map XML file, ‘actionMap’ elements are defined in ‘parameterMap’ to support nested mapped LDAP operations.

2. Write Java code to populate a ‘krustyKrab’ JavaBean object based on the data model in Figure 1. The sample code will look like this:

public static Company buildCompanyDTO(){
  Company company = new Company("o=krustyKrab", "krustyKrab",
    "(416) 111-1111",
    "300 Consilium Pl, Toronto, ON", "M1H 3G2",
    "Krusty Krab Limited",
    null, new ArrayList());

  ... ...
  rnd.addEmployee(squidward);
  rnd.addEmployee(bob);

  company.setCeo(ceo);
  company.addDepartment(sales);
  company.addDepartment(rnd);
  return company;
 }

3. Write Java code to invoke LDAP operation through LdapMapClient to create the whole sub-tree on LDAP. It is just two-line java code:

LdapMapClient ldapClient = LdapMapClientFactory.buildLdapMapClient(
          "com/ldapmapper/sample/ldap.properties");
ldapClient.bind("create_company", company);

When you run this two-line java code, the whole sub-tree is created in LDAP.

Figure6. Create “Krusty Krab” company object as a sub-tree in LDAP through a single java call

Query the “Krusty Krab” sub-tree from LDAP into your Company JavaBean

Similarly, using the nested nested-operation-mapping, you can read a whole LDAP sub-tree from LDAP to your complex JavaBean by a single java call.

1. Define the XML mapping file.

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE ldapMap PUBLIC "-//Spring LDAP - XML Data Mapper//DTD Ldap Map 1.0//EN" "ldap-map.dtd">
  <ldapMap>
<parameterMap id="parametermap_search_company_by_name"
         className="com.ldapmapper.sample.dto.CompanySearch"/>
    <resultMap id="resultmap_company"
             className="com.ldapmapper.sample.dto.Company">
 <attrmap propertyName="name" attributeName="o"/>
 <attrmap propertyName="address"
             attributeName="registeredAddress"/>
 <attrmap propertyName="phone" attributeName="telephoneNumber"/>
 <attrmap propertyName="postalCode" attributeName="postalCode"/>
 <attrmap propertyName="description" attributeName="description"/>
 <actionmap propertyName="ceo" parameterId="ceoSearchDTO"
             actionType="search" id="find_ceo"/>
 <actionmap propertyName="departments"
             parameterId="departmentsSearchDTO" actionType="search"
             id="list_departments"/>
</resultMap>

    <search id="search_company_by_name"
                parameterMapId="parametermap_search_company_by_name"
                resultMapId="resultmap_company">
      <base>${base}</base>
 <scope>${scope}</scope>
      <filter>(&(objectclass=organization)(o=${name}))</filter>
</search>
...
</ ldapMap>

From the sample Map XML file, you will see that the ‘actionMap’ elements are used in ‘resultMap’ to support nested mapped LDAP operations.

2. Write Java code to populate a ‘CompanySearch’ JavaBean object as the input parameter object for the nested search operation.

public static CompanySearch buildCompanySearchDTO(){
  CompanySearch req = new CompanySearch("o=krustyKrab", "",
                    SearchControls.SUBTREE_SCOPE, null, "krustyKrab",
     new CommonSearchDTO(null, "o=krustyKrab",
                    SearchControls.ONELEVEL_SCOPE, null),
     new DepartmentSearch(null, "o=krustyKrab",
                    SearchControls.ONELEVEL_SCOPE, null, null,
     new CommonSearchDTO(null, null,
                    SearchControls.ONELEVEL_SCOPE, null))
   );
  return req;
 }

3. Write Java code to read the whole sub-tree from LDAP to the local JavaBean object

LdapMapClient ldapClient = LdapMapClientFactory.buildLdapMapClient(
            "com/ldapmapper/sample/ldap.properties", false);
List companies = (List)ldapClient.search(
            "search_company_by_name", buildCompanySearchDTO());
for (Iterator it = companies.iterator(); it.hasNext();) {
 Company company = (Company)it.next();
 // manipulating company object here ...
}

You can also find the extensive sample code of other CRUD operations on your complex JavaBean.

Benefits

You can see the following benefits when using this XML Data Mapper.

A Simple JavaBean/LDAP Entry Mapping Framework : It transplants O/RM concept to JNDI/LDAP environment. It can map not only simple JavaBean to single LDAP entry but also complex object to a whole LDAP sub-tree. The mapping is done at runtime by user-defined map xml file.

Easy Coding : It provides a very simple Java API. Developer can easily manipulate their LDAP data through very simple and straight-forward java coding without knowing Spring-LDAP API.

Enhanced Data Mapping : It supports nested LDAP operation, which can significantly reduce the code for manipulating the complex data in LDAP.

Powerful : It leverages powerful features of Spring-LDAP, such as bean wiring, transaction support, context management and exception handling.

Improved development flow: It relieves the tedious and error-prone java code from the developers. It makes the developers focus on the data model and business logic of the applications. By introducing the XML Map file, it decouples LDAP schema from Java application.

Conclusions

This article introduces an iBATIS-style XML Data Mapper framework to extend Spring-LDAP. It simply uses O/RM concept to manipulate LDAP entries through JavaBean operations. From the example in the article, you can find that java code is significantly simplified by moving the mapping logic from java to XML. Moreover, the nested-operation-mapping allows user to manipulate complex data through an intuitive way.

Resources

LDAP Programming, Management and Integration , by Clayton Donley , 2003 Manning Publications Co.
Spring LDAP Reference Documentation, by Mattias Arthursson , Ulrik Sandberg, Eric Dalquist, 2007 Spring Framework
iBATIS Data Mapper Developer’s Guide, by iBATIS
Simplify Directory Access with Spring LDAP, by Sunil D. Patil, 2007 JavaWorld
LDAP and JNDI: Together Forever, by Sameer Tyagi, 2000 JavaWorld

Biography

Colin (Chun) Lu is the systems analyst at Telus Mobility in Toronto, Canada. He has been working in Java/J2EE architecture design and development since 1999. Currently he is working in the area of SOA for Java EE application design and integration. You can reach Colin at colinlucs@gmail.com .

Related Content

Related Resources