SOAPは、Webサービスと呼ばれる、プラットフォーム非依存、言語非依存の分散アプリケーションの新世代のバックボーンとして幅広く採用されています。
しかし、Axisは単なるSOAPエンジンというわけではなく、以下のものも含まれています:
Axisは、(IBMによる"SOAP4J"を起源とする)Apache SOAPの第三世代です。2000年後半に、Apache SOAP v2のコミッタは、このエンジンをさらに柔軟に、設定変更可能に、そして、W3Cから提示されるXMLプロトコル仕様とSOAPの両方を扱えるようにするための議論を始めました。
すぐに、すべてを再構築する必要があることが明確になりました。v2のコミッタの多くから非常に類似した設計が提示されました。それは、柔軟、かつ、構成可能な方式で、限定された機能を実装するメッセージ「ハンドラ」の設定変更可能な「チェーン」を全てのベースとするというものです。
数ヶ月続いた議論と、この方針によるコーディングの後、Axisは以下のキーとなる機能を持つようになりました:
Axisで楽しんで下さい。これがオープンソースの成果であることには注意して下さい。そのコードが何らかの新機能や不具合修正となると思ったら、どうか参加して助けて下さい!Axisの開発者コミュニティは、あなたの参加を待っています。
このガイド内のサンプルを実行する前に、CLASSPATHに以下が含まれているかどうか確認して下さい(もし、CVS checkoutしたものからAxisを構築した場合、これらはaxis-1_0/libではなくxml-axis/java/build/libにあります):
1 import org.apache.axis.client.Call; 2 import org.apache.axis.client.Service; 3 import javax.xml.namespace.QName; 4 5 public class TestClient { 6 public static void main(String [] args) { 7 try { 8 String endpoint = 9 "http://nagoya.apache.org:5049/axis/services/echo"; 10 11 Service service = new Service(); 12 Call call = (Call) service.createCall(); 13 14 call.setTargetEndpointAddress( new java.net.URL(endpoint) ); 15 call.setOperationName(new QName("http://soapinterop.org/", "echoString")); 16 17 String ret = (String) call.invoke( new Object[] { "Hello!" } ); 18 19 System.out.println("Sent 'Hello!', got '" + ret + "'"); 20 } catch (Exception e) { 21 System.err.println(e.toString()); 22 } 23 } 24 }(このファイルは、samples/userguide/example1/TestClient.javaにあります)
ネットワークに接続していれば、このプログラムは以下のように実行できます:
% java samples.userguide.example1.TestClient Sent 'Hello!', got 'Hello!' %さて、何が起こったのでしょうか?11,12行目で、新しいServiceオブジェクトとCallオブジェクトを生成しました。これらは、標準JAX-RPCオブジェクトで、実行するサービスに関するメタデータを格納するために使用されます。14行目では、終端URL、つまり、SOAPメッセージの宛先を設定しています。15行目で、Webサービスの操作(メソッド)名を定義しています。そして、17行目で、実際にパラメータの配列を渡して実行したいサービスを呼び出しています。この場合は、ちょうど1つのStringです。
通信経路上に流れるSOAP要求を見ることで、この引数に何が起こるのかを確認することができます(色をつけた部分を見て、これらが上の呼び出しにおける値と一致していることに注目して下さい):
<?xml version="1.0" encoding="UTF-8"?> <SOAP-ENV:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <SOAP-ENV:Body> <ns1:echoString xmlns:ns1="http://soapinterop.org/"> <arg0 xsi:type="xsd:string">Hello!</arg0> </ns1:echoString> </SOAP-ENV:Body> </SOAP-ENV:Envelope>
このString引数は、自動的にXMLにシリアライズされ、サーバは同一のStringを返します。上ではこれをデシリアライズして表示しています。
注意: 実際にクライアントとサーバ間でやりとりされるXMLを見るためには、配布内に含まれるtcpmon、または、SOAPモニタツールを使用することができます。概要は、付録を参照して下さい。
call.addParameter("testParam", org.apache.axis.Constants.XSD_STRING, javax.xml.rpc.ParameterMode.IN); call.setReturnType(org.apache.axis.Constants.XSD_STRING);ここでは、呼び出しにおける(唯一の)第1パラメータに、testParamという名前を割り当てています。また、ここで、パラメータの型(org.apache.axis.Constants.XSD_STRING)と、このパラメータが入力用、出力用、もしくは、入出力用かを定義します。上の場合は入力用としています。この状態で、プログラムを実行すると、以下のようなメッセージになります:
<?xml version="1.0" encoding="UTF-8"?> <SOAP-ENV:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <SOAP-ENV:Body> <ns1:echoString xmlns:ns1="http://soapinterop.org/"> <testParam xsi:type="xsd:string">Hello!</testParam> </ns1:echoString> </SOAP-ENV:Body> </SOAP-ENV:Envelope>このパラメータが予想通り "testParam" という名前になっていることに注目して下さい。
以下に、echoStringメソッドでよく見られる典型的な応答を示します:
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <SOAP-ENV:Body> <ns1:echoStringResponse xmlns:ns1="http://soapinterop.org/"> <result xsi:type="xsd:string">Hello!</result> </ns1:echoStringResponse> </SOAP-ENV:Body> </SOAP-ENV:Envelope>
赤色で示した部分に注目して下さい。この属性は、スキーマ型宣言であり、Axisは、これを使用して要素の内容が何かを決定します。この例では、JavaのStringオブジェクトにデシリアライズ可能なものとなります。多くのツールキットは、この種の明示的な型情報をXML内に埋め込み、メッセージを「自己記述」可能なものとしています。一方、ツールキットの中には以下のような応答を返すものもあります:
<?xml version="1.0" encoding="UTF-8"?> <SOAP-ENV:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <SOAP-ENV:Body> <ns1:echoStringResponse xmlns:ns1="http://soapinterop.org/"> <result>Hello, I'm a string!</result> </ns1:echoStringResponse> </SOAP-ENV:Body> </SOAP-ENV:Envelope>
メッセージ内に型がありません。この場合、どうすれば<result>要素をどのJavaオブジェクトにデシリアライズすれば良いのかが分かるでしょうか?答えは、メタデータ、つまりデータについてのデータです。この場合は、戻り値として何を想定すれば良いかを通知する、サービスの記述が必要です。以下に、Axisにおけるクライアントサイドでの実現方法を示します:
call.setReturnType( org.apache.axis.Constants.XSD_STRING );
このメソッドは、返される要素が型付けされていない場合、戻り値が定義済みのSOAP String型として設定されたxsi:type属性を持つかのように動作すべきであることを、Axisクライアントに通知します(この動作のサンプルとして、interop echo-testクライアント、samples/echo/TestClient.javaがあります)。
また同様に、想定される戻り値の型をJavaクラスとして指定できる以下のメソッドがあります:
call.setReturnClass(String.class);
ここまでで、クライアントとしてSOAPサービスにアクセスするための基本を理解できたかと思います。しかし、どのようにすれば独自サービスを公開できるでしょうか?
public class Calculator { public int add(int i1, int i2) { return i1 + i2; } public int subtract(int i1, int i2) { return i1 - i2; } }(この非常に小さなクラスは、samples/userguide/example2/Calculator.javaにあります)。
このクラスをSOAP経由で利用可能にするには、どうすれば良いでしょうか?この問いには、2つの答えがあります。まずは、最も簡単な方法から始めましょう。Axisは、このほとんど手間がかからない手法を提供しています!
% copy Calculator.java <your-webapp-root>/axis/Calculator.jws次にステップ2...は、1分間待って下さい。これで終りです!これで、以下のURLでこのサービスにアクセスすることができるはずです(Axis Webアプリケーションが8080ポート上にあると仮定しています):
http://localhost:8080/axis/Calculator.jws
Axisは、自動的にこのファイルを検知し、クラスをコンパイルし、正確にSOAP呼び出しをサービスクラスのJava呼び出しに変換します。試してみて下さい。samples/userguide/example2/CalcClient.javaに電卓クライアントがあります。これを次のように使用して下さい:
% java samples.userguide.example2.CalcClient -p8080 add 2 5 Got result : 7 % java samples.userguide.example2.CalcClient -p8080 subtract 10 9 Got result : 1 %(J2EEサーバが使用しているポートで "-p8080" を置き換えなければならないことに注意して下さい)。
Axisで用意された柔軟性を十分に使用するためには、AxisのWebサービス配備記述子(WSDD)のフォーマットを理解しなければなりません。配備記述子は、Axisに「配備」したい、つまり、Axisエンジンで有効とさせたい、全てのものが含まれています。最も良く配備するものは、Webサービスです。そのため、ここでは、基本的なサービスの配備記述子を見ることから始めましょう(このファイルは、samples/userguide/example3/deploy.wsddです):
<deployment xmlns="http://xml.apache.org/axis/wsdd/" xmlns:java="http://xml.apache.org/axis/wsdd/providers/java"> <service name="MyService" provider="java:RPC"> <parameter name="className" value="samples.userguide.example3.MyService"/> <parameter name="allowedMethods" value="*"/> </service> </deployment>実際のところ非常に単純です。最も外側の要素は、エンジンに対して、これがWSDD配備であること、および、「java」名前空間を定義することを通知しています。そして、service要素が実際にサービスを定義します。サービスは、対象となるチェーンです(Axis アーキテクチャガイドを参照して下さい)。つまり、サービスは要求フロー、折り返しハンドラ(サービスから見るとプロバイダと呼ばれます)、応答フローを全て、もしくは、一部持つことを意味しています。この場合、プロバイダは、Axisに組込み済みのJava RPCサービスを表す "java:RPC" です。実際にこれを扱うクラスは、org.apache.axis.providers.java.RPCProviderです。 異なる様式のサービスやそのプロバイダについては、後で詳しく説明します。
RPCProviderに対して、正しいクラス(例えば、samples.userguide.example3.MyService)のインスタンス化と呼び出しを行うよう通知しなければなりません。これは、クラス名を設定するserviceの1つのパラメータと、そのクラス内のpublicメソッド全てがSOAP経由で呼び出し可能かどうかをエンジンに通知する別のパラメータを指定した<parameter>タグを含めることで行うことができます(「*」がこれを意味します。呼び出し可能なメソッド名を空白、または、カンマで区切ることでSOAPでアクセス可能なメソッドを制限することもできます)。
WSDD記述子は、サービスについての他の情報や、後述の"ハンドラ"と呼ばれるAxisの別の部分も含めることができます。
Axisがサポートするサービスオブジェクト(実際はメソッドを実装するJavaオブジェクト)のスコープには、3種類あります。デフォルトの"Request"スコープは、サービスにSOAP要求が届く度に新しいオブジェクトを作成します。"Application"スコープは、サービスへの全ての要求内で共有されるシングルトンオブジェクトを作成します。"Session"スコープは、サービスにアクセスするセッションが有効なクライアント毎に新しいオブジェクトを作成します。スコープオプションを指定するには、サービスに以下のような<parameter>を追加します("value"は、request、session、applicationのいずれかです):
<service name="MyService"...> <parameter name="scope" value="value"/> ... </service>
% java org.apache.axis.client.AdminClient deploy.wsdd <Admin>Done processing</Admin>このコマンドによって、SOAP経由でサービスにアクセスできるようになります。Clientクラスを実行して確認して下さい。以下のようになるはずです:
% java samples.userguide.example3.Client -lhttp://localhost:8080/axis/services/MyService "test me!" You typed : test me! %配備が本当に行われたかどうかを自分自身で確認したい場合は、サービスの配備を元に戻し、再度呼び出してください。example3/ディレクトリに、"undeploy.wsdd"があります。これまでdeploy.wsddで行ったように、このファイルを使用することができます。このファイルで、AdminClientを実行して下さい。そして、サービスクライアントを再実行し、何が起こるかを確認して下さい。
AdminClientを使用して、サーバ上に配備された全てのコンポーネントの一覧を入手することができます:
% java org.apache.axis.client.AdminClient list <big XML document returned here>ここには、サービス、ハンドラ、転送などが出力されます。この一覧は、サーバの"server-config.wsdd"ファイルの完全なコピーであることに注意して下さい。このファイルについては、少し後でより詳細に説明します。
<deployment xmlns="http://xml.apache.org/axis/wsdd/" xmlns:java="http://xml.apache.org/axis/wsdd/providers/java"> <!-- define the logging handler configuration --> <handler name="track" type="java:samples.userguide.example4.LogHandler"> <parameter name="filename" value="MyService.log"/> </handler> <!-- define the service, using the log handler we just defined --> <service name="LogTestService" provider="java:RPC"> <requestFlow> <handler type="track"/> </requestFlow> <parameter name="className" value="samples.userguide.example4.Service"/> <parameter name="allowedMethods" value="*"/> </service> </deployment>
最初の節では、samples.userguide.example4.LogHandlerクラスで実装された"track"というハンドラを定義しています。このハンドラにオプションを渡して、どのファイルにメッセージを書き出すかを指定します。
次に、LogTestServiceサービスを定義しています。これは、上で最初の例として示したものと同じRPCサービスです。違いは、<service>内の<requestFlow>要素です。これはサービスが呼び出された時に、そのプロバイダより前に呼び出されるハンドラの集合です。"track"への参照を入れることで、このサービスが呼び出される度にそのメッセージが確実にログに残ります。
<service name="AdminService" provider="java:MSG"> <parameter name="className" value="org.apache.axis.util.Admin"/> <parameter name="allowedMethods" value="*"/> <parameter name="enableRemoteAdmin" value="true"/> </service>
警告:リモート管理を有効にすると、認証されていないパーティに対してマシンへのアクセスを与えることになります。この設定を行う場合は、設定にセキュリティを確実に加えて下さい!
DocumentサービスとWrappedサービスは、データに対してSOAPエンコーディングを使用しないという点で似ています。単なるプレインな旧XMLスキーマです。しかし両方とも、既にAxisは、Java表現をXMLに"バインド"するので(詳細については、データバインディング参照)、XML構造を直接扱うのではなく、Javaオブジェクトとして扱うことになります。
DocumentサービスとWrappedサービスの違いについては、まず、以下のような商品注文を含む簡単なSOAPメッセージで説明した方が分かり易いでしょう:
<soap:Envelope xmlns="http://xml.apache.org/axis/wsdd/" xmlns:java="http://xml.apache.org/axis/wsdd/providers/java"> <soap:Body> <myNS:PurchaseOrder xmlns:myNS="http://commerce.com/PO"> <item>SK001</item> <quantity>1</quantity> <description>Sushi Knife</description> </myNS:PurchaseOrder> </soap:Body> </soap:Envelope>
PurchaseOrder用の適切なスキーマを以下に示します:
<schema targetNamespace="http://commerce.com/PO"> <complexType name="POType"> <sequence> <element name="item" type="xsd:string"/> <element name="quantity" type="xsd:int"/> <element name="description" type="xsd:string"/> </sequence> </complexType> <element name="PurchaseOrder" type="POType"/> </deployment>
Document様式のサービスでは、これは以下のようなメソッドにマップします:
public void method(PurchaseOrder po)
言い替えると、<PurchaseOrder>要素全体が、3つのフィールドを持つ1つのBeanとしてメソッド内で扱われます。一方、Wrapped様式のサービスでは、以下のようなメソッドにマップします:
public void purchaseOrder(String item, int quantity, String description)
"Wrapped"の場合、<PurchaseOrder>要素は"ラッパ"であり(これが名前の由来です)、正しい操作を示すためだけに提供されます。メソッドへの引数は、外側の要素を"取り外し"、パラメータとして存在する内側の各要素を取り出したものになります。
Document様式、Wrapped様式は、WSDDでは以下のように示されます:
<service ... style="document">
Document様式用
<service ... style="wrapped">
Wrapped 様式用
WSDL文書(以下参照)から始める場合は、ほとんどDocumentサービスかWrappedサービスかで悩むことはありません。
最後に、Axisを後戻りさせて、Javaオブジェクトに変換させずに実際のXMLとしてコーディングしたい場合に使用される、"Message"様式サービスについて説明します。Message様式サービスのメソッドとして、以下の4つの有効なシグネチャがあります:
public Element [] method(Element [] bodies);
public SOAPBodyElement [] method (SOAPBodyElement [] bodies);
public Document method(Document body);
public void method(SOAPEnvelope req, SOAPEnvelope resp);
最初の2つは、DOM Element、もしくは、SOAPBodyElementの配列をメソッドに渡します。この配列は、エンベロープ内の<soap:body>の内側にある各XML要素を配列要素として持ちます。
3番目のシグネチャでは、<soap:body>を表すDOM Documentを渡し、同じものが返るものと想定します。
4番目のシグネチャでは、要求と応答メッセージを表す2つのSOAPEnvelopeオブジェクトを渡します。サービスメソッド内でヘッダを参照したり編集したりする場合に、このシグネチャが使用されます。応答エンベロープに書き出すものは、戻す時に呼び出し元に全て自動的に送信されます。応答エンベロープには、他のハンドラによって追加されたヘッダが既に含まれている可能性があることに注意してください。
Messageの例
Messageサービスのサンプルは、samples/message/MessageService.javaにあります。MessageService
サービスクラスには、1つのpublicメソッドechoElements
があります。このメソッドは、上述の3メソッドシグネチャの最初のものに一致します:
public Element[] echoElements(Element [] elems)
MsgProvider
ハンドラは、このメソッドを入力メッセージのSOAPボディの子に対応するorg.w3c.dom.Element
オブジェクトの配列を付けて呼び出します。この配列には、よく1つのElement(おそらくは、ある合意されたスキーマに従うXML文書のルート要素)が含まれますが、SOAPボディは複数の子を扱うことができます。このメソッドは、応答メッセージのSOAPボディ内として返されるElement[]
の配列を返します。
Messageサービスは、WSDDファイルと共に配備されなければなりません。以下に、MessageService
クラス用の完全なWSDDを示します:
<deployment name="test" xmlns="http://xml.apache.org/axis/wsdd/" xmlns:java="http://xml.apache.org/axis/wsdd/providers/java" xmlns:xsi="http://www.w3.org/2000/10/XMLSchema-instance"> <service name="MessageService" style="message"> <parameter name="className" value="samples.message.MessageService"/> <parameter name="allowedMethods" value="echoElements"/> </service>
</deployment>
"style"属性は、RPC配備の例と異なる点に注意して下さい。"message"様式は、このサービスがorg.apache.axis.providers.java.RPCProvider
ではなく、org.apache.axis.providers.java.MsgProvider
によって扱われることをAxisに通知します。
配備して、samples.message.TestMsgを実行することで、このサービスを試験することができます(この試験用ドライバが何を行うかについてはソースを参照して下さい)。
xsd:base64Binary | byte[] |
xsd:boolean | boolean |
xsd:byte | byte |
xsd:dateTime | java.util.Calendar |
xsd:decimal | java.math.BigDecimal |
xsd:double | double |
xsd:float | float |
xsd:hexBinary | byte[] |
xsd:int | int |
xsd:integer | java.math.BigInteger |
xsd:long | long |
xsd:QName | javax.xml.namespace.QName |
xsd:short | short |
xsd:string | java.lang.String |
WSDLでオブジェクトがnillable可能であると宣言した場合、つまり、呼び出し元はnilという値を返すことが選択できる場合、プリミティブデータ型は、Byte、Double、Booleanなどといったラッパクラスに置き換えられます。
XSDデータ型は SOAP 'section 5' データ型であり、全てnillableです。従って、ラッパクラスへのマップのみとなります。これらの型が存在する理由は、これらが全て"ID"と"HREF"属性をサポートし、multi-refシリアライゼーションをサポートするRPCエンコードされた文脈内で使用されることです。
明らかに、受信者は受信した失敗のインスタンスを生成する方法が分からないので、この機構は動作しません。サービスのWSDL記述内に例外クラスに関する情報が含まれていない、または、送信者と受信者が実装を共有していない限り、サブクラスではなくjava.rmi.RemoteExceptionのインスタンスをスローするしか確実な方法はありません。
他の言語の実装がこのような例外を受信した場合、faultCodeとしてクラス名が分かりますが、まだ例外のボディのパースが残っています。そこで何が起きるかを調べるには、実験する必要があります。
例外がこの仕様を満たす場合、メソッドを記述するWSDLは、呼び出し元がプラットフォームに関係なく例外のスタブ実装を生成できるように、この例外についても記述します。
繰り返しますが、相互運用性を確実にするためには多少の実験が必要です。呼び出す言語によっては例外の概念が存在しないもの、または、どのように例外が扱われるかといった規則がJavaのように厳格でないものもあることを覚えておいて下さい。
Axisでは、符号無し型をサポートします。従って、C++、C#などで記述されたサービスを使用することと、これらの言語向けに設計されたインタフェースを再実装することができます。しかし、Axisを使用しない、もしくは、他の符号無しサポートを行うSOAPクライアントライブラリを使用するJavaクライアントを停止させることになるため、符号無しデータ型のエクスポートは慎重に行って下さい。少なくとも、JAX-RPCのリビジョンがこれを追加されるまでの間です。
<beanMapping qname="ns:local" xmlns:ns="someNamespace" languageSpecificType="java:my.java.thingy"/><beanMapping>タグは、Javaクラス(おそらくBean)をXML QNameにマップします。qname、および、languageSpecificTypeという 2つの重要な属性があることが分かるでしょう。この場合、"my.java.thingy"クラスをXML QName [someNamespace]:[local]にマップしています。
実際にこれがどのように動作するかを見てみましょう。samples/userguide/example5/BeanService.javaを見て下さい。注目すべきキーとなることは、サービスメソッドへの引数がOrderオブジェクトであることです。Orderは、Axisがデフォルトで理解できる基本型ではありませんので、型マッピングなしにこのサービスを実行しようとすると失敗します(自身で試すには、example5ディレクトリにあるbad-deploy.wsddを使用して下さい)。しかし、配備にbeanMappingを付け加えれば、うまく動作します。以下に、この例の(example5ディレクトリからの)実行方法を示します:
% java org.apache.axis.client.AdminClient -llocal:///AdminService deploy.wsdd <Admin>Done processing</Admin> % java samples.userguide.example5.Client -llocal:// Hi, Glen Daniels! You seem to have ordered the following: 1 of item : mp3jukebox 4 of item : 1600mahBattery If this had been a real order processing system, we'd probably have charged you about now. %
TBD - この節は将来のバージョンで拡張されます!現時点では、(samples/encoding内の)DataSer/DataDeserクラスを参照します。また、org.apache.axis.encoding.serパッケージ内のBeanSerializer、BeanDeserializer、ArraySerializer、ArrayDeserializerなどのクラスも参照します。
<typeMapping qname="ns:local" xmlns:ns="someNamespace" languageSpecificType="java:my.java.thingy" serializer="my.java.Serializer" deserializer="my.java.DeserializerFactory" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
これは、以前に見た<beanMapping>タグとかなり似ていますが、3つの特別な属性が存在します。1つ目は、serializerであり、指定したJavaクラス(つまり、my.java.thingy)のオブジェクトをXMLにマーシャルする際に使用されるシリアライザを生成するシリアライザファクトリのJavaクラス名です。2つ目は、deserializerであり、XMLを正確なJavaクラスにアンマーシャルする際に使用されるデシリアライザを生成するデシリアライザファクトリのJavaクラス名です。最後は、SOAPエンコーディングであるencodingStyleです。
(<beanMapping>タグは、本当はserializer="org.apache.axis.encoding.ser.BeanSerializerFactory"、deserializer="org.apache.axis.encoding.ser.BeanDeserializerFactory"、encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"を持った<typeMapping>タグの単なる省略表現です。しかし、これが入力の手間を省くことができることは明確でしょう!)
Axisは、以下の3方式でWSDLをサポートしています:
ブラウザからサービスURLにアクセスすると、終端がAxisサービスであることを示すメッセージを確認できます。通常は、SOAPを使用してそこにアクセスすることになります。しかし、そのURLの終りに"?wsdl"を付けると、Axisは自動的に配備されたサービス用のサービス記述を生成し、ブラウザにXMLとして返します(試して下さい!)。こうして得られた記述は、保存することも、以下で説明する代理生成への入力として使用することもできます。オンラインパートナへこのWSDLを生成するURLを渡すことで、パートナはこれを使用して、.NET、SOAP::Lite、その他WSDLをサポートするソフトウェアなどのツールキットでサービスにアクセスすることができます。
既存のJavaクラスからWSDLファイルを生成することもできます(Java2WSDL: JavaからWSDLを構築を参照して下さい)。
"org.apache.axis.wsdl.WSDL2Java"に、AXIS WSDL -> Javaのツールがあります。以下に基本的な呼び出し方法を示します:
% java org.apache.axis.wsdl.WSDL2Java (WSDL-file-URL)
これにより、クライアントに必要なバインディングのみ生成されます。AXISは、WSDLからJavaクライアントバインディングを生成する際、JAX-RPC仕様に従います:
% cd samples/addr % java org.apache.axis.wsdl.WSDL2Java AddressBook.wsdl
生成されたファイルは、"AddressFetcher2"ディレクトリに格納されます。ここに格納される理由は、WSDLからのターゲット名前空間とJavaパッケージへの名前空間マップのためです。名前空間についての詳細は、後で説明します。
WSDL句 | 生成されるJavaクラス |
型節における各エントリに対して | Javaクラス |
この型が入出力/出力パラメータとして使用される場合はホルダ | |
各portTypeに対して | Javaインタフェース |
各バインディングに対して | スタブクラス |
各サービスに対して | サービスインタフェース |
サービスの実装(ロケータ) |
WSDL型から生成されるJavaクラスは、WSDL型に由来した名前を持ちます。このクラスは、典型的にはBeanですが、常にではありません。例えば、以下のWSDLを指定した場合(このWSDLは、WSDL2Javaに関する節全体で使用され、アドレス帳サンプルにあります):
<xsd:complexType name="phone"> <xsd:all> <xsd:element name="areaCode" type="xsd:int"/> <xsd:element name="exchange" type="xsd:string"/> <xsd:element name="number" type="xsd:string"/> </xsd:all> </xsd:complexType>
WSDL2Javaは、以下を生成します:
public class Phone implements java.io.Serializable { public Phone() {...} public int getAreaCode() {...} public void setAreaCode(int areaCode) {...} public java.lang.String getExchange() {...} public void setExchange(java.lang.String exchange) {...} public java.lang.String getNumber() {...} public void setNumber(java.lang.String number) {...} public boolean equals(Object obj) {...} public int hashCode() {...} }
上のマップングで注目する点は、XML型名が"phone"であり、生成されたJavaクラスが"Phone"である点です。最初の文字の大文字化は、クラスは大文字から始まるというJavaのコーディング規約に合わせるために行われます。この種のことは、多く起こります。XMLの名前/識別子を表現する規則が、Javaよりも制限が緩いためです。例えば、上の"phone"型内の副要素の1つの名前が、"new"の場合、"new"という名前のJavaのフィールドを作成することはできません。これは予約語であるため、生成されたソースコードのコンパイルは失敗してしまいます。
この種のマッピングをサポートするために、また、XML属性のシリアライゼーション/デシリアライゼーションを有効にするために、型メタデータシステムがあります。このシステムによって、Javaデータクラスを、これらの問題を制御する記述子に関連付けることができます。
WSDL2Javaツールが上のPhoneクラスのようなデータBeanを生成する場合、スキーマに属性が含まれていること、もしくは、Javaのフィールド/プロパティ名に直接マップできない名前があることが分かります。このような場合、このクラス用の型記述子を提供するためのスタティックコードを生成します。型記述子は、基本的には1つ1つがJavaのフィールド/プロパティをXML要素、または、属性にマップするフィールド記述子のコレクションです。
このようなメタデータの例を見たい場合、Axisソース内の"test.encoding.AttributeBean"を参照するか、Javaでは不正となる属性や名前を持つXMLから独自のBeanを生成してみて下さい。
この型は、入出力、または、出力パラメータとして使用されるかもしれません。Javaには、入出力パラメータという概念がありません。この振る舞いを実現するために、JAX-RPCではホルダクラスの使用を指定します。ホルダクラスは、その型のインスタンスを持つ、単純なクラスです。例えば、Phoneクラスのホルダは以下のようになります:
package samples.addr.holders; public final class PhoneHolder implements javax.xml.rpc.holders.Holder { public samples.addr.Phone value; public PhoneHolder() { } public PhoneHolder(samples.addr.Phone value) { this.value = value; } }
ホルダクラスは、その型が入出力パラメータ、または、出力パラメータとして使用される場合にのみ生成されます。ホルダクラスが、元のクラス名に"Holder"という接尾詞を持つこと、および、"holders"という副パッケージを生成することに注意して下さい。
プリミッティブ型用のホルダクラスは、javax.xml.rpc.holdersにあります。
サービス定義インタフェース(SDI)は、WSDLのportTypeから派生したインタフェースです。これは、サービス上の操作にアクセスする際に使用するインタフェースです。例えば、以下のWSDLの場合:
<message name="empty"> <message name="AddEntryRequest"> <part name="name" type="xsd:string"/> <part name="address" type="typens:address"/> </message> <portType name="AddressBook"> <operation name="addEntry"> <input message="tns:AddEntryRequest"/> <output message="tns:empty"/> </operation> </portType>
WSDL2Javaは、以下を生成します:
public interface AddressBook extends java.rmi.Remote { public void addEntry(String name, Address address) throws java.rmi.RemoteException; }
SDIの名前について、注意点があります。SDIの名前は、通常はportTypeの名前になります。しかし、SDIの生成時、WDSL2Javaは、portType、および、バインディングの両方の情報を必要とします(これはまだ形式化されておらず、WSDLバージョン2の議論対象となっています)。
JAX-RPC (4.3.3節)では、次を宣言しています:"Javaインタフェースの名前は、wsdl:portType要素のname属性よりマップされます。... サービス定義インタフェースへのマッピングがwsdl:bindingを使用するならば、... サービス定義インタフェースの名前は、wsdel:binding要素の名前よりマップされます。"
仕様の名前について、注意点があります。そこには、"PRC"という文字列が含まれています。そのため、この仕様、および、WSDL2Javaは、portTypeから生成されるインタフェースは、RPCインタフェースであるという仮定をしています。バインディングからの情報が他のものを通知している(言い替えると、wsdl:bindingの要素を使用する)のであれば、インタフェースの名前は、このバインディングから派生したものになります。
なぜでしょう?1つのportType - pt - と、2つのバインディング - bRPCとbDoc - を持たせることは可能です。文書/リテラルは、インタフェースの見ためを変更するので、これらのバインディングの両方を1つのインタフェースで使用することができません。そのため、2つのインタフェース - 1つのptという名前と別のbDocという名前 - および、2つのスタブ - (ptを実装する)bRPCStubと(bDocを実装する)bDocStubを用意することになります。
醜いですね。しかし、なぜこれが必要かは分かるでしょう。文書/リテラルは、インタフェースの見ためを変更するため、1つのportTypeを参照する複数のバインディングを持つことが可能なため、複数のインタフェースを、それぞれ一意な名前を持つように作成する必要があります。
以下のWSDLの一部分があると:
<binding name="AddressBookSOAPBinding" type="tns:AddressBook"> ... </binding>
WSDL2Javaは、以下を生成します:
public class AddressBookSOAPBindingStub extends org.apache.axis.client.Stub implements AddressBook { public AddressBookSOAPBindingStub() throws org.apache.axis.AxisFault {...} public AddressBookSOAPBindingStub(URL endpointURL, javax.xml.rpc.Service service) throws org.apache.axis.AxisFault {...}
public AddressBookSOAPBindingStub(javax.xml.rpc.Service service) throws org.apache.axis.AxisFault {...}
public void addEntry(String name, Address address) throws RemoteException {...}
}
<service name="AddressBookService"> <port name="AddressBook" binding="tns:AddressBookSOAPBinding"> <soap:address location="http://localhost:8080/axis/services/AddressBook"/> </port> </service>
WSDL2Javaは、以下のサービスインタフェースを生成します:
public interface AddressBookService extends javax.xml.rpc.Service { public String getAddressBookAddress();
public AddressBook getAddressBook() throws javax.xml.rpc.ServiceException;
public AddressBook getAddressBook(URL portAddress) throws javax.xml.rpc.ServiceException; }
WSDL2Javaは、このインタフェースを実装する以下のようなロケータも生成します:
public class AddressBookServiceLocator extends org.apache.axis.client.Service implements AddressBookService { ... }
サービスインタフェースは、WSDLのservice要素内で示された各ポート用のgetメソッドを定義します。ロケータは、このサービスインタフェースの実装であり、これらのgetメソッドを実装します。これは、スタブインタフェースを得るためのロケータとしての機能を提供します。サービスクラスは、デフォルトで、WSDLファイル内で記述された終端URLを指し示すスタブを作成します。しかし、PortTypeを要求する場合に、異なるURLを指定することもできます。
以下にスタブクラスの典型的な使用方法を示します:
public class Tester { public static void main(String [] args) throws Exception { // Make a service AddressBookService service = new AddressBookServiceLocator(); // Now use the service to get a stub which implements the SDI. AddressBook port = service.getAddressBook(); // Make the actual call Address address = new Address(...); port.addEntry("Russell Butek", address); }
}
% java org.apache.axis.wsdl.WSDL2Java --server-side --skeletonDeploy true AddressBook.wsdl
WSDL2Javaがクライアント用としてこれまで生成したクラスを全て生成していること、そして、数個の新しいファイルも生成していることが分かるでしょう:
WSDL句 | 生成されるJavaクラス |
各バインディングに対して | スケルトンクラス |
テンプレートクラスの実装 | |
全てのサービスに対して | 1つのdeploy.wsddファイル |
1つのundeploy.wsddファイル |
"--skeletonDeploy true" オプションを指定しない場合、スケルトンは生成されません。その代わりに、生成されたdeploy.wsddが、直接実装クラスが配備されたことを示します。このような場合、deploy.wsddは、実装クラスの操作とパラメータを記述する特別なメタデータを含みます。 以下に、実装を直接配備させるようにWSDL2Javaを実行させる方法を示します:
% java org.apache.axis.wsdl.WSDL2Java --server-side AddressBook.wsdl
また、以下に生成されるサーバサイドのファイルを示します:
WSDL句 | 生成されるJavaクラス |
各バインディングに対して | テンプレートクラスの実装 |
全てのサービスに対して | 操作についてのメタデータを持つ1つのdeploy.wsddファイル |
1つのundeploy.wsddファイル |
public class AddressBookSOAPBindingSkeleton implements AddressBook, org.apache.axis.wsdl.Skeleton { private AddressBook impl; public AddressBookSOAPBindingSkeleton() { this.impl = new AddressBookSOAPBindingImpl(); } public AddressBookSOAPBindingSkeleton(AddressBook impl) { this.impl = impl; } public void addEntry(java.lang.String name, Address address) throws java.rmi.RemoteException { impl.addEntry(name, address); } }
(実際のスケルトンは、実際はもっと大きくなります。簡単にするために、スケルトンの基本部分のみを示しています。)
このスケルトンは、AddressBookサービスの実装を持ちます。この実装は、スケルトンのコンストラクタに渡されるか、生成された実装のインスタンスが生成されます。AXISエンジンが、スケルトンのaddEntryメソッドを呼び出すと、実際の実装のaddEntryの呼び出しに単純に委譲します。
WSDL2Javaは、バインディングから実装テンプレートも生成します。
public class AddressBookSOAPBindingImpl implements AddressBook {
public void addEntry(String name, Address address) throws java.rmi.RemoteException { } }
このテンプレートは、実際には、試験的な実装として使用されますが、見て分かるように、何も行いません。これは、サービス作成者がこのテンプレートから実装部分を埋めることが意図されています。
WSDL2Javaが、(--server-sideフラグ経由で)実装テンプレートを作成するかを問い合わされる際、未だ存在していない場合に限り生成します。この実装が既に存在する場合、上書きされません。
このツールは、AdminClientで使用するための各サービスにおける"deploy.wsdd"と"undeploy.wsdd"も作成します。一度、実装クラスのメソッドを埋め、コードをコンパイルし、Axisエンジンで利用可能なクラスを作成すると、これらのファイルは、サービスを配備するために使用することができます。
package samples.userguide.example6;
/** * Interface describing a web service to set and get Widget prices. **/ public interface WidgetPrice { public void setWidgetPrice(String widgetName, String price); public String getWidgetPrice(String widgetName); }
注意: デバッグ情報を付けてクラスのコンパイルを行った場合、Java2WSDLは、メソッドのパラメータ名を得るためにデバッグ情報を使用します。
以下に、上の節で記載したインタフェースからwsdlファイル(wp.wsdl)を生成する呼び出しの例を示します:
% java org.apache.axis.wsdl.Java2WSDL -o wp.wsdl -l"http://localhost:8080/axis/services/WidgetPrice" -n "urn:Example6" -p"samples.userguide.example6" "urn:Example6" samples.userguide.example6.WidgetPrice
ここで:
Java2WSDLツールには、更に多くのオプションがあります。詳細は、リファレンスガイドで説明されています。
% java org.apache.axis.wsdl.WSDL2Java -o . -d Session -s -S true -Nurn:Example6 samples.userguide.example6 wp.wsdl
これは、以下のファイルを生成します:
これで、クライアント/サーバサイドのコードの構築、および、Webサービスの配備に必要なファイルがすべて揃いました!
一部のインタフェースは、公開されるものとして設計されていますので、比較的安定しています。Axisのリファクタリングの際、Axisの開発者は、公開されたインタフェースに対する不必要な変更を避け、ユーザへの変更の影響を十分に考慮します。
そのため、公開されたインタフェースのみを使用するように厳守していれば、Axisのリリースを変更する際の痛手が最小化できます。一方、非公開のインタフェースの使用を決めた場合は、リリース間での移行はかなりの練習になるでしょう!インタフェースを公開させたい場合は、axis-userメーリングリストにて議題すべきです。
現時点の公開されたインタフェースの一覧は、以下です:
ユーザガイドを一通り読み、最初の.jwsサービスを作成し、全て完璧にうまくいったことでしょう!さあ、これで実際のプロジェクトに取り掛かる時です。ここでユーザガイドでは説明されていないが必要となる何らかの性質に出会うでしょう。それは単純なことです。Axisのどこかにあることが分かっているが、何によって呼び出されるかどのような時に得られるかが分からないということです。この節は、検索を始める場所を示すことを意図しています。
ここには大分類があります。
Axis Webサービスにおける、ほとんどの "...は、どこで見ることができますか" という問いへの答えは、"MessageContextの中"です。基本的に、与えられた要求/応答に関してAxisが理解していることは全ては、MessageContext経由で取り出すことができます。Axisは、以下を保存しています:
org.apache.axis.Message
オブジェクト)MessageContext.getProperty()
経由で取り出すことができます。知らなければならないことは、プロパティの名前だけです。これは、やりにくいかも知れませんが、org.apache.axis.transport.http.HTTPConstants
内の定義のように、通常は定数です。そのため、例えば、Axisサーブレット用のServletContextを取り出す場合に行うことは、((HttpServlet)msgC.getProperty(HTTPConstants.MC_HTTP_SERVLET)).getServletContext();
です。サービスの内部からは、現在のMessageContextオブジェクトは、スタティックメソッド MessageContext.getCurrentContext()
を使用して常に利用可能です。これにより、MessageContextへの明示的な参照を持たないRPCサービス内からであっても、要求および応答メソッドに対して必要な変更を行うことができます。
org.apache.axis.Message
オブジェクトは、AxisにおけるSOAPメッセージの表現です。要求および応答メッセージは、上述のMessageContextから取り出すことができます。Messageは以下を持ちます:
これまで示したように、MessageContextから始めることにより、作業をAPIを辿って堀り下げ、1つの要求/応答の交換について利用可能な全ての情報を見つけました。MessageContextは、2つのメッセージがあり、それぞれ、SOAPEnvelopeを含んだSOAPPartを持ちます。SOAPEnvelopeは、順番に、通信路上に送信されたSOAPエンベロープの完全な表現を保持します。ここから、SOAPヘッダとSOAPボディの内容を取り出したり設定したりすることができます。利用可能なプロパティの全リストについては、Javadocを参照して下さい。
% java org.apache.axis.utils.tcpmon [listenPort targetHost targetPort]引数を全く指定しなかった場合、以下のような GUI が現れます:
このプログラムを使用するためには、入力方向の接続でtcpmonが監視するローカルポート、その接続をフォワードする対象ホスト、"トンネルする"対象ホストのポート番号を選択しなければなりません。そして、"add" をクリックして下さい。新規のトンネルされた接続用のウィンドウ内に現れるその他のタブに注目して下さい。このパネルには以下のようなものが表示されます:
ローカルポートへのSOAP接続が新規に行われる度に、"Request"パネルに要求が表示され、"Response"パネルにサーバからの応答が表示されます。tcpmonは、全ての要求/応答の組み合わせをログし、トップパネル内の項目を選択することで、任意の特定の組み合わせを表示することができます。選択した項目の削除、全ての削除、後で参照するためにファイルに保存することを行うこともできます。
"resend"ボタンは、現在参照している要求を再送信し、新しい応答を記録します。再送信する前に要求ウィンドウ内のXMLを編集できるので、これは特に便利です。そのため、この素晴らしいツールを使用して、SOAPサーバに対して異なるXMLを与えた場合の影響を試験することができます。編集した要求を再送信する前に、Content-length HTTPヘッダを変更する必要があることに注意して下さい。
このユーティリティでは、ハンドラが作成され、グローバルなハンドラチェインに追加されます。SOAP要求と応答を受け取った時と同様に、SOAPメッセージの情報は、Webブラウザインタフェースを使用して表示させることができるように、SOAPモニタサービスへフォワードされます。
SOAPメッセージの情報は、http://localhost:<port>/axis/SOAPMonitorにアクセスすることで、Webブラウザからアクセスすることができます(ここで、<port>は、アプリケーションサーバが稼働しているポート番号です)。
SOAPメッセージの情報は、SOAPモニタサービスへのソケット接続を開くアプレットを使用することによって、Webブラウザで表示することができます。このアプレットは、Javaプラグイン1.3以上がブラウザにインストールされていることを要求します。正しいプラグインが導入されていない場合は、ブラウザはインストールを促します。
アプレットとの通信用のSOAPモニタサービスで使用されるポートは、設定変更可能です。使用するポートを変更するには、AXIS Webアプリケーション用のweb.xmlファイルを編集して下さい。
用語集
本文書は、日本Apache XMLプロジェクト(本田/田中)により翻訳されました。
翻訳に対するコメントは、jaxmldev@xml.gr.jpに送って下さい。