各クラスの説明
以下の図に本稿のクラス構成を示します。
図内のクラスの各役割をパッケージごとに以下の表にまとめます。
「javax.naming.directory」パッケージ内のクラス・インターフェースは、次の通りです。
クラス名(インターフェース名) | 役割 |
DirContext | ディレクトリサービスのインタフェースとなり、オブジェクトに関連付けられた属性のチェックおよび変更、ディレクトリ検索などのメソッドを持つ。 |
InitialDirContext | ディレクトリ操作を実行するための開始コンテキスト。 |
SearchResult | DirContext.search() メソッドの結果として返されたNamingEnumeration 内のひとつの要素(エントリ)を表す。 |
Attributes | エントリの属性集合。 |
Attribute | エントリの属性。 |
SearchControls | 検索範囲の条件および検索結果として返されたものをカプセル化する。 |
ModificationItem | エントリの変更項目を表す。 |
「to.msn.wings.ldapsample」パッケージ内のクラスは、本サンプルのアプリケーション仕様に特化した処理が記述されます。
クラス名(インターフェース名) | 役割 |
EmployeeCRUDServlet | コントローラの役割をはたす。画面からサブミットされたaction 値を元に、該当するDAO(Data Access Object)のメソッドを呼び、ディレクトリサービスのデータを操作する。 |
Employee | 社員を表すPOJO(Plain Old Java Object)。 |
EmployeeDao | 社員をデータ操作するメソッドを定義したインターフェース。 |
EmployeeDaoLDAPImpl | EmployeeDao の実装クラス。LDAPでデータを操作する処理を記述。 |
EmployeeDaoFactory | EmployeeDao の実インスタンスを生成するファクトリクラス。 |
「to.msn.wings.ldap」パッケージのクラスは、LDAPに依存している処理を記述します。
クラス名 | 役割 |
LDAPServiceManager | LDAPのデータ操作をする処理を記述。 |
「to.msn.wings.jndi」パッケージのクラスは、特定のディレクトリサービスに依存しない汎用的なJNDIを呼び出す処理を記述します。
クラス名 | 役割 |
NamingServiceManager | JNDIのデータ操作メソッドを呼び出す。 |
EntryObject | ディレクトリサービスから検索されたエントリの属性値を格納するクラス。 |
各クラス説明
以下に、サンプル中でLDAPサーバーへのバインド、データ操作をしている箇所のソースを中心に説明します。
バインド
LDAPサーバーに接続する処理はInitialDirContext
クラスのコンストラクタを呼び出し、引数として、環境に依存したパラメータを設定したHashtable
を渡します。
public static synchronized LDAPServiceManager getInstance( String hostname,String principal,String credentials) throws NamingException{ if ( ldapServiceManager == null ){ Hashtable env = new Hashtable(); // コンテキスト初期化ファクトリ env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); // プロバイダURL(例:ldap://localhost/dc=example,dc=com) env.put(Context.PROVIDER_URL , "ldap://" + hostname ); // LDAPバージョン env.put("java.naming.ldap.version" , "3" ); // rootdn(管理者DN) cn=Manager,dc=example,dc=com env.put(Context.SECURITY_PRINCIPAL , principal ); // rootpw(管理者パスワード) secret env.put(Context.SECURITY_CREDENTIALS , credentials ); ldapServiceManager = new LDAPServiceManager( env ); } return ldapServiceManager; } /** コンストラクタ */ private LDAPServiceManager(Hashtable env) throws NamingException{ super( env ); }
/** コンストラクタ */ protected NamingServiceManager(Hashtable env) throws NamingException{ dirContext = new InitialDirContext( env ); }
InitialDirContext
のコンストラクタの引数に設定するHashtable
の設定キー・値を、以下の表にまとめます。
キー | 値 | 説明 |
Context.INITIAL_CONTEXT_FACTORY | com.sun.jndi.ldap .LdapCtxFactory | InitialContextのファクトリクラス |
java.naming.ldap.version | 3 | LDAPのバージョン |
Context.SECURITY_PRINCIPAL | cn=Manager, dc=example,dc=com | slapd.confに設定したrootdn(管理者DN) |
Context.SECURITY_CREDENTIALS | secret | slapd.confに設定したrootpw(管理者パスワード) |
新規登録
エントリの新規登録は、javax.naming.directory.DirContext
インターフェースのcreateSubcontext
メソッドにより行います。
public void add(Employee emp) throws Exception { Attributes attrs = new BasicAttributes(); // 属性集合 // cn属性(社員ID) Attribute attrEmpId = new BasicAttribute( "cn" ); attrEmpId.add( 0 , emp.getId() ); attrs.put( attrEmpId ); // メールアドレス Attribute attrEmail = new BasicAttribute( "mail" ); attrEmail.add(0 , emp.getMail() ); attrs.put( attrEmail ); // sn属性(名前) Attribute attrSn = new BasicAttribute( "sn" ); attrSn.add( 0 , emp.getName() ); attrs.put( attrSn ); // objectClass(必須) Attribute attrObjClass = new BasicAttribute( "objectClass" ); attrObjClass.add( 0 , "top" ); attrObjClass.add( 1 , "person"); attrObjClass.add( 2 , "inetOrgPerson"); attrs.put( attrObjClass ); serviceManager.add( "cn=" + emp.getId() + ",ou=Users,dc=example,dc=com" , attrs ); }
public void add(String dn,Attributes attrs) throws NamingException{ dirContext.createSubcontext( dn , attrs ); }
Context
インターフェースのcreateSubcontext
メソッドの引数を、以下の表にまとめます。
引数 | 型 | 説明 |
name | String | 更新対象のエントリのDN(識別名)を指定 |
attrs | Attributes | 新規登録エントリの属性集合 |
抽出
エントリの抽出は、javax.naming.directory.DirContext
インターフェースのsearch
メソッドにより行います。
public List findByMail(String mail,int matchCond) throws Exception{ String mailCond = makeMatchString( mail , matchCond ); List employeeList = serviceManager.search( "ou=Users,dc=example,dc=com" , "mail=" + mailCond , SearchControls.OBJECT_SCOPE ); List empList = new ArrayList();// リターン用のリスト(型:Employee) for (Iterator iterEmployee = employeeList.iterator(); iterEmployee.hasNext();) { // キー=属性名,値=属性値集合 EntryObject entry = (EntryObject) iterEmployee.next(); Employee emp = createEmployee(entry); empList.add(emp); } return empList; }
public List search(String baseDn,String filter,int searchScope) throws NamingException { SearchControls searchControls = new SearchControls(); searchControls.setSearchScope( searchScope ); NamingEnumeration searchResultEnum = dirContext.search( baseDn , filter , searchControls ); List entryList = new ArrayList(); // 検索結果のループ while ( searchResultEnum.hasMore() ){ SearchResult searchResult = (SearchResult)searchResultEnum.next(); // 属性の集合を取得 NamingEnumeration attrs = searchResult.getAttributes().getAll(); EntryObject entry = new EntryObject(); while ( attrs.hasMore() ) { Attribute attr = (Attribute) attrs.nextElement(); Enumeration attrValEnum = attr.getAll(); // 属性値の集合 List attrValueList = new ArrayList(); while (attrValEnum.hasMoreElements()) { String elem = (String)attrValEnum.nextElement(); attrValueList.add( elem ); } entry.put( attr.getID() , attrValueList ); } entryList.add(entry); } return entryList; }
LDAPのツリー上を検索する際には、
- 検索をツリー上のどこから開始するか
- 検索条件(フィルター)
- ツリー上のどこまでを検索スコープとするか
の3つを指定する必要があります。DirContext
インターフェースのsearch
メソッドに渡す3つの引数を、以下の表にまとめます。
引数 | 型 | 説明 |
baseDN | String | 抽出のツリー上の起点となるDN。 |
filter | String | 抽出条件。 |
searchControls | SearchControls | 検索制御設定を格納したオブジェクト。searchScope 属性に値を設定する。 |
以下の表に、LDAPで用意されている検索スコープ3種類についてまとめます。JNDIでは、この3種類のスコープに対応した定数が、SearchControls
クラスに定義されています。
スコープ | SearchControlsの定数 | 説明 |
ベース | OBJECT_SCOPE | ベースエントリだけを検索の対象とする。 |
1レベル | ONELEVEL_SCOPE | ベースエントリの直下のエントリだけを検索の対象とする。 |
サブツリー | SUBTREE_SCOPE | ベースエントリとその配下のツリー全体を検索の対象とする。 |
更新
エントリの属性更新は、javax.naming.directory.DirContext
インターフェースのmodifyAttributes
メソッドにより行います。
public void update(Employee emp) throws Exception { ModificationItem[] mods = new ModificationItem[2]; Attribute attrSn = new BasicAttribute( "sn" , emp.getName() ); mods[0] = new ModificationItem( DirContext.REPLACE_ATTRIBUTE , attrSn ); Attribute attrMail = new BasicAttribute( "mail" , emp.getMail() ); mods[1] = new ModificationItem( DirContext.REPLACE_ATTRIBUTE , attrMail ); String dn = "cn=" + emp.getId() + ",ou=Users,dc=example,dc=com"; serviceManager.modify( dn , mods ); }
public void modify(String dn , ModificationItem[] modificationItems) throws NamingException{ dirContext.modifyAttributes( dn , modificationItems ); }
DirContext
インターフェースのmodifyAttributes
メソッド引数を、以下の表にまとめます。
引数 | 型 | 説明 |
name | String | 更新対象のエントリのDN(識別名)を指定。 |
mods | ModificationItem[] | 更新後データが格納された配列を指定。 |
属性を変更する場合は、以下のように変更対象属性のAttribute
インスタンスを生成し、ModificationItem
のコンストラクタに渡します。
Attribute attrMail = new BasicAttribute( "mail" , emp.getMail() ); mods[1] = new ModificationItem( DirContext.REPLACE_ATTRIBUTE , attrMail );
削除
エントリの削除は、javax.naming.Context
インターフェースのdestroySubcontext
メソッドにより行います。
public void delete(Employee emp) throws Exception { serviceManager.delete( "cn=" + emp.getId() + ",ou=Users,dc=example,dc=com" ); }
public void delete(String dn) throws NamingException{ dirContext.destroySubcontext( dn ); }
Context
インターフェースのdestroySubcontext
メソッド引数を、以下の表にまとめます。
引数 | 型 | 説明 |
name | String | 削除対象のエントリのDN(識別名)を指定。 |
まとめ
データベースやファイルのデータ操作は慣れていても、LDAPサーバーのデータを操作するともなると、戸惑ってしまう方も多いのではないでしょうか。本稿が、そのようなJavaエンジニアがJavaからLDAPを操作する最初のステップとして参考になれば幸いです。
参考資料
- @IT 『連載第1回:ディレクトリ統合 なぜ「シングル・サインオン」が必要なのか?』 山本秀宣・高橋悟史 著、2002年12月
- FreeBSD 5.1 + OpenLDAP(LDAP Browser/Editor)
- 『OpenLDAP入門』 稲地稔 著、技術評論社、2003年7月
- 『LDAP/OpenLDAPによるユーザ管理/認証ガイド』 LDAP研究会 著、秀和システム、2004年9月
- 『LDAPハンドブック』 河津正人・桑田雅彦・恒田正哉・山形昌也・島村英 著、ソフト・リサーチ・センター、2002年3月