Oracle Unified Directory (OUD)のJavaクラス、メソッドおよび関連する構文と使用方法の詳細は、Oracle Unified Directory Java APIリファレンスを参照してください。この章では、OUDプラグインAPIの使用に関する一般情報について説明します。
この章は次の項で構成されています。
OUDプラグインAPIには、プラグイン構成を格納、取得、変更および検証するための便利な方法が提供されています。
OUDでは、プラグイン構成がプラグイン構成エントリの一部として格納されます。構成要素はOUDのconfig.ldifファイルにキーと値のペアとして格納されます。分かりやすくするために、このメカニズムを使用してください。ただし、OUDプラグイン・アーキテクチャを使用すると、外部ファイルなどの代替方法を使用して構成を取得できます。
プラグイン構成は、デフォルトの構成モデルでキーと値のペアのセットとして表されます。キーと値はOUDサーバーおよびdsconfigコマンド行ツールでRaw文字列として処理されます。キーと値のペアはdsconfigツールとプラグイン・ワークフロー要素に関連付けられたplugin-propertiesプロパティを使用して設定できます。
例3-1に、プラグイン・プロパティを追加する方法を示します。
例3-1 プラグイン・プロパティの追加
dsconfig set-workflow-element-prop \
--element-name ExamplePlugin \
--add plugin-properties:customProperty=localDB1 \
--hostname host1 \
--port 4444 \
--trustStorePath install-dir/OUD/config/admin-truststore \
--bindDN cn=Directory\ Manager \
--bindPasswordFile ****** \
--no-prompt
例3-2では、プラグインExamplePluginはcustomPropertyという名前のカスタム・プロパティを使用して構成されています。このプロパティは汎用のplugin-propertiesパラメータの値として指定されます。
例3-2 カスタム・プロパティの構成
$dsconfig get-workflow-element-prop --element-name ExamplePlugin Property : Value(s) ----------------------:------------------------------------------------------- enabled : true next-workflow-elements : localDB1 plugin-class : oracle.oud.plugin.example.ExamplePlugin plugin-properties : customProperty=localDB1
OUDプラグイン構成は、プラグインの初期化中に提供されるPluginConfigurationインスタンスから入手できます。これは、例3-3に示されているようにinitializePluginメソッドをオーバーライドすることでアクセスできます。
例3-3 OUDプラグイン構成にアクセスするためのinitializePluginのオーバーライド
@Override
public void initializePlugin(PluginConfiguration configuration,PluginContext context) throws PluginException
{
// Plugin configuration as a Set of properties
Set<String> properties = configuration.getProperties();
String aParameter=null;
for(String value: properties)
{
if ( value.startsWith("customProperty=") )
{
aParameter = value.substring(value.indexOf("=")+1);
break;
}
}
// Expected property not found
if ( aParameter == null )
{
throw new PluginException
(context.getTypeBuilder().newMessage("customProperty missing in configuration."));
}
// Either use the configuration right now or make it persistent using class members.
}
この例で、構成はraw構成オブジェクトからプロパティのセットとして取得されます。読み込まれたプロパティはすぐに使用することも、将来プラグインを実装するJavaクラスのメンバーで使用するために格納することもできます。
OUDにはプラグイン構成を取得するための代替方法が提供されています。
プラグイン・プロパティの自動化パーサーを作成する手順は次のとおりです。
クラスoracle.oud.plugin.PluginConfigurationを拡張するJavaインタフェースを作成します。
取得が必要なプロパティごとに、get<property-name>()の書式のゲッターを追加します。property-nameは、プラグイン・プロパティに定義されているキーと値のペアのキーと一致する必要があります。名前の大小文字は無視されます。
返されるメソッド・タイプは、valueOf(String) - java.lang.String.valueOf(String)がこのアサーションと一致する静的メソッドを提供するクラスである必要があります。
プラグイン・プロパティcustomPropertyを解析するJavaインタフェースは例3-4のようになります。
例3-4 プラグイン・プロパティの解析
public interface PropertyConfiguration
extends oracle.oud.plugin.PluginConfiguration
{
/**
* Return the value associated to the key 'customProperty'.
*
* @return the value associated to the key 'customProperty'.
*/
String getCustomProperty();
}
その後、プラグインの初期化を次の例に示されているように上書きできます。
@Override
public void initializePlugin(final PluginConfiguration configuration,
final PluginContext context)
throws PluginException
{
super.initializePlugin(configuration, context);
PropertyConfiguration propertyConfiguration = this.getConfiguration(PropertyConfiguration.class);
String customProperty = propertyConfiguration.getCustomProperty();
// Perform check...
}
プラグイン構成に対する変更は、メソッドhandleConfigurationChange()をオーバーライドすることで動的にキャッチできます。新しい構成は例3-5に示されているように取得できます。
例3-5 変更されたプラグイン構成の取得
@Override
public void handleConfigurationChange(final PluginConfiguration configuration)
throws PluginException
{
// The new configuration is stored in the configuration object
// parse again the plugin configuration
String aParameter;
Set<String> properties = configuration.getProperties();
for(String value: properties)
{
if ( value.startsWith("customProperty=") )
{
aParameter = value.substring(value.indexOf("=")+1);
break;
}
}
}
handleConfigurationChange()メソッドは、OUDサーバーにより管理されているプラグイン・プロパティの更新時にのみ起動されます。構成を外部ファイルに格納するように決定した場合、ファイル・コンテンツに対する変更はここで説明したメカニズムでは動的に検出されません。
プラグインは、oracle.oud.RequestManagerインタフェースによって定義された対応するコールバックを実装することで、OUDサーバーで処理された任意のLDAPリクエストをインターセプトできます。各タイプのLDAP操作がハンドラ・メソッドに対応しています。たとえばadd操作はhandleAdd()メソッドによって管理される、というように順に対応しています。
受け取ったLDAPリクエストはサーバーによって処理されます。このように、リクエストのプロパティを変更することでパフォーマンス、整合性およびセキュリティに関してサーバーに影響を及ぼすことができます。
LDAPリクエストに含まれる各プロパティをゲッターで取得し、セッターで変更できます。
各ハンドラは、関連付けられた3つのパラメータをとります。
このプラグインの前のワークフロー要素によって提供されたとおりのすべてのリクエスト・プロパティを含むLDAPリクエスト
前のワークフロー要素(一度処理されたLDAPリクエストの結果)に戻るために使用する参照である結果ハンドラ
サーバーの各種要素(ロギング・サブシステム、プラグインAPIオブジェクトの作成、クライアント接続、リクエストの破棄など)へのアクセスを提供するツールボックス参照であるコンテキスト
bindリクエストは4つ目のパラメータをとりますが、これはLDAPプロトコルのバージョンであり便宜上の目的でのみ提供されています。abandonおよびunbindメソッドはインターセプトできません。リクエストのabandonはリクエストのコンテキストを使用して検出できます。unbind操作は、クライアント接続がサーバーから切断されることを意味します。
プロセス・チェーン内の各プラグインに配慮する必要がある規定は、LDAPリクエストを受け取ったときの状態のまま返すことです。これはリクエスト・ハンドラのすべての実装に適用されます。これはプラグインで実行される最も重要な事項です。これが重要な理由は、リクエストにすでに結果があってもリクエストが完了していない場合があるためです。
次の例を検討してください: プラグインはロードバランサ後に実行される処理の一部です。リクエストを変更し、受け取った状態でリクエストを返すかわりに変更されたリクエストを返すと、ロードバランサが正しく機能しない場合があります。実際に、このリクエストは最初のルートで変更され、最初のルートが失敗した場合は2番目のルートで再実行されて変更される可能性があります。
要約すると、リクエストは受け取ったときとまったく同じ形で発行する必要があることに注意してください。
次の例では、検索リクエストの有効範囲が変更されます。検索の有効範囲はBASE_OBJECTに変更され、検索リクエストが処理されたときに復元されます。
検索リクエストをインターセプトするには、サンプルのプラグインのhandleSearch(...)メソッドをオーバーライドします。例3-6を参照してください。
例3-6 検索リクエストをインターセプトするためのhandleSearch(...)のオーバーライド
@Override
public void handleSearch(final RequestContext requestContext,
final SearchRequest request,
final SearchResultHandler resultHandler)
throws UnsupportedOperationException {
System.out.println("plug-in: search received " + request);
// Store the received search scope.
SearchScope scopeReceived = request.getScope();
// Set a base search scope for all search requests
request.setScope(SearchScope.BASE_OBJECT);
System.out.println("plug-in: search modified " + request);
// Forward the request to the next plug-in.
this.getConfiguration()
.getFirstNextPlugin()
.handleSearch(requestContext,
request,
resultHandler);
// Restore the original value to give the request back as received.
request.setScope(scopeReceived);
}
Oracle Unified Directoryインスタンスを再起動してJARファイルの変更を有効にします。
OUDインスタンスを停止します。
$ cd instance-directory/OUD/bin
$ stop-ds
C:\> cd instance-directory\OUD\bat
C:\> stop-ds
プラグインJARファイルをlibディレクトリにコピーします。
# cp plugin.jar lib
C:\> copy plugin.jar lib
OUDインスタンスを再起動します。
# start-ds
C:\> start-ds
次のコマンドを実行します。
$ ldapsearch --hostname localhost --port 1389 --bindDN "cn=directory manager" --bindPasswordFile /tmp/password --searchScope sub --baseDN "uid=user.1,ou=people,dc=example,dc=com" "(objectclass=*)"
C:\ ldapsearch --hostname localhost --port 1389 --bindDN "cn=directory manager" --bindPasswordFile C:\tmp\password --searchScope sub --baseDN "uid=user.1,ou=people,dc=example,dc=com" "(objectclass=*)"
各コマンドごとに、ログ・ファイル
(UNIXまたはLinuxではinstance-dir/OUD/logs/server.out、Windowsではinstance-dir\OUD\logs\server.out)
に次のような情報を含む必要があります。
plug-in: search received SearchRequest(name=uid=user.1,ou=people,dc=example,dc=com, scope=sub, dereferenceAliasesPolicy=never, sizeLimit=0, timeLimit=0, typesOnly=false, filter=(objectClass=*), attributes=[], controls=[]) plug-in: search modified SearchRequest(name=uid=user.1,ou=people,dc=example,dc=com, scope=base, dereferenceAliasesPolicy=never, sizeLimit=0, timeLimit=0, typesOnly=false, filter=(objectClass=*), attributes=[], controls=[])
|
注意: リクエストは、呼出しにより次のプラグインに渡されます。this.getConfiguration().getFirstNextPlugin().handleSearch(...) これはまさしく super.handleSearch(...) |
リクエストの代替変更方法は、元のリクエストをラッパーという名前の特殊なオブジェクトにラッピングすることです。リクエスト・ラッパーは、ラッピングするリクエストとまったく同じJavaインタフェースを提供する実装で、メソッド上で実行されるすべての呼出しをラッピングされたリクエストに転送します。
プロパティの値を変更するには、該当するメソッドをオーバーライドします。例3-7に、検索リクエストの有効範囲を変更する方法を示します。
例3-7 検索結果の有効範囲の変更
@Override
public void handleSearch(final RequestContext requestContext,
final SearchRequest request,
final SearchResultHandler resultHandler)
throws UnsupportedOperationException {
SearchRequest newRequest = new SearchRequestWrapper(request)
{
@Override
public SearchScope getScope()
{
// Change the scope of this request.
return SearchScope.BASE_OBJECT;
}
};
// Forward the request to the next plug-in.
this.getConfiguration()
.getFirstNextPlugin()
.handleSearch(requestContext,
newRequest,
resultHandler);
}
この代替方法にはラッピングされたリクエストがそのまま残るという利点があります。このように、有効範囲プロパティは変更されなかったため復元する必要はありません。
ただし、この代替方法を使用する場合、問題が発生することがあります。ラッピングされたリクエストは、そのアウトバウンドのラッパーについて認識していません。処理がラッパー・リクエストのレベルで実行される場合で、この処理にラッパーのレベルで再定義されたプロパティが含まれている場合、それらのプロパティは無視されます。ラッピングされたリクエストは、それ自身のプロパティに対してのみアクセス権があります。
ラッパーはpackage oracle.oud.requestsのすべてのタイプのリクエストに対して提供されます。
多くの場合、プラグインはリクエストをインターセプトし、なんらかの処理を実行してからチェーン内の次のワークフローにリクエストを転送します。非常に多くの場合で、次のワークフロー要素は正確に1つのみ存在します。この場合、リクエストはスーパー・インスタンスの対応するメソッドを起動することで次の要素に渡すことができます。
リーフ・プラグインの場合、oracle.oud.AbstractPluginにより実装されるすべてのリクエスト・ハンドラをオーバーライドする必要があり、リクエストが次の項で説明されているようにして返される必要があります。
一部の特殊なケースでは、プラグインの後にワークフロー要素がいくつか続く場合があります。プラグインの実装は、どのワークフロー要素にリクエストを転送するかを判断する必要があります。次のワークフロー要素の一覧は、PluginConfigurationインスタンスからgetNextPlugins()メソッドまで取得できます。その後、例3-8に示されているように、適切なメソッドを直接起動することでリクエストが適切なワークフロー要素に転送されます。
例3-8 リクエストの転送
@Override
public void handleBind(final RequestContext requestContext,
final int version,
final BindRequest request,
ResultHandler resultHandler)
throws UnsupportedOperationException
{
// Get the original bind DN from the bind request
DN originalDn = request.getName();
// Transform the bind DN according to custom algorithm
DN newDn = transformDN(originalDn);
BindRequestWrapper wrapper = new BindRequestWrapper(request);
// Update the wrapper object
wrapper.setName(newDn);
// Retrieve the list of next plugins and figure out which one to use
List<Plugin> nextPlugins = this.getConfiguration().getNextPlugins();
// Pass the request to the appropriate plugin (assume the first one here)
nextPlugins.get(0).handleBind(requestContext,
version,
wrapper,
resultHandler);
}
場合によっては、プラグインがリクエストをインターセプトし、チェーン内の次のワークフロー要素にリクエストを転送するかわりに自分自身で結果を返すことができます。
結果オブジェクト・インスタンスは、oracle.oud.plugin.PluginContext.TypeBuilderクラスのnewResult()メソッドを使用して作成できます。その後、この結果は、ハンドラ・メソッドの引数として渡されたresultHandlerオブジェクトからhandleResult()またはhandleErrorResult()メソッドを呼び出すことでプラグインの呼出し元に返すことができます。例3-9に、bindリクエストをインターセプトしてInvalid Credentialsエラーを返す方法を示します。
例3-9 バインド・リクエストのインターセプトおよび無効な資格証明エラーの戻し
@Override
public void handleBind(final RequestContext requestContext,
final int version,
final BindRequest request,
ResultHandler resultHandler)
throws UnsupportedOperationException
{
// Get the original bind DN from the bind request
DN originalDn = request.getName();
// Apply custom logic to decide whether access is granted or not
// Assume invalid credentials
// Create a Result object
Result error = getPluginContext().getTypeBuilder().newResult(ResultCode.INVALID_CREDENTIALS);
// Return it to the plugin caller
resultHandler.handleErrorResult(error);
}
同様に、LDAPエントリをoracle.oud.plugin.PluginContext.TypeBuilderクラスのnewSearchResultEntry()メソッドを使用して作成できます。その後、このエントリは、handleSearch()メソッドの引数として渡されたsearchResultHandlerオブジェクトからhandleSearchResultEntry()またはhandleErrorResult()メソッドを起動することでプラグインの呼出し元に返すことができます。
レスポンスをインターセプトする必要があるプラグインは、次のワークフロー要素にリクエストを発行する前に独自のResultHandlerインスタンスを提供することで対象を明示的に登録する必要があります。ResultHandlerのhandleResult()メソッドは、引数で渡された対応するResultインスタンスで操作が成功したときに起動されます。逆に、ResultHandlerのhandleErrorResult()はエラーが発生したときに起動されます。
カスタムのResultHandler実装で結果を調べて変更できますが、元のErrorHandlerの適切なメソッド(handleResult()またはhandleErrorResult())を起動して呼出し元のワークフロー要素に結果を渡す責任があります。分かりやすくするために、カスタムのResultHandlerをDefaultResultHandler objectclassの特殊化として実装してください。デフォルトでは、結果とエラーはチェーン内の上流のワークフロー要素に渡され、該当するメソッドのみをプラグイン実装によってオーバーライドする必要があります。
同様に、最終検索結果および検索エントリの両方をインターセプトするには検索操作にSearchResultHandlerを使用する必要があります。handleEntry()メソッドは、次のワークフロー要素によってLDAPエントリが返されるたびに起動されます。カスタムのSearchResultHandler実装は、元のSearchResultHandlerのhandleEntry()メソッドを起動してエントリをチェーンに送信する必要があります。
例3-10で、プラグインはバインド・エラーのみをインターセプトします。
例3-10 バインド・エラーのインターセプト
@Override
public void handleBind(final RequestContext requestContext,
final int version,
final BindRequest request,
ResultHandler resultHandler)
throws UnsupportedOperationException
{
// Create a new ResultHandler to intercept bind result
CustomResultHandler customBindHandler = new CustomResultHandler(resultHandler);
// Pass the request to the next plug-in with the custom ResultHandler
super.handleBind(requestContext,
version,
request,
customBindHandler);
}
// implementation of a custom ResultHandler to intercept errors
private class CustomResultHandler
extends DefaultResultHandler
{
public CustomResultHandler(ResultHandler resultHandler)
{
super(resultHandler);
}
@Override
public void handleErrorResult(Result error)
{
// Invoked when Bind fails
// Examine the result and implement some logic
// Pass the result up the chain
super.handleErrorResult(error);
}
}
例3-11では検索エントリと最終検索結果をインターセプトします。
例3-11 検索エントリと最終検索結果のインターセプト
@Override
public void handleSearch(final RequestContext requestContext,
final SearchRequest request,
SearchResultHandler resultHandler)
throws UnsupportedOperationException
{
// Create a new SearchResultHandler to intercept search entries
// and result
CustomSearchResultHandler customHandler = new CustomSearchResultHandler(resultHandler);
// Pass the request to the next plug-in with the custom ResultHandler
super.handleSearch(requestContext,
request,
customHandler);
}
// implementation of a custom SearchResultHandler to intercept entries and errors
private class CustomSearchResultHandler
extends DefaultSearchResultHandler
{
public CustomSearchResultHandler(SearchResultHandler resultHandler)
{
super(resultHandler);
}
@Override
public void handleErrorResult(Result error)
{
// Invoked when Search fails
// Examine the result and implement some logic
// Pass the result up the chain
super.handleErrorResult(error);
}
@Override
public void handleResult(Result result)
{
// Invoked when Search complete
// Examine the result and implement some logic
// Pass the result up the chain
super.handleResult(result);
}
@Override
public boolean handleEntry(SearchResultEntry entry)
{
// Invoked for every search entry to be returned
// Examine the result and implement some logic
// Pass the entry up the chain
return super.handleEntry(entry);
}
}
リクエスト結果が結果ハンドラと呼ばれるオブジェクトを使用して返されます。すべてのLDAP操作が、検索操作を除いて同じ種類の結果を共有します。検索操作にはエントリおよび参照である追加の結果が含まれます。LDAP操作は、リクエストと結果ハンドラのペアで構成されます。リクエストは、リクエストのプロパティにアクセスするために使用します。結果ハンドラは、処理されたリクエストの結果を前のプラグインにポストするために使用します。
例3-12で、前のプラグインにより提供された結果ハンドラは次のプラグインに直接渡されます。その結果、次のプラグインにより返される結果は次のプラグインから前のプラグインに、プラグイン自身をスキップして直接渡されます。リクエストが処理されたことを検出する方法は、handlerSearch(...)呼出しから戻る方法のみです。
例3-12 handlerSearch(...)の戻りによるリクエスト処理完了の検出
@Override
public void handleSearch(final RequestContext requestContext,
final SearchRequest request,
final SearchResultHandler resultHandler)
throws UnsupportedOperationException
{
// Pass the resultHandler reference received from the previous plug-in to
// the next plug-in. This implies that the next plug-in will post the
// result of the search request directly to the previous plug-in.
this.getConfiguration()
.getFirstNextPlugin()
.handleSearch(requestContext,
request,
resultHandler);
// The search request was processed by next plug-in.
}
後続のプラグインにより返される結果をインターセプトするには、プラグインが独自の結果ハンドラを提供する必要があります。
結果ハンドラでは2つのメソッドを定義します。
リクエストが成功したときに次のプラグインにより呼び出されるhandleResult(Result)
リクエストが成功したときに次のプラグインにより呼び出されるhandleErrorResult(Result)
検索結果ハンドラでは2つの追加メソッドを定義します。これらのメソッドは、次のプラグインがまだその他のエントリや参照を返せることを指定するTrue、または予想されるエントリや参照がもうないことを次のプラグインに示すFalseを返す必要があります。たとえば、サイズの限度に達した場合は予想されるエントリや参照はもうありません。
エントリが返されたときに次のプラグインにより返されるhandleEntry(SearchResultEntry)
参照が返されたときに次のプラグインにより返されるhandleReference(DN, SearchResultReference)
OUDプラグインAPIには、oracle.oud.plugin.DefaultResultHandlerという名前のデフォルトの実装が結果ハンドラの実装のために提供されています。このJavaクラスが結果ハンドラ(ほとんどの場合、前のプラグインにより提供された結果ハンドラ)をラッピングし、受け取った結果をラッピングされた結果ハンドラにデフォルトで転送します。結果をキャプチャするために、プラグインが対象となる種類の結果をオーバーライドする必要があります。同様のデフォルト実装が検索結果ハンドラに存在します: oracle.oud.plugin.DefaultSearchResultHandler。
例3-13に、リクエストが不成功の場合に結果のログを記録する方法を示します。
例3-13 リクエストが不成功の場合の結果のロギング
public class EchoErrorResultHandler
extends DefaultResultHandler
{
public EchoErrorResultHandler(ResultHandler resultHandler)
{
super(resultHandler);
}
@Override
public void handleErrorResult(Result error)
{
// Echo the result of the request.
System.out.println("plug-in: error result " + error);
// Let the default behavior forward the result to the wrapped result
// handler
super.handleErrorResult(error);
}
}
例3-14に、リクエストが不成功の場合に検索操作で結果を印刷する方法を示します。
例3-14 検索操作での結果の印刷
@Override
public void handleSearch(final RequestContext requestContext,
final SearchRequest request,
final SearchResultHandler resultHandler)
throws UnsupportedOperationException
{
// The result handler passed to the next plug-in will echo the result in
// case the request was not successful.
this.getConfiguration()
.getFirstNextPlugin()
.handleSearch(requestContext,
request,
new EchoErrorResultHandler(resultHandler));
// The search request was processed by next plug-in.
}
次のことに注意してください。
結果ハンドラはリクエストに関連付けられていません。リクエストに対する参照を結果ハンドラ内に保持することで関連付けを維持するのは開発者の判断によります。
受け取ったリクエストの各インスタンスごとに、カスタムの結果ハンドラの新しいインスタンスが必要です。
検索リクエストのエラーのログを記録するために、上に示したようにサンプル・プラグインを変更します。
Oracle Unified Directoryインスタンスを再起動してJARファイルの変更を有効にします。
OUDインスタンスを停止します。
$ cd instance-directory/OUD/bin
$ stop-ds
C:\> cd instance-directory\OUD\bat
C:\> stop-ds
プラグインJARファイルをlibディレクトリにコピーします。
# cp plugin.jar lib
C:\> copy plugin.jar lib
OUDインスタンスを再起動します。
# start-ds
C:\> start-ds
次のコマンドを実行して、存在しないユーザーを検索します。
ldapsearch --hostname localhost --port 1389 --bindDN "cn=directory manager" --bindPasswordFile /tmp/password --searchScope sub --baseDN "uid=user.unknown,ou=people,dc=example,dc=com" "(objectclass=*)" the command displays SEARCH operation failed Result Code: 32 (No Such Entry) Additional Information: The search base entry 'uid=user.unknown,ou=people,dc=example,dc=com' does not exist Matched DN: ou=people,dc=example,dc=com
ldapsearch --hostname localhost --port 1389 --bindDN "cn=directory manager" --bindPasswordFile C:\tmp\password --searchScope sub --baseDN "uid=user.unknown,ou=people,dc=example,dc=com" "(objectclass=*)" the command displays SEARCH operation failed Result Code: 32 (No Such Entry) Additional Information: The search base entry 'uid=user.unknown,ou=people,dc=example,dc=com' does not exist Matched DN: ou=people,dc=example,dc=com
各コマンドごとに、ログ・ファイル
(UNIX、Linuxではinstance-dir/OUD/logs/server.out、またはWindowsではinstance-dir\OUD\logs\server.out)
に次のような情報を含む必要があります。
plug-in: error result Result(resultCode="No Such Entry", matchedDN="ou=people,dc=example,dc=com", diagnosticMessage="The search base entry 'uid=user.unknown,ou=people,dc=example,dc=com' does not exist", referrals=null, controls=[])
例3-15では、検索リクエストにより返されたエントリをカウントしてそのログを記録します。EntryCounterResultHandlerは、handleEntry(...)メソッドが呼び出されるたびにカウンタの値を増やします。
例3-15 検索リクエストにより返されたエントリのカウント
public class EntryCounterResultHandler
extends DefaultSearchResultHandler
{
// The number of search result entries returned by this search result
// handler.
private int entriesCount;
public EntryCounterResultHandler(SearchResultHandler resultHandler)
{
super(resultHandler);
}
@Override
public boolean handleEntry(SearchResultEntry entry)
{
this.entriesCount++;
return super.handleEntry(entry);
}
public int getEntriesCount()
{
return this.entriesCount;
}
}
検索リクエスト・ハンドラは、処理された各検索リクエストごとに返されたエントリをカウントする結果ハンドラを渡すように変更されます。次のプラグインによりリクエストが処理された後、返されたエントリの数がログに記録されます。
例3-16 検索リクエスト・ハンドラの変更
@Override
public void handleSearch(final RequestContext requestContext,
final SearchRequest request,
final SearchResultHandler resultHandler)
throws UnsupportedOperationException
{
EntryCounterResultHandler counter =
new EntryCounterResultHandler(resultHandler);
// The result handler passed to the next plug-in will count the number of
// entries returned by the next plug-in.
this.getConfiguration()
.getFirstNextPlugin()
.handleSearch(requestContext,
request,
counter);
// The search request was processed by next plug-in.
System.out.println(String.format("plug-in: request %s returned %d entries",
request,
counter.getEntriesCount()));
}
検索リクエストで返されたエントリの個数をログに記録する手順は次のとおりです。
サンプル・プラグインを上の例3-16に示されているように変更します。
Oracle Unified Directoryインスタンスを再起動してJARファイルの変更を有効にします。
OUDインスタンスを停止します。
$ cd instance-directory/OUD/bin
$ stop-ds
C:\> cd instance-directory\OUD\bat
C:\> stop-ds
プラグインJARファイルをlibディレクトリにコピーします。
# cp plugin.jar lib
C:\> copy plugin.jar lib
OUDインスタンスを再起動します。
# start-ds
C:\> start-ds
次のコマンドを実行して、登録されたすべてのユーザーを表示します。
ldapsearch --hostname localhost --port 1389 --bindDN "cn=directory manager" --bindPasswordFile /tmp/password --searchScope sub --baseDN "ou=people,dc=example,dc=com" "(objectclass=*)"
ldapsearch --hostname localhost --port 1389 --bindDN "cn=directory manager" --bindPasswordFile C:\tmp\password --searchScope sub --baseDN "ou=people,dc=example,dc=com" "(objectclass=*)"
各コマンドごとに、ログ・ファイル
(UNIXまたはLinuxではinstance-dir/OUD/logs/server.out、Windowsではinstance-dir\OUD\logs\server.out)
に次のような情報を含む必要があります。
plug-in: request SearchRequest(name=ou=people,dc=example,dc=com, scope=sub, dereferenceAliasesPolicy=never, sizeLimit=0, timeLimit=0, typesOnly=false, filter=(objectClass=*), attributes=[], controls=[]) returned 51 entries the number of returned entries corresponds to the 50 users plus the entry ou=people,dc=example,dc=com
場合によって、アプリケーションで検索リクエストに含まれたフィルタとの相互作用が必要なことがあります。この相互作用はビジター・デザイン・パターンに基づいたメカニズムにより指定され、Javaインタフェースoracle.oud.types.FilterVisitor<R,P>により定義されています。
LDAPプロトコルではフィルタのタイプが10個指定されています: and、or、not、equalityMatch、substrings、greaterOrEqual、lessOrEqual、present、approxMatchおよびextensibleMatch。
ビジター・フィルタでは、フィルタの各タイプごとにvisitAndFilter(...)、visitOrFilter(...)などのハンドラを定義します。
フィルタが解析される際、解析対象のフィルタを構成するフィルタのタイプが識別されます。識別したタイプに関連付けられたビジター・メソッドが順番に呼び出されます。
FilterVisitor<R,P>は2つのパラメータをとります:
<R>は各ビジター・ハンドラで返されるタイプです。
<P>は各ビジター・ハンドラに対して提供できるパラメータです。
例3-17では、解析対象のフィルタにおける属性の存在をチェックするFilterVisitorの実装が提供されます。objectclass=*のように属性が値*に関連付けられている場合、属性がフィルタに存在します。その場合、<R>が評価の結果に対応します。<R>は、属性が解析対象のフィルタに存在する場合はTRUE、属性がない場合はFALSEの値を持つブール値として定義されています。<P>はパラメータで、どの属性をチェックする必要があるかを定義する文字列に対応します。解析対象のフィルタがobjectclass=*の場合、パラメータobjectclassを使用してビジターを呼び出すとTRUEが返されます。他の値はFALSEを返します。
サブフィルタ(and、orおよびnot)で構成されたビジターは、それを構成しているすべてのサブフィルタにアクセスしてチェックを転送します。
例3-17では、説明のために、関連するビジターも情報を記録しています。
例3-17 解析対象のフィルタにおける属性の存在チェック
private class PresenceOfFilterVisitor
implements FilterVisitor<Boolean,
String>
{
@Override
public Boolean visitAndFilter(final String presenceName,
final List<Filter> subFilters)
{
System.out.println("plug-in: visit AND with " + subFilters);
boolean result = false;
// Iterate through all sub filters with this filter visitor.
for(Filter subFilter: subFilters)
{
result = subFilter.accept(this, presenceName);
if ( result )
{
break;
}
}
return result ? Boolean.TRUE : Boolean.FALSE;
}
@Override
public Boolean visitApproxMatchFilter(final String presenceName,
final String attributeDescription,
final ByteString assertionValue)
{
return Boolean.FALSE;
}
@Override
public Boolean visitEqualityMatchFilter(final String presenceName,
final String attributeDescription,
final ByteString assertionValue)
{
System.out.println("plug-in: visit EQUAL with " + attributeDescription + "=" + assertionValue);
return Boolean.FALSE;
}
@Override
public Boolean visitExtensibleMatchFilter(final String presenceName,
final String matchingRule,
final String attributeDescription,
final ByteString assertionValue,
final boolean dnAttributes)
{
return Boolean.FALSE;
}
@Override
public Boolean visitGreaterOrEqualFilter(final String presenceName,
final String attributeDescription,
final ByteString assertionValue)
{
return Boolean.FALSE;
}
@Override
public Boolean visitLessOrEqualFilter(final String presenceName,
final String attributeDescription,
final ByteString assertionValue)
{
return Boolean.FALSE;
}
@Override
public Boolean visitNotFilter(final String presenceName,
final Filter subFilter)
{
System.out.println("plug-in: visit NOT with " + subFilter);
// Visit the associated filter with this filter visitor.
return subFilter.accept(this, presenceName);
}
@Override
public Boolean visitOrFilter(final String presenceName,
final List<Filter> subFilters)
{
System.out.println("plug-in: visit OR with " + subFilters);
boolean result = false;
// Iterate through all sub filters with this filter visitor.
for(Filter subFilter: subFilters)
{
result = subFilter.accept(this, presenceName);
if ( result )
{
break;
}
}
return result ? Boolean.TRUE : Boolean.FALSE;
}
@Override
public Boolean visitPresentFilter(final String presenceName,
final String attributeDescription)
{
System.out.println("plug-in: visit Presence with '" + attributeDescription + "'");
return presenceName.equalsIgnoreCase(attributeDescription) ? Boolean.TRUE
: Boolean.FALSE;
}
@Override
public Boolean visitSubstringsFilter(final String presenceName,
final String attributeDescription,
final ByteString initialSubstring,
final List<ByteString> anySubstrings,
final ByteString finalSubstring)
{
return Boolean.FALSE;
}
@Override
public Boolean visitUnrecognizedFilter(final String presenceName,
final byte filterTag,
final ByteString filterBytes)
{
return Boolean.FALSE;
}
}
例3-18は、プラグインにより処理される検索リクエスト・フィルタにおけるobjectclass=*の存在を検証してログを記録します。
例3-18 objectclass=*の存在の検証およびロギング
@Override
public void handleSearch(final RequestContext requestContext,
final SearchRequest request,
final SearchResultHandler resultHandler)
throws UnsupportedOperationException
{
Filter filter = request.getFilter();
System.out.println("plug-in: visitor returned "
+ filter.accept(new PresenceOfFilterVisitor(),
"objectclass"));
// Pass the resultHandler reference received from the previous plug-in to
// the next plug-in. This implies that the next plug-in will post the
// result of the search request directly to the previous plug-in.
super.handleSearch(requestContext,
request,
resultHandler);
// The search request was processed by next plug-in.
}
プラグインにより処理される検索リクエスト・フィルタにおけるobjectclass=*の存在を検証してログを記録する手順は次のとおりです。
サンプル・プラグインを上の例3-18に示されているように変更します。
Oracle Unified Directoryインスタンスを再起動してJARファイルの変更を有効にします。
OUDインスタンスを停止します。
$ cd instance-directory/OUD/bin
$ stop-ds
C:\> cd instance-directory\OUD\bat
C:\> stop-ds
プラグインJARファイルをlibディレクトリにコピーします。
# cp plugin.jar lib
C:\> copy plugin.jar lib
OUDインスタンスを再起動します。
# start-ds
C:\> start-ds
次のコマンドを実行して、登録されたすべてのユーザーを表示します。
ldapsearch --hostname localhost --port 1389 --bindDN "cn=directory manager" --bindPasswordFile /tmp/password --searchScope sub --baseDN "ou=people,dc=example,dc=com" "(objectclass=*)"
ldapsearch --hostname localhost --port 1389 --bindDN "cn=directory manager" --bindPasswordFile C:\tmp\password --searchScope sub --baseDN "ou=people,dc=example,dc=com" "(objectclass=*)"
各コマンドごとに、ログ・ファイル
(UNIXまたはLinuxではinstance-dir/OUD/logs/server.out、またはWindowsではinstance-dir\OUD\logs\server.out)
に次のような情報を含む必要があります。
plug-in: visit Presence with 'objectClass' plug-in: visitor returned true
異なるフィルタでコマンドを実行すると、ビジター・メカニズムがどのように機能するかが示されます。フィルタ&(|(!(uid=user.1)))で検索すると次のログが記録されます。
plugin: visit AND with [(|(!(uid=user.1)))]
plugin: visit OR with [(!(uid=user.1))]
plugin: visit NOT with (uid=user.1)
plugin: visit EQUAL with uid=user.1
plugin: visitor returned false
APIはLDAPに似た呼出しをOUD内に作成するメソッドを提供します。
内部LDAPリクエストは、クライアントからの外部リクエストによって直接開始されるのではなく、プラグインによって内部的に開始されるという意味で内部的です。内部リクエスト・コールは、プラグインで、クライアント・リクエストが存在しない操作をOUDで実行する必要がある場合に使用します。たとえば、プラグインでユーザー・エントリに対する検索リクエストを実行し、クライアントからバインド・リクエストを受け取ったときに追加の資格証明を取得できます。
oracle.oud.plugin.RequestManagerコールバックは、内部操作を含め、OUDにより処理されたすべての操作に対して起動されます。多くの場合、プラグインはクライアント・アプリケーションによって直接開始された操作に対してのみ適用されます。内部操作と通常の操作は、リクエスト・オブジェクトでisInternal()メソッドを呼び出すことで区別できます。
内部LDAPリクエストは、oracle.oud.plugin RequestBuilderオブジェクト・クラスを介して作成されます。requestBuilderに対する参照は、クライアント・アプリケーションから受け取ったリクエストと関連付けられたRequestContextからgetRequestBuilder()を介して取得できます。
内部操作を実行するために使用されるユーザー資格証明は、作成時に指定されています。一般に、内部操作は現在のセキュリティ・コンテキスト内で、プラグインをトリガーしたユーザーの資格証明を使用して実行されます。場合によっては、内部操作に特権アクセスが必要なことがあります。たとえば、バインド・リクエストを処理する前に実行される内部検索は、その時点で現在のユーザーがまだ認証されていないためanonymousとして実行されます。
特権リクエストを作成するには、呼出しrequestContext.getRequestBuilder(true)とともに特権RequestBuilderを使用します。このビルダーから作成したリクエストのみがroot.otherwiseの特権で実行し、デフォルトのrequestBuilderをrequestContext.getRequestBuilder(false)を介して取得できます。
OUDプラグインAPIには、内部リクエストを起動するための2つの方法が提供されています。最初のモードでは、プラグインは該当する後続ワークフロー要素がチェーン内に構成されていれば、それを起動することで現在のプラグイン・ワークフロー内で呼出しを実行します。2つ目のモードでは、プラグインはLDAPに似た呼出しをエンド・クライアントからのものと同様にOUD内に作成します。各呼出しには、ルーターが操作に適したワークフローを選択できるようにする機能が提供されます。
内部リクエストからの結果は、結果ハンドラを使用して、第3.4項「結果の処理」に説明されているようにして取得できます。
プラグインの次のワークフロー要素は、プラグイン構成から呼出しconfiguration.getNextPlugins()を介して取得できます。これらのプラグインの名前は、getName()メソッドを使用して取得できます。名前を取得した後、次のワークフロー要素が複数構成されている場合にはどのワークフロー要素にリクエストを送る必要があるかを選択できます。たとえば、ロード・バランシング・サービスを提供しているプラグインには複数の後続ワークフロー要素が構成されている可能性があります。内部リクエストを開始した後、ターゲット・ワークフローの場所が決まれば、適切なハンドラ・メソッドを介してリクエストを発行できます。
例3-19「プラグインの後続ワークフロー要素の起動」では、属性customTimeStampを受け入れるようにサーバー・スキーマを変更する必要があります。このプラグインは、内部のmodify操作を使用してログイン時刻をユーザー・エントリ属性customTimeStampに格納します。
処理の時点でバインドはまだ完了していないため、特権リクエスト・ビルダーgetRequestBuilder(true)を使用する必要があることに注意してください。したがって、ユーザーは匿名とみなされます。
例3-19 プラグインの後続ワークフロー要素の起動
@Override
public void handleBind(final RequestContext requestContext,
final int version,
final BindRequest request,
ResultHandler resultHandler)
throws UnsupportedOperationException
{
...
// Get a privileged request builder
RequestBuilder myRequestBuilder = requestContext.getRequestBuilder(true);
// Create a new modify request using that builder
// Target LDAP entry is the user about to be authenticated
ModifyRequest addTimestampModifyRequest = myRequestBuilder.newModifyRequest(request.getName());
// Populate the modification object
addTimestampModifyRequest.addModification(ModificationType.REPLACE,
"customTimeStamp", System.currentTimeMillis()) ;
// Create a ResultHandler to catch the result of the modify operation
ResultHandler modifyResultHandler = new CustomModifyResultHandler(resultHandler);
// submit the request to the next workflow element
getConfiguration().getFirstNextPlugins().handleModify(requestContext,
addTimestampModifyRequest, modifyResultHandler);
...
}
このモードでは、リクエストは内部リクエスト・マネージャ・オブジェクトを介して実行されます。このオブジェクトはRequestContextからメソッドgetInternalRequestManager()を介して取得できます。その後、リクエストを適切なハンドラ・メソッドを介して発行できます。
各リクエストは適切なワークフローにルーティングされるため、所定のワークフロー内のプラグインにより開始された内部リクエストが同じワークフローにルーティングされる可能性があります。プラグインが自分自身で生成したリクエストをインターセプトする場合があります。内部操作の処理で予期しない再帰的ループを防ぐために、内部操作が発行されたときに追加の添付(コンテキスト情報)を内部操作に添付できます。この添付は、新しいリクエストを受け取ったときにプロキシにより取得およびチェックしてループを検出し適切なアクションをとることができます。添付は、リクエスト・オブジェクトにより実装されたAttachmentHolderインタフェースにより管理できます。
例3-20では、変更の前にユーザーのエントリ内でcustomTimeStamp属性を検索しています。変更リクエストが現在のユーザー資格証明付きで作成され、内部リクエスト・マネージャを介してエンド・クライアントからのものと同様に発行されます。分かりやすくするために例外処理をコード例から削除しました。
例3-20 内部リクエスト・マネージャを使用した内部操作の実行
public void handleModify(final RequestContext requestContext,
final ModifyRequest request,
ResultHandler resultHandler)
throws UnsupportedOperationException
{
...
// Get a standard request builder
RequestBuilder myRequestBuilder = requestContext.getRequestBuilder(false);
// Create a new search request using that builder
// Target LDAP entry is the user about to be modified
SearchRequest getLastTimestampRequest = myRequestBuilder.newSearchRequest(
request.getName(), SearchScope.BASE_OBJECT,
getPluginContext().getTypeBuilder().newFilter("(objectclass=*)"),
"currentTimeStamp");
// Create a ResultHandler to catch the result of the search operation
SearchResultHandler searchResultHandler = new CustomSearchResultHandler(resultHandler);
// submit the request via the internal request manager
requestContext.getInternalRequestManager().handleSearch(requestContext,
getLastTimestampRequest, searchResultHandler);
...
}
例3-21に、ループを処理する方法を示します。検索リクエストが初めてプラグインにより受け取られたときは、名前がnbLoopsの添付はありません。プラグインは添付(name=nbLoops, value=1)を含むリクエストにフラグを設定し、その後そのリクエストを内部リクエスト・マネージャに対してリバランスします。検索リクエストは最終的にプラグインに戻ります。プラグインが2度目に添付を取得したとき、値を2に増やし、リクエストに取得したものを設定します。その後、内部リクエスト・マネージャに対してリバランスします。3度目、値(2)はMAX_LOOPSより大きいか等しいため、プラグインはリクエストを次のワークフロー要素に送ります(method super.handleSearch(...)を使用
例3-21 添付処理: ループ処理
// Let search requests loop 2 times within the internal request manager,
// before sending them to next WorkflowElement
public static final int MAX_LOOPS = 2;
@Override
public void handleSearch(RequestContext requestContext,
SearchRequest request,
SearchResultHandler resultHandler)
throws UnsupportedOperationException
{
String name = "nbLoops";
Integer nbLoops = 0;
Set<String> attachmentNames = request.getAttachmentNames();
// Get "nbLoops" attachment value, if ound in the request
if (attachmentNames.contains(name))
{
nbLoops = (Integer) request.getAttachment(name);
}
// if we reach max number of loops...
if (nbLoops >= MAX_LOOPS)
{
// ...remove attachment
request.removeAttachment(name);
// forward request to next WorkflowElement
super.handleSearch(requestContext,
request,
resultHandler);
} else
{
// increment nbLoops value
nbLoops++;
// set attachment nbLoops new value
request.setAttachment(name, nbLoops);
// log request (as internal op) + attachment value
Logger logger = requestContext.getLogger();
HashMap<String, String> map = new HashMap<String, String>();
map.put("nbLoops", Integer.toString(nbLoops));
logger.logSearchRequestIntermediateMessage(request, map);
// re-balance tge search request via the internal request manager
requestContext.getInternalRequestManager().handleSearch(requestContext,
request,
resultHandler);
}
プラグイン実装は、予期しないエラー状態が発生したときにPluginExceptionのサブクラスを発生させることができます。サーバーの動作は例外がいつ発生したかによります。LDAP操作の処理中に発生した場合、クライアント・アプリケーションに対してLDAPエラー80「内部エラー」が返されます。プラグインの初期化中に発生した場合、プラグインは無効になります。
プラグインAPI内で生成されたキャッチされない例外は、OUDデバッグ・ログにWarningレベルで記録されます。
プラグインの標準出力は、そのプラグインをホストしているOUDディレクトリ・サーバー・インスタンスにあるログ・ファイル(UNIXまたはLinuxではinstance-dir/OUD/logs/debug、Windowsではinstance-dir\OUD\logs\debug)にリダイレクトされます。
プラグインの開発中に、次のdsconfigコマンドを使用してデバッグ・ログを有効にできます。
dsconfig set-log-publisher-prop \ --publisher-name "File-Based Debug Logger" \ --set default-debug-level:warning \ --set enabled:true
プラグイン実装は、OUD access、errorまたはdebugログにoracle.oud.plugin.RequestContext.Loggerインタフェースを使用してメッセージを記録できます。
instance-directory/config/java.propertiesから取得したstart-ds.java-argsの値プラス-Xdebug -Xrunjdwp:transport=dt_socket,address=127.0.0.1:8888,server=y,suspend=nを使用してOPENDS_JAVA_ARGSをエクスポートします
OUDインスタンスを再起動します。
これでデバッグ・ポート8888が開きます。
ポート8888上のOUDプロセスにアタッチし、IDEを介してプラグインをデバッグします。
instance-directory/config/java.propertiesから取得したstart-ds.java-argsの値プラス-Xdebug -Xrunjdwp:transport=dt_socket,address=127.0.0.1:8888,server=y,suspend=yを使用してOPENDS_JAVA_ARGSをエクスポートします
OUDインスタンスを再起動します。
これでデバッグ・ポート8888が開きます。
この時点で、(pluginInitialization()メソッドを使用して)プラグイン初期化コードをデバッグする前にポート8888上のOUDプロセスに3回アタッチする必要があります。
java.propertiesファイルを変更するよりもOPENDS_JAVA_ARGSをエクスポートしてください。OPENDS_JAVA_ARGSのエクスポートにOUDインスタンス構成ファイルの変更は不要で、本番でデバッグJVM argsがエクスポートされる危険はありません。