Motivation

Interldap is in Java. We choose this language for the number of framework available, the diversity and width of its community, the open-source orientation of many Java communities, and many other good or bad reasons.

One of them is the possibility (and ease) to deliver really nice and integrated full stack of softwares : you can create demo package so that the user only have to have a Java environment enable, and then just launch the demo : it brings it own in memory test database, its own server, etc. Maven is a great tools for that (when it want to play nice) : download sources, run "mvn jetty:run", test.

One other is the ease of doing Unit Tests. Eventually, unit test have to be done. It's better if the test environment is simple to set up.

For a long time, InterLDAP had a real problem to achieve these two points. Well, as the name suggest, it heavily relays upon LDAP directories, more precisely open source LDAP directory. Well, who said OpenLDAP ?

Open LDAP has a lot of quality, and for a long, the first of them was that it was "open". But using OpenLDAP as a directory for testing an Java LDAP framework and/or application is quite a burden. And it forces a potential tester to install/configure something not exactly simple in his computer. Again, not the best way to ease the things.

But the wind of times blows, and it brings some really interesting thing, as FOSS LDAP directories totally written in Java. These directories - apart from being potential alternatives to the de fait monopole of OpenLDAP - could resolve our two concerns:

  • ease unit/integration test of our Java application;
  • ease the test of Java LDAP application : just download&test, no third software installation and configuration, possibility to use a temporary directory...

This document need a full rewrite, the things are evolving quite quickly in the OpenDS corner. It seems that the 1.0.0 version that should be out in a near future (falls 2007 ?) will bring a lot of facilities to embedded OpenDS (most are in place in the 1.0.0-beta4 release of august, the 26th). Neil A. Wilson's blog already gives some information on that point, especially with this presentation (pdf)


The candidates

As far as I know, there is two Open Source LDAP directories available at this time:

The two of them are LDAP directories written in Java, implementing LDAPv3 protocol, open source. Apache DS is a pure community contributed softawre, OpenDS is a Sun project, with Sun management, managing Sun teams.

We test the two of them with the only goal of using them as an embedded directory for testing and demo purpose. So, for us, here, things like scaling capabilities, ease of management, reliabilty, security, replication, or all the kind of thing an LDAP admin will put #1 in his “must have” list are not relevant.

The two candidates have a really welcoming community, and the developers are very reactive. The two communities found that using their directory as a (vulgar) testing tool was almost a perversion, but they help me to bring the things up and running. I found the ApacheDS documentation (again, for my purpose) really better than OpenDS ones (I continually loose myself in the OpenDS wiki), but for OpenDS defence, OpenDS is in alpha version (0.8), ApacheDS as already two stable version (1.0.x and 1.5.x). Finally, we choose to go OpenDS because ApacheDS do not support some extended control (especially proxy auth control (RFC 4370) on which most of our pooling API lays), but without that, we would have chosen ApacheDS.

Downloading and testing the source code

All the following example are used in Ldap core simple implementation. The source code is available with that command:

$ svn co svn://svn.forge.objectweb.org/svnroot/interldap/interldap-core-simple/trunk interldap-core-simple

Now, you should just have to launch:

$ cd interldap-core-simple
$ mvn test
and go take a coffe while Maven is downloading all his stuff. Alas, OpenDs is not available in a Maven repository, so you have to download it here and add it to your local maven repos. You have to take a version “core server” >= 0.9.0-build001 (which is whose used. If you take a newer one, take care to update both following command and pom.xml file). Unzip the archive, and add the core and je jar available in lib directory:
$ mvn install:install-file -DgroupId=org.opends -DartifactId=opends -DgeneratePom=true -Dversion=0.9.0-b001 -Dpackaging=jar -Dfile=/path/to/OpenDS.jar
$ mvn install:install-file -DgroupId=berkeleydb -DartifactId=je -DgeneratePom=true -Dversion=3.2.23 -Dpackaging=jar -Dfile=path/to/je.jar

You also will need ldapbp.jar, available as part of the Sun LDAP service provider or directly in Spring SVN here:

$ wget https://svn.sourceforge.net/svnroot/springframework/repos/repo-ext/com/sun/ldapbp/1.0/ldapbp-1.0.jar
$  mvn install:install-file -DgroupId=com.sun -DartifactId=ldapbp -DgeneratePom=true -Dversion=1.0 -Dpackaging=jar -Dfile=/path/toldapbp-1.0.jar

OK, this time, tests should run (and pass or not, depending of the state of the svn :)

Unit test for Java LDAP application

So, in the two following parts, I will show how it is possible to use these directories for test unit purpose, or nonetheless, how we achieve that.

We use Maven 2 for build and depency management, Spring for a large part of configuration, and we want to test a DAO based on Spring-LDAP.

We use a standard Maven 2 tree structure :

src
 |- main
 |    `- java 
 |        `- ldap.dao
 |             |- IldapDao.java
 |             `- SpringLdapDao.java
 `- test
      |- java 
      |   `- ldap
      |         |- util
      |         |    `- EmbeddedJavaLdapServer.java
      |         `- dao
      |             `- TestSpringLdapDao.java
      |
      `- resources
          `- ldap
                |- util
                |    `- [server resources]
                `- dao
                    `- [Spring conf for test DAO]

We want to test SpringLdapDao.java, EmbeddedJavaLdapServer.java is an utility class that gives macro function to set-up the directory and feed it with data for test.

The complete source code for the simple LDAP API using Spring-LDAP, and the tests ApacheDS and OpenDS are available on the forge, project's name interldap-core-simple .

LDAP Unit test with ApacheDS and Spring

This use case for ApacheDS is really well documented, so the main work was to follow what is written in Using ApacheDS for unit test . The doc is great, but we had to modify the procedure a little, because we use Spring (and so Spring test) in our application. So I just change the main class of the tutorial to be “EmbeddedApacheDs.java” (see : EmbeddedApacheDs.java ). This class has a setUp() method which set up the directory (ah ah, how original it is :), and inits a first partition named “dc=interldap,dc=org”. It also add the base element for that partition.

It's used in TestSpringELdapEntryDao_ApacheDS.java just as that:

//launch the ldap server
ldapServer.setUp();

//import ldif InputStream is = ldapServer.getClass().getResourceAsStream("bootstrap_test.ldif");

if(null == is) { throw new RuntimeException("The boostrap LDIF file has not been found"); }

ldapServer.importLdif(is);

And that's all, quite pleasant… Unfortunatlly, ApacheDS does not support the proxy auth extended control, so we have to abandon it.

LDAP Unit test with OpenDS and Spring

This part was a little harder to achieve. The documentation on OpenDS wiki on this topic is really light, but the mailing list gives a good starting point with the thread Open DS as an embedded LDAP server to unit test app . This thread point the class “org.opends.server.TestCaseUtils.java” wich take care of set-up an environment for an OpenDS instance.

I will explain in the following paragraph what are the main piece of configuration to take care of.

Directory structure

OpenDS need a directory structure with a read/write access to work. These directories are used to store data, locks, logs, etc. This is the minimal directory structure (that I found, perhaps it's not relevant in all cases) !

opends-root-directory

|-- config

| |-- config.ldif

| |-- schema

| `-- upgrade

|-- db

|-- locks

`-- logs

Configuration Files

OpenDS needs some configuration file to bootstrap :

  • config.ldif : the main OpenDS config file, explains here ;
  • standards schema : these file describe the standard schema supported by OpenDS ;
  • “config upgrade” files : these files do not seem to be mandatory, but OpenDS complain if they are not present.
These file can be found in the “config” and config/schema, config/upgrade) directory of OpenDS distribution. So, the completed directory structure, with config files :

opends-root-directory

||-- config

| |-- config.ldif

| |-- schema

| | |-- 00-core.ldif

| | |-- 01-pwpolicy.ldif

| | |-- 02-config.ldif

| | |-- 03-changelog.ldif

| | |-- 03-rfc2713.ldif

| | |-- 03-rfc2714.ldif

| | |-- 03-rfc2739.ldif

| | |-- 03-rfc2926.ldif

| | |-- 03-rfc3112.ldif

| | |-- 03-rfc3712.ldif

| | |-- 03-uddiv3.ldif

| | `-- 04-rfc2307bis.ldif

| `-- upgrade

| |-- config.ldif.1918

| |-- schema.ldif.1918

| `-- schema.ldif.current

|-- db

|-- locks

`-- logs

Bootstrap algorithm

The start up algorithm is quite simple :

  1. create the working directory structure ;
  2. get the singleton instance of directory server ;
  3. bootstrap the server ;
  4. set-up with initial config the server ;
  5. start the server.
In code, this is traduced by :
public static void startServer() throws Exception {
  // copy to r/w location and init directory structure
  //this funcion
  initOpendsDirectory(“path/to/config/directories/to/copy/from”, ”/path/to/working/directory”);

String configClass = "org.opends.server.extensions.ConfigFileHandler";

DirectoryServer directoryServer = DirectoryServer.getInstance(); directoryServer.bootstrapServer();

directoryServer.initializeConfiguration(configClass,”/path/to/working/directory/config/config.ldif”); directoryServer.startServer(); }

private static void initOpendsDirectory(String copyFromConfigDirectory, String targetRootDirectory) throws IOException { File workingDirectory = new File(targetRootDirectory); // delete recursively workingDirectory if (workingDirectory.exists()) { FileUtils.deleteDirectory(workingDirectory); } if (workingDirectory.exists()) { throw new IOException("Failed to delete: " + workingDirectory); } if (!workingDirectory.mkdirs()) { throw new IOException("Failed to create:" + workingDirectory); }

// copy config & schema FileUtils.copyDirectory(new File(copyFromConfigDirectory), new File(targetRootDirectory , CONFIG_DIR));

// create missing directories // db backend, logs, locks String[] subDirectories = { "db", "ldif", "locks", "logs" }; for (String s : subDirectories) { new File(workingDirectory, s).mkdir(); } }

And now, you should have a running OpenDS, using the default back-end.

Just a word about Back-ends

OpenDS use several back-ends to store data. It is identified by its ID, and need to be initialised before being used. The default config.ldif file set-up a standard, Berkeley DB JE enabled back-end, identified by “userRoot”. For the test, it might be useful to have an in-memory back-up that can be re-initialised between two tests.

This code enable a memory back-end :

public static void initializeTestBackend(boolean createBaseEntry) throws Exception {
  MemoryBackend memoryBackend = null;
  DN baseDN = DN.decode(“base.dn.for.test”);
  if (memoryBackend == null) {
    memoryBackend = new MemoryBackend();
    memoryBackend.setBackendID("test");
    memoryBackend.setBaseDNs(new DN[] { baseDN });
    memoryBackend.initializeBackend();
    DirectoryServer.registerBackend(memoryBackend);
  }

memoryBackend.clearMemoryBackend();

if (createBaseEntry) { Entry e = createEntry(baseDN); memoryBackend.addEntry(e, null); } }

Examples

A complete example of OpenDS used for unit testing purpose is available in the test for a simple DAO for InterLDAP core implementation.

An other example is the use of OpenDS as an embedded LDAP server in a webapp. The LDAP server is embedded to facilitate tests and development : just downlod the webapp, launch jetty:run, and enjoy (without the burden of an LDAP directory configuration).

See the InterLDAP WUI using Tapestry 5 project.