DirContextAdapter
による属性アクセスと操作の簡素化
あまり知られていない — そしておそらく過小評価されている — Java LDAP API の機能の 1 つは、DirObjectFactory
を登録して、見つかった LDAP エントリからオブジェクトを自動的に作成する機能です。Spring LDAP は、この機能を利用して、特定の検索およびルックアップ操作で DirContextAdapter
(Javadoc) インスタンスを返します。
DirContextAdapter
は、特にデータを追加または変更する場合に、LDAP 属性を操作するための便利なツールです。
ContextMapper
を使用した検索とルックアップ
LDAP ツリーでエントリが見つかると、その属性と識別名 (DN) が Spring LDAP によって使用され、DirContextAdapter
が構築されます。これにより、次のように、AttributesMapper
の代わりに ContextMapper
(Javadoc) を使用して、見つかった値を変換できます。
public class PersonRepoImpl implements PersonRepo {
...
private static class PersonContextMapper implements ContextMapper {
public Object mapFromContext(Object ctx) {
DirContextAdapter context = (DirContextAdapter)ctx;
Person p = new Person();
p.setFullName(context.getStringAttribute("cn"));
p.setLastName(context.getStringAttribute("sn"));
p.setDescription(context.getStringAttribute("description"));
return p;
}
}
public Person findByPrimaryKey(
String name, String company, String country) {
Name dn = buildDn(name, company, country);
return ldapClient.search().name(dn).toObject(new PersonContextMapper());
}
}
前の例で示したように、Attributes
および Attribute
クラスを経由せずに、属性値を名前で直接取得できます。これは、複数値の属性を操作する場合に特に便利です。複数値属性から値を抽出するには、通常、Attributes
実装から返された属性値の NamingEnumeration
をループする必要があります。DirContextAdapter
は、getStringAttributes()
(Javadoc) または getObjectAttributes()
(Javadoc) メソッドでこれを行います。次の例では、getStringAttributes
メソッドを使用しています。
getStringAttributes()
を使用した複数値属性値の取得 private static class PersonContextMapper implements ContextMapper {
public Object mapFromContext(Object ctx) {
DirContextAdapter context = (DirContextAdapter)ctx;
Person p = new Person();
p.setFullName(context.getStringAttribute("cn"));
p.setLastName(context.getStringAttribute("sn"));
p.setDescription(context.getStringAttribute("description"));
// The roleNames property of Person is an String array
p.setRoleNames(context.getStringAttributes("roleNames"));
return p;
}
}
AbstractContextMapper
を使用する
Spring LDAP は、AbstractContextMapper
(Javadoc) と呼ばれる ContextMapper
の抽象基本実装を提供します。この実装は、指定された Object
パラメーターの DirContexOperations
へのキャストを自動的に処理します。AbstractContextMapper
を使用すると、前に示した PersonContextMapper
を次のように書き直すことができます。
AbstractContextMapper
を使用する private static class PersonContextMapper extends AbstractContextMapper {
public Object doMapFromContext(DirContextOperations ctx) {
Person p = new Person();
p.setFullName(ctx.getStringAttribute("cn"));
p.setLastName(ctx.getStringAttribute("sn"));
p.setDescription(ctx.getStringAttribute("description"));
return p;
}
}
DirContextAdapter
を使用したデータの追加と更新
` 属性値を抽出するときに便利ですが、DirContextAdapter
はデータの追加と更新に関連する詳細を管理するためにさらに強力です。
DirContextAdapter
を使用したデータの追加
次の例では、DirContextAdapter
を使用して、データの追加で示されている create
リポジトリメソッドの改善された実装を実装しています。
DirContextAdapter
を使用したバインディング public class PersonRepoImpl implements PersonRepo {
...
public void create(Person p) {
Name dn = buildDn(p);
DirContextAdapter context = new DirContextAdapter(dn);
context.setAttributeValues("objectclass", new String[] {"top", "person"});
context.setAttributeValue("cn", p.getFullname());
context.setAttributeValue("sn", p.getLastname());
context.setAttributeValue("description", p.getDescription());
ldapClient.bind(dn).object(context).execute();
}
}
バインドする 2 番目のパラメーターとして DirContextAdapter
インスタンスを使用することに注意してください。これは Context
である必要があります。属性を明示的に指定しないため、3 番目のパラメーターは null
です。
また、objectclass
属性値を設定するときに setAttributeValues()
メソッドを使用することにも注意してください。objectclass
属性は複数値です。多値属性データを抽出する際の問題と同様に、多値属性の構築は退屈で冗長な作業です。setAttributeValues()
メソッドを使用することで、自分に合った DirContextAdapter
ハンドルを使用できます。
DirContextAdapter
を使用したデータの更新
modifyAttributes
を使用した更新が推奨される方法であることは以前に説明しましたが、そのためには、属性の変更を計算し、それに応じて ModificationItem
配列を構築するタスクを実行する必要があります。DirContextAdapter
は、次のように、これらすべてを実行できます。
DirContextAdapter
を使用した更新 public class PersonRepoImpl implements PersonRepo {
...
public void update(Person p) {
Name dn = buildDn(p);
DirContextOperations context = ldapClient.search().name(dn).toEntry();
context.setAttributeValue("cn", p.getFullname());
context.setAttributeValue("sn", p.getLastname());
context.setAttributeValue("description", p.getDescription());
ldapClient.modify(dn).attributes(context.getModificationItems()).execute();
}
}
SearchSpec#toEntry
を呼び出すと、結果はデフォルトで DirContextAdapter
インスタンスになります。lookup
メソッドは Object
を返しますが、toEntry
は戻り値を DirContextOperations
(DirContextAdapter
が実装するインターフェース) に自動的にキャストします。
LdapTemplate#create
メソッドと LdapTemplate#update
メソッドに重複したコードがあることに注意してください。このコードは、ドメインオブジェクトからコンテキストにマップされます。次のように、別のメソッドに抽出できます。
public class PersonRepoImpl implements PersonRepo {
private LdapClient ldapClient;
...
public void create(Person p) {
Name dn = buildDn(p);
DirContextAdapter context = new DirContextAdapter(dn);
context.setAttributeValues("objectclass", new String[] {"top", "person"});
mapToContext(p, context);
ldapClient.bind(dn).object(context).execute();
}
public void update(Person p) {
Name dn = buildDn(p);
DirContextOperations context = ldapClient.search().name(dn).toEntry();
mapToContext(person, context);
ldapClient.modify(dn).attributes(context.getModificationItems()).execute();
}
protected void mapToContext (Person p, DirContextOperations context) {
context.setAttributeValue("cn", p.getFullName());
context.setAttributeValue("sn", p.getLastName());
context.setAttributeValue("description", p.getDescription());
}
}
属性値としての DirContextAdapter
および識別名
LDAP でセキュリティグループを管理する場合、識別名を表す属性値を持つのが一般的です。識別名の等価性は文字列の等価性とは異なるため (たとえば、識別名の等価性では空白と大文字と小文字の違いは無視されます)、文字列の等価性を使用した属性変更の計算は期待どおりに機能しません。
たとえば、member
属性の値が cn=John Doe,ou=People
で、ctx.addAttributeValue("member", "CN=John Doe, OU=People")
を呼び出す場合、文字列が実際には同じ識別名を表している場合でも、属性は 2 つの値を持つと見なされます。
Spring LDAP 2.0 の時点で、属性変更メソッドに javax.naming.Name
インスタンスを提供すると、DirContextAdapter
は属性変更を計算するときに識別名の等価性を使用するようになります。前の例を ctx.addAttributeValue("member", LdapUtils.newLdapName("CN=John Doe, OU=People"))
に変更すると、次の例に示すように、変更はレンダリングされません。
public class GroupRepo implements BaseLdapNameAware {
private LdapClient ldapClient;
private LdapName baseLdapPath;
public void setLdapClient(LdapClient ldapClient) {
this.ldapClient = ldapClient;
}
public void setBaseLdapPath(LdapName baseLdapPath) {
this.setBaseLdapPath(baseLdapPath);
}
public void addMemberToGroup(String groupName, Person p) {
Name groupDn = buildGroupDn(groupName);
Name userDn = buildPersonDn(
person.getFullname(),
person.getCompany(),
person.getCountry());
DirContextOperation ctx = ldapClient.search().name(groupDn).toEntry();
ctx.addAttributeValue("member", userDn);
ldapClient.modify(groupDn).attributes(ctx.getModificationItems()).execute();
}
public void removeMemberFromGroup(String groupName, Person p) {
Name groupDn = buildGroupDn(String groupName);
Name userDn = buildPersonDn(
person.getFullname(),
person.getCompany(),
person.getCountry());
DirContextOperation ctx = ldapClient.search().name(groupDn).toEntry();
ctx.removeAttributeValue("member", userDn);
ldapClient.modify(groupDn).attributes(ctx.getModificationItems()).execute();
}
private Name buildGroupDn(String groupName) {
return LdapNameBuilder.newInstance("ou=Groups")
.add("cn", groupName).build();
}
private Name buildPersonDn(String fullname, String company, String country) {
return LdapNameBuilder.newInstance(baseLdapPath)
.add("c", country)
.add("ou", company)
.add("cn", fullname)
.build();
}
}
前の例では、ベース LDAP パスへの参照の取得に従って、ベース LDAP パスを取得するために BaseLdapNameAware
を実装します。これが必要なのは、メンバー属性値としての識別名が常にディレクトリルートからの絶対値である必要があるためです。
完全な PersonRepository
クラス
Spring LDAP と DirContextAdapter
の有用性を説明するために、次の例は LDAP の完全な Person
リポジトリ実装を示しています。
import java.util.List;
import javax.naming.Name;
import javax.naming.NamingException;
import javax.naming.directory.Attributes;
import javax.naming.ldap.LdapName;
import org.springframework.ldap.core.AttributesMapper;
import org.springframework.ldap.core.ContextMapper;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.filter.AndFilter;
import org.springframework.ldap.filter.EqualsFilter;
import org.springframework.ldap.filter.WhitespaceWildcardsFilter;
import static org.springframework.ldap.query.LdapQueryBuilder.query;
public class PersonRepoImpl implements PersonRepo {
private LdapClient ldapClient;
public void setLdapClient(LdapClient ldapClient) {
this.ldapClient = ldapClient;
}
public void create(Person person) {
DirContextAdapter context = new DirContextAdapter(buildDn(person));
mapToContext(person, context);
ldapClient.bind(context.getDn()).object(context).execute();
}
public void update(Person person) {
Name dn = buildDn(person);
DirContextOperations context = ldapClient.lookupContext(dn);
mapToContext(person, context);
ldapClient.modify(dn).attributes(context.getModificationItems()).execute();
}
public void delete(Person person) {
ldapClient.unbind(buildDn(person)).execute();
}
public Person findByPrimaryKey(String name, String company, String country) {
Name dn = buildDn(name, company, country);
return ldapClient.search().name(dn).toObject(getContextMapper());
}
public List<Person> findByName(String name) {
LdapQuery query = query()
.where("objectclass").is("person")
.and("cn").whitespaceWildcardsLike("name");
return ldapClient.search().query(query).toList(getContextMapper());
}
public List<Person> findAll() {
EqualsFilter filter = new EqualsFilter("objectclass", "person");
return ldapClient.search().query((query) -> query.filter(filter)).toList(getContextMapper());
}
protected ContextMapper getContextMapper() {
return new PersonContextMapper();
}
protected Name buildDn(Person person) {
return buildDn(person.getFullname(), person.getCompany(), person.getCountry());
}
protected Name buildDn(String fullname, String company, String country) {
return LdapNameBuilder.newInstance()
.add("c", country)
.add("ou", company)
.add("cn", fullname)
.build();
}
protected void mapToContext(Person person, DirContextOperations context) {
context.setAttributeValues("objectclass", new String[] {"top", "person"});
context.setAttributeValue("cn", person.getFullName());
context.setAttributeValue("sn", person.getLastName());
context.setAttributeValue("description", person.getDescription());
}
private static class PersonContextMapper extends AbstractContextMapper<Person> {
public Person doMapFromContext(DirContextOperations context) {
Person person = new Person();
person.setFullName(context.getStringAttribute("cn"));
person.setLastName(context.getStringAttribute("sn"));
person.setDescription(context.getStringAttribute("description"));
return person;
}
}
}
場合によっては、オブジェクトの識別名 (DN) は、オブジェクトのプロパティを使用して構築されます。前の例では、Person の国、会社、フルネームが DN で使用されています。つまり、これらのプロパティを更新するには、実際には、Attribute 値の更新に加えて、rename() 操作を使用して LDAP ツリー内のエントリを移動する必要があります。これは非常に実装固有であるため、ユーザーがこれらのプロパティを変更できないようにするか、必要に応じて update() メソッドで rename() 操作を実行することにより、自分自身を追跡する必要があります。オブジェクトディレクトリマッピング (ODM) を使用すると、ドメインクラスに適切にアノテーションを付ければ、ライブラリが自動的にこれを処理できることに注意してください。 |