この章では、Oracle Virtual Directoryのカスタマイズ方法について説明します。この章の内容は次のとおりです。
Oracle Virtual Directoryには、次の言語で、Oracle Directory Services Managerインタフェースのローカライズされた翻訳が用意されています。
フランス語
イタリア語
ドイツ語
スペイン語
ポルトガル語(ブラジル)
日本語
繁体字中国語
簡体字中国語
韓国語
Webブラウザの言語設定を使用して、Oracle Directory Services Managerインタフェースに言語を設定できます。言語設定に関する固有の情報は、使用しているWebブラウザのドキュメントを参照してください。
注意:
|
Oracle Virtual Directoryではカスタム・アダプタの作成機能もサポートされています。これには、定義済のAPIによってほとんどすべてのデータ・ソースに接続できるプラグインを使用します。たとえば、カスタム・アダプタを使用して、Webサービスで入手できる情報を抽出できます。カスタム・アダプタは、それ自体には機能がないアダプタです。アダプタ・レベルのプラグインを構成して、かわりに機能を実装できるプレースホルダです。デフォルトで、カスタム・アダプタはどのデータ・ソースにもマップされていません。Oracle Directory Services Managerの「プラグイン」タブでカスタム・アダプタに追加されたDiameterプラグインなどのプラグインは、カスタム・アダプタにデータを提供します。通常、カスタム・アダプタは、Oracle Virtual Directoryを非LDAPサービスまたは非データベース・サービス(Webサービスなど)に接続する必要があるユーザーによって記述されます。
この項の内容は次のとおりです。
Oracle Directory Services Managerを使用してカスタム・アダプタを作成するには、次の手順を実行します。
Oracle Directory Services Managerにログインします。
タスク選択バーから「アダプタ」を選択します。「アダプタ」ナビゲーション・ツリーが表示されます。
「アダプタの作成」ボタンをクリックします。新規アダプタ・ウィザードが表示されます。
アダプタのタイプを定義するには、次の手順を実行します。
「アダプタ・タイプ」リストから「カスタム」を選択します。
「アダプタ名」フィールドにカスタム・アダプタの一意の名前を入力します。アダプタ名の値は、そのアダプタを参照する必要のある他の構成フィールドで使用されます。
「アダプタ・テンプレート」リストから「デフォルト」テンプレートを選択します。
「次へ」をクリックします。「設定」画面が表示されます。
「アダプタ接尾辞/ネームスペース」フィールドに有効なベースDNを(DN形式で)入力します。このフィールドには、アダプタが情報を提供するルートDNを定義します。定義されているDNとその下の子エントリには、アダプタのネームスペースが含まれます。「アダプタ接尾辞/ネームスペース」フィールドに入力する値は、戻されるエントリが持つベースDN値です。たとえば、「アダプタ接尾辞/ネームスペース」フィールドにdc=mydomain,dc=comと入力すると、すべてのエントリはdc=mydomain,dc=comで終わります。
「次へ」をクリックします。カスタム・アダプタの設定の概要が表示されます。設定を確認し、「終了」をクリックしてカスタム・アダプタを作成します。「アダプタ」ツリーに、カスタム・アダプタが表示されます。
カスタム・アダプタを作成したら、「カスタム・アダプタの構成」の手順に従って構成できます。
この項では、カスタム・アダプタ設定の構成方法を説明します。この項の内容は次のとおりです。
カスタム・アダプタを作成したら、「アダプタ」ツリーでアダプタ名をクリックし、「一般」タブをクリックして次に示すフィールドに値を設定し、「適用」をクリックしてアダプタの一般設定を構成できます。
このフィールドには、アダプタが情報を提供するルートDNを定義します。定義されているDNとその下の子エントリには、アダプタのネームスペースが含まれます。このフィールドに入力する値は、戻されるエントリが持つベースDN値です。たとえば、このフィールドにdc=mydomain,dc=comと入力すると、すべてのエントリはdc=mydomain,dc=comで終わります。
アダプタは、アクティブ(有効)または非アクティブ(無効)として構成できます。非アクティブに構成されたアダプタは、サーバーの再起動時やアダプタの起動の試行時には起動しません。非アクティブの設定は、古い構成を使用可能な状態にしておく場合や、構成から削除せずにスタンバイ状態にしておく場合に使用します。デフォルト設定はアクティブです。
アダプタを作成したら、「アダプタ」ツリーでアダプタ名をクリックして「ルーティング」タブをクリックし、「ルーティング設定の概要」を参照することで、そのアダプタのルーティングを構成できます。
注意: バインド操作をサポートするかどうかが不明なカスタム・アダプタを定義する際には、「バインド・サポート」ルーティング設定を有効化します。 |
アダプタを作成したら、「アダプタ」ツリーでアダプタ名をクリックして「プラグイン」タブをクリックし、「アダプタ・プラグインの管理」および「アダプタへのマッピングの適用」を参照することで、そのアダプタにプラグインおよびマッピングを適用できます。
この項では、Oracle Virtual DirectoryのカスタムJavaプラグインの開発方法について説明します。この項の内容は次のとおりです。
Oracle Virtual Directoryでは、Oracle Virtual Directoryのパススルー時にLDAP操作を処理および操作できるカスタムJavaプラグインを作成してデプロイできます。プラグインは、すべてのリクエストを把握し処理するグローバル・レベル、または特定のアダプタに対するリクエストのみを把握し処理するアダプタ・レベルに配置できます。また、特定の操作やネームスペースに対して実行されるようにプラグインを作成およびデプロイすることもできます。
注意: カスタムJavaプラグインを使用して属性名を変更する場合、DB_Groupsマッピングで行われるように、カスタム・コードが受信フィルタ・オブジェクトを上書きする場合にのみ、Oracle Virtual Directoryで、名前の変更された属性/値の検索がサポートされます。 |
Oracle Virtual Directoryの各プラグインには、表18-1にリストされているように、特定の実装ポイントがあります。
表18-1 プラグインの実装ポイント
実装ポイント | 説明 |
---|---|
構成 |
プラグインの構成データ。構成のカスタム部分は、初期化パラメータの名前と値のペアで構成されています。 |
起動/停止 |
|
可用性 |
|
操作 |
呼び出される様々な操作メソッド。 |
この章では、表18-1にリストされている実装ポイントを説明して、カスタム・プラグインの作成方法を示します。この章では、バインド操作が失敗したか成功したかを検出するBad Password Countプラグインという仮想のプラグインの例に関して説明します。操作が成功した場合は件数が消去され、バインドが失敗した場合は件数が増加します。仮想のBad Password Countプラグインを使用すると、不正なパスワード件数をディレクトリの外から変更することができなくなります。
注意: この章で説明するBad Password Countプラグインは仮想の例で、Oracle Virtual Directoryプラグインおよびそのチェーン・システムがどのように機能するかを示すために使用されます。作成すればサポートされますが、Oracle Virtual DirectoryにはBad Password Countプラグインは用意されていません。 |
Oracle Virtual Directoryのプラグインは、Java Servlet 2.3フィルタ・モデルに基づく実装に準拠しています。このモデルでは、操作が続行される場合、前操作、後操作および決定の処理に単一のメソッドが使用されます。複数のプラグインを結合して、プラグインのチェーンが形成されます。このチェーン実装を説明するために、仮想の例であるBad Password Countプラグインが、ディレクトリに追加されるエントリに不正なパスワード件数属性を追加するかどうかを決定する次のような状況を検討します。
addメソッドが呼び出された場合は、リクエストを操作することができます。これにより、渡された属性やその値を操作すること(ActiveDirectory
を標準のLDAPディレクトリとしてマスクしている場合は、オブジェクト・クラス値inetOrgPerson
のuser
への変更など)や、データの記憶域を(カスタム・アダプタなどの)ディレクトリ以外のシステムまたはデータベース・システムに処理することが可能になります。別のプラグインを経由して仮想ディレクトリでリクエストをさらに処理できるようにするには、chain.nextAdd
メソッドを呼び出します。多くのプラグイン・メソッドには、対応するchain.next<XXX>
メソッドがあります。プラグインによるリクエストの処理をそれ以上許可しない場合は、chain.next<XXX>
コールを除外します。
注意: アダプタ・タイプはプラグインとは無関係です。プラグインは、どのようなタイプのアダプタにも追加できます。 |
カスタム・プラグインを作成する前に、com.octetstring.vde.chain.Plugin
インタフェースを実装するか、com.octetstring.vde.chain.BasePlugin
クラスを拡張するかを決定する必要があります。BasePlugin
クラスを使用すると、プラグイン開発者は、プラグインによって処理される操作用のメソッドのみを実装できます。この章の例のプラグインでは、BasePluginクラスを拡張して実装を簡略化しています。
この項の次に示す項では、Oracle Virtual Directoryのプラグインの実装ポイントについて説明します。
プラグインの1番目の実装ポイントは構成です。プラグインは、Oracle Virtual Directory構成システムによって提供される、一連の単純な名前と値のペアを使用して構成されます。このペアは、プラグインのinit
メソッドに対するparams
引数を介してプラグイン開発者に提供されます。この章の例のプラグインには、次の構成オプションがあります。
countAttribute
: 不正なパスワード件数を保存するすべてのユーザー・エントリに追加される属性。
addOnCreate
: ユーザーの作成時にプラグインがこの属性を追加する場合にtrueに設定されるブール値。
objectClassForAdd
: 属性が追加されるユーザーを表すオブジェクト・クラス。
ignoreOnModify
: countAttribute
の変更リクエストが無視される場合にtrueに設定されるブール値。
これらの構成オプションは、2番目の実装ポイントであるライフ・サイクル・メソッドで取得されます。init
メソッドはサーバー起動時にプラグインを初期化する際に呼び出され、destroyメソッドはプラグインを停止する際に呼び出されます。例18-1に、サンプルのinit
メソッドを示します。
例18-1 サンプルのinitメソッド
/** * Passes initialization information to the Plug-in * * @param initParams * Hashmap of key/value pairs specified in initial config * @param name * The name specified in the config for this Plug-in */ public void init(PluginInit initParams, String name) throws ChainException { //the countAttribute parameter is required if (!initParams.containsKey(BadPasswordCount.CONFIG_COUNT_ATTRIBUTE)) { throw new ChainException(name + ": The " + BadPasswordCount.CONFIG_COUNT_ATTRIBUTE + " attribute is required"); } this.countAttribute = new DirectoryString(initParams .get(BadPasswordCount.CONFIG_COUNT_ATTRIBUTE)); this.attribType = SchemaChecker.getInstance().getAttributeType( this.countAttribute); //determine if add on create this.addOnCreate = initParams .containsKey(BadPasswordCount.CONFIG_ADD_ON_CREATE) && initParams.get(BadPasswordCount.CONFIG_ADD_ON_CREATE) .equalsIgnoreCase("true"); if (this.addOnCreate) { if (this.addOnCreate && !initParams .containsKey(BadPasswordCount.CONFIG_OBJECTCLASS_FOR_ADD)) { throw new ChainException(name + ": When adding count attribute, the parameter " + BadPasswordCount.CONFIG_OBJECTCLASS_FOR_ADD + " is required"); } String[] objectClasses = initParams .getVals(BadPasswordCount.CONFIG_OBJECTCLASS_FOR_ADD); this.objectClasses = new HashSet(); for (int i = 0, m = objectClasses.length; i < m; i++) { this.objectClasses.add(new DirectoryString(objectClasses[i])); } } else { this.addOnCreate = false; } logger.info("Adding on create : " + this.addOnCreate); //determine if the modify operation should be ignored this.ignoreModify = initParams .containsKey(BadPasswordCount.CONFIG_IGNORE_MODIFY) && initParams.get(BadPasswordCount.CONFIG_IGNORE_MODIFY) .equalsIgnoreCase("true");
例18-1のメソッドでは、プラグインを設定するための初期化パラメータが確認されています。構成情報が不十分な場合は、プラグインにより例外がスローされ、サーバーによる操作目的での使用が可能になるようプラグインを構成できない原因になります。接続の解放またはサービスの停止が必要な場合以外は、destroyメソッドを実装する必要はありません。
使用可能な実装ポイントは、構成、開始および停止の実装ポイントに続きます。availableメソッドは、特定のLDAP操作に対する各プラグインの呼出しが可能になる前に呼び出されます。availableメソッドによりtrueが戻されると、プラグインが実行されます。例18-2では、availableメソッドによりRequest
オブジェクトのignoreOnModify
オプションの有無が確認されています。オプションが定義されている場合、プラグインはスキップされます。同様に、addonCreate
オプションがfalseに設定されている場合も、プラグインはスキップされます。
例18-2 ignoreOnModifyオプションの有無を確認するサンプルのメソッド
/** * Determines if a plugin is available for the current chain * * @param chain * @param base * @return True or False if available for a particular chain & base */ public boolean available(Chain chain, DirectoryString base) { if (chain.getOperationType() == Chain.ADD_OP && !this.addOnCreate) { return false; } else if (chain.getOperationType() == Chain.MOD_OP && this.ignoreOnModify) { return false; } else { return true; } }
availableメソッドがtrueを戻すと、リクエストの操作部分が実行されます。
最後の実装ポイントは、操作の実装です。例18-3に、次のバインド操作のコード実装を示します。
例18-3 バインド操作の実装の例
/** * Moves through the "bind" operation's chain * * @param chain * The current chain * @param dn * The DN for the user * @param password * The user's password * @param result * The result of the bind */ public void bind(Chain chain, Credentials creds, DirectoryString dn, BinarySyntax password, Bool result) throws DirectoryException, ChainException { // Pre-event processing // calls the next plug-in in the chain (or comment out if a handler) try { chain.nextBind(creds, dn, password, result); } catch (DirectoryException e) { throw e; } // Post-event processing if (result.booleanValue()) { // success, reset count setPasswordCount(chain, creds, dn, 0); } else { Vector searchAttributes = new Vector(); searchAttributes.add(this.countAttribute); ChainVector results = new ChainVector(); chain.getVSI().get(chain.getRequest(), creds, dn, new Int8((byte) 0), ParseFilter.parse("(objectClass=*)"), new Bool(false), searchAttributes, results); if (results.size() > 0) { EntrySet es = (EntrySet) results.get(0); Entry entry = es.getNext(); Vector values = entry.get(this.countAttribute); Syntax value = (Syntax) values.get(0); IntegerSyntax is = new IntegerSyntax(value.getValue()); setPasswordCount(chain, creds, dn, ((int) is.getLongValue()) + 1); } else { setPasswordCount(chain, creds, dn, 1); } } } private void setPasswordCount(Chain chain, Credentials creds, DirectoryString dn, int count) throws DirectoryException, ChainException { Vector values = new Vector(); values.add(new IntegerSyntax(count)); EntryChange modify = new EntryChange(EntryChange.MOD_REPLACE, this.countAttribute, values); Vector changes = new Vector(); changes.add(modify); chain.getVSI().modify(chain.getRequest(), creds, dn, changes); }
例18-3のメソッドでは、パスワード失敗件数がパスワード・ポリシーという形でディレクトリ内に維持されている例が示されています。メソッドでは、操作の前処理も実行されておらず、バインド操作の引継ぎも試行されていないことに注意してください。プラグインのbindメソッドがchain.nextBind
メソッドをすぐに呼び出し、独自のロジックで続行する前にバインドが完了するまで待機します。コントロールがchain.nextBind
から戻されてバインドが完了すると、プラグインによりバインドが成功したかどうかが確認されます。バインドが成功した場合には、プラグインにより失敗件数属性がゼロに設定されます。バインドが失敗した場合には、現行の失敗件数が取得され、増加した値が設定されます。
bindメソッドは、仮想サービス・インタフェース(VSI)を使用してバインディング・ユーザーのレコードを変更します。VSIインタフェースは、プラグインがグローバルにデプロイされているかアダプタのコンテキスト内にデプロイされているかにかかわらず、ディレクトリ情報への一貫したアクセス方法としてOracle Virtual Directory全体で使用できます。これはVSIが、現行のプラグインの後にチェーン内の次のプラグインを開始することにより、常にOracle Virtual Directoryをコールすることで実現されます。たとえば、プラグインの前にマッパーが存在し、後にキャッシュが存在する場合、VSIへのコールはキャッシュのみを経由します。
プラグインはバインド失敗件数の維持を論理的に担っているため、プラグインのmodifyメソッドを実装する必要があります。このため、LDAPクライアントが件数の変更を試行してもブロックされます。例18-4のプラグインのmodifyメソッドは、modify変更リストにcount属性が含まれている場合に例外をスローするために実装されています。
例18-4 サンプルのmodifyメソッド
/** * Moves through the "modify" operation's chain * * @param chain * The current chain * @param creds * The currnet user's credentials * @param name * The name of the object being modified * @param changeEntries * The group of EntryChange Objects */ public void modify(Chain chain, Credentials creds, DirectoryString name, Vector changeEntries) throws DirectoryException, ChainException { Iterator it = changeEntries.iterator(); while (it.hasNext()) { EntryChange ec = (EntryChange) it.next(); if (ec.getAttr().equals(this.countAttribute)) { throw new DirectoryException(LDAPResult.CONSTRAINT_VIOLATION .intValue(), "Can not modify password count attribute"); } } chain.nextModify(creds, name, changeEntries); }
ステータス・コードおよびメッセージとともにDirectoryException
がスローされています。この例外が別のプラグインによって捕捉されない場合、例外はメッセージとコードの両方でクライアントに戻されます。ignoreOnModify
が構成されているかどうかの判断はavailableメソッドに委任してあるため、この例では確認する必要はありません。設定されている場合、例18-4のプラグインのmodifyメソッドは呼び出されていません。
検索には、実装されるメソッドが1つではなく3つあるため、その他の操作とは異なります。1つ目のメソッドはgetで、その他の操作メソッドと同様の役割を果します。このメソッドを使用すると、プラグイン開発者は検索リクエストを前処理し、戻り値を後処理できます。戻される各Entryを処理できる一方、メモリー使用率の点から見ると非常に非効率的です。getメソッドの結果を処理することは、クライアントに戻す前に、すべての結果をメモリーに取得することを意味します。このため、効率的な方法で属性を変更または追加でき、クライアントに戻される各Entryに対して実行されるpostSearchEntry
メソッドが用意されています。検索操作が完了したことをマークするpostSearchComplete
メソッドもあります。
postSearchEntry
処理を効率的に使用するために、Oracle Virtual Directoryでは、EntrySet
と呼ばれる特別なクラスを使用して結果セットの処理が行われます。getメソッドは、Vectorを戻すことにより結果を戻し、その結果には1つ以上のEntrySetが含まれます(詳細は「EntrySetの作成」を参照)。
Oracle Virtual Directoryでは、ディレクトリの各オブジェクトはcom.octetstring.vde.Entry
オブジェクトで表されます。各エントリには、オブジェクトの名前と属性値を持つ属性が含まれています。すべてのエントリ・オブジェクトは、com.octetstring.vde.EntrySet
インタフェースの実装を使用してOracle Virtual Directoryで処理されます。エントリ・セットでは、特定のデータ・ソースから戻されたすべてのエントリが保存または処理されます。通常のOracle Virtual Directory処理では、検索リクエスト中に呼び出された各アダプタにより、Oracle Virtual Directoryから戻される結果のリストに独自のEntrySet実装が追加されます。また、プラグインにより、結果のVector配列に追加のEntrySetオブジェクトが挿入される場合もあります。検索リクエストを実行するためにすべてのアダプタへの問合せが完了すると、各EntrySetはクライアントに送信されるエントリに関して調査されます。
すべてのアダプタでEntrySet実装が作成されますが、プラグインでもEntrySetインタフェースのインスタンスが作成され、それを使用して検索リクエスト中にクライアントにエントリが戻されます。
次に、EntrySetの作成にプラグインで使用できる2つの方法を示します。
EntrySetの作成にプラグインで使用できる最も簡単な方法は、com.octetstring.vde.backend.extensible.ExtensibleEntrySet
クラスを使用して、Entry
オブジェクトのjava.util.Vector
に基づくEntrySetを作成する方法です。次にその手順を示します。
新規のjava.util.Vector配列を作成します。
VectorにすべてのEntryオブジェクトを追加します。
コンストラクタに前述のVectorを渡すExtensibleEntrySetの新規インスタンスを作成します。
例18-5に、銘柄記号に基づいて株価を取得するためにWebサービスを使用するプラグインの例を示します。プラグインは、カスタム・アダプタの概念を実装するように設計されています。カスタム・アダプタは、それ自体には機能がないアダプタで、アダプタ・レベルのプラグインを構成して、かわりに機能を実装できるプレースホルダです。Stock Serviceの例では、プラグインはカスタム・アダプタに対して構成されます。プラグインは、すべてのイベントの処理を担当します。つまり、Stock Serviceプラグインではchain.getNext()
メソッドは呼び出されません。
プラグインのgetメソッドにより、VectorにEntryオブジェクト(株価エントリ)のリストが追加され、そのVectorに基づいてExtensibleEntrySetが作成されます。
例18-5 ExtensibleEntrySetを使用したEntrySet作成の例
public void get(Chain chain, Credentials creds, DirectoryString base, Int8 scope, Filter filter, Bool typesonly, Vector attributes, Vector result) throws DirectoryException, ChainException { // Since this method is a handler, chain.getNext is not called. if (scope.intValue() == SearchScope.BASEOBJECT && base.equals(this.suffix)) { // handle the logical root of the adapter Entry root = this.getSimpleEntry(this.suffix); Vector entries = new Vector(); entries.add(root); result.add(new ExtensibleEntrySet(entries)); return; } //This adapter only supports searches based on an equality match //or an or'ing of equality matches if (filter.getSelector() != Filter.EQUALITYMATCH_SELECTED && filter.getSelector() != Filter.OR_SELECTED) { throw new DirectoryException("Only equality match or an or'ing "+ "of equality matches are allowed"); } Vector entries = new Vector(); //If the filter is an OR filter, we can iterate over every quote if (filter.getSelector() == Filter.OR_SELECTED) { Iterator it = filter.getOr().iterator(); while (it.hasNext()) { Entry entry = getStockEntry((Filter) it.next()); if (entry != null) { entries.add(entry); } } } else { //single quote Entry entry = getStockEntry(filter); if (entry != null) { entries.add(entry); } } //We use the ExtensibleEntrySet as a simple holder for entry sets. result.add( new ExtensibleEntrySet(entries)); }
ExtensibleEntrySetを使用してEntrySetを作成するのは最も簡単な方法ですが、処理がクライアントに戻される前にすべての結果をコンパイルする必要があるため、最も効率のよい方法ではありません。この場合、フィルタの各項目に対するサービスがコールされます。このリクエストをより効率的に処理できるのは、LDAPアダプタ操作と同じように、Stock Serviceからリクエストされる時に新規エントリを取得してEntrySetsを作成する方法です。
LDAPアダプタのEntrySetに次のEntryが要求されると、システムにより、リモート・サーバーから一度に1つずつ次のエントリが取得されます。これは、LDAPプロトコルが意図する動作です。クライアントはすべてのエントリが処理される前にエントリの取得を開始できるため、この方法ははるかに効率的です。また、この方法では、クライアントによるエントリ取得の停止や、問合せの中断も可能です。
プラグインがリクエストどおりにエントリを戻すEntrySetを作成するためには、com.octetstring.vde.EntrySet
の実装を作成する必要があります。各EntrySetで、次のメソッドを実装する必要があります。
boolean hasMore()
EntrySetに他のエントリがある場合にtrueを戻します。このメソッドにより悪い影響が出ないように注意する必要があります。
Entry getNext()
EntrySetの次のエントリを戻します。他にエントリがない場合にはNULLを戻します。
void cancelEntrySet()
このメソッドは、EntrySetが完全に実行されない場合に呼び出されます。これにより、カスタムのEntrySet実装が保持しているシステム・リソースが解放されます。
例18-6は、証券Webサービスからアダプタを作成する例18-5のプラグイン実装と同じです。ただし、例18-6では、getメソッドにより作成されるのは、カスタムのEntrySetに渡される記号のリストのみです。例18-6のgetメソッドにより、VectorにEntryオブジェクト(株価エントリ)のリストが追加され、そのVectorに基づいてExtensibleEntrySetが作成されます。
例18-6 カスタムのEntrySetへの引渡しを行うサンプルのgetメソッド
public void get(Chain chain, Credentials creds, DirectoryString base, Int8 scope, Filter filter, Bool typesonly, Vector attributes, Vector result) throws DirectoryException, ChainException { if (scope.intValue() == SearchScope.BASEOBJECT && base.equals(this.suffix)) { Entry root = this.getSimpleEntry(this.suffix); Vector entries = new Vector(); entries.add(root); result.add(new ExtensibleEntrySet(entries)); return; } //This adapter only supports searches based on an equality match //or an or'ing of equality matches if (filter.getSelector() != Filter.EQUALITYMATCH_SELECTED && filter.getSelector() != Filter.OR_SELECTED) { throw new DirectoryException("Only equality match or an or'ing"+ " of equality matches are allowed"); } String rdn="uid"; ArrayList symbols = new ArrayList(); //If the filter is an OR filter, we can iterate over every quote if (filter.getSelector() == Filter.OR_SELECTED) { Iterator it = filter.getOr().iterator(); while (it.hasNext()) { //Extract the symbol from the filter String symbol = new String(filter.getEqualityMatch(). getAssertionValue().toByteArray()); //The attribute being checked in the equality search //doesn't really matter, but we need an RDN for each entry rdn = new String(filter.getEqualityMatch(). getAttributeDesc().toByteArray()); symbols.add(symbol); } } else { //single quote //Extract the symbol from the filter String symbol = new String(filter.getEqualityMatch(). getAssertionValue().toByteArray()); //The attribute being checked in the equality search doesn't //really matter, but we need an RDN for each entry rdn = new String(filter.getEqualityMatch(). getAttributeDesc().toByteArray()); symbols.add(symbol); } //We use the ExtensibleEntrySet as a simple holder for entry sets. result.add( new StockEntrySet(symbols.iterator(),rdn,this.base)); }
例18-6では、銘柄記号のリストは、検索フィルタのorを反復することによって作成されています。コンパイルされたリストは、例18-7に示すカスタムのEntrySet実装に渡されます。
例18-7 カスタムのEntrySetに渡されるデータの例
public class StockEntrySet implements EntrySet { Iterator quotes; String rdn; String base; public StockEntrySet(Iterator quotes, String rdn,String base) { this.rdn = rdn; this.quotes = quotes; this.base = base; } public Entry getNext() throws DirectoryException { Entry entry = this.getStockEntry((String) quotes.next()); if (entry == null) { if (this.hasMore()) { return this.getNext(); } else { return null; } } else { return entry; } } public boolean hasMore() { return quotes.hasNext(); } /** * Returns an entry for a stock quote * @param filter * @return An entry for the stock quote, or null for none. * @throws DirectoryException */ public Entry getStockEntry(String symbol) throws DirectoryException { //Create a new entry with the symbol as the RDN Entry entry = new Entry(new DirectoryString(rdn + "=" + symbol + "," + this.base)); //This uses an Apache Axis generated client stub NetXmethodsServicesStockquoteStockQuoteService service = new NetXmethodsServicesStockquoteStockQuoteServiceLocator(); try { NetXmethodsServicesStockquoteStockQuotePortType quoteService = service. getNetXmethodsServicesStockquoteStockQuotePort(); double value = quoteService.getQuote(symbol); if (value == -1) { return null; } //Create the attribute for the entry Vector vals = new Vector(); vals.add(new DirectoryString(symbol)); entry.put(new DirectoryString(rdn),vals); vals = new Vector(); vals.add(new DirectoryString("top")); vals.add(new DirectoryString("stockForOrganization")); entry.put(new DirectoryString("objectClass"),vals); vals = new Vector(); vals.add(new DirectoryString(Double.toString(value))); entry.put(new DirectoryString("quote"),vals); return entry; } catch (ServiceException e) { throw new DirectoryException("Could not load web service : " + e.getMessage()); } catch (RemoteException e) { throw new DirectoryException("Could not load web service : " + e.getMessage()); } } public void cancelEntrySet() { // nothing to do } }
例18-7のEntrySet実装では、現在どの記号が処理されているかを追跡するjava.util.Iteratorが使用されています。StockEntrySet
クラスでは、クライアントのかわりにOracle Virtual DirectoryによってEntryがリクエストされるまで、エントリ結果作成のためにWebサービスが呼び出されることはありません。
プラグインでは、1つ以上の株式の検索がサポートされているため、すべての検索で有効な結果が戻されるとはかぎりません。株式のリストを完全に検索し終わる前に、getNext
でOracle Virtual DirectoryにNULL
という結果が戻され、Oracle Virtual Directoryで結果が空であるとみなされる場合について考えてみます。この状況に対処するために、getNext
には、getStockEntry
の呼出しの後に特別なコード・ブロックが追加されています。getStockEntry
でNULL
が戻され、リクエストされた株式の反復が終了していない場合には、次の候補を処理するためにgetNext
によりgetNext自体が呼び出されます。この再帰は、有効な結果が1つ以上戻されるか、すべての問合せが完了するまで続きます。
LDAPフィルタ処理は複雑になる場合もあります。Oracle Virtual Directoryプラグインのコンテキストでは、前処理または後処理の際の2度、フィルタの解析に役立ちます。どちらの方法にもそれぞれのメリットとデメリットがあり、必ずしも相互に排他的であるとはかぎりません。
後処理のフィルタ処理
後処理のフィルタ処理では、結果として戻されるエントリが必要なフィルタに一致するかどうかが、com.octetstring.vde.util.FilterUtils.evalFilter(Entry e, Filter f)
メソッドを使用して確認されます。これはフィルタを処理する最も簡単な方法で、Entryオブジェクトの集合としてメモリーに残る事前定義済の少量のデータ・セットを処理する際に便利です。一般的に、フィルタを別の形式(SQLのWHERE
句や外部APIの特別なオブジェクト・モデルなど)に変換する必要がある場合には、このメソッドは適していません。
前処理のフィルタ処理
前処理フィルタはフィルタの解析に使用され、解析したフィルタを変更された検索に適用するか、検索ターゲットが解釈できる別の形式に変換する際に使用されます。前処理のフィルタ処理では、LDAPフィルタがSQLのWHERE
句に変換されます。これを実行するには、フィルタ・オブジェクトを調査する必要があります。たとえば、次のLDAPフィルタをSQLのWHERE
句に変換するとします。
(&(|(user=jsmith)(user=lswanson)(user=ccarson))(dept=payroll))
このLDAPフィルタには、ユーザーがjsmith、lswansonまたはccarsonで部門がpayrollのすべてのレコードと指定されています。図18-1に、このLDAPフィルタを図で示します。
図18-1のフィルタをSQLのWHERE
句に変換するには、ツリーを調査する再帰機能を使用します。Oracle Virtual Directoryでは、この例のフィルタはFilterオブジェクトの階層として表されます。これらのオブジェクトには、調査可能なツリーを作成するために別のFilterオブジェクトの集合が含まれます。図18-1のフィルタには、図18-2に示すオブジェクト・モデルが含まれています。フィルタ要素を表すために使用されているクラス名は、操作または演算子の下に表示されています。
図18-2に示されているツリーを調査するために、フィルタのgetSelector()
メソッドに問い合せてフィルタのタイプを判断する再帰メソッドが使用されます。フィルタのタイプが判明したら、getFilterType
メソッドを使用してその値を抽出する必要があります。たとえば、フィルタがuser=jsmith
などの等価フィルタの場合、フィルタ・オブジェクトの値はcurrentFilter.getEqualityMatch()
から取得します。この場合の戻り値はAttributeValueAssertion
で、属性名および値がOracleとして保存されます。取得したら、値は文字列オブジェクトに変換できます。Filter_and
およびFilter_or
オブジェクトは、操作中の子フィルタを反復するためのjava.util.Iterator
クラスを戻します。
LDAPフィルタでは、関連ごとに項目が2つに制限されるわけではありません。OR
部分には3つのオペランドがあります。SQLで許可されているオペランドは操作ごとに2つのみであるため、図18-2のツリーはバイナリ・ツリーに変換する必要があります。
図18-3では、OR
操作は、2つの別々のOR
操作に分割されています。フィルタの最後のWHERE
句は、((user=jsmith) OR ((user=lswanson) OR (user=ccarson))) AND (dept=payroll)
です。LDAPの前置記法は、操作ごとに2つのオペランドのみを伴うSQLのLIKE中置記法に変換されています。例18-8に、変換のソース・コードを示します。
例18-8 LDAPの前置記法をSQLの表記法に変換するためのサンプルのソース・コード
import com.octetstring.vde.util.*; import com.octetstring.ldapv3.*; import java.util.*; public class ConvertFilter { public static void main(String[] args) throws Exception { String ldapFilter = "(&(|(user=jsmith)(user=lswanson)" + (user=ccarson))(dept=payroll))"; System.out.println("Ldap Filter : " + ldapFilter); System.out.println("SQL WHERE : " + filterToSQL(ParseFilter.parse(ldapFilter))); } /** *Converts an ldap filter to an SQL WERE clause *@param currentFilter The filter being converted */ public static String filterToSQL(Filter currentFilter) { String[] filterVal; String infix=""; switch (currentFilter.getSelector()) { case Filter.EQUALITYMATCH_SELECTED : // (attrib=val) filterVal = getString(currentFilter.getEqualityMatch()); return filterVal[0] + "=" + filterVal[1]; case Filter.PRESENT_SELECTED : // (attrib=*) return new String(currentFilter.getPresent().toByteArray()) + "=*"; case Filter.GREATEROREQUAL_SELECTED : // (attrib>=val) filterVal = getString(currentFilter.getGreaterOrEqual()); return filterVal[0] + ">=" + filterVal[1]; case Filter.LESSOREQUAL_SELECTED : // (attrib<=val) filterVal = getString(currentFilter.getLessOrEqual()); return filterVal[0] + "<=" + filterVal[1]; case Filter.SUBSTRINGS_SELECTED : // (attrib=val*ue) filterVal = getString(currentFilter.getLessOrEqual()); return filterVal[0] + " LIKE " + filterVal[1]; case Filter.AND_SELECTED : // &((attrib=val)(attrib2=val2)) Filter_and andFilter = currentFilter.getAnd(); infix = ""; for (Iterator andEnum = andFilter.iterator(); andEnum.hasNext();) { Filter aFilter = (Filter) andEnum.next(); infix += "(" + filterToSQL(aFilter) + ") AND "; } infix = infix.substring(0,infix.lastIndexOf("AND")) + " "; return infix; case Filter.OR_SELECTED : // &((attrib=val)(attrib2=val2)) Filter_or orFilter = currentFilter.getOr(); infix = ""; for (Iterator orEnum = orFilter.iterator();orEnum.hasNext();) { Filter aFilter = (Filter) orEnum.next(); infix += " ( " + filterToSQL(aFilter) + " ) OR "; } infix = infix.substring(0,infix.lastIndexOf("OR")) + " "; return infix; case Filter.NOT_SELECTED : // !(&((attrib=val)(attrib2=val2))) return " NOT (" + filterToSQL(currentFilter.getNot()) + ") "; case Filter.APPROXMATCH_SELECTED : // (attrib~=val) filterVal = getString(currentFilter.getApproxMatch()); return filterVal[0] + " LIKE " + filterVal[1]; case Filter.EXTENSIBLEMATCH_SELECTED : //not standard return ""; //not supported } //will never reach return ""; } /** *Converts an AttributeValueAssertion to a two element array with the *first being the attribute name and the second being the value */ public static String[] getString(AttributeValueAssertion ava) { String matchAttr = new String(ava.getAttributeDesc().toByteArray()); String matchVal = new String(ava.getAssertionValue().toByteArray(),"UTF8"); return new String[] {matchAttr,matchVal}; } }
この項では、使用可能なOracle Virtual Directoryのクラスの高度な概要について説明します。Oracle Virtual Directoryのクラスの詳細は、『Oracle Fusion Middleware Java API Reference for Oracle Virtual Directory』(Javadoc)を参照してください。この項の内容は次のとおりです。
仮想サービス・インタフェース(VSI)には、LDAPと同じようにOracle Virtual Directoryをコールするメソッドが用意されています。VSIは、コンテキストがグローバル・プラグインであるかアダプタ・レベルのプラグインであるかにかかわらず、同様に機能します。
チェーン内に3つのプラグインがあり、その中の1つ目のプラグインがVSIへのコールを実行する場合、2つ目および3つ目のプラグインにも出現するように、コールのコンテキストはOracle Virtual Directoryです。コールが2つ目のプラグインから実行された場合、コールは3つ目のプラグインのみを経由します。コールが3つ目のプラグインから実行された場合、プラグインがグローバルであるかローカルであるかにかかわらず、そのコールはどのプラグインも経由しません。プラグインがグローバルの場合、コールはOracle Virtual Directoryのルーティング・システムを経由してアダプタ・レベルのチェーンまで、グローバル・チェーンの外でも続行されます(ルーターによって1つ以上のアダプタが選択されているかどうかによります)。これにより、Oracle Virtual Directoryへのコールの一貫性が保証されるだけでなく、プラグインが自身をコールする無限ループからも保護されます。VSIは、各プラグイン・メソッドに渡されるチェーン・オブジェクトを使用して取得されます。
グローバル・サービス・インタフェース(GSI)には、Oracle Virtual Directoryに対して、エンド・クライアントから実行されているようなLDAPに似たコールを行うメソッドが用意されています。各コールはアクセス制御システム(有効な場合)を介して処理され、ルーターによって操作に適切なアダプタを選択することが可能になります。GSIは、LDAPリスナーやWebゲートウェイが通信に使用するのと同じインタフェースです。
GSIは、chain.getVSI().getGSI()
を呼び出すことでVSIを使用して取得できます。このハンドルを使用すると、add
、bind
、delete
、get
、getByDN
、modify
およびrename
メソッドを呼び出せます。
警告: GSIでは、現在のプラグインの前にあるコンテキストをコールすると、プラグインが無限ループに捕捉される場合があります。これにより、プラグイン・コードが繰り返しコールされ、予期しない結果を招く原因になる可能性があります。意図的に発生させる場合を除き、プラグインがスタックを遡ってコールし、ループが発生する可能性のあるシナリオには注意してください。一般的に、特定のアダプタをコールする必要がある場合を除き、VSIを使用するのが最も安全です。Oracle Virtual Directoryには、ループ検出メカニズムは用意されていません。スタックのオーバーフローまたはメモリー不足が原因によるOracle Virtual Directoryとカスタム・プラグインとのクラッシュが検出された場合、ループが原因である可能性が高いです。 |
アダプタ・サービス・インタフェース(ASI)には、ルーター・レベルにおいて、または特定のアダプタに対して直接、LDAPと同じようにOracle Virtual Directoryをコールするメソッドが用意されています。Oracle Virtual Directoryの結合ビュー・アダプタおよびそのジョイナでは、検索および結合の対象であるアダプタとの通信にASIが使用されます。ASIインタフェースは、プラグイン・クラスの検索情報を提供するために構成された場合など、内部アダプタから情報を取得する際に便利です。
ASIは、chain.getVSI().getASI()
を呼び出すことでVSI経由で取得されます。このハンドルを使用すると、add
、bind
、delete
、get
、getByDN
、modify
およびrename
メソッドを呼び出せます。各メソッドには、アダプタ名のパラメータを指定するものと指定しないものの2種類があります。アダプタ名が指定されているメソッドを使用して特定のアダプタを選択するか、その他の任意のメソッドを使用して、ルーターがルーティング・ロジックおよびルーティング構成に基づいて適切なアダプタを選択するようにします。
警告: ASIでは、現在のプラグインの前にあるコンテキストをコールすると、プラグインが無限ループに捕捉される場合があります。これにより、プラグイン・コードが繰り返しコールされ、予期しない結果を招く原因になる可能性があります。意図的に発生させる場合を除き、プラグインがスタックを遡ってコールし、ループが発生する可能性のあるシナリオには注意してください。一般的に、特定のアダプタをコールする必要がある場合を除き、VSIを使用するのが最も安全です。Oracle Virtual Directoryには、ループ検出メカニズムは用意されていません。スタックのオーバーフローまたはメモリー不足が原因によるOracle Virtual Directoryとカスタム・プラグインとのクラッシュが検出された場合、ループが原因である可能性が高いです。 |
VSI、GSIおよびASIのいずれも共通のインタフェースを共有しており、追加機能が用意されているインタフェースもあります。詳細は、『Oracle Fusion Middleware Java API Reference for Oracle Virtual Directory』を参照してください。
次のリストでは、サポートされているASIメソッドを説明します。
add()
LDAPの追加操作を実行します。このメソッドには2つのバージョンがあり、Oracle Virtual Directoryルーターによるターゲット・アダプタの選択、または特定のアダプタの選択が可能です。
bind()
LDAPのバインド操作を実行します。Oracle Virtual Directoryルーターによるアダプタの選択、または特定のアダプタの適用が可能です。
delete()
LDAPの削除操作を実行します。Oracle Virtual Directoryルーターによるアダプタの選択、または特定のアダプタの適用が可能です。
get()
LDAPの取得操作を実行します。Oracle Virtual Directoryルーターによる適切なアダプタの選択が可能です。getメソッドは、EntrySet値のjava.util.Vectorを戻します。EntrySetは、問合せが行われた各アダプタに含まれます。
getbyDN()
特定のDNを使用してLDAPベースの検索を実行する便利なメソッドです。コール元は、選択して特定のアダプタを指定するか、Oracle Virtual Directoryルーターによる選択を行います。
modify()
LDAPの変更操作を実行します。コール元は、特定のアダプタを指定するか、ルーターによる自動選択を選択します。
rename()
LDAPの名前変更操作を実行します。コール元は、特定の送信元および宛先アダプタを指定するか、ルーターによる自動選択を選択します。
Oracle Virtual Directoryの結合ビュー・アダプタではジョイナを使用して、特定のアダプタからのエントリを結合し、プライマリ・アダプタのエントリとマージします。ジョイナは、新しいジョイナの実装に必要な基本的な操作やメソッドを定義する抽象クラスです。ジョイナは、結合済のエントリに対して操作を実行する必要がある場合に結合ビュー・アダプタによりコールされます。ジョイナは、LDAP操作前のデータ操作を可能にする事前アクション操作を定義します。また、コールされる操作に応じて、ジョイナによるターゲット結合アダプタ内のターゲット・エントリの選択を可能にするmapOperationTargetByEntry
メソッドも定義します。
ジョイナは、プライマリ・アダプタおよびターゲット・アダプタとともにインスタンス化されます。結合ビュー・アダプタは常にプライマリ・アダプタのコンテキスト内で機能し、マッピングや操作をターゲット結合アダプタで実行する必要がある場合にジョイナ・メソッドを呼び出します。
結合ビュー・アダプタの取得操作により、プライマリ・アダプタの結果のみに基づいてJoinEntrySet
が作成されます。続いてOracle Virtual DirectoryクライアントがOracle Virtual Directoryの結果をポーリングすると、JoinEntrySet
クラスによりジョイナJoinByEntry
メソッドが呼び出され、結合アダプタへのコールとエントリ結果のマージが行われます。2つ以上の結合関係が構成されている場合、定義されているすべての関係に基づいてエントリが完全に結合されるまで、エントリ・セットの処理はすべての結合をループします。
ジョイナ・コンストラクタ・メソッドは、結合ビュー・アダプタによってジョイナがインスタンス化される際に呼び出されます。この状況は、最初のLDAP操作が(遅延構成という形で)結合ビュー・アダプタによって処理されるまで発生しません。コンストラクタは、関連するターゲット・アダプタ名とともに、構成ファイルのジョイナの構成パラメータに渡されます。
createJoinFilter
メソッドは、一般的にはローカル・メソッドで、AdapterServiceInterface
への後続のコール用に検索フルターを作成するためにJoinByEntry
メソッドによって呼び出されます。
Oracle Virtual Directoryでは、次のユーティリティ・クラスがサポートされます。
PluginUtils
このクラスは、renameAttribute
、copyAttribute
などを含むマッピング機能の基本的なツールボックスです。通常、これらのクラスは、マッピング・スクリプトで使用されますが、Javaプラグインでも使用できます。
FilterTools
このクラスには、LDAP検索フィルタを作成および操作するためのメソッドがあります。
ParseFilter
ParseFilter
クラスには、文字列とフィルタを相互に変換する機能があります。
DNUtility
このクラスには、個々のDN名コンポーネントの操作を可能にするDNの展開や作成など、DN操作ルーチンが用意されています。
LDAPResult
LDAPResult
は、Int8
値を戻す任意のメソッドから戻される結果の比較に使用できるユーティリティ・クラスです。これらの定数は、結果コードのLDAPエラー・コードへの変換に便利です。
VDELogger
Loggerクラスには、Oracle Virtual Directoryロギング・ファシリティ(Log4J
)へのインタフェースがあります。このクラスを使用して、コンソールまたは監査メッセージをOracle Virtual Directoryのメッセージと統合します。
PasswordEncryptor
このクラスには、文字列値をCrypt、SHA、SSHAなどの様々なハッシュ形式に暗号化する多様なメッソドがあります。
Oracle Virtual Directoryでは、次のユーティリティ・クラスがサポートされます。
Attribute
Attribute
は、Entryクラスとともに使用される基本的なオブジェクトです。Attributeでは(属性名などの)タイプが定義され、その値が含まれます。また、クローニングおよび等価テスト用のメソッドが用意されています。
Credentials
セッションの資格証明を保持する基本的なオブジェクト。このオブジェクトには、IPアドレス、binddnおよびパスワード(必要な場合)が含まれます。通常、AdapterServiceInterface
に関連する多くの操作では、関連するのはbinddn
のみです。
エントリ
このオブジェクトは、LDAPエントリおよびLDAPの変更リクエストなどの部分エントリの保持に使用されます。多くの場合、FilterTool
ユーティリティをこれらのオブジェクトと組み合せてフィルタをテストします。
EntryChange
このオブジェクトには、LDAP変更項目が含まれます。変更リクエストを処理する際には、通常、EntryChangeオブジェクトのVectorがAdapterServiceInterface
に渡されます。各EntryChangeには、単一エントリへの単一の変更が含まれます。
EntrySet
EntrySetには、アダプタからの一連の問合せ結果が含まれます。メソッドが最初にEntrySetを受け取る際には、エントリの結果セットがメモリーに存在しない場合があります。getNext
メソッドを呼び出すことで、EntrySetから一意のEntryオブジェクトが戻されます。getNext
が呼び出されるたびに、関連するアダプタまたはプラグイン・クラス・コードが呼び出され、存在する場合には次のEntryが取得されます。他のエントリの可用性をテストするには、hasMore()
メソッドを使用します。
ヒント: 結果セット全体を処理する場合以外は、getNext() を直接呼び出さないようにしてください。LDAPクライアントから呼出しを実行することをお薦めします。Oracle Virtual Directoryプラグイン・クラスの場合には、特別なメソッドpostSearchEntry() が用意されており、クライアントに戻される際に各エントリを変更できます。アダプタから到着するたびにエントリを処理するのではなく、Oracle Virtual Directoryで結果セット全体を一度にロードするなど、不必要にgetNext() を呼び出すと、メモリーの過剰な使用およびパフォーマンス低下の原因になる可能性があります。 |
Filter
Filter
オブジェクトは、標準のLDAPフィルタを表します。このオブジェクトには、LDAPフィルタの設定、テストおよび比較に便利なメソッドがあります。Filterオブジェクトには、その他のフィルタ・オブジェクトの階層(Filter_and
、Filter_or
など)が含まれる場合もあります。
LDAPURL
このクラスには、標準のLDAPURL
を解析または作成するためのメソッドがあります。
Oracle Virtual Directoryでは次のデータ型をサポートしています。
BinarySyntax
パスワードまたはユーザー証明書などの任意のバイナリ値。
DirectoryString
大/小文字が区別されない文字列。
IASString
大/小文字が区別される文字列。
IntegerSyntax
整数値。
DistinguishedName
識別名の値(比較はDN等価ルールに準拠します)。
Oracle Virtual Directoryでは、次の例外がサポートされます。
DirectoryBindException
バインドに失敗した場合にスローされる例外。
DirectoryException
任意のディレクトリ・エラー時にスローされる一般的な例外。詳細はgetMessage()
を参照してください。LDAPエラー・コードを確認する場合はgetLDAPErrorCode()
を参照してください。この例外は、アダプタまたは別のプラグインによって生成されます。
DirectorySchemaViolation
リモート・スキーマまたはローカル・スキーマに違反するエントリの追加や変更が試行されると、スキーマ違反が発生します。
InvalidDNException
InvalidDNException
は、無効なDNがパラメータとして渡されるとオブジェクトおよびユーティリティによってスローされます。