devxlogo

POJO-Based Solutions for LDAP Access: One Good, One Better

POJO-Based Solutions for LDAP Access: One Good, One Better

y keeping each class focused on a single responsibility, plain old Java object (POJO)-based application development improves attributes such as readability, testability, and maintainability, which increases overall software quality. Nevertheless, non-trivial applications need to deal with concerns other than primary business logic. Failing to cleanly address the multiple concerns involved with such applications could lead to classes that take on too many responsibilities. Dependency injection (DI), annotation, and aspect-oriented programming (AOP) are three proven techniques that can help manage these complexities and ensure that POJOs remain true POJOs (click here for a good summary on how they do so).

To demonstrate these techniques, this article compares two solutions for reading LDAP-stored configuration data, a common application configuration requirement. The first uses Spring LDAP, which benefits from dependency injection, and the second takes advantage of Java annotation and AOP. You will learn not only how to address this common configuration need, but you also will see how following sound design principles leads to better solutions and why advanced language features help.

Figure 1. LDAP Tree: The LDAP tree containing the sample configuration data is stored in OpenLDAP and rendered by the Apache Studio LDAP browser.

The Configuration Data

Sidebar 1. Defining a Custom Schema for Configuration Data defines and explains a custom schema to specify the configuration data. However, you can easily adapt the solution to any schema. After loading the schema into a LDAP server of your choosing, you can use your own LDAP browser to enter some configuration data (see Figure 1).

The list of common names (CN) defines the distinguished name (DN), which uniquely identifies a particular configuration data. For example, cn=threshold, cn=app1, dc=my-domain, and dc=com traverse the LDAP tree backwards from the leaf node with the CN of threshold to the root.

Click here to download the source code for the solutions.

Solution 1. Spring LDAP-Enabled

The next step is to read the data from the applications. The data access object (DAO) defines the abstraction for reading the configuration data:

public interface PropertyDAO {    Object getProperty(String[] key, PropertyType type);}

For LDAP stored properties, the search key specifies the ordered list of CNs that uniquely identifies a particular LDAP entry. PropertyType is an enumeration of various return types that the calling application client expects of the property values.

The Java API for accessing LDAP suffers from similar shortcomings as JDBC. It forces you to deal with many mundane, low-level details. Spring LDAP, following a similar objective as Spring JDBC, simplifies the API by raising the abstraction level; it translates low-level checked exceptions into higher-level, Spring LDAP-defined runtime exceptions.

In this example, the LdapPropertyDAOImpl class implements the DAO interface using Spring LDAP (See Listing 1. DAO Implementation). LdapTemplate is the key class provided by Spring LDAP. The lookup method expects two parameters. The first is the DN, the second is an AttributesMapper that maps from LDAP attributes to the expected configuration data type. ATTRIBUTE_TYPE specifies the LDAP data schema.

The TypeConverter class uses Java reflection to convert String types to various Java built-in types:

public class TypeConverter {    public static Object fromStringTo(PropertyType type, String value) {        try {            if (PropertyType.STRING.equals(type)) {                return value;            } else {                Class c = Class.forName((type.getFQCN()));                 Method m = c.getMethod("valueOf", String.class);                 Object obj = m.invoke(null, value);                 m = obj.getClass().getMethod(type.getPrimitiveType() + "Value");                 return m.invoke(obj, (Object[])null);            }        } catch (Exception e) {            throw new RuntimeException(e);        }    }    public static Collection toStringCollection(Attribute attr)         throws NamingException {        return Collections.list(attr.getAll());    }}

You can add support for additional types such as Date if needed. The changes would be isolated to TypeConverter and PropertyType. This implementation handles only collections of type String. To work with collections of other types, you need to pass an extra parameter specifying the collection element type in the calling client application. The reason for this, as explained by the Java Tutorials, is that generics are implemented using type erasure?a process where the compiler removes the type parameters and type arguments information within a class or method.

The Account class demonstrates how to call the DAO to retrieve a configurable threshold value through LDAP:

public class Account {    public void businessLogicUsingThreshold() {        long threshold = getThreshold();        // business logic using the threshold ...    }    long getThreshold() {        String[] key = new String[] {            "dc", "com", "dc", "my-domain", "cn", "app1", "cn", "threshold"};        return ((Long)dao.getProperty(key, PropertyType.LONG)).longValue();     }    public void setDao(PropertyDAO dao) {        this.dao = dao;    }    private PropertyDAO dao;}

The getThreshold() implementation invokes the DAO’s getProperty method to retrieve a property value of primitive type long. The first parameter is the LDAP property key, constructed with an array of Strings specifying the ordered list of CN that uniquely identifies the node in the LDAP tree. The second parameter indicates that the expected return type is long. Because the DAO interface returns the generic Object type, the return needs to be cast to the expected type.

This is a fairly good solution (see Figure 2 for class diagrams). There is a clear separation between the interface and the implementation, so that a different implementation?for example, one based on property files?can be substituted without affecting the client. The LDAP DAO implementation delegates to Spring LDAP’s LdapTemplate class to handle low-level LDAP access details, which simplifies the implementation. For frequently accessed data such as various logging levels, you could improve the read performance by introducing caching at the DAO implementation level. An existing caching library, such as ehcache, provides flexible and high-performance caching.

Figure 2. Class Diagrams for Solution 1: Here are the major components that implement the DAO pattern, based on Spring LDAP.

Spring’s DI feature makes the dependency on the DAO simply a property of the Account class. The Spring container handles the lifecycle of the DAO, which frees the Account class from the responsibilities of directly instantiating the DAO. This approach also removes the dependency on any particular DAO implementation. Because you can easily substitute different implementations, stubbing and mock-based unit testing are easier. Spring manages the LdapDAOImpl class’s dependency on LdapTemplate in a similar fashion.

The Spring configuration specifies the dependency of Account on the DAO implementation, which in turn depends on LdapTemplate:

  …

Solution 2. Enhanced with Annotation and AOP

Although the DAO interface requires only the property key and the property type as the two input parameters, the calling syntax is more complicated than necessary. You have to pass the data type in explicitly, which duplicates what is already in the getter return type. The construction of the array type key, coupled with the explicit type cast, also makes the calling syntax less clear. A much cleaner version based on Java 5 annotations is possible:

public class AnnotatedAccount {    public void businessLogicUsingThreshold() {        System.out.println("threshold = " + getThreshold());        // business logic using the threshold...    }    @PropertyKey(        {"dc", "com", "dc", "my-domain", "cn", "app1", "cn", "threshold"})    long getThreshold() {        return threshold;    }    void setThreshold(long value) {        this.threshold = value;    }    private long threshold;}

The annotation represents the property key, while the getter’s signature contains the data type. As long as the two values are made available to the DAO implementation at runtime, this new solution could reuse the same DAO while simplifying the client interface.

The AspectJ implementation of AOP can extract both the annotation value and the getter return type at runtime. Because the explicit key construction and the type cast are absent, the resulting client code is cleaner and can focus purely on its account-related business logic.

First, let’s define the annotation that is to be consumed by an aspect:

@Documented@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public @interface PropertyKey {    /**     * for properties stored in LDAP, the key is the array of Common Names     * that represents the Distinguished Name, which need to match the      * top-down hierarchical order of the CNs     */    String[] value();}

This annotation specifies the following:

  • The annotation is part of the public API of the annotated elements.
  • It is to be retained by the JVM, so it can be read reflectively at runtime.
  • It can be used to annotate only method declarations.
  • The String array specifies the ordered list of CNs that defines the DN.

Next, the PropertyAccess aspect extracts the key from the annotation and the return type from the method signature. It then passes them to the DAO:

aspect PropertyAccess {    pointcut propertyGetter(PropertyKey key) : @annotation(key);    Object around(PropertyKey key) : propertyGetter(key) {        String returnType =             ((MethodSignature)thisJoinPointStaticPart.getSignature()).                getReturnType().getName();        return dao.getProperty(            key.value(),             PropertyType.fromString(returnType));    }    public void setDao(PropertyDAO dao) {        this.dao = dao;    }    private PropertyDAO dao;}

The aspect first defines the propertyGetter pointcut that captures the methods marked with the annotation and makes the annotation available to aspect advice methods at runtime. The around advice obtains the return type from the joinpoint context and the DN from the annotation values. It then passes them to the DAO to perform LDAP lookup. From the DAO onward, the program flow is the same as the Spring LDAP-enabled version of the solution.

The new Spring configuration file is very similar to the first version, except now it is the aspect that has a dependency on the DAO. Because of the strong integration between Spring and AspectJ, Spring can treat the aspect almost as a regular POJO:

                                                                                        … 

Comparing the Two Solutions

The most noticeable difference between the class diagrams of the two solutions is the dependency: in the first version, the Account class is directly dependent on the DAO interface, and in the second, the aspect depends on the DAO (See Figure 3). The annotated version of the Account and the aspect are both dependent on annotation PropertyKey in the second version.

Figure 3. Class Diagrams for Solution 2: The first solution is enhanced with annotation and AOP.

First of all, the new version makes unit testing easier. To remove the dependency on LDAP, the first version would require you to create a mock POJO implementation of the property client:

&public class MockPropertyDAO implements PropertyDAO {    @Override    public Object getProperty(String[] key, PropertyType type) {        return keyValues.get(Arrays.toString(key));    }    public void setProperty(String[] key, Object value) {        keyValues.put(Arrays.toString(key), value);    }    private Map keyValues = new HashMap();}

The test case for the Account class then has the mock DAO injected into the Account class so that you could set different threshold values for different test scenarios:

public class AccountTest {    @BeforeClass    public static void setUpBeforeClass() throws Exception {        account.setDao(dao);    }    @Test    public void testThreshold1() {        dao.setProperty(thresholdKey, 1000L);        assertEquals(1000L, account.getThreshold());        account.businessLogicUsingThreshold();        // validation logic...    }    private static Account account = new Account();    private static MockPropertyDAO dao = new MockPropertyDAO();    private String[] thresholdKey = new String[] {        "dc", "com", "dc", "my-domain", "cn", "app1", "cn", "threshold"};}

In contrast, the second version would require you to expose the LDAP configuration data as a POJO property. Either by excluding the aspect from the compilation of the test classes or with modifications of the pointcut, the test case can just call the setter to set the property to whatever value the test scenarios need.

For example, this modified pointcut renders the aspect advice non-operational if the getters are called from JUnit4 test cases:

pointcut propertyGetter(PropertyKey key) :     @annotation(key) && !cflow(junit4TestCase());pointcut junit4TestCase() :    execution(@Test * *()) ||     execution(@BeforeClass * *()) || execution(@AfterClass * *()) ||     execution(@Before * *()) || execution(@After * *());

Compared with AccountTest, the test for annotated Account is simpler and more intuitive because you do not need to deal with DAO and you manipulate the desired property directly through the annotated Account:

public class AnnotatedAccountTest {    @Test    public void testThreshold1() {        account.setThreshold(1000L);        assertEquals(1000L, account.getThreshold());        account.businessLogicUsingThreshold();        // validation logic...    }    private AnnotatedAccount account = new AnnotatedAccount();}

What other benefits does the new solution bring? Compare the two versions of the getters:

// version 1private long getThreshold() {    String[] key = new String[] {        "dc", "com", "dc", "my-domain", "cn", "app1", "cn", "threshold"};    return ((Long)dao.getProperty(key, PropertyType.LONG)).longValue(); }// version 2@PropertyKey({"dc", "com", "dc", "my-domain", "cn", "app1", "cn", "threshold"})private long getThreshold() {    return threshold;}

As you can see, the annotated version becomes more readable because it is succinct. A less obvious benefit is that with annotation, the style of accessing LDAP changes from imperative to declarative. The annotation specifies the additional information regarding the getter, not how it is going to be consumed. It further raises the abstraction level for dealing with configuration data. So even if the DAO interface changes from String array to comma-separated-value String, the client does not need to change.

With the help of the aspect, the expected return type is captured by the joint point context. So, the client does not need to pass in explicitly the type value that is already in the getter signature. Moreover, because the around advice automatically casts the return type to match the annotated method return type, the need for explicit type casting (as required by the first version) is eliminated, further simplifying the calling syntax. This results in client code that is both easy to read and change. These benefits stand out even more with a larger code base that has many LDAP-based configuration values.

From a Good Solution to a Better Solution

All the good code qualities?such as testability, simplicity, readability, and maintainability?derive from the fact that the client code is POJO-based. Through the combined use of DI, annotation, and AOP, the Account class manages to remain a POJO even when faced with multiple application concerns.

When running unit tests, you can easily remove the dependency on LDAP by performing dependency injection with different configurable values. Instead of explicitly calling out to read the LDAP value, annotation marks the getter and specifies the property key. This prevents the infrastructure service-related code from tangling with the core business logic.

Finally, the aspect consumes the annotation and the getter signature and makes the calls to the DAO from a single place. This prevents LDAP access code from scattering. Because the separate concerns are neatly handled in a modular manner, code becomes clear, and changes are isolated and easier to make.

The initial solution?building on DI-based Spring LDAP and following the DAO pattern?is already reasonably good. By relentlessly eliminating redundancy and reducing complexity, and armed with good understanding of advanced techniques such as annotation and AOP, you will discover a simpler and more modular solution.

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist