ApacheDSを使ってLDAPアクセスをユニットテストする(カスタムスキーマ編)

前回の記事の続きです。LDAPでカスタムスキーマを使っている場合に、ApacheDSでユニットテストができるかどうか調べてみた。

参考

やり方

参考ページを見ると、ApacheDSではバージョン1.5からdynamic schema updatesに対応したとある。LDAPサーバの再起動なしにスキーマの更新ができるようになっている。ということでユニットテストに使いやすくなっている。

参考ページによると、やり方としてはいくつかある。 Apache Directory Studio Schema Editorを使う方法は、ユニットテストに組み込めないので除外。

  1. Using LDIF to load schema elements in RFC 4512 format (RFC 4512フォーマットのLDIFを使う方法)
  2. Using JNDI to add the schema elements programmatically (JNDIを使ってプログラムで追加する方法)
  3. Using JNDI to add schema elements in RFC 4512 format programmatically (RFC 4512フォーマットをJNDIを使ってプログラムで追加する方法)

2と3はLDIFの内容をそのまま投入するかどうかの違いであんまり変わらないので、1と3について紹介する。また、調べてみるとApacheDSのテストフレームワークを拡張してプログラムで追加する方法もできるので、これも紹介しておく。

RFC 4512フォーマットのLDIFを使う方法

ApacheDSでは、RFC 4512フォーマットのLDIFでスキーマを登録することができる。作成するLDIFは以下のような形式となる。DNは"cn=schema"とすること。クラスパスからロードするため、作成したLDIFはsrc/test/resourcesに配置しておく。

dn: cn=schema
changetype: modify
add: attributeTypes
attributeTypes: ( 1.3.6.1.4.1.18060.0.4.3.2.1
        NAME 'numberOfGuns'
        DESC 'Number of guns of a ship'
        EQUALITY integerMatch
        SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
        SINGLE-VALUE
 )
-
add: objectClasses
objectClasses: ( 1.3.6.1.4.1.18060.0.4.3.3.1
        NAME 'ship'
        DESC 'An entry which represents a ship'
        SUP top
        STRUCTURAL
        MUST cn
        MAY ( numberOfGuns $ description )
 )
-

DN"cn=schema"に対して、複数の操作を行っているので"-"で区切る。また、最後の"-"も必要なので注意*1

このLDIFをユニットテスト実行時にLDAPにインポートするためには、テストデータと同様に@ApplyLdifFilesでLDIFファイル名を指定すれば良い。ポイントとしては、テストデータのインポートの前にスキーマを更新するように、LDIFの順番に注意すること。

@RunWith(FrameworkRunner.class)
@CreateLdapServer(transports = { @CreateTransport(protocol = "LDAP", port = 50389) })
@CreateDS(partitions = @CreatePartition(name = "example", suffix = "dc=example,dc=org"))
@ApplyLdifFiles({ "custom-schema.ldif", "test-server.ldif", })
public class SearchTests extends AbstractLdapTestUnit {

RFC 4512フォーマットをJNDIを使ってプログラムで追加する方法

JNDIでLDAPサーバにアクセスし、プログラムでスキーマを更新することもできる。例えば、以下のように@BeforeClassのメソッド内で記述すれば良い。ただし、これだと@ApplyLdifFilesのテストデータのインポートが先に動いてしまうので注意が必要。この時点ではまだスキーマが追加されていなくてエラーになってしまう。メソッドレベルに@ApplyLdifFilesを定義すれば@BeforeClassの後にインポートされるようにはなるが、テストデータはそのテストメソッド内でしか使えなくなるので、この方式の使いどころはほとんどないかも。

	@BeforeClass
	public static void createSchema() throws Exception {
		Hashtable<String, String> env = new Hashtable<String, String>();

		env.put(Context.INITIAL_CONTEXT_FACTORY,
				"com.sun.jndi.ldap.LdapCtxFactory");
		env.put(Context.PROVIDER_URL, "ldap://localhost:50389");

		env.put(Context.SECURITY_PRINCIPAL, "uid=admin,ou=system");
		env.put(Context.SECURITY_CREDENTIALS, "secret");

		DirContext ctx = new InitialDirContext(env);

		Attributes atAttrs = new BasicAttributes(true);
		atAttrs.put(
				"attributeTypes",
				"( 1.3.6.1.4.1.18060.0.4.3.2.1 NAME 'numberOfGuns' DESC 'Number of guns of a ship' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )");
		ctx.modifyAttributes("cn=schema", DirContext.ADD_ATTRIBUTE, atAttrs);

		Attributes ocAttrs = new BasicAttributes(true);
		ocAttrs.put(
				"objectClasses",
				"( 1.3.6.1.4.1.18060.0.4.3.3.1 NAME 'ship' DESC 'An entry which represents a ship' SUP top STRUCTURAL MUST cn MAY ( numberOfGuns ) )");
		ctx.modifyAttributes("cn=schema", DirContext.ADD_ATTRIBUTE, ocAttrs);
	}

ApacheDSのテストフレームワークを拡張してプログラムで追加する方法

ApacheDSのテストフレームワークを拡張して、プログラムで制御することもできる。テスト実行時に動作するDirectoryServiceFactoryをカスタマイズできるようになっているので、これを利用する。
まず、DefaultDirectoryServiceFactoryを継承したクラスを作成する。initメソッドでの初期化後に、スキーマの更新プログラムを記述する。この時はまだLDAPサーバとしては起動していないので、ApacheDSのAPIを利用してスキーマを登録する必要がある。

package org.apache.directory.server.core.factory;

import java.util.ArrayList;
import java.util.List;

import org.apache.directory.server.core.CoreSession;
import org.apache.directory.server.core.DirectoryService;
import org.apache.directory.shared.ldap.entry.Modification;
import org.apache.directory.shared.ldap.entry.ModificationOperation;
import org.apache.directory.shared.ldap.entry.client.ClientModification;
import org.apache.directory.shared.ldap.entry.client.DefaultClientAttribute;
import org.apache.directory.shared.ldap.name.DN;

public class MyDefaultDirectoryServiceFactory extends
		DefaultDirectoryServiceFactory {

	MyDefaultDirectoryServiceFactory() {
	}

	@Override
	public void init(String name) throws Exception {
		super.init(name);

		DirectoryService service = getDirectoryService();
		CoreSession session = service.getAdminSession();

		Modification item1 = new ClientModification(
				ModificationOperation.ADD_ATTRIBUTE,
				new DefaultClientAttribute(
						"attributeTypes",
						"( 1.3.6.1.4.1.18060.0.4.3.2.1 NAME 'numberOfGuns' DESC 'Number of guns of a ship' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )"));

		Modification item2 = new ClientModification(
				ModificationOperation.ADD_ATTRIBUTE,
				new DefaultClientAttribute(
						"objectClasses",
						"( 1.3.6.1.4.1.18060.0.4.3.3.1 NAME 'ship' DESC 'An entry which represents a ship' SUP top STRUCTURAL MUST cn MAY ( numberOfGuns $ description) )"));

		List<Modification> items = new ArrayList<Modification>();
		items.add(item1);
		items.add(item2);
		
		session.modify(new DN("cn=schema"), items);
	}
}

上記のカスタムクラスを利用するように、テストコードでは以下の用に記述する。@CreateDSにfactory属性を追加し、作成したカスタムクラスを設定する。

@RunWith(FrameworkRunner.class)
@CreateLdapServer(transports = { @CreateTransport(protocol = "LDAP", port = 50389) })
@CreateDS(factory = MyDirectoryServiceFactory.class, partitions = @CreatePartition(name = "example", suffix = "dc=example,dc=org"))
@ApplyLdifFiles({ "custom-schema.ldif", "test-server.ldif", })
public class SearchTests extends AbstractLdapTestUnit {

なお、この方法であれば2つ目の方法と異なり、クラスレベルの@ApplyLdifFilesより前に動作するのでテストデータをテストクラス内で共有することが可能となる。

まとめ

  • カスタムスキーマを使っているケースでも、ApacheDSでユニットテストができることが分かった。
  • 方法としては、大きくLDIFを作成してインポートする方法と、テストコード内でプログラムで追加する方法がある。

*1:LDIFの書き方を紹介しているサイトでは、最後の"-"がなかったりするがLDIFの仕様としては必要。参考:[http://en.wikipedia.org/wiki/LDAP_Data_Interchange_Format:title=]