CHAPTER 8

image

Object-Directory Mapping

In this chapter, you will learn

  • The basics of ODM.
  • Spring LDAP ODM implementation.

Enterprise Java developers employ object-oriented (OO) techniques to create modular, complex applications. In the OO paradigm, objects are central to the system and represent entities in the real world. Each object has an identity, state, and behavior. Objects can be related to other objects through inheritance or composition. LDAP directories, on the other hand, represent data and relationships in a hierarchical tree structure. This difference leads to an object-directory paradigm mismatch and can cause problems in communication between OO and directory environments.

Spring LDAP provides an Object-Directory Mapping (ODM) framework that bridges the gap between the object and directory models. The ODM framework allows us to map concepts between the two models and orchestrates the process of automatically transforming LDAP directory entries into Java objects. ODM is similar to the more familiar Object-Relational Mapping (ORM) methodology that bridges the gap between object and relational database worlds. Frameworks such as Hibernate and Toplink have made ORM popular and an important part of the developer’s toolset.

Though Spring LDAP ODM shares the same concepts as ORM, it does have the following differences:

  • Caching of LDAP entries is not possible.
  • ODM  metadata is expressed through class-level annotations.
  • No XML configuration is available.
  • Lazy loading of entries is not possible.
  • A query language, such as HQL, does not exist. Loading of objects is done via DN lookups and standard LDAP search queries.

Spring ODM Basics

The Spring LDAP ODM is distributed as a separate module from the core LDAP project. To include the Spring LDAP ODM in the project, the following dependency needs to be added to project’s pom.xml file:

<dependency>
    <groupId>org.springframework.ldap</groupId>
    <artifactId>spring-ldap-odm</artifactId>
    <version>${org.springframework.ldap.version}</version>
    <exclusions>
        <exclusion>
            <artifactId>commons-logging</artifactId>
            <groupId>commons-logging</groupId>
        </exclusion>
    </exclusions>
</dependency>

The Spring LDAP ODM is available under the org.springframework.ldap.odm package and its subpackages. The core classes of Spring LDAP ODM are represented in Figure 8-1. You will look at each of these classes in detail throughout this chapter.

9781430263975_Fig08-01.jpg

Figure 8-1. Spring LAP ODM core classes

Central to the LDAP ODM is the OdmManager that provides generic search and CRUD operations. It acts as a mediator and transforms data between LDAP entries and Java objects. The Java objects are annotated to provide the transformation metadata. Listing 8-1 shows the OdmManager API.

Listing 8-1.

Package org.springframeworkldap.odm.core;
 
import java.util.List;
import javax.naming.Name;
import javax.naming.directory.SearchControls;
 
public interface OdmManager {
 
   void create(Object entry);
   <T> T read(Class<T> clazz, Name dn);
   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);
}

The OdmManager’s create, update, and delete methods take a Java object and use the information in it to perform corresponding LDAP operations. The read method takes two parameters, a Java class that determines the type to return and a fully qualified DN that is used to look up the LDAP entry. The OdmManager can be viewed as a slight variation on the Generic DAO pattern you saw in Chapter 5.

Spring LDAP ODM provides an out-of-the-box implementation of the OdmManager, the aptly named OdmManagerImpl. In order to function properly, an OdmManagerImpl uses the following three objects:

  • A ContextSource implementation for communicating with the LDAP Server.
  • A ConverterManager implementation to convert LDAP data types to Java data types and vice versa.
  • A set of domain classes that needs to be managed by the ODM implementation.

To simplify the creation of OdmManagerImpl instances, the framework provides a factory bean, OdmManagerImplFactoryBean. Here is the necessary configuration for creating OdmManager instances:

<bean  id="odmManager" class="org.springframework.ldap.odm. core.impl.OdmManagerImplFactoryBean">
    <property  name="converterManager" ref="converterManager"  />
    <property  name="contextSource" ref="contextSource" />
    <property  name="managedClasses">
        <set>
            <value>FULLY_QUALIFIED_CLASS_NAME</value>
        </set>
    </property>
</bean>

The OdmManager delegates the conversion management of the LDAP attributes to Java fields (and vice versa) to a ConverterManager. The ConverterManager itself relies on a set of Converter instances for the actual conversion purposes. Listing 8-2 shows the Converter interface API. The convert method accepts an object as its first parameter and converts it into an instance of the type specified by the toClass parameter.

Listing 8-2.

package org.springframework.ldap.odm.typeconversion.impl;
 
public interface Converter {
   <T> T convert(Object source, Class<T> toClass) throws Exception;
}

The generic nature of the converters makes it easy to create specific implementations. Spring LDAP ODM provides a ToStringConverter implementation of the Converter interface that converts the given source object into a String. Listing 8-3 provides the ToStringConverter API implementation. As you can see, the conversion takes place by simply invoking the toString method on the source object.

Listing 8-3.

package org.springframework.ldap.odm.typeconversion.impl.converters;
 
import org.springframework.ldap.odm.typeconversion.impl.Converter;
 
public final class ToStringConverter implements Converter {
 
   public <T> T convert(Object source, Class<T> toClass) {
      return toClass.cast(source.toString());
   }
}

The inverse of this implementation is the FromStringConverter, which converts a java.lang.String object into any specified toClass type. Listing 8-4 provides the FromStringConverter API implementation. The converter implementation creates a new instance by invoking the toClass parameter’s constructor and passing in the String object. The toClass type parameter must have a public constructor that accepts a single java.lang.String type parameter. For example, the FromStringConverter can convert String data to an Integer or Long data type.

Listing 8-4.

package org.springframework.ldap.odm.typeconversion.impl.converters;
 
import java.lang.reflect.Constructor;
import org.springframework.ldap.odm.typeconversion.impl.Converter;
 
public final class FromStringConverter implements Converter {
 
   public <T> T convert(Object source, Class<T> toClass) throws Exception {
      Constructor<T> constructor = toClass.getConstructor(java.lang.String.class);
      return constructor.newInstance(source);
   }
}

These two converter classes should be sufficient for converting most LDAP data types to common Java field types such as java.lang.Integer, java.lang.Byte, etc. and vice versa. Listing 8-5 shows the XML configuration involved in creating FromStringConverter and ToStringConverter instances.

Listing 8-5.

<bean id="fromStringConverter" class="org.springframework.ldap.odm.typeconversion.impl.converters.FromStringConverter" />
<bean id="toStringConverter" class="org.springframework.ldap.odm.typeconversion.impl.converters.ToStringConverter" />

Now you are ready to create an instance of ConverterManager and register the above two converters with it. Registering a converter involves specifying the converter itself, a fromClass indicating the type of the source object the converter is expecting, and a toClass indicating the type the converter will return. To simplify the Converter registration process, Spring ODM provides a ConverterConfig class. Listing 8-6 shows the XML configuration for registering the toStringConverter instance.

Listing 8-6.

<bean id="toStringConverter" class="org.springframework.ldap.odm.typeconversion.impl.ConverterManagerFactoryBean$ConverterConfig">
   <property name="converter" ref="toStringConverter"/>
   <property name="fromClasses">
      <set>
         <value>java.lang.Integer</value>
      </set>
   </property>
   <property name="toClasses">
      <set>
         <value>java.lang.String</value>
      </set>
   </property>
</bean>

As you can see, ConverterConfig is an inner class of the org.springframework.ldap.odm.typeconversion.impl.ConverterManagerFactoryBean class. This configuration tells the ConverterManager to use the toStringConverter bean for converting java.lang.Integer types to String types. Internally, the converter is registered under a key that is computed using the following algorithm:

key = fromClass.getName() + ":" + syntax + ":" + toClass. getName();

Sometimes you would like the same converter instance to be used for converting from a variety of data types. The ToStringConverter, for example, can be used to convert additional types such as java.lang.Long, java.lang.Byte, java.lang.Boolean, etc. To address such scenarios, the ConverterConfig accepts a set of from and to classes that the converter can deal with. Listing 8-7 shows the modified ConverterConfig that accepts several fromClasses.

Listing 8-7.

<bean id="toStringConverter" class="org.springframework.ldap.odm.typeconversion.impl.ConverterManagerFactoryBean$ConverterConfig">
   <property name="converter" ref="toStringConverter" />
   <property name="fromClasses">
      <set>
         <value>java.lang.Byte</value>
         <value>java.lang.Integer</value>
         <value>java.lang.Boolean</value>
      </set>
   </property>
   <property name="toClasses">
      <set>
         <value>java.lang.String</value>
      </set>
   </property>
</bean>

Each class specified in the above fromClasses set would be paired with a class in the toClasses set for converter registration. So if you specify n fromClasses and m toClasses, it would result in n*m registrations for the converter. Listing 8-8 shows fromStringConverterConfig, which is quite similar to the previous configuration.

Listing 8-8.

<bean id="fromStringConverterConfig" class="org.springframework.ldap.odm.typeconversion.impl.ConverterManagerFactoryBean$ConverterConfig">
   <property name="converter" ref="fromStringConverter" />
   <property name="fromClasses">
      <set>
         <value>java.lang.String</value>
      </set>
   </property>
   <property name="toClasses">
      <set>
         <value>java.lang.Byte</value>
         <value>java.lang.Integer</value>
         <value>java.lang.Boolean</value>
      </set>
   </property>
</bean>

Once you have the necessary converter configuration, new ConverterManager instances can be created using the ConverterManagerFactoryBean. Listing 8-9 shows the required XML declaration.

Listing 8-9.

<bean id="converterManager" class="org.springframework.ldap.odm.typeconversion.impl.ConverterManagerFactoryBean">
   <property name="converterConfig">
      <set>
         <ref bean="fromStringConverterConfig"/>
         <ref bean="toStringConverterConfig"/>
      </set>
   </property>
</bean>

This concludes the setup needed for using the ODM framework. In the next sections, you will look at annotating the domain classes and using this configuration for LDAP reads and writes. Before you do that, let’s recap what you did up to this point (see Figure 8-2).

9781430263975_Fig08-02.jpg

Figure 8-2. OdmManager inner workings

  1. OdmManager instances are created by OdmManagerImplFactoryBean.
  2. OdmManager uses ConverterManager instances for conversion between LDAP and Java types.
  3. For the conversion from a specific type to another specific type, the ConverterManager uses a converter.
  4. ConverterManager instances are created by ConverterManagerFactoryBean.
  5. The ConverterManagerFactoryBean uses the ConverterConfig  instances to simplify the Converter registration. The ConverterConfig class takes the fromClasses, toClasses, and the converter that goes along with the relationship.

ODM Metadata

The org.springframework.ldap.odm.annotations package contains annotations that can be used to turn simple Java POJOs into ODM manageable entities. Listing 8-10 shows the Patron Java class that you will convert into an ODM entity.

Listing 8-10.

public class Patron {
   private String lastName;
   private String firstName;
   private String telephoneNumber;
   private String fullName;
   private String mail;
   private int employeeNumber;
 
   // Getters and setters
 
   @Override
   public String toString() {
      return "Dn: " + dn + ", firstName: " + firstName + ", fullName: " +       fullName + ", Telephone Number: " + telephoneNumber;
   }
}

You will start the conversion by annotating the class with @Entry. This marker annotation tells the ODM Manager that the class is an entity. It is also used to provide the objectClass definitions in LDAP that the entity maps to. Listing 8-11 shows the annotated Patron class.

Listing 8-11.

@Entry(objectClasses= { "inetorgperson", "organizationalperson", "person", "top" })
public class Patron {
   // Fields and getters and setters
}

The next annotation you need to add is the @Id. This annotation specifies the entry’s DN and can only be placed on a field that is a derivative of javax.naming.Name class. To address this, you will create a new field called dn in the Patron class. Listing 8-12 shows the modified Patron class.

Listing 8-12.

@Entry(objectClasses= { "inetorgperson", "organizationalperson", "person", "top" })
public class Patron {
 
   @Id
   private Name dn;
   // Fields and getters and setters
}

The @Id annotation in the Java Persistence API specifies the identifier property of the entity bean. Additionally, its placement determines the default access strategy the JPA provider will use for mapping. If the @Id is placed over a field, field access is used. If it is placed over a getter method, property access will be used. However, Spring LDAP ODM only allows field access.

The @Entry and @Id are the only two required annotations to make the Patron class an ODM entity. By default, all the fields in the Patron entity class will automatically become persistable. The default strategy is to use the name of the entity field as the LDAP attribute name while persisting or reading. In the Patron class, this would work for attributes such as telephoneNumber or mail because the field name and the LDAP attribute name are the same. But this would cause problems with fields such as firstName and fullName as their names are different from the LDAP attribute names. To address this, ODM provides the @Attribute annotation that maps the entity fields to object class fields. This annotation allows you to specify the name of the LDAP attribute, an optional syntax OID, and an optional type declaration. Listing 8-13 shows the completely annotated Patron entity class.

Listing 8-13.

@Entry(objectClasses = { "inetorgperson", "organizationalperson", "person", "top" })
public class Patron {
    
   @Id
   private Name dn;
    
   @Attribute(name = "sn")
   private String lastName;
 
   @Attribute(name = "givenName")
   private String firstName;
   private String telephoneNumber;
 
   @Attribute(name = "cn")
   private String fullName;
   private String mail;
 
   @Attribute(name = "objectClass")
   private List<String> objectClasses;
 
   @Attribute(name = "employeeNumber", syntax = "2.16.840.1.113730.3.1.3")
   private int employeeNumber;
    
   // Getters and setters
    
   @Override
   public String toString() {
      return "Dn: " + dn + ", firstName: " + firstName + "," + " fullName: " + fullName + ", Telephone Number: " + telephoneNumber;
   }
}

There are times where you wouldn’t want to persist certain fields of an entity class. Typically these involve fields that are computed. Such fields can be annotated with @Transient annotation indicating that the field should be ignored by OdmManager.

ODM Service Class

Spring-based enterprise applications typically have a service layer that holds the application’s business logic. Classes in the service layer delegate persistent specifics to a DAO or Repository layer. In Chapter 5, you implemented a DAO using LdapTemplate. In this section, you will create a new service class that uses the OdmManager as a DAO replacement. Listing 8-14 shows the interface of the service class you will be implementing.

Listing 8-14.

package com.inflinx.book.ldap.service;
 
import com.inflinx.book.ldap.domain.Patron;
 
public interface PatronService {
 
   public void create(Patron patron);
   public void delete(String id);
   public void update(Patron patron);
   public Patron find(String id);
}

The service class implementation is given in Listing 8-15. In the implementation, you inject an instance of OdmManager. The create and update method implementations simply delegate the calls to the OdmManager. The find method converts the passed-in id parameter to the fully qualified DN and delegates the actual retrieval to OdmManager’s read method. Finally, the delete method uses the find method to read the patron and uses the OdmManager’s delete method to delete it.

Listing 8-15.

package com.inflinx.book.ldap.service;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.ldap.core.DistinguishedName;
import org.springframework.ldap.odm.core.OdmManager;
import org.springframework.stereotype.Service;
import com.inflinx.book.ldap.domain.Patron;
 
@Service("patronService" )
public class PatronServiceImpl implements PatronService {
 
   private static final String PATRON_BASE = "ou=patrons,dc=inflinx,dc=com";
 
   @Autowired
   @Qualifier("odmManager" )
   private OdmManager odmManager;
    
   @Override
   public void create(Patron patron) {
      odmManager.create(patron);
   }
   @Override
   public void update(Patron patron) {
      odmManager.update(patron);
   }
   @Override
   public Patron find(String id) {
      DistinguishedName dn = new DistinguishedName(PATRON_BASE);
      dn.add("uid", id);
      return odmManager.read(Patron.class, dn);
   }
   @Override
   public void delete(String id) {
      odmManager.delete(find(id));
   }
}

The JUnit test to verify the PatronService implementation is shown in Listing 8-16.

Listing 8-16.

package com.inflinx.book.ldap.service;
 
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.ldapunit.util.LdapUnitUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.ldap.NameNotFoundException;
import org.springframework.ldap.core.DistinguishedName;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.inflinx.book.ldap.domain.Patron;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
 
@RunWith(SpringJUnit4ClassRunner.class )
@ContextConfiguration("classpath:repositoryContext-test.xml" )
public class PatronServiceImplTest {
 
   @Autowired
   private PatronService patronService;
   private static final String PORT = "12389";
   private static final String ROOT_DN = "dc=inflinx,dc=com";
 
   @Before
   public void setup() throws Exception {
      System.out.println("Inside the setup");
      LdapUnitUtils.loadData(new ClassPathResource("patrons.ldif"), PORT);
   }
 
   @After
   public void teardown() throws Exception {
      System.out.println("Inside the teardown");
      LdapUnitUtils.clearSubContexts(new DistinguishedName(ROOT_DN), PORT);
   }
 
   @Test
   public void testService() {
      Patron patron = new Patron();
          
      patron.setDn(new DistinguishedName("uid=patron10001," + "ou=patrons,dc=inflinx,dc=com"));
      patron.setFirstName("Patron");
      patron.setLastName("Test 1");
      patron.setFullName("Patron Test 1");
      patron.setMail("[email protected]" );
      patron.setEmployeeNumber(1234);
      patron.setTelephoneNumber("8018640759");
      patronService.create(patron);
 
      // Lets read the patron
      patron = patronService.find("patron10001");
      assertNotNull(patron);
    
      patron.setTelephoneNumber("8018640850");
      patronService.update(patron);
      patron = patronService.find("patron10001");
      assertEquals(patron.getTelephoneNumber(), "8018640850");
      patronService.delete("patron10001");
          
      try {
         patron = patronService.find("patron10001");
         assertNull(patron);
      }
      catch(NameNotFoundException e) {
      }
   }
}

The repositoryContext-test.xml file contains snippets of the configuration you have seen so far. Listing 8-17 gives the complete content of the XML file.

Listing 8-17.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
 
   <context:component-scan base-package="com.inflinx.book.ldap" />
   <bean id="contextSource" class="org.ldapunit.context.EmbeddedContextSourceFactory">
      <property name="port" value="12389" />
      <property name="rootDn" value="dc=inflinx,dc=com" />
      <property name="serverType" value="OPENDJ" />
   </bean>
   <bean id="odmManager" class="org.springframework.ldap.odm.core.impl.OdmManagerImpl">
      <constructor-arg name="converterManager" ref="converterManager" />
      <constructor-arg name="contextSource" ref="contextSource" />
      <constructor-arg name="managedClasses">
         <set>
            <value>com.inflinx.book.ldap.domain.Patron</value>
         </set>
      </constructor-arg>
   </bean>
   <bean id="fromStringConverter" class="org.springframework.ldap.odm.typeconversion.impl.converters.FromStringConverter" />
   <bean id="toStringConverter" class="org.springframework.ldap.odm.typeconversion.impl.converters.ToStringConverter" />
    
   <!-- Configuration information for a single instance of FromString -->
   <bean id="fromStringConverterConfig" class="org.springframework.ldap.odm.typeconversion.impl.ConverterManagerFactoryBean$ConverterConfig">
      <property name="converter" ref="fromStringConverter" />
      <property name="fromClasses">
         <set>
            <value>java.lang.String</value>
         </set>
      </property>
      <property name="toClasses">
         <set>
            <value>java.lang.Byte</value>
            <value>java.lang.Integer</value>
            <value>java.lang.Boolean</value>
         </set>
      </property>
   </bean>
   <bean id="toStringCoverterConfig" class="org.springframework.ldap.odm.typeconversion.impl.ConverterManagerFactoryBean$ConverterConfig">
      <property name="converter" ref="toStringConverter" />
      <property name="fromClasses">
         <set>
            <value>java.lang.Byte</value>
            <value>java.lang.Integer</value>
            <value>java.lang.Boolean</value>
         </set>
      </property>
      <property name="toClasses">
         <set>
            <value>java.lang.String</value>
         </set>
      </property>
   </bean>
   <bean id="converterManager" class="org.springframework.ldap.odm.typeconversion.impl.ConverterManagerFactoryBean">
      <property name="converterConfig">
         <set>
            <ref bean="fromStringConverterConfig"/>
            <ref bean="toStringCoverterConfig"/>
         </set>
      </property>
   </bean>
</beans>

Configuration Simplifications

The configuration in Listing 8-17 may look daunting at first. So to address this, let’s create a new ConverterManager implementation that simplifies the configuration process. Listing 8-18 shows the DefaultConverterManagerImpl class. As you can see, it uses the ConverterManagerImpl class internal to its implementation.

Listing 8-18.

package com.inflinx.book.ldap.converter;
 
import org.springframework.ldap.odm.typeconversion.ConverterManager;
import org.springframework.ldap.odm.typeconversion.impl.Converter;
import org.springframework.ldap.odm.typeconversion.impl.ConverterManagerImpl;
import org.springframework.ldap.odm.typeconversion.impl.converters.FromStringConverter;
import org.springframework.ldap.odm.typeconversion.impl.converters.ToStringConverter;
 
public class DefaultConverterManagerImpl implements ConverterManager {
 
   private static final Class[] classSet = { java.lang.Byte.class, java.lang.Integer.class, java.lang.Long.class, java.lang.Double.class, java.lang.Boolean.class };
   private ConverterManagerImpl converterManager;
 
   public DefaultConverterManagerImpl() {
      converterManager = new ConverterManagerImpl();
      Converter fromStringConverter = new FromStringConverter();
      Converter toStringConverter = new ToStringConverter();
      for(Class clazz : classSet) {
         converterManager.addConverter(String.class, null, clazz, fromStringConverter);
         converterManager.addConverter(clazz, null, String.class, toStringConverter);
      }
   }
 
   @Override
   public boolean canConvert(Class<?> fromClass, String syntax, Class<?> toClass) {
      return converterManager.canConvert(fromClass, syntax, toClass);
   }
 
   @Override
   public <T> T convert(Object source, String syntax, Class<T> toClass) {
      return converterManager.convert(source,syntax,toClass);
   }
}

Using this class reduces the needed configuration quite a bit, as shown in Listing 8-19.

Listing 8-19.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
 
   <context:component-scan base-package="com.inflinx.book.ldap" />
   <bean id="contextSource" class="org.ldapunit.context.EmbeddedContextSourceFactory">
      <property name="port" value="12389" />
      <property name="rootDn" value="dc=inflinx,dc=com" />
      <property name="serverType" value="OPENDJ" />
   </bean>
   <bean id="odmManager" class="org.springframework.ldap.odm.core.impl.OdmManagerImplFactoryBean">
      <property name="converterManager" ref="converterManager" />
      <property name="contextSource" ref="contextSource" />
      <property name="managedClasses">
         <set>
            <value>com.inflinx.book.ldap.domain.Patron</value>
         </set>
      </property>
   </bean>
   <bean id="converterManager" class="com.inflinx.book.ldap.converter.DefaultConverterManagerImpl" />
</beans>

Creating Custom Converter

Consider the scenario where your Patron class uses a custom PhoneNumber class for storing a patron’s phone number. Now, when a Patron class needs to be persisted, you need to convert the PhoneNumber class to String type. Similarly, when you read a Patron class from LDAP, the data in the telephone attribute needs to be converted into PhoneNumber class. The default ToStringConverter and FromStringConverter will not be useful for such conversion. Listing 8-20 and Listing 8-21 show the PhoneNumber and modified Patron classes, respectively.

Listing 8-20.

package com.inflinx.book.ldap.custom;
 
public class PhoneNumber {
 
   private int areaCode;
   private int exchange;
   private int extension;
    
   public PhoneNumber(int areaCode, int exchange, int extension) {
      this.areaCode = areaCode;
      this.exchange = exchange;
      this.extension = extension;
   }
    
   public boolean equals(Object obj) {
      if(obj == null || obj.getClass() != this.getClass())
      { return false; }
 
      PhoneNumber p = (PhoneNumber) obj;
         return (this.areaCode == p.areaCode) && (this.exchange == p.exchange) && (this.extension == p.extension);
   }
    
   public String toString() {
      return String.format("+1 %03d %03d %04d", areaCode, exchange, extension);
   }
 
   // satisfies the hashCode contract
   public int hashCode() {
      int result = 17;
      result = 37 * result + areaCode;
      result = 37 * result + exchange;
      result = 37 * result + extension;
      
          return result;
   }
}

Listing 8-21.

package com.inflinx.book.ldap.custom;
 
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 = { "inetorgperson", "organizationalperson", "person", "top" })
public class Patron {
 
   @Id
   private Name dn;
 
   @Attribute(name= "sn")
   private String lastName;
 
   @Attribute(name= "givenName")
   private String firstName;
 
   @Attribute(name= "telephoneNumber")
   private PhoneNumber phoneNumber;
 
   @Attribute(name= "cn")
   private String fullName;
   private String mail;
 
   @Attribute(name= "objectClass")
   private List<String> objectClasses;
 
   @Attribute(name= "employeeNumber", syntax = "2.16.840.1.113730.3.1.3")
    private int employeeNumber;
 
   // Getters and setters
 
   @Override
   public String toString() {
      return "Dn: " + dn + ", firstName: " + firstName + "," + " fullName: " + fullName + ", " + "Telephone Number: " + phoneNumber;
   }
}

To convert PhoneNumber to String, you create a new FromPhoneNumberConverter converter. Listing 8-22 shows the implementation. The implementation simply involves calling the toString method to perform the conversion.

Listing 8-22.

package com.inflinx.book.ldap.custom;
 
import org.springframework.ldap.odm.typeconversion.impl.Converter;
 
public class FromPhoneNumberConverter implements Converter {
 
   @Override
   public <T> T convert(Object source, Class<T> toClass) throws Exception {
      T result = null;
      if(PhoneNumber.class.isAssignableFrom(source.getClass()) && toClass.equals(String.class)) {
         result = toClass.cast(source.toString());
      }
      return result;
   }
}

Next, you need an implementation to convert the LDAP string attribute to Java PhoneNumber type. To do this, you create the ToPhoneNumberConverter, as shown in Listing 8-23.

Listing 8-23.

package com.inflinx.book.ldap.custom;
 
import org.springframework.ldap.odm.typeconversion.impl.Converter;
 
public class ToPhoneNumberConverter implements  Converter {
 
   @Override
   public <T> T convert(Object source, Class<T> toClass) throws Exception {
      T result = null;
      if(String.class.isAssignableFrom(source.getClass()) && toClass == PhoneNumber.class) {
      // Simple implementation
      String[] tokens = ((String)source).split(" ");
      int i = 0;
      if(tokens.length == 4) {
         i = 1;
      }
      result = toClass.cast(new PhoneNumber(
         Integer.parseInt(tokens[i]),
         Integer.parseInt(tokens[i+1]),
         Integer.parseInt(tokens[i+2])));
      }
      return result;
   }
}

Finally, you tie up everything in the configuration, as shown in Listing 8-24.

Listing 8-24.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
 
   <context:component-scan base-package="com.inflinx.book.ldap" />
   <bean id="contextSource" class="org.ldapunit.context.EmbeddedContextSourceFactory">
      <property name="port" value="12389" />
      <property name="rootDn" value="dc=inflinx,dc=com" />
      <property name="serverType" value="OPENDJ" />
   </bean>
   <bean id="odmManager" class="org.springframework.ldap.odm.core.impl.OdmManagerImpl">
      <constructor-arg name="converterManager" ref="converterManager" />
      <constructor-arg name="contextSource" ref="contextSource" />
      <constructor-arg name="managedClasses">
         <set>
            <value>com.inflinx.book.ldap.custom.Patron</value>
         </set>
      </constructor-arg>
   </bean>
   <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="fromPhoneNumberConverter" class="com.inflinx.book.ldap.custom.FromPhoneNumberConverter" />
   <bean id="toPhoneNumberConverter" class="com.inflinx.book.ldap.custom.ToPhoneNumberConverter" />
 
   <!-- Configuration information for a single instance of FromString -->
   <bean id="fromStringConverterConfig" class="org.springframework.ldap.odm.typeconversion.impl.ConverterManagerFactoryBean$ConverterConfig">
      <property name="converter" ref="fromStringConverter" />
      <property name="fromClasses">
         <set>
            <value>java.lang.String</value>
         </set>
      </property>
      <property name="toClasses">
         <set>
            <value>java.lang.Byte</value>
            <value>java.lang.Integer</value>
            <value>java.lang.Boolean</value>
         </set>
      </property>
   </bean>
   <bean id="fromPhoneNumberConverterConfig" class="org.springframework.ldap.odm.typeconversion.impl.ConverterManagerFactoryBean$ConverterConfig">
      <property name="converter" ref="fromPhoneNumberConverter" />
      <property name="fromClasses">
         <set>
            <value>com.inflinx.book.ldap.custom.PhoneNumber</value>
         </set>
      </property>
      <property name="toClasses">
         <set>
            <value>java.lang.String</value>
         </set>
      </property>
   </bean>
   <bean id="toPhoneNumberConverterConfig" class="org.springframework.ldap.odm.typeconversion.impl.ConverterManagerFactoryBean$ConverterConfig">
      <property name="converter" ref="toPhoneNumberConverter" />
      <property name="fromClasses">
         <set>
            <value>java.lang.String</value>
         </set>
      </property>
      <property name="toClasses">
         <set>
            <value>com.inflinx.book.ldap.custom.PhoneNumber</value>
         </set>
      </property>
   </bean>
   <bean id="toStringConverterConfig" class="org.springframework.ldap.odm.typeconversion.impl.ConverterManagerFactoryBean$ConverterConfig">
      <property name="converter" ref="toStringConverter"/>
      <property name="fromClasses">
         <set>
            <value>java.lang.Byte</value>
            <value>java.lang.Integer</value>
            <value>java.lang.Boolean</value>
         </set>
      </property>
      <property name="toClasses">
         <set>
            <value>java.lang.String</value>
         </set>
      </property>
   </bean>
   <bean id="converterManager" class="org.springframework.ldap.odm.typeconversion.impl.ConverterManagerFactoryBean">
      <property name="converterConfig">
         <set>
            <ref bean="fromPhoneNumberConverterConfig"/>
            <ref bean="toPhoneNumberConverterConfig"/>
            <ref bean="fromStringConverterConfig"/>
            <ref bean="toStringConverterConfig"/>
         </set>
      </property>
   </bean>
</beans>

The modified test case for testing the newly added converters is shown in Listing 8-25.

Listing 8-25.

@RunWith(SpringJUnit4ClassRunner.class )
@ContextConfiguration("classpath:repositoryContext-test3.xml")
public class PatronServiceImplCustomTest {
 
   @Autowired
   private PatronService patronService;
   private static final String PORT = "12389";
   private static final String ROOT_DN = "dc=inflinx,dc=com";
 
   @Before
   public void setup() throws Exception {
      System.out.println("Inside the setup");
      LdapUnitUtils.loadData(new ClassPathResource("patrons.ldif"), PORT);
   }
 
   @After
   public void teardown() throws Exception {
      System.out.println("Inside the teardown");
      LdapUnitUtils.clearSubContexts(new DistinguishedName(ROOT_DN), PORT);
   }
 
   @Test
   public void testService() {
      Patron patron = new Patron();
      patron.setDn(new DistinguishedName("uid=patron10001," + "ou=patrons,      dc=inflinx,dc=com"));
      patron.setFirstName("Patron"); patron.setLastName("Test 1");
      patron.setFullName("Patron Test 1");
      patron.setMail("[email protected]" );
      patron.setEmployeeNumber(1234);
      patron.setPhoneNumber(new PhoneNumber(801, 864, 8050));
      patronService.create(patron);
 
      // Lets read the patron
      patron = patronService.find("patron10001");
      assertNotNull(patron);
      
          System.out.println(patron.getPhoneNumber());
      patron.setPhoneNumber(new PhoneNumber(435, 757, 9369));
      patronService.update(patron);
      
          System.out.println("updated phone: " + patron.getPhoneNumber());
      patron = patronService.find("patron10001");
      
          System.out.println("Read the phone number: " + patron.getPhoneNumber());
      assertEquals(patron.getPhoneNumber(), new PhoneNumber(435, 757, 9369));
      
          patronService.delete("patron10001");
      try {
         patron = patronService.find("patron10001");
         assertNull(patron);
      }
      catch(NameNotFoundException e) {
      }
   }
}

Summary

Spring LDAP’s Object-Directory Mapping (ODM) bridges the gap between object and directory models. In this chapter, you learned the basics of ODM and looked at annotations for defining ODM mappings. You then took a deep dive into the ODM framework and built a Patron service and custom converters.

Up to this point in the book, you have created several variations of Service and DAO implementations. In the next chapter, you will explore Spring LDAP’s support for transactions.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset