JNDI SPI は、開発者がネームサービスとディレクトリサービスのさまざまな「サービスプロバイダ」を記述し、それらに対応するサービスを、JNDI API を使用するアプリケーションからアクセスできるよう準備するための手段です。サービスプロバイダは、共同で JNDI API の要求を満たすモジュールのセットです。また、JNDI では複数の名前空間にまたがる名前 (複数) の使用が許されるので、サービスプロバイダの実装で 1 つの操作を行うために、別のサービスプロバイダとの相互作用が必要な場合があります。SPI には、複数の異なるプロバイダ実装が共同でクライアントの JNDI 操作を行えるようにするためのメソッドがあります。
このドキュメントでは、SPI のコンポーネントについて解説し、開発者が JNDI のためのサービスプロバイダを作成する方法について説明します。読者は JNDI API ドキュメントの内容に習熟しているものと仮定します。
サービスプロバイダの開発者は、必ず JNDI API ドキュメントの「セキュリティについて」を読んでください。JNDI を使用する開発者、特にサービスプロバイダの開発者が考慮すべき重要な内容が含まれています。
JNDI API の下に位置する実装には複数のタイプがあります。サービスプロバイダには最低 1 つの「コンテキスト実装」が含まれます。コンテキスト実装は、Context
インタフェースまたはそのサブインタフェース (DirContext
、EventContext
、LdapContext
など) を実装します。実装の複雑さは、主に背後のサービスの複雑さに依存し、次にその実装がサポートする JNDI の機能の数に依存します。第 2 章では、コンテキスト実装の構築の詳細について説明します。
コンテキスト実装には、さまざまな方法でアクセスできます。もっとも一般的な方法は、初期コンテキストからアクセスする方法です。第 3 章では、初期コンテキストからコンテキスト実装にアクセスする 2 つの方法を説明します。初期コンテキストファクトリを通じて行う方法と、URL コンテキストファクトリを通じて行う方法です。
JNDI アーキテクチャは、コンテキスト実装の動作を強化するために使用できるコンポーネントまたは実装を定義します。これにより、ユーザとアプリケーションは実装をカスタマイズすることができます。これらのコンポーネントは「ファクトリ」を通じてサポートされます。JNDI には 3 種類のファクトリが定義され、それらを利用するための SPI メソッドがあります。これらのファクトリについては、第 4 章に説明されています。
JNDI SPI は javax.naming.spi
パッケージに含まれています。ここでは、SPI の概要について説明します。SPI の詳細は、対応する javadoc ドキュメントを参照してください。
NamingManager
クラスにはプロバイダ関連の操作を行う static メソッドが含まれます。たとえば、Reference
を使ってオブジェクトのインスタンスを生成するメソッドや、java.naming.factory.initial
プロパティを使って初期コンテキストのインスタンスを取得するメソッド、ObjectFactoryBuilder
と InitialContextFactoryBuilder
をインストールするメソッドなどがあります。DirectoryManager
クラスには、DirContext
関連の操作のための類似した static メソッドが含まれます。
InitialContextFactory
は、初期コンテキストのインスタンスを生成するためのインタフェースです。詳細は、節 3.1 を参照してください。
InitialContextFactoryBuilder
は、InitialContextFactory
のインスタンスを生成するためのインタフェースです。詳細は、節 3.3 を参照してください。
ObjectFactory
は、名前空間に保存されている情報を使ったオブジェクト生成をサポートするためのインタフェースです。DirObjectFactory
は ObjectFactory
のサブインタフェースです。DirContext
インタフェースを実装するコンテキスト実装により使用されます。詳細は、節 4.1 を参照してください。
ObjectFactoryBuilder
は、オブジェクトファクトリを生成するためのインタフェースです。詳細は、節 4.1.4 を参照してください。
StateFactory
は、ネームサービスまたはディレクトリサービスでサポートされる保存可能な形式へのオブジェクトの変換をサポートするためのインタフェースです。DirStateFactory
は StateFactory
のサブインタフェースで、DirContext
インタフェースを実装するコンテキスト実装により使用されます。DirStateFactory.Result
は、DirStateFactory.getStateToBind()
から返される、java.lang.Object
と Attributes
のペアを保持するためのクラスです。詳細は、節 4.2 を参照してください。
Resolver
インタフェースは、Context
への拡張インタフェースをサポートするための連合に参加できるようにプロバイダを実装するためのメソッドを定義します。詳細は、「コンテキストのサブインタフェースを通じた解釈処理」 を参照してください。
ResolveResult
は、Resolver.resolveToClass()
の呼び出しの戻り値です。解釈に成功したオブジェクトと、未解釈の名前が含まれます。
サービスプロバイダ構築の基本作業の 1 つは、Context
インタフェースまたはそのサブインタフェースのどれかを実装するクラスを定義することです。このクラスは「コンテキスト実装」と呼ばれます。コンテキスト実装の開発には、次のガイドラインを参照してください。
一般に、Context
インタフェース (またはサブインタフェース) 内のメソッドおよび、NamingManager
または DirectoryManager
ユーティリティメソッドにパラメータとして渡されるオブジェクトは、呼び出し側が所有します。多くの場合、パラメータは最終的にコンテキスト実装に到達します。呼び出し側がオブジェクトを所有しているため、コンテキスト実装がそのオブジェクトを修正することは禁じられています。さらに、コンテキスト実装がそのオブジェクトへのポインタを保持できるのは操作の間のみで、それ以降のポインタの保持は許可されません。コンテキスト実装がパラメータに含まれる情報を保持する必要がある場合は、そのコピーを保持するものとします。
パラメータ所有権の目的から、コンテキストインスタンス上の操作は、「その操作によって生成されるすべての照会が続行している間」、または「列挙がまだ使用されているときに操作が NamingEnumeration
を返した場合」は、まだ終了していないと見なされます。
コンテキストのインスタンスは再入可能である必要はありません。同じコンテキストインスタンスを同時にアクセスする必要のある 2 つのスレッドは、互いに同期して必要なロックを行うものとします。
しかし、異なるコンテキストインスタンスは、複数スレッドの同時アクセスに対して安全でなければなりません。つまり、それぞれのコンテキストインスタンスに同時に操作を行う 2 つのスレッドは、互いにアクセスを同期する必要があってはなりません。たとえば、2 つのコンテキストが同じリソース (同じ接続など) を共有している場合でも、2 つの別々のスレッドが明示的な同期を行わなくてもそれぞれのコンテキストでの処理が可能 (かつ安全) でなければなりません。
並行性制御の目的から、あるコンテキストのインスタンス上の操作は、「その操作によって生成されるすべての照会が続行している間」、または「列挙がまだ使用されているときに操作が NamingEnumeration
を返した場合」は、まだ終了していないと見なされます。
コンテキスト実装は、その実装がサポートする Context
インタフェースまたはそのサブインタフェース内の個々のメソッドのための実装を定義します。
メソッドがサポートされていない場合は、OperationNotSupportedException
をスローするものとします。
付録 A には、フラットなメモリ内名前空間を実装するコンテキスト実装の例が示されています。
JNDI では、Java アプリケーションのプログラマにとって自然で直観的な Context
(およびそのサブインタフェース) の実装が、プロバイダにより提供されることが奨励されます。たとえば、名前空間上でプリンタ名をルックアップするとき、プログラマは当然、処理対象のプリンタオブジェクトが戻されることを予期します。
Context ctx = new InitialContext(); Printer prt = (Printer)ctx.lookup(somePrinterName); prt.print(someStreamOfData);
同様に、アプリケーションのオブジェクトを背後のサービスに保存するとき、アプリケーションが背後のデータ表現について知る必要がない場合には、自然で直観的な実装がもっとも可搬性があり便利です。
しかし、背後のディレクトリサービスまたはネームサービスにバインドされているのは通常、Java プログラミング言語のオブジェクトではなく、実際のオブジェクトを検出またはアクセスするために使用できる参照情報です。このケースは多く、特に既存のインストール済みマシンにおけるサービスをアクセスおよび共有する Java アプリケーションでは、ごく一般的です。参照は事実上、実際のオブジェクトへの「ポインタ」の役割を果たします。プリンタの例では、実際にバインドされているのはプリンタへのアクセス方法に関する情報 (プロトコルタイプ、サーバアドレスなど) などです。アプリケーションの開発者がこの便利なモデルを使用できるようにするには、背後のサービスから Java プログラミング言語の適切なオブジェクトへのデータの変換をコンテキスト実装が行う必要があります。
この目標を達成するには、さまざまな方法があります。あるコンテキスト実装が、1 つのディレクトリが返せるオブジェクトのすべての実装クラスへのアクセスを持つ場合があります。 また、あるコンテキス実装が、オブジェクトの実装クラスを探すための特別なクラスローダを持つ場合もあります。JNDI には、参照を表現するための標準的な方法として Reference
クラスがあります。アプリケーションとコンテキスト実装では、独力で個別の機構を創作するよりも、このクラスを使用することが奨励されます。ただしこれによって、同じ目的を達成するためにコンテキスト実装が独自の機構を使うことができなくなるということはありません。
JNDI には、コンテキスト実装が Java プログラミング言語内のオブジェクトを背後のサービスでの形式に依存しない方法で読み込んだり保存したりするためのユーティリティがあります。ここでは、これらのユーティリティについて説明します。これらのユーティリティは、「オブジェクトファクトリ」および「状態ファクトリ」と呼ばれる、実際の変換を行うコンポーネントと相互作用します。これらのファクトリについては、第 4 章に説明されています。
JNDI には、背後のサービスから読み込んだデータを Java プログラミング言語のオブジェクトに変換するためにコンテキスト実装が使用すべき、次のメソッドがあります。
Object NamingManager.getObjectInstance(Object refInfo, Name name, Context nameCtx, Hashtable env) throws Exception; Object DirectoryManager.getObjectInstance(Object refInfo, Name name, Context nameCtx, Hashtable env, Attributes attrs) throws Exception;
refInfo
は、基本的なサービスから読み込まれる (オブジェクトを表現する) データです。 name
はオブジェクトの名前、nameCtx
は name
が解釈されるコンテキストです。name
と nameCtx
を対で使用すると、オブジェクトに関する情報を refInfo
から得られるよりも多く取得することができます。env
は、getObjectInstance()
の呼び出し元のコンテキストの環境です。attrs
は、オブジェクトについて、ディレクトリから読み込まれる属性のコレクションで、通常は refInfo
を取得するために使用されるものと同じ要求です。完全なコレクションが要求されなかった場合は、属性の完全なコレクションでないことがあります。
次のメソッドに返すオブジェクトを構築するときは、コンテキスト実装は getObjectInstance()
を呼び出すものとします。 または、バウンド情報からオブジェクトを生成する独自の機構をコンテキスト内で使いたい場合は、その機構を呼び出すものとします (文字列のオーバーロードは示されない)。
javax.naming.Context.lookup(Name name) javax.naming.Context.lookupLink(Name name) javax.naming.Binding.getObject() javax.naming.directory.SearchResult.getObject()
ここに例を示します。プリンタは Reference
を使って名前空間内に表現されています。プリンタの Reference
を実際の Printer
オブジェクトに変換するために、コンテキスト実装は NamingManager.getObjectInstance()
メソッドを使用します。この方法では、背後のサービスはプリンタに関する特定の情報を知る必要がありません。
Object lookup(Name name) { ... Reference ref = <some printer reference looked up from naming service>; return NamingManager.getObjectInstance(ref, name, this, env); }
次の例では、プリンタは属性のコレクションとしてディレクトリ内に表現されているものとします。プリンタのディレクトリエントリを実際の Printer
オブジェクトに変換するために、コンテキスト実装は DirectoryManager.getObjectInstance()
を使用します。
Object lookup(Name name) { ... Attributes attrs = <read attributes from directory>; Reference ref = <construct reference from attributes>; return DirectoryManager.getObjectInstance(ref, name, this, env, attrs); }
JNDI には、オブジェクトを背後のサービスに保存する前に変換するための、次のメソッドがあります。
Object NamingManager.getStateToBind( Object obj, Name name, Context nameCtx, Hashtable env) throws NamingException; DirStateFactory.Result DirectoryManager.getStateToBind( Object obj, Name name, Context nameCtx, Hashtable env, Attributes attrs) throws NamingException;
obj
は、基本のサービスに保存されるオブジェクトです。name
はオブジェクトの名前、nameCtx
は name
が解釈されるコンテキストです。name
と nameCtx
を対で使用すると、オブジェクトに関する情報を obj
から得られるよりも多く取得することができます。env
は、getStateToBind()
の呼び出し元のコンテキストの環境です。attrs
は属性のコレクションで、オブジェクトにバインドされます。DirStateFactory.Result
はオブジェクトおよびその属性のコレクションを含むクラスです。
アプリケーションから与えられたオブジェクトを保存するときは、その前にコンテキスト実装は getStateToBind()
を呼び出すものとします。 または、バウンド情報からオブジェクトを生成する独自の機構をコンテキスト内で使いたい場合は、その機構を呼び出すものとします (文字列のオーバーロードは示されない)。
javax.naming.Context.bind(Name name, Object o) javax.naming.Context.rebind(Name name, Object o) javax.naming.DirContext.bind(Name name, Object o, Attributes attrs) javax.naming.DirContext.rebind(Name name, Object o, Attributes attrs)
次に、Context
実装による Context.bind
のサポートの例を示します。
// First do transformation obj = NamingManager.getStateToBind(obj, name, ctx, env); // Check for Referenceable if (obj instanceof Referenceable) { obj = ((Referenceable)obj).getReference(); } if (obj instanceof Reference) { // store as ref } else if (obj instanceof Serializable) { // serialize } else { ... }
次に、DirContext
実装による DirContext.bind
のサポートの例を示します。
// First do transformation DirStateFactory.Result res = DirectoryManager.getStateToBind( obj, name, ctx, env, inAttrs); obj = res.getObject(); Attributes outAttrs = res.getAttributes(); // Check for Referenceable if (obj instanceof Referenceable) { obj = ((Referenceable)obj).getReference(); } if (obj instanceof Reference) { // store as ref and add outAttrs } else if (obj instanceof Serializable) { // serialize and add outAttrs } else if (obj instanceof DirContext) { // grab attributes and merge with outAttrs } else { ... }
これらの例で示したように、コンテキスト実装はさまざまなタイプのオブジェクト (Reference
、Serializable
、DirContext
) を保存できる場合があります。コンテキスト実装が直接 Referenceable
オブジェクトを保存できないが getStateToBind()
がそのようなオブジェクトを返すときは、コンテキスト実装は代わりに Referenceable.getReference()
を呼び出して結果の Reference
を保存するものとします。
さまざまなタイプのオブジェクトを保存できるコンテキスト実装の場合、次の一般的なタイプについては、この順序に従うものとします。
この順序を推奨する理由は、bind()
/rebind()
メソッドの呼び出し側の意図をもっとも的確に示す順序と考えられるからです。たとえば、Reference
は Serializable
なので、Serializable
チェックを最初に実行すると、どの Reference
オブジェクトも参照形式では保存されません (すべて直列化される)。
コンテキストに文字列の名前の引数が与えられたとき、その名前は複数の名前空間にまたがる可能性のある合成名を表すか、または、単一の名前空間に属する単一の複合名要素 (単一または複数の基本名から作られている) が含まれます。コンテキスト実装は名前のどの部分をコンテキスト内で解釈または処理するかを判断し、残りを次のコンテキストに渡さなければなりません。これは、構文解析的に名前を調べて行なっても、動的に名前を解釈して行なってもかまいません。
コンテキストは CompositeName
のインスタンスの場合には、Name
引数を与えられ、この引数は合成名として扱われます。それ以外の場合は、CompoundName
クラスまたはその他の複合名実装によって実装される複合名として扱われます。
コンテキストは、すべてのコンテキスト操作の解釈フェーズを実行することにより連合に参加します。lookup()
メソッドは必ずサポートしなければなりません。その他のメソッドのサポートは任意ですが、連合に参加するコンテキストの場合は、すべての操作で暗黙の解釈処理をサポートしなければなりません。
図 1: bind() の実行のための、中間コンテキストを通じた解釈の例
たとえば、bind()
操作をサポートしないコンテキストを考えます。このコンテキストが bind()
のための中間コンテキストとして使用されると、操作を次のコンテキストに続けるために、このコンテキストは操作の解釈処理部分を実行しなければなりません。このコンテキストが内部でのバインディングの作成のみを要求されている場合には、単に OperationNotSupportedException
をスローするだけです。図 1 は、bind()
操作を目的のコンテキストで実行するために中間コンテキストを通じて渡す方法を示します。
コンテキスト実装の視点から見ると、属性を取得するためにDirContext ctx = new InitialDirContext(); Attributes attrs = ctx.getAttributes(someName);
getAttributes()
が複数のネーミングシステムをたどることが必要な場合があります。これらのネーミングシステムのいくつかは Context
インタフェースのみをサポートし、DirContext
インタフェースをサポートしていません。これらのネーミングシステムは、ターゲットのコンテキストに向けての解釈のための中間的存在として使用されます。ターゲットのコンテキストは DirContext
インタフェースをサポートしなければなりません。この例を図 2 に示します。
図 2: DirContext でない中間コンテキストを通じた解釈処理
中間のネーミングシステムが Context
の拡張への連合に参加するためには、それらのシステムは Resolver
インタフェースを実装しなければなりません。Resolver
インタフェースは、Context
の特定のサブインタフェースをサポートしない中間コンテキストを通じて解釈を行うために JNDI フレームワークによって使用されます。このインタフェースは、resolveToClass()
メソッドの 2 つのオーバーロード形式により構成されます。このメソッドは名前を部分的に解釈するために使用され、要求されたサブインタフェースのインスタンスである最初のコンテキストで終わります。このメソッドと Context
インタフェース内のすべてのメソッドの解釈フェーズのサポートを提供することにより、コンテキスト実装は、Context
の拡張機能 (サブインタフェース) のための中間コンテキストとして機能します。
public interface Resolver { public ResolveResult resolveToClass(Name name, Class contextType)
throws NamingException; public ResolveResult resolveToClass(String name, Class contextType)
throws NamingException; }
(複数要素の) 合成名の解釈は 1 つのネーミングシステムから次のネーミングシステムに進み、複数のネーミングシステムにまたがった要素の解釈は一般に、各々のネーミングシステムに対応するコンテキスト実装により処理されます。コンテキスト実装の視点から見ると、コンテキスト実装は自分の責任範囲外の要素を次のネーミングシステム (のコンテキスト実装) に渡します。
次のネーミングシステムのためのコンテキスト実装を探す手段はいくつかあります。「明示的」な方法として、1 つのネーミングシステムが次のネーミングシステム内のコンテキスト (またはコンテキストへの Reference
) に結びつく「接続点」を使用して探す方法があります。たとえば、合成名 "cn=fs,ou=eng/lib/xyz.zip" で、LDAP 名 "cn=fs,ou=eng" が解釈されて、1 つのファイルシステムコンテキストに渡され、次にその中で "lib/xyz.zip" が解釈されます。
別の方法として、次のネーミングシステムを「暗示的」に探すこともできます。たとえば、コンテキスト実装は自分が解釈したオブジェクトのサービスに特定の知識に基づいて、次のネーミングシステムを選ぶことができます。たとえば、合成名 "ldap.wiz.com/cn=fs,ou=eng" で、DNS 名 ldap.wiz.com
により DNS エントリの名前が定まります。DNS の先の、次のネーミングシステムを探すために、DNS コンテキスト実装は、エントリ内で見つかった SRV リソース記録 (この場合は LDAP コンテキストを指名する) を使ってコンテキストを構築できます。 この場合には、偶然 LDAP コンテキストの名前が定まります。次のネーミングシステムをこの方法で探した場合は、1 つのネーミングシステムから次のネーミングシステムへの境界を示すために JNDI の合成名区切り文字が使用され、この区切り文字は「次のネーミングシステムへの暗示的ポインタ」とみなされます。
次のネーミングシステムが見つかっても、コンテキスト実装は次のネーミングシステムに合成名の残りの未解釈部分を渡さなければなりません。
複数の名前空間にまたがる名前に対する操作を実行するとき、中間のネーミングシステムのコンテキストは次のネーミングシステムに操作を渡す必要があります。コンテキストはこのために、操作がどこまで進んだかを示す情報を含む CannotProceedException
を最初に作成します。この処理の中で、コンテキストは例外の解釈済みオブジェクト (resolved object)、解釈済み名称 (resolved name)、未解釈名称 (remaining name)、環境 (environment) の部分を設定します2 (Context.rename()
メソッドの場合は解釈済み新名称 (resolved newname) の部分も設定)。
次にコンテキストは CannotProceedException
を static メソッド NamingManager
.getContinuationContext()
に渡すことにより、JNDI から「継続コンテキスト」を取得します。
public class NamingManager { public static Context getContinuationContext( CannotProceedException e) throws NamingException; ... }
getContinuationContext()
は例外の中の情報を使用して、操作を続行するためのコンテキストインスタンスを生成します。
DirContext
操作の継続コンテキストを取得するために、Directory-Manager.getContinuationDirContext()
を使用します。
public class DirectoryManager { public static getContinuationDirContext( CannotProceedException e) throws NamingException; ... }
継続コンテキストを取得したら、名前の未解釈の残り部分を使って操作を続行します。
たとえば、bind()
操作を続行しようとする場合は、コンテキスト実装の中のコードは次のようになります。
public void bind(Name name, Object obj) throws NamingException { ... try { internal_bind(name, obj); ... } catch (CannotProceedException e) { Context cctx = NamingManager.getContinuationContext(e); cctx.bind(e.getRemainingName(), obj); } }
この例で bind()
は、バインドの実際の処理の実行と、処理がこのネーミングシステムを越えることがわかったときの CannotProceedException
例外のスローを、内部メソッド internal_bind()
に依存します。次に、操作を続行するために、例外は getContinuationContext()
に渡されます。操作を続行できないときは、継続コンテキストは CannotProceedException
を元の bind()
操作の呼び出し元に対してスローします。
連合の構成によっては、1 つのネーミングシステムの解釈結果が次のネーミングシステムを示さないことがあります。コンテキスト実装に得られる結論は、「解釈は現在のネーミングシステムで終わったが、次のネーミングシステムに進まなければならない」ことです。
たとえば、合成名 "lib/xyz.zip/part1/abc" が、ZIP 形式のファイルの名前を指定する "lib/xyz.zip" と、ZIP ファイル内のエントリの名前を指定する "part1/abc" の 2 つの部分で構成されているとします。"lib/xyz.zip" の解釈結果はファイルオブジェクトですが、必要な結果は ZIP エントリの名前を解釈するコンテキストです。同様に、ある合成名が tar 形式のファイル内のエントリの名前を指定している場合は、その合成名のファイル要素に必要な解釈結果は tar エントリを解釈するコンテキストです。
実際には、ファイルの形式によっては、ファイルシステムの名前空間下ではどんなタイプのコンテキストでも連合がなされる可能性があります。このような関係は対称であるべきです。つまり、ZIP ファイルコンテキストとそれに類似した他のコンテキストは、その他の、ファイルシステム以外の名前空間の下で連合が可能でなければなりません。さらに、ファイルシステムのコンテキスト実装の開発者および、ZIP ファイル、tar ファイル、その他の未定義の形式のための各コンテキスト実装の開発者が、独立して作業できるようにするべきです。
このようなタイプの連合をサポートするために、JNDI には「nns 参照」(nns:next naming system) と呼ばれる特別な形の Reference
が定義されています。この Reference
には、nns
というタイプアドレスがあります。このアドレスコンテンツは解釈済みのオブジェクト (上の例では ZIP ファイル) です。ファイルシステムの例では、ファイルシステムのコンテキスト実装は次のような nns 参照を作成します。
RefAddr addr = new RefAddr("nns") { public Object getContent() { return theFile; } }; Reference ref = new Reference("java.io.File", addr);
次に、コンテキスト実装は、nns 参照を解釈済みオブジェクトとして使用することにより、CannotProceedException
を作成し (「接続点」の場合と同様に)、解釈済みファイル名と空白要素から成る解釈済みの名前を作成します。空白要素は次のネーミングシステムへの暗示的なポインタとして使用され、次のネーミングシステムへの位置まで解釈が進んだことを示します。解釈済みのオブジェクトと解釈済みの名前の値がどのように対応するかに留意してください。コンテキスト実装は次に、CannotProceedException
を getContinuationContext()
に渡します。
CannotProceedException
内の解釈済みオブジェクト (resolved object) の場合のように、getContinuationContext()
はこの nns 参照を受け取るコンテキスト実装を探します。たとえば ZIP ファイルのコンテキスト実装は、nns 参照と、ファイルの名前 (与えられたコンテキストに対する相対名) などの情報を受け取ります。コンテキスト実装は、そのファイルが ZIP ファイルであると判断すると、そのファイル内の名前を解釈するためのコンテキストを生成します。
連合のための JNDI SPI のフレームワークの中心は、CannotProceedException
です。CannotProceedException
には、NamingException
スーパークラスから継承した、解釈済みの名前または解釈済みのオブジェクトや未解釈の名前などの情報が含まれています。さらに、CannotProceedException
には「代替」名と「代替」名コンテキストも含まれます。NamingException
から解釈された名前が完全な合成名 (操作の最初のコンテキストに相対の名前) であるのに対し、代替名は代替名コンテキストに相対の解釈済み名です。つまり、代替名は解釈済みの名前と同じでなくてもかまいません。代替名と代替名コンテキストは NamingManager
/DirectoryManager.getObjectInstance()
への引数として使用されます。これらの引数により、このメソッドが呼び出したファクトリが解釈済みオブジェクトに関する詳細な情報を取得することができます (たとえば、オブジェクトに関する特別な属性を取得するために使用するなど)。これらのファクトリについては、第 4 章に説明されています。
JNDI SPI フレームワークでは「前方検索」が重視され、次のネーミングシステムの検出が試みられます。 しかし、コンテキスト実装によっては、検出されたあとに解釈のチェーンを「後ろ向きに」たどってコンテキスト上の情報を得ることが必要です。たとえば、ホストネーミングシステムの連合から外された特定のコンテキスト実装が、「そのコンテキスト実装がホスト情報を見つける唯一の方法は、自分の上位 (直接の上位ではない可能性がある) のネーミングシステムに問い合わせることである」ような設計になっているかもしれません。それを行うには、そのコンテキスト実装には、解釈が現在点までどのように進んできたのかに関する「コンテキスト上の情報」が必要です。
連合に関してすでに説明したことをまとめると次のようになります。 複数の名前空間にまたがる名前に操作を実行するときは、コンテキスト実装は最初に、解釈がどこまで進んでいるかを示す情報を含む CannotProceedException
を生成します。コンテキスト実装は次に、getContinuationContext()
を呼び出すことにより JNDI から継続コンテキストを取得します。コンテキスト上の情報の取得をサポートするために、getContinuationContext()
は継続コンテキストの環境に java.naming.spi.CannotProceedException
環境プロパティを Cannot-ProceedException
引数の値を指定して自動的に追加します。このプロパティは継続コンテキストによって継承され、そのコンテキストの実装が例外のフィールドを調べるために使用されます。
LDAP スタイルのディレクトリサービスでは、クライアントの要求を他のサーバにリダイレクトするための「照会 (referral)」の概念がサポートされています。照会は、すでに説明した連合継続の機構とは異なります。 照会は JNDI クライアントに示され、次にそれを追跡するかどうかはクライアントが決定できます。 これに対し、CannotProceedException
が返されるのは、これ以上解釈を進めることができない場合だけです。もう 1 つの違いは、個々のコンテキスト実装が、照会を使って操作を継続する (およびそれを行う機構を自身で決定する) 能力を提供する点です。連合では、継続の機構は個々のコンテキスト実装の範囲外で、個々のコンテキスト実装は、JNDI SPI フレームワークが提供する共通の連合機構を利用します。
照会をサポートするコンテキスト実装は、ReferralException
のサブクラスを定義し、その抽象メソッドに実装を提供します。getReferralContext()
は操作を続行する位置でコンテキストを返し、getReferralInfo()
は照会先の情報を、そのコンテキスト実装に適した形式で返します。
環境プロパティ java.naming.referral
は、コンテキスト実装が照会を処理する方法を指定します。照会に遭遇したとき、または照会の追跡中に問題に遭遇したときに例外をスローするよう求められているコンテキスト実装では、アプリケーションに対して ReferralException
をスローします。操作を続行するために、アプリケーションは元のメソッドに与えたのと同じ引数を使って、照会コンテキストについてメソッドを再度呼び出します。次のコード例は、アプリケーションが ReferralException
を使用する方法を示します。3
while (true) { try { bindings = ctx.listBindings(name); while (bindings.hasMore()) { b = (Binding) bindings.next(); ... } break; } catch (ReferralException e) { ctx = e.getReferralContext(); } }
アプリケーションが、元の引数を使ってメソッドを再び呼び出すというこの慣習に従うのは簡単です。この方法では、操作を続行するための十分な情報を照会コンテキストの実装に与えるために、ReferralException
の実装に負担がかかります。再度呼び出される操作に引数が余分に渡される場合があります。照会コンテキストの実装は、余分な情報や不必要な情報を無視してもかまいません。
操作から、照会に追加して別の結果が返される場合があります。たとえば、コンテキストを探しているとき、いくつかの照会に加えて、どこで詳細結果が得られるかに関する複数の結果をサーバが返すことがあります。これらの結果と照会は、プロトコルレベルでインタリーブされる場合があります。照会がユーザの介入を必要とする (つまり、自動的には追跡されない) 場合は、最初に検索の列挙を通じて結果を返すものとします。結果が返されると、照会例外をスローできます。これにより、シンプルなプログラミングモデルを使って、照会とその結果セットとの間の明確な関係をユーザに示すことができます。
JNDI には、ディレクトリ内の属性を表現するために Attribute
インタフェースが定義されています。属性は、1 つの属性識別子 (文字列) と 1 セットの属性値によって構成されます。 属性値は、Java プログラミング言語の任意のオブジェクトです。Attribute
には、属性の定義と構文定義をディレクトリのスキーマから取得するためのメソッドも定義されています。
public class Attribute { public DirContext getAttributeDefinition() throws NamingException; public DirContext getAttributeSyntaxDefinition()
throws NamingException; ... }
ユーティリティクラス BasicAttribute
には、これらのメソッドの有用な実装はありません。このようなスキーマ情報をサポートするディレクトリコンテキスト実装は、そのスキーマ機構に基づくこれらの 2 つのメソッドを実装する (おそらく BasicAttribute
のサブクラスを作成し、これらの 2 つのメソッドをオーバーライドすることにより)、Attribute
の実装を提供するものとします。次に、コンテキスト実装は Attribute
のインスタンスを返すよう要求されると、これらのサブクラスのインスタンスを返すものとします。コンテキスト実装は、これらの 2 つのメソッドの有意な実装を持たない Attribute
インスタンスを受け取った場合は、その属性の定義と構文を判断するために適切なデフォルトを使用し、属性値のクラス名や属性の識別子に使われている規約などの情報を使うものとします。
DirContext
インタフェースには、次のスキーマ関連メソッドがあります。
public class DirContext { ... public DirContext getSchema(Name name) throws NamingException; public DirContext getSchema(String name) throws NamingException; public DirContext getSchemaClassDefinition(Name name) throws NamingException; public DirContext getSchemaClassDefinition(String name) throws NamingException; }
getSchema()
は指名されたオブジェクトへのスキーマツリーを返し、getSchemaClassDefinition()
は指名されたオブジェクトのスキーマクラス定義を含むコンテキストを返します。単一のグローバルなスキーマを持つシステムでは、name
引数の値にかかわらず、同じスキーマツリーを返します。きめ細かなスキーマ定義をサポートするシステムでは、調査されるコンテキストによって異なるスキーマツリーを返します。
コンテキスト実装は、EventContext
/EventDirContext
インタフェース内のメソッドの実装を提供することにより、イベント通知をサポートします。これらのインタフェースにより提唱されるイベントモデルは、マルチスレッドモデルを使って簡単にサポートできます。アプリケーションが addNamingListener()
を使ってコンテキストにリスナーを登録すると、コンテキストは要求を記録し、イベントを生成するために必要な情報を集めるための処理を開始します。その結果、コンテキストはイベントを生成するための情報を受け取ると、そのイベントをリスナーにただちに渡します。普通、登録を行うスレッドはリスナーを実行するスレッドとは異なります。また普通、コンテキスト実装は自分が作成したスレッドを使用し、リスナーメソッドの実行を管理します。1 つのイベントが複数のリスナーにディスパッチされた場合は、コンテキスト実装はリスナーメソッドを同時に別々のスレッドで実行することを選択できます (一般的にはこれを推奨)。
addNamingListener()
メソッドは NamingListener
のインスタンスを受け取ります。このインスタンスには、NamingListener
のサブインタフェースが 1 つ以上実装されていることがあります。リスナーが複数のサブインタフェースを実装している場合は、コンテキスト実装は、登録を満たすために必要なリソースの保存を試みるものとします。たとえば、1 つの実装が複数のサブインタフェースの要求をすべて捕らえる単一の要求をサーバに発行します。
コンテキストがさらにイベントを発生させることができない場合には、可能であれば、コンテキスト実装はリスナーに対して NamingExceptionEvent
を発生させ、自動的にそのリスナーの登録を取り消すことができるものとします。たとえば、サーバへの接続がリスナーの登録の後で切断され、イベントを渡すための情報が利用できない場合には、コンテキストは NamingExceptionEvent
をリスナーにただちに渡します。
一般に環境プロパティは最大限の可搬性を保証するために定義されます。個々のサービスプロバイダはこれらの汎用プロパティを、そのサービスに適応した特性にマップします。プロバイダに関係のないプロパティは記録するにとどめて、無視します。サービスプロバイダに固有のプロパティまたはプリファレンスの、異なるプロバイダにまたがる適用性が制限されている場合には、それらのプロパティまたはプリファレンスを保存するためにも環境を使用できます。
JNDI API ドキュメントの節 6.1 に、環境プロパティがどのように命名されるかの説明があります。サービスプロバイダに固有のプロパティには、プロバイダに対する一意性を反映する接頭辞を付けます。サービスプロバイダのパッケージ名を先頭に付ける手法がよく使われます。たとえば、Sun の LDAP プロバイダは主に com.sun.jndi.ldap
パッケージに含まれるので、Sun の LDAP プロバイダに固有のプロパティには、先頭に「com.sun.jndi.ldap.」が付きます。
初期コンテキストを作成するとき (InitialContext
またはそのサブクラスからのコンストラクタを使用)、アプリケーションは環境をパラメータとして提供できます。パラメータは、Hashtable
またはそのサブクラスのいずれか (Properties
など) として表現されます。JNDI クラスライブラリは、このパラメータと他のソースからデータを拡張し (JNDI API ドキュメントの第 6 章を参照)、これをコンテキスト実装に渡します。
他のすべてのパラメータと同様に、コンテキスト実装が受け取った環境パラメータは呼び出し側が所有しています。コンテキスト実装は、受け取った環境パラメータのコピーを作成するか、「呼び出し側がパラメータに加えた変更がコンテキスト実装から見えるものに影響しないこと、およびその逆を保証する」手段をとるものとします。また、環境パラメータが Properties
のインスタンスである場合は、パラメータ上での列挙と Hashtable.get()
では最上位のプロパティのみが調べられます (入れ子にされたデフォルトは調べない)。これは、予期される動作です。コンテキスト実装には、Properties
インスタンスの入れ子にされたデフォルト内の値の取得または列挙は予期されません。
JNDI ライブラリは、異なるソース (初期コンテキストへの環境パラメータ、リソースファイル、適用される場合、システムプロパティとアプレットパラメータなど) からプロパティをマージする責任を負います (JNDI API ドキュメントの第 6 章を参照)。コンテキスト実装は普通、与えられた環境から必要なプロパティだけを読み込みます。コンテキスト実装が他のソースを参考にする必要はほとんどありません。
環境は、コンテキストメソッドがコンテキストから次のコンテキストに進むときに、親から子に継承されます。特定のコンテキストによって無視されるプロパティが環境内にあるかどうかにかかわらず、コンテキストインスタンスの環境全体が、子コンテキストのインスタンスに継承されます。
この環境「継承」特性を実装するには、コンテキスト実装はコンテキストインスタンスから次のコンテキストインスタンスに環境を引き渡さなければなりません。1 つのコンテキスト実装の中では、Context
コンストラクタへの引数、または Context
インスタンスを生成するための NamingManager/DirectoryManager.getObjectInstance()
メソッドへの引数として環境を渡すことにより、これを行うことができます。
連合内の複数のコンテキスト実装にまたがる場合は、NamingManager.getContinuationContext()/DirectoryManager.getContinuationDirContext()
の CannotProceedException
パラメータの一部として環境を渡すことにより、これがサポートされます。 渡された環境は次に、操作を続行するコンテキストのインスタンスを生成するときに使用されます。
継承は、「各々のコンテキストが独自の環境の視野を持つ」というセマンティクスが維持されている限りは、どんな方法で実装してもかまいません。たとえば、絶対必要になるまで環境のコピーを延期するために、copy-on-write 実装を使うことができます。
コンテキストの環境は、Context
インタフェースの addToEnvironment()
メソッドと removeFromEnvironment()
メソッドの使用を通じて更新できます。
public interface Context { ... public Object addToEnvironment(String propName, Object propVal) throws NamingException; public Object removeFromEnvironment(String propName) throws NamingException; }
これらのメソッドは、Context
の「この」インスタンスの環境を更新します。コンテキスト実装に関係しない環境プロパティは無視されますが、環境の一部として維持されます。更新された環境は Context
のこのインスタンスに影響し、すべての新しい子 Context
インスタンスに継承されますが、すでに存在する Context
インスタンスには影響しません。Context
上で空白名をルックアップすると、その他の子と同様に継承された環境を持つ新しい Context
インスタンスが返されます。
詳細は、JNDI API ドキュメントの節 6.6 を参照してください。
サービスプロバイダには各々、そのプロバイダに固有のプロパティを含むオプションのリソースファイルがあります。このリソースの名前は次のようになります。
ここで prefix は、プロバイダのコンテキスト実装のパッケージ名で、ピリオド (".") はスラッシュ("/") に変換されます。たとえば、あるサービスプロバイダがクラス名[prefix/]jndiprovider.properties
com.sun.jndi.ldap.LdapCtx
のコンテキスト実装を定義するとします。このプロバイダのためのプロバイダリソースは、com/sun/jndi/ldap/jndiprovider.properties
と指定されます。
JNDI クラスライブラリは、JNDI API ドキュメントの節 6.5.2 に説明されているように、プロパティの値を決める必要があるときは、このファイルを調べます。
プロパティの値を決める必要があるとき、サービスプロバイダは一般に、環境から直接値を取得します。自分のプロバイダリソースファイルに格納する固有のプロパティを、サービスプロバイダが定めることもあります。その場合は、それらのプロパティを自分のプロパティリソースファイルから読み込んでから、JNDI API ドキュメントの節 6.5.2 で説明するアルゴリズムに従った方法でマージしなければなりません。
クライアント/サーバプロトコルを使用するコンテキスト実装では、クライアントとサーバの間の接続とコンテキストの間に 1 対 1 のマッピングは必要ではありません。JNDI は、接続を直接扱わない高レベル API です。必要な接続管理はコンテキスト実装によって行われます。そのため、複数のインスタンスで 1 つの接続を共有することもあり、またコンテキスト実装は独自のアルゴリズムを使って接続とネットワークの使い方を保存してもかまいません。したがって、コンテキストインスタンスでメソッドが呼び出されるときに、コンテキスト実装は要求された操作を実行する以外になんらかの接続管理を行う必要がある可能性があります。
いくつかの環境プロパティは、コンテキストの接続に影響します。たとえば、アプリケーションがセキュリティ関連のプロパティを変更すると、コンテキスト実装がそれらの更新されたプロパティを使って接続を修正したり新しく作成することが必要になる場合があります。変更の前に、接続が他のコンテキストと共有されていた場合は、プロパティが更新されていないコンテキストに接続の変更が影響してはなりません。
すべてのネーミングメソッドはコンテキストに関係して実行されるので、アプリケーションにはこれらのメソッドを起動するために最初のコンテキストが必要です。この最初のコンテキストを、「初期コンテキスト」と呼びます。初期コンテキスト内のバインディングはポリシーセットにより決定され、その後は初期コンテキスト実装により決定されます。 その決定には、おそらくグローバルな企業全体規模の名前空間のネーミングのための標準ポリシーが使用されます。初期コンテキストには、たとえば、インターネット DNS 名前空間へのバインディング、企業全体規模の名前空間へのバインディング、アプリケーションを実行しているユーザに属する個人ディレクトリへのバインディングが 1 つずつ含まれている場合などがあります。
アプリケーションは、次の呼び出しを行うことにより初期コンテキストを取得します。
代替コンストラクタにより環境を引数として渡すことができます。これにより、アプリケーションは初期コンテキストの作成で使用されるプリファレンスまたはセキュリティ情報を引き渡すことができます。Context ctx = new InitialContext();
Hashtable env = new Hashtable();4 env.put(Context.SECURITY_PRINCIPAL, "jsmith"); env.put(Context.SECURITY_CREDENTIALS, "xxxxxxx"); Context ctx = new InitialContext(env);
アプリケーションは、初期コンテキストの取得に続いて、Context
メソッドの呼び出しを実行できます。
Object obj = ctx.lookup("this/is/a/test");
InitialContext
クラス (およびサブクラス) は、デフォルトのアルゴリズムを使って実装を選択します。 このアルゴリズムは、「初期コンテキストファクトリビルダ」(この次に説明) をインストールすることによりオーバーライドできます。
InitialDirContext
は InitialContext
のサブクラスです。初期コンテキストを使ってディレクトリ操作を行うために使用されます。 InitialLdapContext
クラスは InitialDirContext
のサブクラスです。初期コンテキストを使って特別な LDAP v3 操作を行うために使用されます。ここで説明したアルゴリズムとポリシーは、InitialDirContext
と InitialLdapContext
にも当てはまります。説明の Context
を適宜 DirContext
または LdapContext
に置き換えてください。
「初期コンテキストファクトリ」は、第 2 章で概説したガイドラインに従って実装されたコンテキストのインスタンスを生成するクラスです。このファクトリは、InitialContext
クラス (またはサブクラス) のコンストラクタによって使用されます
環境を与えられると、ファクトリは Context
(またはそのサブインタフェース) のインスタンスを返します。
public interface InitialContextFactory { public Context getInitialContext(Hashtable env)
throws NamingException; }
付録 A に、InitialContextFactory
の例があります。
コンテキストインスタンスが生成されたあとは、InitialContext
上で URL 以外の名前 (下記参照) を使ってメソッドが呼び出されると、そのメソッドはコンテキストインスタンスに渡され、そこで呼び出されます。
JNDI は、使用する初期コンテキスト実装を java.naming.factory.initial
プロパティを使って選択します。このプロパティには、1 つの初期コンテキストファクトリの完全修飾クラス名が含まれています。このクラスは InitialContextFactory
インタフェースを実装し、引数をとらない public のコンストラクタを持たなければなりません。JNDI はこの初期コンテキストファクトリクラスをロードしてから、その上で getInitialContext()
を呼び出して、初期コンテキストとして使用される Context
インスタンスを取得します。
特別な初期コンテキストを使おうとするアプリケーションは、InitialContext
(またはサブクラス) コンストラクタに渡された環境の中に、またはリソースファイル、システムプロパティ、アプレットパラメータのいずれかを通じて、java.naming.factory.initial
プロパティを提供しなければなりません。
java.naming.factory.initial
プロパティが設定されていない場合は、初期コンテキストに代わって背後のコンテキストを作成する処理は試行されません。初期コンテキストは、たとえば次で説明するように、URL 名の処理に使用し続けることができます。
URL5 文字列が初期コンテキストに渡された場合は、対応する「URL コンテキスト実装」を使って解釈されます。この機能は InitialContext
クラス (およびサブクラス) によってサポートされ、java.naming.factory.initial
環境プロパティの設定とは無関係です。
この機能により、ある URL コンテキスト実装が利用可能な任意の名前空間に、アプリケーションが初期コンテキストを使って到達することが可能になります。たとえば、次のコードは初期コンテキストから、ある LDAP 名前空間のリストを作成します。
new InitialContext().list("ldap://lserver/ou=eng,o=wiz,c=us");
たとえば、LDAP URL 文字列がスキーマ ID "ldap" を持ち、ファイル URL がスキーマ ID "file" を持つとします。
URL コンテキスト実装は Context
インタフェース (とサブインタフェース) を実装するクラスで、サポートするスキーマの URL 文字列である名前の引数を受け取ります。たとえば、LDAP URL コンテキストは URL 文字列 "ldap" を受け取ります。
URL 文字列名が URL コンテキストに渡されると、String
を受け取るコンテキストメソッドは、その名前を URL スキーマにより定義される構文を持つ URL 名として取り扱います。最初の要素が URL 文字列名である Name
オブジェクトが URL コンテキストに渡されると、最初の要素が URL 文字列として取り扱われ、残りは連合に使用されます (つまり、最初の要素の解釈により、残りの解釈に使用するネーミングシステムが示される)。Name
インスタンスは CompositeName
であるべきで、それ以外の場合は InvalidNameException
をスローするものとします。
URL 文字列以外の名前引数と、不適切なスキーマ ID を持つ URL 文字列は InvalidNameException
をスローして拒否するものとします。
「URL コンテキストファクトリ」は、1 つ以上のスキーマのための URL コンテキストのインスタンスを生成するクラス (実際には特別なタイプの「オブジェクトファクトリ」(節 4.1 を参照)) です。
package_prefix + "."+ scheme_id + "."+ scheme_idURLContextFactory
デフォルトのパッケージ接頭辞 com.sun.jndi.url
がリストの最後に付いています。
たとえば、URL が "ldap://somehost:389
" で、java.naming.factory.url.pkgs
に "com.widget:com.wiz.jndi
" が含まれている場合、InitialContext
クラスは、次のクラスをロードしていずれかのインスタンスの生成に成功するまで試すことにより、対応するファクトリクラスを探します。
ファクトリクラスはcom.widget.ldap.ldapURLContextFactory com.wiz.jndi.ldap.ldapURLContextFactory com.sun.jndi.url.ldap.ldapURLContextFactory
ObjectFactory
インタフェース (「URL コンテキストファクトリ」 を参照) を実装し、引数をとらない public のコンストラクタを持ちます。InitialContext
クラスは、ファクトリの getObjectInstance()
メソッドに解釈済みオブジェクトとしてスキーマ ID を渡し、このメソッドはその URL スキーマのための URL コンテキストを作成します。URL コンテキストは次に、最初に InitialContext
に与えられた URL で目的の Context
または DirContext
操作を行うために使用されます。
サービスプロバイダが URL コンテキストファクトリと URL コンテキスト実装を提供しなければならないという条件はありません。サービスプロバイダがこれらを提供するのは、URL スキーマを持つ URL 文字列名を InitialContext
クラスに受け取らせたい場合だけです。たとえば、サービスプロバイダが、初期コンテキストファクトリと、そのファクトリを通じてアクセスされるコンテキスト実装だけを提供してもかまいません。
アプリケーションで URL 文字列を扱いたくない場合は、NamingManager.getInitialContext()
メソッドを使用できます。 このメソッドは、java.naming.factory.initial
環境プロパティで指定されているファクトリを使ってコンテキストインスタンスを生成します。
このメソッドは、アプリケーションが初期コンテキストファクトリが作成したコンテキストによって実装されているインタフェースにアクセスする必要があるが、その初期コンテキストが Context
、DirContext
、LdapContext
のいずれでもない場合にも便利です。次のコードのフラグメントは、NamingManager.getInitialContext()
を使ってコンテキストを取得し、それをサブクラスにキャストします。
初期コンテキストファクトリビルダ (後で説明) をインストールすると、FooContext ctx = (FooContext) NamingManager.getInitialContext(env); ... Object obj = ctx.lookup(name); ctx.fooMethod1(...);
NamingManager.getInitialContext()
の結果に影響するので注意してください。
初期コンテキストファクトリ「ビルダ」は、初期コンテキストファクトリのインスタンスを生成するクラスです。
アプリケーションは、初期コンテキスト実装を探して構築する方法についての独自のポリシーを定義するために、初期コンテキストファクトリビルダをインストールすることができます。ビルダがインストールされている場合は、初期コンテキストファクトリの作成の責任をビルダが単独で負います。通常 JNDI が使用するデフォルトのポリシー (java.naming.factory.initial
または URL サポート) は採用されません。
初期コンテキストファクトリビルダの実装は、InitialContextFactoryBuilder
インタフェースを実装しなければなりません。このインタフェースの createInitialContextFactory()
メソッドは InitialContextFactory
のインスタンスを生成します。
ビルダがインストールされたあとは、アプリケーションは InitialContext
、InitialDirContext
、InitialLdapContext
のうちいずれかのコンストラクタを使うか、NamingManager.getInitialContext()
を使って初期コンテキストを取得できます。いずれかのコンストラクタを使う場合は、そのクラスの本質は、NamingManager.getInitialContext()
から返される背後のコンテキスト実装のラッパーです。
Context
、DirContext
、LdapContext
から拡張したインタフェースをサポートする初期コンテキストを準備する必要がある場合は、サービスプロバイダは InitialContext
、InitialDirContext
、InitialLdapContext
のうちいずれかのサブクラスを提供する必要があります。
InitialContext
と InitialDirContext
が行うのと同じ方法での URL のサポートを追加するには、サブクラスは InitialContext
で利用できる protected メソッドを次のように使用するものとします。これが、名前の引数を受け取るメソッドを持つインタフェースにとって意味のある唯一の方法です。
たとえば、FooContext
が DirContext
のサブインタフェースであるとします。その初期コンテキスト実装は、使用する実際の初期コンテキストを取得する getURLOrDefaultInitFooCtx()
メソッドを (Name
と String
パラメータの両方に対し) 定義します。
public class InitialFooContext extends InitialDirContext { ... protected FooContext getURLOrDefaultInitFooCtx(Name name) throws NamingException { Context answer = getURLOrDefaultInitCtx(name); if (!(answer instanceof FooContext)) { throw new NoInitialContextException("Not a FooContext"); } return (FooContext)answer; } // similar code for getURLOrDefaultInitFooCtx(String name) }
FooContext
インタフェース内の、名前の引数を受け取る新しいメソッドの実装を行うときは、getURLOrDefaultInitFooCtx()
を次のように使用します。
public Object FooMethod1(Name name, ...) throws NamingException { return getURLOrDefaultInitFooCtx(name).FooMethod1(name, ...); }
FooContext
インタフェース内の、名前の引数のないメソッドまたは URL サポートが不要なメソッドの実装を行うには、InitialContext.getDefaultInitCtx()
を使用します。
protected FooContext getDefaultInitFooCtx() throws NamingException { Context answer = getDefaultInitCtx(); if (!(answer instanceof FooContext)) { throw new NoInitialContextException("Not an FooContext"); } return (FooContext)answer; } public Object FooMethod2(Args args) throws NamingException { return getDefaultInitFooCtx().FooMethod2(args); }
実装には、クラスのための適切なコンストラクタを準備するものとします。コンストラクタは、スーパークラスの適切なコンストラクタを呼び出します。スーパークラスのコンストラクタを呼び出す前に環境を変更または検査する必要がある場合は、初期コンテキストの初期化を制御するための boolean 型のフラグを受け取る protected コンストラクタを使用し、次に、init()
メソッドを使ってコンテキストを初期化します。次に例を示します。
この新しい初期コンテキストを使うクライアントプログラムは、次のようになります。public InitialFooContext(Hashtable environment, Object otherArg) throws NamingException { super(true); // don't initialize yet // Clone environment and adjust Hashtable env = (environment == null) ? new Hashtable(11) : (Hashtable)environment.clone(); ... init(env); }
import com.widget.jndi.InitialFooContext; ... FooContext ctx = new InitialFooContext(env); Object obj = ctx.lookup(name); ctx.FooMethod1(name, ...);
JNDI では、コンテキスト実装がネームサービスまたはディレクトリサービスのオブジェクトを読み込んだり書き込んだりする方法を、アプリケーション、アプリケーションの開発者またはユーザ、あるいはサービスプロバイダがカスタマイズできます。LDAP v3 コントロールクラスのナロー変換についても、同様の機能を使用できます。
これらの機能は、コンテキスト実装にプラグインするモジュールとして考えることができます。
JNDI には名前空間に保存されている情報を使う、汎用的なオブジェクト (Context
のインスタンスを含む) 生成方法があります。その情報のタイプ (java.lang.Object
) は任意です。たとえば、Reference
、URL、またはオブジェクトを生成するために必要な他のデータなどです。名前空間に保存されているこのような情報をオブジェクトに変換する処理は、「オブジェクトファクトリ」の使用を通じてサポートされます。オブジェクトファクトリは ObjectFactory
インタフェース (または DirObjectFactory
サブインタフェース) を実装するクラスです。
あるオブジェクトに関する参照情報 (public interface ObjectFactory { public Object getObjectInstance(Object refObj, Name name, Context nameCtx, Hashtable env) throws Exception; } public interface DirObjectFactory extends ObjectFactory { public Object getObjectInstance(Object refObj, Name name, Context nameCtx, Hashtable env, Attributes attrs) throws Exception; }
refObj
)、そのオブジェクトの名前とバインドされている場所に関する追加情報、および追加の環境情報 (たとえば、オブジェクトを生成するユーザに関する ID 情報や認証情報など) を与えられると、ファクトリはその参照情報が表すオブジェクトを生成しようとします。たとえば、プリンタに関する参照情報を与えられると、プリンタオブジェクトファクトリは Printer
のインスタンスを返します。 DirContext
の実装とともに使用されるオブジェクトファクトリの場合には、ファクトリにはオブジェクトに関する属性も与えられます。ファクトリがさらに多くの属性または情報を必要とする場合は、name
引数または nameCtx
引数を使ってネームサービスまたはディレクトリサービスから直接情報を取得できます。
与えられた引数を使ってオブジェクトを生成できないときは、ファクトリは null
を返すものとします。たとえば、プリンタオブジェクトファクトリにディスクドライブに関するデータが与えられた場合は null
を返します。ファクトリは、他のオブジェクトファクトリを試みるべきではない場合にのみ例外をスローするものとします。そのため、ファクトリはその実装からスローされる実行時例外に留意すべきです。たとえば、プリンタオブジェクトファクトリにプリンタのデータが与えられたが、そのデータの形式が正しくない場合には、例外をスローします。
オブジェクトファクトリは JNDI の複数の場所で、基本的には参照情報をオブジェクトに変換するために使用されます。オブジェクトファクトリは、連合、初期コンテキストでの URL 処理、および (プリンタの例で説明したように) データをアプリケーションが期待する形式に変換するために使用されます。
Reference
には、クラス名とオブジェクトファクトリの位置を返すためのメソッドが含まれます。次のメソッドが Reference
にあります。
ディレクトリサービスまたはネームサービスから読み出されたオブジェクトがpublic class Reference { ... public String getClassName(); public String getFactoryClassName(); public String getFactoryClassLocation(); }
Reference
または Referenceable
のインスタンスである場合は、対応するオブジェクトファクトリは、Reference
内の情報を使って探すことができます。getFactoryClassName()
メソッドは ObjectFactory
インタフェースを実装するファクトリクラスの名前を取得します。このファクトリは ObjectFactory
インタフェースを実装し、引数をとらない public のコンストラクタを持たなければなりません。 getFactoryClassLocation()
は、ファクトリのクラス実装のコードベースを取得します。 これは、空白文字で区切られた URL のリストです。
JNDI は ObjectFactory
インスタンス上で、Reference
と環境を引数として使用して getObjectInstance()
を呼び出すことによりオブジェクトを生成します。結果は、getClassName()
により識別されるクラスのインスタンスです。
アプリケーションに返されるオブジェクトのインスタンスを生成するために必要なすべてのクラスは、JNDI に準備されている機構を使って用意できます。アプリケーションは、ローカルでクラスをインストールする必要はありません。
図 3: 名前空間から参照を使ってオブジェクトを取り戻す例
プリンタの例に戻り、あるプリンタを表すためのインタフェースが Printer
で、そのインタフェースの実装が BSDPrinter
クラスであるとします。BSDPrinter
は Referenceable
インタフェースを実装し、BSDPrinter
のインスタンスの構築方法に関する情報とプリントサーバとの通信のためのアドレス情報を Reference
クラスを使って保存します。Reference
には、オブジェクト ("Printer"
) のクラス名、プリンタオブジェクトファクトリ ("PrinterFactory
") のクラス名、およびファクトリのクラス実装をロードするための URL が含まれます。ファクトリクラス名と実装位置を使って、JNDI は最初に PrinterFactory
の実装をロードし、PrinterFactory
のインスタンスを生成します。次に、JNDI はファクトリ上で getObjectInstance()
を呼び出して、参照を使って Printer
のインスタンスを生成します。たとえば、参照の中の 1 つのアドレスが "bsd
" タイプのアドレスを持ち、プリントサーバのホスト名 ("lobby-printserver
") が含まれているとします。PrinterFactory
インスタンスはアドレスタイプ ("bsd
") を使って BSDPrinter
インスタンスの生成を決定し、アドレスコンテンツ ("lobby-printserver
") をコンストラクタに渡します。結果の BSDPrinter
オブジェクトは、lookup()
の結果として返されます。
コンテキスト実装の観点から見ると、これは NamingManager
/DirectoryManager.getObjectInstance()
を呼び出すことにより、すべて自動的に行われます。
アプリケーションが lookup()
から返された BSDPrinter
インスタンス上で print()
を呼び出すと、データが印刷のために lobby-printserver
マシン上のプリンタサーバに送られます。アプリケーションは、名前空間に保存されている Reference
の詳細や、ジョブを実行するために使用するプロトコル、および BSDPrinter
クラスがローカルで定義されているかネットワークを通じてロードされたかどうかについて通知される必要はありません。背後のサービスに保存されている情報を、Printer
インタフェースを実装するオブジェクトに変換する処理は、サービスプロバイダ (プリンタ名からプリンタアドレス情報へのバインディングを保存)、プリンタサービスプロバイダ (PrinterFactory
クラスと BSDPrinter
クラスを提供)、JNDI SPI フレームワーク (前の二者を結び付けて、アプリケーションが直接使用できるオブジェクトを返す) が協同することにより透過的に行われます。
このようなオブジェクトのためのサービスプロバイダは、次のことを行わなければなりません。
Referenceable
を実装する、あるいは Reference
のサブクラスであるオブジェクト (例: BSDPrinter
) のクラスを定義します。
Reference
と、オブジェクトの参照アドレスを定義します。
ObjectFactory
(例: PrinterFactory
) を実装するファクトリクラスを定義します。このクラスの getObjectInstance()
メソッドは、ステップ 2 で定義された Reference
を与えられるとステップ 1 で定義されたクラス (例: BSDPrinter
) のインスタンスを生成します。
Reference
に、URL タイプのアドレスは含まれるがファクトリのクラス名と位置は含まれない場合、またはその参照が URL を含む文字列の配列である場合は、JNDI は節 3.2 で説明した URL コンテキストファクトリサポートを使ってファクトリを探し、次にアドレス内の URL 文字列をファクトリの getObjectInstance()
メソッドに渡します。JNDI が予期する URL コンテキストファクトリの実装の動作については、4.1.6 節で説明します。
このようなオブジェクトのためのサービスプロバイダは、次のことを行わなければなりません。
BSDPrinter
) のクラスを定義します。
ObjectFactory
を実装する URL コンテキストファクトリを定義します。このクラスの getObjectInstance()
メソッドは、ステップ 2 で定義された URL を与えられるとステップ 1 で定義されたクラス (例: BSDPrinter
) のインスタンスを生成します。
Reference
からのファクトリ情報の抽出または URL の使用に加え、JNDI は java.naming.factory.object
プロパティで指定されているオブジェクトファクトリを探すこともできます。 このプロパティは環境内またはプロバイダリソースファイルにあります (節 2.9.5 を参照)。このプロパティには、オブジェクトファクトリの完全修飾クラス名のコロンで区切られたリストがあります。各クラスは ObjectFactory
インタフェースを実装し、引数をとらない public のコンストラクタを持たなければなりません。リスト内の各クラスに対し、JNDI はファクトリクラスのロードとインスタンス生成を試み、与えられた環境引数とオブジェクトを使ってそのインスタンス上で ObjectFactory/DirObjectFactory.getObjectInstance()
メソッドの呼び出しを試みます。生成に成功すると、結果のオブジェクトが返されます。 失敗した場合は、JNDI はリスト内の次のクラスに同じ手順を使い、リストが終わるかファクトリが null
以外の結果を返すまでこれを繰り返します。
図 4: java.naming.factory.object を使って名前空間からオブジェクトを取り戻す例
プリンタの例では、名前空間内のプリンタを表現するために Reference
を使うのではなく、他の情報が保存されます。その情報があとで取得されると、その情報を Printer
インスタンスに変換する試みの次に、java.naming.factory.object
で指定されたオブジェクトファクトリが試みられます。
このようなオブジェクトのためのサービスプロバイダは、次のことを行わなければなりません。
BSDPrinter
) のクラスを定義します。
Reference
である必要はありません。対応するオブジェクトファクトリが理解できるものなら何でもかまいません (例: サーバ名 "printer type=bsd; host=lobby-printserver
" を含む文字列)。
ObjectFactory
(例: PrinterFactory
) を実装するファクトリクラスを定義します。このクラスの getObjectInstance()
メソッドは、ステップ 2 で定義されたクラス (例: printer type=bsd; host=lobby-printserver
") のインスタンスを与えられるとステップ 1 で定義されたクラス (例: BSDPrinter
) のインスタンスを生成します。
サービスプロバイダは、オブジェクトをバインドまたはルックアップするときに、自動的に実際のオブジェクト (例: BSDPrinter
) と参照情報 (ステップ 2、例: printer type=bsd; host=lobby-printserver
") の間で変換を行うものとします。
アプリケーションでオブジェクトの生成に特別なファクトリを使いたい場合は、アプリケーションの java.naming.factory.object
環境プロパティにそのファクトリのクラス名を入れ、そのファクトリのクラスとオブジェクトクラスを準備しなければなりません。
オブジェクトファクトリ「ビルダ」は、オブジェクトファクトリのインスタンスを生成するクラスです。
アプリケーションは、オブジェクトファクトリの実装を探して構築する方法についての独自のポリシーを定義するために、オブジェクトファクトリビルダをインストールすることができます。ビルダがインストールされている場合は、オブジェクトファクトリの生成の責任をビルダが単独で負います。通常 JNDI が使用するデフォルトのポリシー (Reference
、URL 文字列、java.naming.factory.object
プロパティ) は採用されません。
図 5: ファクトリビルダを使って名前空間からオブジェクトを取り戻す例
オブジェクトファクトリビルダのためのサービスプロバイダは、次のことを行わなければなりません。
ObjectFactory
を実装するオブジェクトファクトリを定義します。
ObjectFactoryBuilder
を実装するクラスを定義します。このクラスの createObjectFactory()
メソッドは、ステップ 1 の ObjectFactory
クラスのためのコンストラクタを使用します。
このファクトリビルダを使用するアプリケーションは、これをインストールしなければなりません。
NamingManager.setObjectFactoryBuilder(builder);
「コンテキストファクトリ」は Context
のインスタンスを生成するオブジェクトファクトリです。特定のネームサービスまたはディレクトリサービスのためのこれらのコンテキストの実装を、「コンテキスト実装」と呼びます。コンテキスト実装については、第 2 章で説明しました。他のオブジェクトファクトリと同様に、コンテキストファクトリは、すでに説明した 3 つの機構 (Reference
、URL スキーマ ID、および java.naming.factory.object
プロパティ内のリスト) のいずれからでも取得できます。
URL コンテキストファクトリは、特殊なコンテキストファクトリです。このファクトリは、ObjectFactory.getObjectInstance()
を実装するとき、次の規則に従います。
refObj
が null
の場合は、このファクトリに関連付けられているスキーマの URL を解釈するためのコンテキストを作成します。結果のコンテキストは特定の URL に結び付いていません。たとえば、
getObjectInstance(null, null, null, env)
ldap://ldap.wiz.com/o=wiz,c=us
" または "ldap://ldap.umich.edu/
", ...) を解釈できるコンテキストが返されます。
refObj
が URL 文字列である場合は、その URL により識別されるオブジェクトを生成します。たとえば、
getObjectInstance("ldap://ldap.wiz.com/o=wiz,c=us", null, null, env);
ldap.wiz.com
上で "o=wiz,c=us
" により指名されるオブジェクトが返されます。これがコンテキストを指名する場合は、次に (関連する) LDAP 名 (例: "cn=Jane Smith
") を解釈するために使用できます。
refObj
が URL 文字列の配列である場合は、それらの URL は参照先のコンテキストに関して等しいと見なされます。URL が等しいこと、または等しい必要があることの確認はコンテキストファクトリに任されています。URL の配列内の順序は重要ではありません。getObjectInstance()
で返されるオブジェクトは単一の URL の場合と同じで、URL により指名されるオブジェクト (通常はコンテキスト) です。
refObj
がそれ以外のタイプの場合は、getObjectInstance()
の動作は実装により決まります。
URL コンテキストファクトリは、URL に解釈のために渡されたときに InitialContext
クラスにより使用されます。URL コンテキストファクトリは、名前空間に保存されている URL から Java プログラミング言語のオブジェクトを生成するためにも使用されます (4.1.2 節を参照)。
JNDI には、基本的なコンテキスト実装による保存が可能な形式にオブジェクトを変換するための機構があります。その形式は、基本的なコンテキスト実装がアクセス可能ならどんなタイプでもかまいません。たとえば、Reference
、URL、Serializable
オブジェクト、属性セットなど、基本的なコンテキスト実装で許容されるデータです。任意のオブジェクトを名前空間に保存できるデータに変換することは、「状態ファクトリ」の使用によりサポートされます。状態ファクトリは、StateFactory
インタフェース (または DirStateFactory
サブインタフェース) を実装するクラスです。
オブジェクト (public interface StateFactory { public Object getStateToBind(Object obj, Name name, Context nameCtx, Hashtable env) throws NamingException; } public interface DirStateFactory { public DirStateFactory.Result getStateToBind(Object obj, Name name, Context nameCtx, Hashtable env, Attributes attrs) throws NamingException; }
obj
)、オブジェクトの名前とどこにバインドするかに関するオプションの情報、および追加の環境情報 (たとえば、名前空間をアクセスするユーザについての識別または認証情報) を与えられると、ファクトリはバインディングに適したオブジェクトの生成を試みます。普通、状態ファクトリは目的のネームサービスまたはディレクトリサービスとコンテキスト実装の両方またはどちらかについての知識を持ち、どのデータ形式が許容されるかを認識しています。DirContext
実装に使用される状態ファクトリの場合は、ファクトリにはオブジェクトとともに保存されているいくつかの属性も与えられます。ファクトリがオブジェクトに関してさらに多くの情報を必要とする場合は、name
/nameCtx
引数を使ってネームサービスまたはディレクトリサービスから直接取得することができます。たとえば、LDAP ディレクトリのためのプリンタ状態ファクトリは、プリンタを表す属性セットを返すことができます。
与えられた引数を使ってデータを返すことができない場合は、ファクトリは null
を返すものとします。たとえば、プリンタ状態ファクトリにディスクオブジェクトが与えられたときは、null
を返します。他の状態ファクトリが試されるべきではない場合、その状態ファクトリは例外をスローするだけです。したがって、ファクトリはその実装からスローされる例外に留意すべきです。たとえば、プリンタ状態ファクトリにプリンタオブジェクトが与えられたが属性が矛盾する場合には、ファクトリは例外をスローします。
最終的に、ファクトリの出力形式は基本的なネームサービスまたはディレクトリサービスによって決まります。たとえば、CORBA Object Services (COS) ネームサービスのためのコンテキスト実装がサービスに保存できるのは CORBA オブジェクト参照だけであり、LDAP のためのコンテキスト実装が保存できるのは属性だけです。 ただし、これらの属性内の情報の符号化には多くの柔軟性があります。
サービスプロバイダは普通、予期される各 (共通) タイプの入力にそれぞれファクトリを提供し、アプリケーションはそれらのファクトリのセットを自分の状態ファクトリに拡張できます。たとえば、COS ネームサービスのサービスプロバイダには Java Remote Method Invocation (RMI) オブジェクトを CORBA オブジェクト参照に変換するための状態ファクトリがあります。そのプロバイダのユーザが、Microsoft COM オブジェクト参照を CORBA オブジェクト参照に変換するための状態ファクトリを追加できます。
JNDI は、java.naming.factory.state
プロパティで指定された状態ファクトリを探します。 このプロパティは環境またはプロバイダリソースファイルにあります ( 節 2.9.5 を参照)。このプロパティには、状態ファクトリの完全修飾クラス名のコロンで区切られたリストがあります。各クラスは StateFactory
インタフェースを実装し、引数をとらない public のコンストラクタを持たなければなりません。リスト内の各クラスに対し、JNDI はファクトリクラスのロードとインスタンス生成を試み、与えられたオブジェクト、名前、コンテキスト、環境、属性の引数を使って StateFactory/DirStateFactory.getStateToBind()
メソッドの呼び出しを試みます。ファクトリから生じる結果が null
以外の場合は、その結果が返されます。 それ以外の場合は、JNDI はリスト内の次のクラスに同じ手順を使い、リストが終わるかファクトリが null
以外の結果を返すまでこれを繰り返します。
LDAP v3 プロトコルを使って、応答コントロールにサーバから送られた応答を添付することができます。コントロールは、OID 文字列 IDと ASN.1 BER で符号化されたバイトコード列からなります。外部の情報または補助がない場合は、コンテキスト実装が返せるのは Control
インタフェースの簡単な実装だけで、これは OID とバイト列を返します。
JNDI には、応答コントロールを扱うための次の abstract クラスがあります。
コンテキスト実装は応答コントロールを受け取ると、static のpublic abstract javax.naming.ldap.ControlFactory { ... public static Control getControlInstance(Control ctl, Context ctx, Hashtable env) throws NamingException; public abstract Control getControlInstance(Control ctl) throws NamingException; }
getControlInstance()
メソッドを呼び出して、コントロールをよりユーザフレンドリーなアクセスメソッドにナロー変換できるコントロールファクトリを探します。そのようなコントロールは、たとえば ASN.1 BER のバイト列を復号化し、情報を Java タイプとして返せるメソッドへのアクセスを提供できます。このようなコントロールファクトリが見つからない場合は、元の応答コントロールが返されます。次は、時刻を復号化する仮想の TimeResponseControl
の例です。
コントロールファクトリが責任を持つコントロールの数は 1 つでも複数でもかまいません。与えられた引数を使ってコントロールを返すことができない場合、ファクトリはpublic class TimeResponseControl implements Control { long time; // Constructor used by ControlFactory public TimeResponseControl(String OID, byte[] berVal) throws NamingException { // check validity of OID time = // extract time from berVal }; // Type-safe and User-friendly method public long getTime() { return time; } // Low-level methods public String getID() { return TIME_OID; } public byte[] getEncodedValue() { return // original berVal } ... }
null
を返すものとします。普通、コントロールファクトリは、コントロールの OID をファクトリがサポートする OID のリストと単に照合するだけです。他のコントロールファクトリを試みるべきではない場合にのみ、コントロールファクトリは例外をスローするものとします。したがって、ファクトリはその実装からスローされる例外に留意すべきです。たとえば、コントロールファクトリにそのファクトリがサポートする OID が与えられたが、バイト列に符号化の誤りがある場合には、例外をスローすべきです。
public class VendorXControlFactory extends ControlFactory { public VendorXControlFactory () { } public Control getControlInstance(Control orig) throws NamingException { if (isOneOfMyControls(orig.getID())) { ... // determine which of ours it is and call its constructor return new TimeResponseControl(orig.getID(), orig.getEncodedValue()); } return null; // not one of ours } }
JNDI は、java.naming.factory.control
プロパティで指定された応答コントロールファクトリを探します。 このファクトリは環境またはプロバイダリソースファイルにあります (節 2.9.5 を参照)。このプロパティには、コントロールファクトリの完全修飾クラス名のコロンで区切られたリストがあります。各クラスは ControlFactory
インタフェースを実装し、引数をとらない public コンストラクタを持たなければなりません。リスト内の各クラスに対し、JNDI はファクトリクラスのロードとインスタンス生成を試み、与えられたコントロール、コンテキスト、環境の引数を使って ControlFactory.getControlInstance()
インスタンスメソッドの呼び出しを試みます。ファクトリから生じる結果が null
以外の場合は、その結果が返されます。 それ以外の場合は、JNDI はリスト内の次のクラスに同じ手順を使い、リストが終わるかファクトリが null
以外の結果を返すまでこれを繰り返します。
ファクトリ内のメソッドにパラメータとして渡されたオブジェクトは、すべて呼び出し側が所有します。したがってファクトリは、操作またはオブジェクトの修正の期間を超えてオブジェクトへのポインタを保持することを禁じられています。ファクトリがパラメータに含まれる情報を操作期間を超えて保存する必要がある場合は、パラメータのコピーを独自に保持するものとします。
ファクトリのインスタンスは再入可能とします。つまり、あるファクトリの単一のインスタンス上のメソッドを複数のスレッドが同時に呼び出すことができるものとします。
CannotProceedException
は、コンテキスの内部メソッドの 1 つが「処理中の名前がそのメソッドのネームシステムの範囲を越えていること」を検知したときにスローされていることがよくあります。例外をスローするプロセスは、コンテキストの実装に依存しています。
3 これはアプリケーション内のコードであることに注意してください。「連合内での操作の続行」のコード例は、コンテキスト実装内のコードです。
4 この目的に、Hashtable のサブクラス (Properties など) を使用することもできます。
5 このドキュメントで "URL" は、RFC 1738 とそれに関連する RFC により定義される URL 文字列を指します。URL 文字列は RFC の構文規則に準拠する任意の文字列です。対応するサポートが java.net.URL
クラスまたは Web ブラウザに必ずあるとは限りません。URL 文字列は、String
の名前パラメータまたは Name
パラメータの最初の要素として渡されます。
jndi@java.sun.com
Copyright © 1999, Sun Microsystems, Inc. All rights reserved.