LdapTemplate: AttributesMapper vs ContextMapper

BACKGROUND

When using Spring’s LdapTemplate, there are two ways to transform the queried results: AttributesMapper and ContextMapper.

List<MyBean> list = ldapTemplate.search(
    '',
    '(cn=some-group-name)',
    // AttributesMapper or ContextMapper 
)

Here’s the comparison between these mapper classes.

AttributesMapper

If you are migrating your existing LDAP queries to Spring’s LdapTemplate, AttributesMapper seems ideal because you can copy most of the code over because it provides javax.naming.directory.Attributes.

List<MyBean> list = ldapTemplate.search(
    '',
    '(cn=some-group-name)',
    new AttributesMapper<MyBean>() {
        @Override
        MyBean mapFromAttributes(final Attributes attributes) throws NamingException {
            return new MyBean(
                cn: attributes.get('cn')?.get(),
                members: attributes.get('member')?.getAll()?.toSet() as Set<String> ?: []
            )
        }
    }
)

However, you have to handle possible null values if the attribute keys do not exist.

ContextMapper

With ContextMapper, it handles null values for us. Spring also provides an abstract class called AbstractContextMapper to further simplify the code.

List<MyBean> list = ldapTemplate.search(
    '',
    '(cn=some-group-name)',
    new AbstractContextMapper<MyBean>() {
        @Override
        protected MyBean doMapFromContext(final DirContextOperations ctx) {
            return new MyBean(
                cn: ctx.getStringAttribute('cn'),
                members: ctx.getStringAttributes('member')
            )
        }
    }
)

Spring: Component Scan Selected Classes

PROBLEM

Let’s assume we have a package with the following classes where each class is either annotated with Spring’s @Service, @Component, @Controller or @Repository.

app
├── A.groovy
├── B.groovy
├── C.groovy
├── D.groovy
└── E.groovy

When writing unit test, we want Spring to component scan class A and class B.

SOLUTION

Before we begin, we configure Log4j to log Spring in debug level.

<logger name="org.springframework">
    <level value="debug"/>
</logger>

Step 1

If we configure the test class like this…

@ContextConfiguration
class ASpec extends Specification {
    @Configuration
    @ComponentScan(
            basePackageClasses = [A]
	)
    static class TestConfig {
    }

    def "..."() {
        // ...
    }
}

It will scan all Spring components that reside in the same package as class A.

Debugging log:-

[DEBUG] [ClassPathBeanDefinitionScanner] [findCandidateComponents:294] - Identified candidate component class: file [/path/target/classes/app/A.class]
[DEBUG] [ClassPathBeanDefinitionScanner] [findCandidateComponents:294] - Identified candidate component class: file [/path/target/classes/app/B.class]
[DEBUG] [ClassPathBeanDefinitionScanner] [findCandidateComponents:294] - Identified candidate component class: file [/path/target/classes/app/C.class]
[DEBUG] [ClassPathBeanDefinitionScanner] [findCandidateComponents:294] - Identified candidate component class: file [/path/target/classes/app/D.class]
[DEBUG] [ClassPathBeanDefinitionScanner] [findCandidateComponents:294] - Identified candidate component class: file [/path/target/classes/app/E.class]

Step 2

We can set includeFilters to include just class A and class B…

@ContextConfiguration
class ASpec extends Specification {
    @Configuration
    @ComponentScan(
            basePackageClasses = [A],
            includeFilters = [@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = [A, B])]
	)
    static class TestConfig {
    }

    def "..."() {
        // ...
    }
}

… but it doesn’t do anything.

Debugging log:-

[DEBUG] [ClassPathBeanDefinitionScanner] [findCandidateComponents:294] - Identified candidate component class: file [/path/target/classes/app/A.class]
[DEBUG] [ClassPathBeanDefinitionScanner] [findCandidateComponents:294] - Identified candidate component class: file [/path/target/classes/app/B.class]
[DEBUG] [ClassPathBeanDefinitionScanner] [findCandidateComponents:294] - Identified candidate component class: file [/path/target/classes/app/C.class]
[DEBUG] [ClassPathBeanDefinitionScanner] [findCandidateComponents:294] - Identified candidate component class: file [/path/target/classes/app/D.class]
[DEBUG] [ClassPathBeanDefinitionScanner] [findCandidateComponents:294] - Identified candidate component class: file [/path/target/classes/app/E.class]

Step 3

To fix this, we set useDefaultFilters to false to disable any automatic detection of classes annotated with Spring’s @Service, @Component, @Controller or @Repository.

@ContextConfiguration
class ASpec extends Specification {
    @Configuration
    @ComponentScan(
            basePackageClasses = [A],
            useDefaultFilters = false,
            includeFilters = [@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = [A, B])]
    )
    static class TestConfig {
    }

    def "..."() {
        // ...
    }
}

Now, we get the intended behavior.

Debugging log:-

[DEBUG] [ClassPathBeanDefinitionScanner] [findCandidateComponents:294] - Identified candidate component class: file [/path/target/classes/app/A.class]
[DEBUG] [ClassPathBeanDefinitionScanner] [findCandidateComponents:294] - Identified candidate component class: file [/path/target/classes/app/B.class]

Better Preconditions: v0.1.0

DEPENDENCY

<dependency>
  <groupId>com.github.choonchernlim</groupId>
  <artifactId>better-preconditions</artifactId>
  <version>0.1.0</version>
</dependency>

Introduction

The goal of Better Preconditions is to provide a set of Java APIs that allows developers to create succinct, yet readable and testable preconditions.

Why Write Preconditions?

Let’s assume we have the following code:-

public void save(final String name, final LocalDate birthDate) {
    dao.save(new Entity(name.toUpperCase(), birthDate));
}

Although this example is simple and trivial, every developer that looks at this code will interpret this API differently. For example:-

  • Can name be blank?
  • What if we pass in a null value for name?
  • Can birth date be null?
  • Can birth date be after today’s date?

The truth of the matter is we cannot create an API that handles every possible scenario. Otherwise, we will never get our products out the door.

Thus, it is better to safeguard our API with a set of preconditions to ensure our fellow developers (or even you) know what this API needs before it performs the real work. Further, it provides a living documentation that would never go stale. Here’s an example of what the preconditions might look like:-

public void save(final String name, final LocalDate birthDate) {
    // precondition 1: name cannot be blank
    // precondition 2: birth date cannot be null
    // precondition 3: birth date cannot be after today's date

    dao.save(new Entity(name.toUpperCase(), birthDate));
}

What’s wrong with Guava Preconditions?

The biggest advantage of using Guava Preconditions is its flexibility. The biggest disadvantage of it is also its flexibility. Here’s an example written with Guava Preconditions:-

public void save(final String name, final LocalDate birthDate) {
    checkArgument(!nullToEmpty(name).trim().isEmpty(), "Name cannot be blank");
    checkNotNull(birthDate, "Birth date cannot be null");
    checkArgument(!birthDate.isAfter(LocalDate.now()), "Birth date cannot be after today's date");

    dao.save(new Entity(name.toUpperCase(), birthDate));
}

A couple of observations:-

  • Too verbose, which makes the code very messy. If we start inserting the raw values into the error messages, it becomes even messier.
  • We use checkArgument(..) and checkNotNull(..) most of the time. When an exception is thrown, we either get IllegalArgumentException or NullPointerException. This makes the API very confusing to unit test if there are many preconditions.

Introducing the power of Better Preconditions

Let’s rewrite the example above with Better Preconditions:-

public void save(final String name, final LocalDate birthDate) {
    expect(name, "Name")
            .not().toBeBlank()
            .check();

    expect(birthDate, "Birth Date")
            .not().toBeNull()
            .not().toBeAfter(LocalDate.now(), "Today's Date")
            .check();

    dao.save(new Entity(name.toUpperCase(), birthDate));
}

A couple of observations:-

  • Short and succinct.
  • Each thrown exception provides very useful error message for debugging purpose. For example, if birth date is after today’s date, the error message would be Birth Date [ 2015-02-01 ] must not be after Today's Date [ 2015-01-01 ]
  • When one of the preconditions fails, a specific exception is thrown. For example:-
    • blank name throws StringBlankPreconditionException
    • null birth date throws ObjectNullPreconditionException
    • birth date after today’s date throws JodaTimeAfterPreconditionException

    This makes it simpler to unit test because we can easily catch specific exception without any doubts.

Conclusion

Lastly, play around with it. This post barely scratches the surface of what Better Preconditions can do for you. If it works for you, great. If it doesn’t work for you and you are still interested to use it, create an issue at my GitHub page so that I can fix it. Due to my current real world workload, I’m using the 80/20 rule… the provided APIs should solve 80% of the problem.

To learn more about Better Preconditions, visit https://github.com/choonchernlim/better-preconditions

Guava: Testing equals(..) and hashcode(..)

PROBLEM

Let’s assume we want to test the following equals(..):-

public class Person {
    private String name;
    private int age;

    @Override
    public boolean equals(Object o) {
        if (o == null || getClass() != o.getClass()) {
            return false;
        }

        Person person = (Person) o;
        return Objects.equal(name, person.name);
    }

    @Override
    public int hashCode() {
        return Objects.hashCode(name);
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

A correctly implemented equals(..) must be reflexive, symmetric, transitive, consistent and handles null comparison.

In another word, you have to write test cases to pass at least these 5 rules. Anything less is pure bullshit.

SOLUTION

You can write these tests yourself… or you can leverage Guava’s EqualsTester. This library will test these 5 rules and ensure the generated hashcode matches too.

First, include the needed dependency:-

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava-testlib</artifactId>
    <version>18.0</version>
    <scope>test</scope>
</dependency>

Instead of writing JUnit tests, I’ll be writing Spock specs, which is essentially built on top of Groovy, because it allows me to write very clear and clean tests.

class PersonSpec extends Specification {

    def person = new Person(name: 'Mike', age: 10)

    def "equals - equal"() {
        when:
        new EqualsTester().
                addEqualityGroup(person,
                                 new Person(name: 'Mike', age: 10),
                                 new Person(name: 'Mike', age: 20)).
                testEquals()

        then:
        notThrown(AssertionFailedError.class)
    }

    def "equals - not equal"() {
        when:
        new EqualsTester().
                addEqualityGroup(person,
                                 new Person(name: 'Kurt', age: 10)).
                testEquals()

        then:
        thrown(AssertionFailedError.class)
    }
}

Oh… wait for it… BOOM!

Java + Groovy: Creating Immutable List

Java: Mutable List

// class java.util.Arrays$ArrayList
final List mutableList = Arrays.asList(1, 2, 3);

Java: Immutable List

// class java.util.Collections$UnmodifiableRandomAccessList
final List immutableList = Collections.unmodifiableList(Arrays.asList(1, 2, 3));

Java: Immutable List using Guava

// class com.google.common.collect.RegularImmutableList
final ImmutableList guavaImmutableList = ImmutableList.of(1, 2, 3);

Groovy: Immutable List

// class java.util.Collections$UnmodifiableRandomAccessList
final def groovyImmutableList = [1, 2, 3].asImmutable()