Web Services Invocation Framework (WSIF) は、サービスがどこでどのように提供されるかには関係なくWebサービスを呼び出すための簡単なAPIを提供するツールキットです。前回の記事では (参考文献を参照)、WSIFの必要性、その設計の背後にある思想、および主な機能のいくつかについて説明しました。WSIFのWSDL駆動APIを、Webサービスを使用するための従来型のAPIと比較したり、ポート・タイプ・コンパイラーとスタブレス呼び出しについて説明したりしました。
WSIFには他にももっと機能があります。WSIFのアーキテクチャーにより、ポート・ファクトリーを経由して呼び出しポートを発見することが可能です。カスタマイズされたバインディングを使ってサービス呼び出しを実行し、これをフレームワークにプラグインする呼び出しポートをインプリメントすることができます。独自のポート・ファクトリーを設計し、呼び出しポートの検索または作成が、カスタマイズされたアルゴリズムを使って行われるようにすることもできます。最後に、WSIFなら、メッセージ内部で使用されるデータに関し、どんな 固有のタイプのシステムでも使用できます。この記事では、上記の機能を可能にするWSIFのアーキテクチャーに関する特徴を説明し、この柔軟なアーキテクチャーを活用する特定の方法を論じます。
WSIFは、以下のステップによってサービスの操作を呼び出します。
- WSDL文書をロードします。
- このサービスのためのポート・ファクトリーを作成します。
- ポート・ファクトリーを使用してサービス・ポートを得ます。
- 必要であればメッセージを作成します。そのために、ある固有のタイプ・システムに従ってタイプ付けされたメッセージ・パーツを使用します。
- 呼び出しを行います。そのために、呼び出す操作の名前が付いたポートと、操作の必要に応じて入力または出力 (あるいはその両方) のメッセージを提供します。
ここでかぎとなる抽象概念は、WSIFPort という、WSDLポートのランタイムの表現です。これは、特定のバインディングに基づいて実際の呼び出しを実行する責任を担います。したがって、たとえばWSIFSOAPPort などがあります。これは、抽象サービス操作の呼び出しに、このサービスのWSDLに指定されたSOAPバインディングを使用します。アーキテクチャーの柔軟性は、WSIFPort インターフェースを中心としています。このインターフェースをリスト1 に示します。
リスト1:
WSIFPort インターフェース
public interface WSIFPort {
public boolean executeRequestResponseOperation (String op,
WSIFMessage input,
WSIFMessage output,
WSIFMessage fault)
throws WSIFException;
// some other methods not specified here
}
|
このインターフェースのインプリメンテーションは、指定された抽象入出力メッセージを使って操作を呼び出す方法を知らなければならないでしょう。
WSIFPort のSOAPのインプリメンテーションは、Apache SOAP APIに基づいている場合、このサービスのWSDL文書のSOAPバインディング情報に基づいてCall オブジェクトを作成することになるでしょう。そして、抽象入力メッセージを使って呼び出しに必要なパラメーターを作成します。SOAPの応答が使用されて抽象出力メッセージが取り込まれ、これが次いでクライアントによって調べられることになります。
WSIFPortFactory は、特定の呼び出しに使用されるWSIFPort を取り出す責任を担います。そのインターフェースは、リスト2 に示されているとおりです。
リスト2:
WSIFPortFactory インターフェース
public interface WSIFPortFactory {
public WSIFPort getPort () throws WSIFException;
public WSIFPort getPort (String portName) throws WSIFException;
}
|
getPort のパラメーターなしのバージョンは、このインターフェースのインプリメンテーションが、カスタマイズされたアルゴリズムを使って呼び出し用のWSIFPortを選択できるようにするということに注意してください。WSIFの配布版には、このインターフェースの2つのインプリメンテーションがあります。すなわち、静的ポート・ファクトリー と動的ポート・ファクトリー です。静的ポート・ファクトリーは、WSIFPort オブジェクトのリストを保管し、getPort() が発行されると、疑似ランダム的な方法でそのオブジェクトの1つを戻します。動的ポート・ファクトリーは、動的プロバイダーのリストを保管します。各プロバイダーは、サービスのWSDL情報に基づき、ランタイムにWSIFPort を作成できます。動的ポート・ファクトリーでgetPort() が呼び出されると、ファクトリーは疑似ランダム的な方法で動的プロバイダーを1つ選出し、これの作成するWSIFPort が戻されることになります。ポート・ファクトリーのアーキテクチャーの全体像を図1 に示します。
図1: WSIFのポートとポート・ファクトリーのアーキテクチャー
(dW-J お詫び: Figure 1の2段目の右側のBoxの中の文字列が、"WSIF Static Port factory"ではなく、"WSIF Dynamic Port Factory"が正解です。)
ここまでの部分で、どのようにWSIFが特定のインプリメンテーションから分離しているか、またポートを発見して作成するためにポート・ファクトリーがどのように使用されているかを説明しました。ポートによって呼び出しを実行することはできますが、呼び出し自体を実行する前の不可欠なステップは、操作に必要なメッセージの作成です。WSDLのメッセージは、あるタイプ・システムに結び付けられた、名前付きのパーツから構成されています。一般的に言って、WSDLのパーツは、タイプ・システムとしてXMLスキーマを使用してタイプ付けされています。これは、言語に依存しない方法であり、極めて強力です。クライアントからこうしたサービスを呼び出すことは、このスキーマ・タイプをより便利なタイプ・システムにマッピングし、このタイプ・システムに属するオブジェクトとスキーマ・タイプに属するオブジェクト同士を変換できるようにすることによって実行されます。たとえば、Javaが固有のタイプ・システムである場合、Javaとスキーマの間の変換は、その明示された目的のために設計されたクラスを使って直列化と非直列化を行うことによって実行されます。
WSIFのパーツのアーキテクチャーは、どんな固有のタイプ・システムでもメッセージ・パーツに使用できるように設計されており、同じメッセージの中のパーツが異なるタイプ・システムを使ってタイプ付けできるようになっています。後者が必要なのは、2つ以上の別個のWSDLメッセージに由来するパーツが異なる固有のタイプ・システムに関連付けられており、これらを単一のメッセージに結合しなければならない場合です。種々の固有のタイプ・システムを許容することに加えて、WSIFメッセージ・オブジェクトを一様に見ることができるよう、メッセージ・パーツを解釈する共通した方法が必要です。WSIFが採用するアプローチは、スキーマがメッセージ・パーツの使用する標準的な抽象タイプであり、そうしたスキーマ・タイプすべてには対応するJavaBeanがあるというものです。JavaBeanは、スキーマからJavaへの規範的なマッピングを使って定義できます。このアプローチは、WSIFパーツ用の共通したタイプ・システムを備えるものとなります。したがって、WSIFPart インターフェースはリスト3 のようになります。
リスト3:
WSIFPart インターフェース
public interface WSIFPart {
public Class getJavaType ();
public Object getJavaValue ();
}
|
このインターフェースの特定のインプリメンテーションは、どのような内部タイプ・システムでも使用できます。ただしその表現が、対応する規範的なJavaタイプに変換できなければなりません。WSIFに用意されているWSIFPart インターフェースの唯一のインプリメンテーションは、
WSIFJavaPart です。これは、タイプ・システムとしてJavaを使用します。他のインプリメンテーションで共通の使用パターンを使用し、効率を向上させることができます。たとえば、文書スタイルのSOAPバインディングのように、使用される最も一般的なバインディングにXML文書の交換が含まれている場合のことを考えてみてください。この場合、パーツの値をJavaオブジェクトとしてではなく、文字どおりのXMLとして表現すれば、大変効率的なはずです。そうすれば、呼び出しに文書スタイルSOAPバインディングが使用される場合、構文解析も直列化 / 非直列化も不要になります。
バインディング選択アルゴリズムの変更は極めて容易です。開発者がすべきことは、WSIFPortFactory の独自のインプリメンテーションを作成することか、既存のインプリメンテーションを拡張することだけです。
WSIFDynamicPortFactory を考慮してみましょう。これは、特定のWSDLバインディングのWSIFPort をオンデマンドで生成する動的プロバイダーのリストを保管します。このポート・ファクトリーは、プロバイダーのタイプに従って動的プロバイダーを返します。したがって、たとえば、SOAPポートを処理する動的プロバイダーや、定義するかもしれないCORBAポートを処理する動的プロバイダーなどを認識します。望ましいポートを選択するよう、独自のgetPort() メソッドを使用してこのポート・ファクトリーを拡張できます。これが役に立つ状況を例として示すために、
リスト4 に示すアドレス帳のWebサービスを考慮してみましょう。
リスト4: アドレス帳Webサービス
<?xml version="1.0" ?>
<definitions targetNamespace="http://www.ibm.com/namespace/wsif/samples/ab"
xmlns:tns="http://www.ibm.com/namespace/wsif/samples/ab"
xmlns:typens="http://www.ibm.com/namespace/wsif/samples/ab/types"
xmlns:xsd="http://www.w3.org/1999/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:java="http://schemas.xmlsoap.org/wsdl/java/"
xmlns="http://schemas.xmlsoap.org/wsdl/">
<!-- type defs come here, but we skip them for brevity -->
<!-- message declarations come here, but we will skip them for brevity -->
<!-- port type declarations -->
<portType name="AddressBook">
<operation name="addEntry">
<input message="tns:AddEntryRequest"/>
<output message="tns:AddEntryResponse"/>
</operation>
<operation name="getAddressFromName">
<input message="tns:GetAddressFromNameRequest"/>
<output message="tns:GetAddressFromNameResponse"/>
</operation>
</portType>
<!-- binding declarations -->
<binding name="SOAPBinding" type="tns:AddressBook">
<soap:binding style="rpc"
transport="http://schemas.xmlsoap.org/soap/http"/>
<operation name="addEntry">
<soap:operation soapAction=""/>
<input>
<soap:body use="encoded"
namespace="http://www.ibm.com/namespace/wsif/samples/ab"
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
</input>
<output>
<soap:body use="encoded"
namespace="http://www.ibm.com/namespace/wsif/samples/ab"
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
</output>
</operation>
<operation name="getAddressFromName">
<soap:operation soapAction=""/>
<input>
<soap:body use="encoded"
namespace="http://www.ibm.com/namespace/wsif/samples/ab"
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
</input>
<output>
<soap:body use="encoded"
namespace="http://www.ibm.com/namespace/wsif/samples/ab"
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
</output>
</operation>
</binding>
<binding name="JavaBinding" type="tns:AddressBook">
<java:binding/>
</binding>
<!-- service declaration -->
<service name="AddressBookService">
<port name="SOAPPort" binding="tns:SOAPBinding">
<soap:address location="http://localhost:8080/soap/servlet/rpcrouter"/>
</port>
<port name="JavaPort" binding="tns:JavaBinding">
<java:address class="services.addressbook.AddressBook"/>
</port>
</service>
</definitions>
|
このサービスは2つのバインディングを提供します。すなわち、HTTPを介したSOAPと、固有のJavaです。WSDLにはJavaクラスを直接的にバインディングする標準的な方法がないとはいえ、それはわたしたちの理解のレース・カーにとって些細な減速帯に過ぎません。したがって、そのことについては深く論じません。このサービスのために複数のバインディングが使用可能であるため、クライアントはパフォーマンスが最良と考えられるバインディング (Javaバインディングなど) を使用することを望むでしょう。なぜなら、おそらくそれは、ネットワーク操作や直列化 / 非直列化操作などを介さずに、直接的に抽象操作をJavaメソッドの呼び出しに変換することを意味するからです。もちろん、Javaバインディングが使用可能なのはサービスの配置された環境のみです。SOAPバインディングのように、インターネットで公用に供することはできません。したがって、行いたいのは、可能な場合にJavaバインディングを選択し、その他の場合はSOAPバインディングを使用することです。これは、リスト5 にあるように、独自のWSIFPortFactory を作成することによって行えるでしょう。
リスト5: カスタマイズされたバインディング選択アルゴリズム (コード全体が必要な場合は参考文献を参照)
public class MyWSIFPortFactory extends WSIFDynamicPortFactory {
public WSIFPort getPort () throws WSIFException {
WSIFException ex = null;
WSIFPort portInstance = null;
// examine list of available ports
// do we have a port with a Java binding?
for(int i = 0; i < myPortsArr.length; ++i) {
try {
Port port = myPortsArr[i];
Binding binding = port.getBinding();
if (binding instanceof JavaBinding) {
// attempt to create a port that can
// make invocations using this binding
portInstance = createDynamicWSIFPort(def, service, port);
if(portInstance != null) {
return portInstance;
}
}
} catch(WSIFException wex) {
if(ex == null) {
ex = wex;
}
}
}
// Javaバインディング呼び出しのためのポートが作れなかった
// (おそらく、このサービスにJavaポートが定義されていなかったか、
// 自分自身がサービスを提供する側の環境にいなかったため)。
// この場合、WSDL中の他のポートを返さなければならない。
// 長くなるので、残りの部分は省略。コード全体については、記事の最後の
// Resourcesのセクションを見てください。}
}
|
リスト5 の例のコード全体 (参考文献から入手可能) をダウンロードした場合、これを実行するには、次の手順に従わなければなりません。
- zipアーカイブをディレクトリーに解凍します (この例ではC:\Forgeを使いました)。すると、C:\Forget\wsif-1.0-my-factoryなどのサブディレクトリーが作成されるはずです。
- 例をコンパイルする準備ができたら、WSIFのドキュメンテーションの説明どおりに、必要なJARファイルがすべて含まれていることを確認します。
- C:\FORGE\wsif-1.0-my-factory\samplesのサンプル・ソース・コードをコンパイルします。そして、コンパイルされたJava .classファイルが入っているこのディレクトリーを環境変数に追加します。
これがすべて正常にコンパイルできたら、リスト6 にある最初のコマンドをコマンド行から実行します。このコマンドは、ローカルな例ファイルを使用し、変更を加えた動的呼び出しプログラムを使用します。
リスト6: ローカル・アプリケーションでの動的呼び出し
C:\jdk1.3\bin\java.exe -classpath ".;C:\Forge\wsif-1.0-my-factory\samples;
C:\Forge\wsif-1.0\samples;C:\Forge\wsif-1.0\samples\generated;
C:\Forge\wsif-1.0\samples\generated;C:\Forge\xml-soap\xml_soap22.jar;C:\Forge
\jaxp-1.1\jaxp.jar;C:\Forge\jaxp-1.1\crimson.jar;C:\Forge\javamail-1.2\mail.jar;
C:\Forge\jaf-1.0.1\activation.jar;C:\Forge\wsif-1.0\lib\wsif-all-1.0.jar"
clients.MyDynamicInvoker samples\services\stockquote\Stockquote.wsdl
getQuote IBM
Reading WSDL document from 'samples\services\stockquote\Stockquote.wsdl'
Preparing WSIF dynamic invocation
DEBUG: examining for selection port SOAPPort
DEBUG: selected soap port SOAPPort
DEBUG: examining for selection port JavaPort
DEBUG: selected java port JavaPort
Preffered port name 'JavaPort'
Executing operation getQuote
Result:
quote=90.05
Done!
|
リスト7 に2番目の例を示します。ここではローカルな例ファイルからではなく、Webサービス・サイトXmethods.netからWSDLを使用します。
リスト7: ネットワークを介した動的呼び出し
C:\jdk1.3\bin\java.exe -classpath ".;C:\Forge\wsif-1.0-my-factory\samples;
C:\Forge\wsif-1.0\samples;C:\Forge\wsif-1.0\samples\generated;
C:\Forge\wsif-1.0\samples\generated;C:\Forge\xml-soap\xml_soap22.jar;
C:\Forge\jaxp-1.1\jaxp.jar;C:\Forge\jaxp-1.1\crimson.jar;
C:\Forge\javamail-1.2\mail.jar;C:\Forge\jaf-1.0.1\activation.jar;
C:\Forge\wsif-1.0\lib\wsif-all-1.0.jar" clients.MyDynamicInvoker
http://services.xmethods.net/soap/urn:xmethods-delayed-quotes.wsdl getQuote IBM
Reading WSDL document from 'http://services.xmethods.net/soap/urn:xmethods-delayed-quotes.wsdl'
Preparing WSIF dynamic invocation
DEBUG: examining for selection port net.xmethods.services.stockquote.StockQuotePort
DEBUG: selected soap port net.xmethods.services.stockquote.StockQuotePort
Preffered port name 'net.xmethods.services.stockquote.StockQuotePort'
Executing operation getQuote
Result:
Result=90.0
Done!
|
ランタイムにバインディングのインプリメンテーションを更新 / 追加する
SOAPインプリメンテーションをアップグレードしたいとします。このアーキテクチャーを使えば、
ユーザー・コードまたはスタブ・コードを再コンパイルせずに、新しいインプリメンテーションへの移行を完了できます。なぜなら、WSIF APIは同じままだからです。新しいWSIFPort インプリメンテーションを作成するとします。これは、新しいSOAPインプリメンテーションの備える、変更の加えられたSOAPクライアントAPIを使用して呼び出しを行います。次いで、WSDL SOAPポートから新しいWSIFPort インプリメンテーションへの変換を行う動的プロバイダーを作成します。この動的プロバイダーを登録すると、以前に登録されたSOAP動的プロバイダーは置き換えられ、WSDL SOAPポートすべての呼び出しは、新しいWSIFPort インプリメンテーションを介して行われるようになります。
独自のWSDLバインディングを定義する状況を考慮してみましょう。まず第一に、WSIFランタイムが新しいバインディングでWSDL文書をロードできることを確認しなければなりません。そのためには、WSDL4J (参考文献を参照) の拡張レジストリーAPIを使ってWSDLに追加する拡張性要素のためのハンドラーを定義しなければなりません。これをどのように行うかは、詳細に説明しません。次いで、WSIFPort と動的プロバイダーのインプリメンテーションを作成し、この新しい動的プロバイダーをポート・ファクトリーに登録できます。登録方法は、更新したSOAPバインディングの場合と同じです。
このアーキテクチャーから得られる最も重要な利点の1つは、管理環境ごとにクライアント・スタブをカスタマイズ可能点です。WSIFスタブはすべて、リスト8 に指定されている基底クラスを拡張したものです。
リスト8: WSIFStub基底クラス
// some methods and other details omitted
public abstract class WSIFStub {
/**
* Locates a port factory using JNDI if available,
* otherwise uses dynamic port factory with
* pre-registered dynamic providers
*/
protected void locatePortFactory(.....) {
.....
}
/**
* Initializes stub with ports defined in WSDL document
* document must contain *exactly* one service and all
* ports must implement one and only one port type.
*/
protected void initializeFromLocation (...) {
.....
}
/**
* Return the port currently being used.
*/
public WSIFPort getPort () {
return wp;
}
/**
* Return the port factory currently being used.
*/
public WSIFPortFactory getPortFactory () {
return wpf;
}
/**
* Create a new proxy using the given WSIFPortFactory.
*/
public void setPortFactory (WSIFPortFactory wpf) {
.....
}
/**
* Create a new proxy pre-configured to use the given port
* for interacting with the service.
*/
public void setPort (WSIFPort wp) {
.....
}
/**
* Select the port to use by giving the name of the port
* that is desired. If a port of that name cannot be retrived from
* the port factory than an exception will be thrown. The port
* I use will only be updated if this method is successful.
*/
public void selectPort (String portName) {
.....
}
}
|
このスタブによってもたらされる益は極めて大きなものです。複数のアプリケーションが何も再コンパイルせず同じスタブを使える一方で、管理環境の方は自由に通信方式の変更ができるのです。
リスト5 のケースを考えてみてください。ここでは、カスタマイズされたバインディング選択アルゴリズムを使って独自のポート・ファクトリーを作成しました。JNDIのあるアプリケーション・サーバー環境での操作であれば、このポート・ファクトリーのインプリメンテーションを、適切に名前を付けられたJNDIコンテキストにバインドできるでしょう。スタブは、初期化中にポート・ファクトリーをルックアップするときに、新しいインプリメンテーションを入手することになります。もちろん、必要な場合には、スタブでsetPortFactory を直接的に呼び出すことによって、スタブが使用するポート・ファクトリーに変更を強制することができます。あるいは、スタブのsetPort メソッドを使用して、特定のポートを使用するよう強制することもできます。
Webサービスが進化を続けるにつれ、SOAP以外のプロトコルを使用してサービスのエンドポイントにアクセスすることが一般的になることでしょう。したがって、Webサービスを使用するアプリケーションを作成するときには、SOAPレベルではなく、WSDLレベルで動作するAPIを使用することが必要になります。
WSIFはWebサービスの呼び出しを調べる抽象的な方法を提供するので、バインディングの依存関係から自由な呼び出しAPIを使うアプリケーションが作成できます。WSIFには、カスタマイズ可能なスタブを生成するためのポート・タイプ・コンパイラーがあります。WSIF APIは非常に単純なため、アプリケーションはこれを直接に使用してスタブレス呼び出しができます。WSIFのアーキテクチャーにより、柔軟性に富んだ方法でカスタム・ポートとポート・ファクトリーが定義でき、どんな固有のタイプ・システムを使ってもメッセージが作成できます。生成されるスタブには、アプリケーション・サーバーなどの管理環境用のエントリー・ポイントがあり、呼び出しを行うためにスタブが使用するポートまたはポート・ファクトリーにランタイムの変更を加えることができます。
Webサービスには、バインディングの依存関係から自由で、かつ拡張可能な呼び出しフレームワークが必要であり、WSIFはこの方向への第一歩です。
- WSIFがどのように現在のWebサービスの呼び出しモデルに勝っているかを論じるとともに、WSIFの主な機能のいくつかを説明している、導入の記事をご覧ください。
- この記事のリスト5の完全なコード、およびリスト6と7の例はここからダウンロードできます。
-
alphaWorksのWSIF配布ファイルをダウンロードし、簡単なサンプルを試してください。WSIFのサポートする複数の異なる呼び出しスタイル、およびプロトコル固有のクライアントAPIと比較した場合のその利点を直に調べることができます。
- どんな種類の拡張が可能かについては、WSDLの仕様書をご覧ください。また、WebサービスにアクセスするためのSOAPバインディングを定義するためにWSDLの拡張メカニズムがどのように使用されるかも調べることができます。
-
SOAP仕様書そのものもご覧ください。
- 今までにWebサービスでのプログラミングをした経験がないなら、Web Services ToolKit は、そのための優れた出発点となります。
Nirmal K. Mukhi氏は、IBMのT J Watson Research Labで研究に携わる社員です。彼は、2000年11月以来、Webサービスのさまざまなテクノロジーを研究してきました。その以外に、AI、創作的な執筆活動、そして時代遅れのコンピューター・ゲームにも興味があります。Nirmal氏の連絡先はnmukhi@us.ibm.com です。
Aleksander SlominskiはIndiana Universityの博士課程の学生です。同大学のIU Extreme! Labで研究助手として、XML/SOAP使用可能なバージョンのCommon Component Architectureのインプリメントに携わっています。XML Pull Parserの設計とインプリメントにも携わる一方、XMLのパフォーマンスと使いやすさの側面にも興味があります。2001年の夏、IBMのT J Watson Research Labの実習生として、WSIFの最初のバージョンに取り組みました。Alekの連絡先はaslom@indiana.edu です。