UnboundID LDAP SDK for Java には, ディレクトリサービスにおけるエントリと Java におけるオブジェクトとを橋渡しする試みとして, Persistence Framework というものがあります。 リレーショナルデータベースとオブジェクト指向言語における JPA や Hibernate などの ORM に比べたらまだまだかもしれませんが, なかなかおもしろそうな気がしてはいます。
少しだけ触ってみましたので, ここに書きとめておきたいと思います。
ちなみに, 手元の環境の LDAP サーバーは OpenLDAP 2.4.38 でして, そのディレクトリにはすでに次のようなエントリが存在しているものとします。
dn: ou=Prog Rock,dc=localdomain objectClass: organizationalUnit ou: Prog Rock
今回は, (1) このエントリの直下に inetOrgPerson
クラスのエントリをひとつ追加し, (2) 追加したエントリの属性を一部変更し, (3) そしてそのエントリを削除する, ということをやってみました。 利用した API は, ここ最近ちょっと気に入っている UnboundID LDAP SDK for Java の 2.3.5 です。 Java 実行環境は JDK 7u45 です。
エントリに対応するクラスを用意します
ディレクトリサービスにおけるエントリに対応するオブジェクトのクラスを, まずは用意します。 そのクラスには @LDAPObject
というアノテーションを付与し, エントリの属性に対応するフィールドには @LDAPField
というアノテーションを付与します。
import com.unboundid.ldap.sdk.ReadOnlyEntry; import com.unboundid.ldap.sdk.persist.LDAPEntryField; import com.unboundid.ldap.sdk.persist.LDAPField; import com.unboundid.ldap.sdk.persist.LDAPObject; import java.util.LinkedList; import java.util.List; @LDAPObject(structuralClass="inetOrgPerson") public class Musician { @LDAPEntryField private ReadOnlyEntry entry; @LDAPField(inRDN=true) private List<String> cn; @LDAPField private List<String> sn; @LDAPField private List<String> description; @LDAPField private List<String> givenName; private Musician() {} public Musician(String commonName, String surname) { if (commonName == null || surname == null || commonName.isEmpty() || surname.isEmpty()) throw new IllegalArgumentException(); cn = new LinkedList<>(); cn.add(commonName); sn = new LinkedList<>(); sn.add(surname); } public String getDN() { if (entry == null) return null; return entry.getDN(); } public void addDescriptions(String... descriptions) { if (description == null) description = new LinkedList<>(); for (String s : descriptions) if (s != null && !s.isEmpty() && !description.contains(s)) description.add(s); if (description.isEmpty()) description = null; } }
@LDAPField
を付与したフィールドのうち, どれかひとつは DN 文字列を構成する RDN とならなければなりませんので, どれかひとつに isRDN=true
を指定することが必要です。 各フィールドを List
としたのは, LDAP においてはひとつの属性が複数の値を持つことが一般的だからです。 (属性の定義によってはそうでないものもあるようですが。) List
の代わりに配列にしなかったのは, 属性の数は可変長だからです。 本当は, ある属性が複数の値を持ちうる場合には重複が許されないらしいので Set
を使うことも考えましたが, 追加された順番が意味を持つ局面もありうるので, List
を採用し, そのうえで追加の際には重複をはじくことにするという書き方にしました。 なお, List
の実装は, 基本的にランダムアクセスが必要となる機会はおそらくあまりなくて, 追加や削除をおこなう機会のほうが多いんじゃないかと思ったので, ArrayList
ではなく LinkedList
にしました。
ところどころ isEmpty
かどうかといった確認を入れていますが, それは LDAP においては空文字列のような長さ 0 の値は原則として禁止されているからです。 長さ 0 の値をあてがって LDAP サーバーに叱られるくらいなら, そもそもその属性を持たせなければいいわけでして, null
にしておけば 「その属性を持たない」 を意味することになります。 (ただし, cn
と sn
という二つの属性は, inetOrgPerson
における MUST 属性ですので, null
を許容せず IAE を投げることにしました。)
なお, 引数を取らないコンストラクターが必要らしいので, それも用意しました。 アクセスレベルは private でもかまわないようです。
少しためしてみます
さっそく, ためしてみます。 以下のクライアントコードは, 新たに作成した Musician
オブジェクトを, LDAP サーバーに新規エントリとして追加しようとするものです。
// LDAPConnection connection = ... Musician musician = new Musician("Mike Oldfield", "Oldfield"); musician.addDescriptions("A British musician born in 1953"); String parentDN = "ou=Prog Rock,dc=localdomain"; LDAPPersisterpersister = LDAPPersister.getInstance(Musician.class); persister.add(musician, connection, parentDN);
成功すれば, LDAP サーバーには, 次のようなエントリが作成されるはずです。
dn: cn=Mike Oldfield,ou=Prog Rock,dc=localdomain objectClass: inetOrgPerson cn: Mike Oldfield description: A British musician born in 1953 sn: Oldfield
内容を一部変更したいと思います
Musician
クラスに, 次のようなメソッドを追加してみます。
public void deleteDescriptions(String... descriptions) { if (description == null) return; for (String s : descriptions) if (s != null && !s.isEmpty()) description.remove(s); if (description.isEmpty()) description = null; } public void addGivenNames(String... givenNames) { if (givenName == null) givenName = new LinkedList<>(); for (String s : givenNames) if (s != null && !s.isEmpty() && !givenName.contains(s)) givenName.add(s); if (givenName.isEmpty()) givenName = null; }
追加したメソッドを使ってみたいと思います。 次のようなクライアントコードを実行します。
// LDAPConnection connection = ... String dn = "cn=Mike Oldfield,ou=Prog Rock,dc=localdomain"; SearchScope scope = SearchScope.BASE; SearchResult result = connection.search(dn, scope, "(objectClass=*)"); SearchResultEntry entry = result.getSearchEntry(dn); LDAPPersisterpersister = LDAPPersister.getInstance(Musician.class); Musician musician = persister.decode(entry); musician.addDescriptions("Tubular Bells (1973) is one of his best."); musician.deleteDescriptions("A British musician born in 1953"); musician.addGivenNames("Mike", "Michael", "Michael Gordon"); persister.modify(musician, connection, dn, true);
新規エントリを追加するわけではなく, 既存エントリを変更するわけですから, Musician
オブジェクトは新たに作成するのではなく検索オペレーションの結果から取得しています。 そして, description
属性と givenName
属性に対応する各フィールドに変更を加えたら, それをディレクトリ上の実際のエントリのほうにも反映させます。
成功すれば, このエントリは次のように変化しているはずです。
dn: cn=Mike Oldfield,ou=Prog Rock,dc=localdomain objectClass: inetOrgPerson cn: Mike Oldfield sn: Oldfield givenName: Mike givenName: Michael givenName: Michael Gordon description: Tubular Bells (1973) is one of his best.
用が済んだので削除してもよろしいでしょうか
削除します。
// LDAPConnection connection = ... String dn = "cn=Mike Oldfield,ou=Prog Rock,dc=localdomain"; SearchScope scope = SearchScope.BASE; SearchResult result = connection.search(dn, scope, "(objectClass=*)"); SearchResultEntry entry = result.getSearchEntry(dn); LDAPPersisterpersister = LDAPPersister.getInstance(Musician.class); Musician musician = persister.decode(entry); persister.delete(musician, connection);
はい, これでこのエントリは, 削除されました。
result: 32 No such object
まとめ
うまくいくと, ちょっとうれしいです。
No comments:
Post a Comment