Powered by SmartDoc

複雑な型の場合のWebサービス

これまで、話を単純にして理解度を上げるため、Webサービスでワイヤーを流れるデータの型を単純な型を持つものに絞って議論してきた。

本来、XML/SOAPは豊かなデータ表現が出来るので、ここからは、複雑な型を持つデータをSOAP-RPCやJAX-RPCがどう扱うかをSOAP/WSDL/JAX-RPCの復習を兼ねて取り上げていく。

SOAP Encoding

SOAPエンコーディングはXMLスキーマに基づく型システムを使用しています。はスキーマタイプです。

単純型(simpleType)はXMLスキーマの組み込み型にマッピングされることを以前学んだはずです。

複合型(comlexType)は関連付けられた型を持ついくつかの部品から成っています。これらの部品はアクセサ(accessor)によって識別されます。アクセサはXML要素名か要素の位置を指します。構造体(Struct)の場合が前者で配列(Array)の場合は後者です。

SOAPでは配列型はSOAP-ENC:Arrayまたはそれから派生した型によって表します。

Compound Type Struct
<element name="Book"/>
<complexType>
    <element name="author" type="xsd:string"/>
    <element name="preface" type="xsd:string"/>
    <element name="intro" type="xsd:string"/>
</complexType>

<e:Book>
    <author>Henry Ford</author>
    <preface>Prefatory text</preface>
    <intro>This is a book.</intro>
</e:Book>
Compound Type Array
<element name="myFavoriteNumbers" type="SOAP-ENC:Array"/>
<myFavoriteNumbers SOAP-ENC:arrayType="xsd:int[2]">
    <number>3</number>
    <number>4</number>
</myFavoriteNumbers>
Compound Type Array
<SOAP-ENC:Array SOAP-ENC:arrayType="xsd:ur-type[4]">
    <thing xsi:type="xsd:int">12345</thing>
    <thing xsi:type="xsd:decimal">6.789</thing>
    <thing xsi:type="xsd:string">
        Of Mans First Disobedience, and the Fruit
        Of that Forbidden Tree, whose mortal tast
        Brought Death into the World, and all our woe,
    </thing>
    <thing xsi:type="xsd:uriReference">
        http://www.dartmouth.edu/~milton/reading_room/
    </thing>
</SOAP-ENC:Array>

<SOAP-ENC:Array SOAP-ENC:arrayType="xsd:ur-type[4]">
    <SOAP-ENC:int>12345</SOAP-ENC:int>
    <SOAP-ENC:decimal>6.789</SOAP-ENC:decimal>
    <xsd:string>
         Of Mans First Disobedience, and the Fruit
         Of that Forbidden Tree, whose mortal tast
         Brought Death into the World, and all our woe,
    </xsd:string>
    <SOAP-ENC:uriReference>
        http://www.dartmouth.edu/~milton/reading_room/
    </SOAP-ENC:uriReference >
</SOAP-ENC:Array>

ワイア上のデータの流れ

前にその一部を紹介したINTEROPのサーバーでは様々な型のSOAPを確認できます。

method: echoStringArray
<SOAP-ENV:Body>
  <m:echoStringArray xmlns:m="http://soapinterop.org/">
    <inputStringArray SOAP-ENC:arrayType="ns:string[2]"
       SOAP-ENC:offset="[0]" xmlns:ns="http://www.w3.org/2001/XMLSchema">
      <item>hello</item>
      <item>world</item>
    </inputStringArray>
  </m:echoStringArray>
</SOAP-ENV:Body>
method: echoIntegerArray
<SOAP-ENV:Body>
  <m:echoIntegerArray xmlns:m="http://soapinterop.org/">
    <inputIntegerArray SOAP-ENC:arrayType="ns:int[2]" SOAP-ENC:offset="[0]"
         xmlns:ns="http://www.w3.org/2001/XMLSchema">
      <item>100</item>
      <item>200</item>
    </inputIntegerArray>
  </m:echoIntegerArray>
</SOAP-ENV:Body>
method: echoStruct - Request from client
POST /interop HTTP/1.0
Host: www.whitemesa.net
User-Agent: White Mesa SOAP Interop Client/1.0
Content-Type: text/xml; charset="utf-8"
Content-Length: 574
SOAPAction: "http://soapinterop.org/"

<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope
 SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
 xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
 xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
 xmlns:xsd="http://www.w3.org/2001/XMLSchema"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<SOAP-ENV:Body>
  <m:echoStruct xmlns:m="http://soapinterop.org/">
    <inputStruct>
      <varInt>42</varInt>
      <varFloat>0.005</varFloat>
      <varString>hello world</varString>
    </inputStruct>
  </m:echoStruct>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
method: echoStruct
<complexType name="SOAPStruct">
    <all>
        <element name="varString" type="xsd:string" />
        <element name="varInt" type="xsd:int" />
        <element name="varFloat" type="xsd:float" />
    </all>
</complexType>

<ExampleStruct>
    <varString>Hello</varString>
    <varInt>8</varInt>
    <varFloat>10.2</varFloat>
</ExampleStruct>
method: echoStruct - Response from server
HTTP/1.0 200 OK
Date: Wed, 20 Jun 2001 02:46:02 GMT
Server: WhiteMesa SOAP Server/2.3
Content-Type: text/xml; charset="utf-8"
Content-Length: 580

<?xml version="1.0" encoding="UTF-8"?>

<SOAP-ENV:Envelope
 SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
 xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
 xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
 xmlns:xsd="http://www.w3.org/2001/XMLSchema"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<SOAP-ENV:Body>
  <m:echoStructResponse xmlns:m="http://soapinterop.org/">
    <return>
      <varInt>42</varInt>
      <varFloat>0.005</varFloat>
      <varString>hello world</varString>
    </return>
  </m:echoStructResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
method: echoStructArray
<SOAP-ENV:Body>
  <m:echoStructArray xmlns:m="http://soapinterop.org/">
    <inputStructArray SOAP-ENC:arrayType="ns:SOAPStruct[2]"
        SOAP-ENC:offset="[0]" xmlns:ns="http://soapinterop.org/xsd">
      <item>
        <varInt>42</varInt>
        <varFloat>0.005</varFloat>
        <varString>hello world</varString>
      </item>
      <item>
        <varInt>42</varInt>
        <varFloat>0.005</varFloat>
        <varString>hello world</varString>
      </item>
    </inputStructArray>
  </m:echoStructArray>
</SOAP-ENV:Body>
Group B echoStructAsSimpleTypes
<complexType name="SOAPStruct">
    <complexContent>
        <all>
            <element name="varString" type="xsd:string"/>
            <element name="varInt" type="xsd:int"/>
            <element name="varFloat" type="xsd:float"/>
        </all>
    </complexContent>
</complexType>

<ExampleStruct>
    <varString>Hello</varString>
    <varInt>8</varInt>
    <varFloat>10.2</varFloat>
</ExampleStruct>
echoStructAsSimpleTypes - Request from client
<SOAP-ENV:Body>
   <m:echoStructAsSimpleTypes xmlns:m="http://soapinterop.org/">
       <inputStruct>
           <varInt>42</varInt>
           <varFloat>0.005</varFloat>
           <varString>hello world</varString>
       </inputStruct>
   </m:echoStructAsSimpleTypes>
</SOAP-ENV:Body>
echoStructAsSimpleTypes - Response from server
<SOAP-ENV:Body>
    <m:echoStructAsSimpleTypesResponse xmlns:m="http://soapinterop.org/">
        <outputString>hello world</outputString>
        <outputInteger>42</outputInteger>
        <outputFloat>0.005</outputFloat>
    </m:echoStructAsSimpleTypesResponse>
</SOAP-ENV:Body>
echoSimpleTypesAsStruct - Request from client
<SOAP-ENV:Body>
    <m:echoSimpleTypesAsStruct xmlns:m="http://soapinterop.org/">
         <inputString>hello world</inputString>
         <inputInteger>42</inputInteger>
         <inputFloat>0.005</inputFloat>
    </m:echoSimpleTypesAsStruct>
</SOAP-ENV:Body>
echo2DStringArray
<complexType name="ArrayOfString2D"/>
  <complexContent>
    <restriction base="SOAP-ENC:Array">
      <sequence>
        <element name="item" type="string" minOccurs="0"
           maxOccurs="unbounded" nillable="true"/>
        </sequence>
      <attributeGroup ref="SOAP-ENC:commonAttributes"/>
      <attribute ref="SOAP-ENC:offset"/>
      <attribute ref="SOAP-ENC:arrayType" wsdl:arrayType="xsd:string[,]"/>
    </restriction>
  </complexContent>
</complexType>
echo2DStringArray - Request from client
<SOAP-ENV:Body>
  <m:echo2DStringArray xmlns:m="http://soapinterop.org/">
    <input2DStringArray SOAP-ENC:arrayType="ns:string[2,3]"
        SOAP-ENC:offset="[0,0]" xmlns:ns="http://www.w3.org/2001/XMLSchema">
      <item>row0col0</item>
      <item>row0col1</item>
      <item>row0col2</item>
      <item>row1col0</item>
      <item>row1col1</item>
      <item>row1col2</item>
    </input2DStringArray>
  </m:echo2DStringArray>
</SOAP-ENV:Body>
echo2DStringArray - Response from server
<SOAP-ENV:Body>
  <m:echo2DStringArrayResponse xmlns:m="http://soapinterop.org/">
     <return SOAP-ENC:arrayType="ns:string[2,3]" SOAP-ENC:offset="[0,0]"
          xmlns:ns="http://www.w3.org/2001/XMLSchema">
       <item>row0col0</item>
       <item>row0col1</item>
       <item>row0col2</item>
       <item>row1col0</item>
       <item>row1col1</item>
       <item>row1col2</item>
     </return>
  </m:echo2DStringArrayResponse>
</SOAP-ENV:Body>
echoNestedStruct
<complexType name="SOAPStructStruct">
    <complexContent>
        <all>
            <element name="varString" type="xsd:string"/>
            <element name="varInt" type="xsd:int"/>
            <element name="varFloat" type="xsd:float"/>
            <element name="varStruct" type="sb:SOAPStruct"/>
       </all>
    </complexContent>
</complexType>

<ExampleStruct>
    <varString>Hello</varString>
    <varInt>8</varInt>
    <varFloat>10.2</varFloat>
    <varStruct>
       <varString>Goodbye</varString>
       <varFloat>99.007</varFloat>
       <varInt>42</varInt>
    </varStruct>
</ExampleStruct>
echoNestedStruct - Request from client
<SOAP-ENV:Body>
    <m:echoNestedStruct xmlns:m="http://soapinterop.org/">
        <inputStruct>
            <varInt>42</varInt>
            <varFloat>0.005</varFloat>
            <varString>hello world</varString>
            <varStruct>
                 <varString>nested struct</varString>
                 <varInt>99</varInt>
                 <varFloat>4.0699e-12</varFloat>
            </varStruct>
        </inputStruct>
    </m:echoNestedStruct>
</SOAP-ENV:Body>
echoNestedStruct - Response from server
<SOAP-ENV:Body>
    <m:echoNestedStructResponse xmlns:m="http://soapinterop.org/">
        <return>
            <varInt>42</varInt>
            <varFloat>0.005</varFloat>
            <varString>hello world</varString>
            <varStruct>
                 <varString>nested struct</varString>
                 <varInt>99</varInt>
                 <varFloat>4.0699e-12</varFloat>
            </varStruct>
        </return>
    </m:echoNestedStructResponse>
</SOAP-ENV:Body>
NestedArray
<complexType name="SOAPArrayStruct">
    <complexContent>
        <all>
             <element name="varString" type="xsd:string"/>
             <element name="varInt" type="xsd:int"/>
             <element name="varFloat" type="xsd:float"/>
             <element name="varArray" type="sb:Arrayofstring"/>
        </all>
    </complexContent>
</complexType>

<ExampleStruct>
    <varString>Hello</varString>
    <varInt>8</varInt>
    <varFloat>10.2</varFloat>
    <varArray SOAP-ENC:arrayType="xsd:string[3]" SOAP-ENC:offset="[0]">
        <item>red</item>
        <item>blue</item><item>green</item>
    </varArray>
</ExampleStruct>
NestedArray - Request from client
<SOAP-ENV:Body>
<m:echoNestedArray xmlns:m="http://soapinterop.org/">
<inputStruct>
<varInt>42</varInt>
<varFloat>0.005</varFloat>
<varString>hello world</varString>
<varArray SOAP-ENC:arrayType="xsd:string[3]" SOAP-ENC:offset="[0]">
<item>red</item>
<item>blue</item>
<item>green</item>
</varArray>
</inputStruct>
</m:echoNestedArray>
</SOAP-ENV:Body>
NestedArray - Response from server
<SOAP-ENV:Body>
    <m:echoNestedArrayResponse xmlns:m="http://soapinterop.org/">
        <return>
            <varInt>42</varInt>
            <varFloat>0.005</varFloat>
            <varString>hello world</varString>
            <varArray SOAP-ENC:arrayType="xsd:string[3]" SOAP-ENC:offset="[0]">
                <item>red</item>
                <item>blue</item>
                <item>green</item>
            </varArray>
        </return>
    </m:echoNestedArrayResponse>
</SOAP-ENV:Body>

WSDLのtypes

WSDLのtypes要素の説明をもう一度繰り返しておきましょう。

今までの例には登場していなかったのですが、WSDLの中で大事な働きをするタグが一つ残っています。それが、types要素です。types要素は、Schemaを利用して新しい型を定義して、その型をWSDLで使うことを可能とします。types要素とmessage要素は、新しい型のワイアリング・フォーマット、ネットワーク上に実際にどのような形のメッセージが流れるかを規定します。次の節では、そのことを、実例を挙げて、少し詳しく見ることにしましょう。

SOAP / XMLの型をJavaの型に対応させる---- typesとmessage

配列の定義とtypes要素の利用

先の例は単純な例でしたが、次は、整数の配列の例です。整数の配列データをサーバに送り、それをそのままエコーバックするRPC echoIntegerArrayでは、ワイア上をどのようなXMLメッセージが流れ、WSDLがそれをどのように記述しているかを見てみたいと思います。実際に、ネットワークを流れるのは、次のようなデータです。ここでは、SOAP-ENCという接頭子に注目してください。これは、SOAP流のencodeのスタイルを意味します。SOAPには、配列をエンコードするルールが内蔵されています。

SOAPデータ
<SOAP-ENV:Body>
  <m:echoIntegerArray xmlns:m="http://soapinterop.org/">
    <inputIntegerArray SOAP-ENC:arrayType="ns:int[2]" SOAP-ENC:offset="[0]"
        xmlns:ns="http://www.w3.org/2001/XMLSchema">
      <item>100</item>
      <item>200</item>
    </inputIntegerArray>
  </m:echoIntegerArray>
</SOAP-ENV:Body>

対応するportTypeでのoperationの定義とmessageの定義は次のようなものです。

portType
<operation name="echoIntegerArray" parameterOrder=inputIntegerArray">
    <input message="tns:echoIntegerArrayRequest" />
    <output message="tns:echoIntegerArrayResponse" />
</operation>
message
<message name="echoIntegerArrayRequest">
    <part name="inputIntegerArray" type="s:ArrayOfint" />
</message>
<message name="echoIntegerArrayResponse">
    <part name="return" type="s:ArrayOfint" />
</message>

ただ、単純な例の場合ですと、次のようなpartタグのtype属性を見れば、

<part name="inputString" type="xsd:string" />

inputSringの型が、"XML Schema Part 2: Datatypes"で定義されたString型であることは明確なのですが、この配列の例では、

<part name="inputIntegerArray" type="s:ArrayOfint" />

のArrayOfintの型の意味がはっきりしません(意味はもちろん「intの配列」という意味でしょうが)。

実は、この例では、今まで見てきたWSDL中のoperationタグとmessageタグだけでは、先のワイア上のデータを作ることはできないのです。先のワイア上のXMLメッセージは、次のようなtypes要素で、型ArrayOfintが定義されて初めて可能になります。

単純な型が、Schema上のデータ型として定義されたように、ここでもSchemaを使って、そのcomplexTypeとして、型ArrayOfintが定義されています。

types要素
<types>
  <schema xmlns="http://www.w3.org/2001/XMLSchema"
      targetNamespace="http://soapinterop.org/xsd">
    <import namespace="http://schemas.xmlsoap.org/soap/encoding/" />
    <complexType name="ArrayOfint">
      <complexContent>
        <restriction base="SOAP-ENC:Array">
          <attribute ref="SOAP-ENC:arrayType" wsdl:arrayType="int[]" />
        </restriction>
      </complexContent>
    </complexType>
……
……
  </schema>
</types>

Structとその配列

XMLのStructは、Cの構造体、JavaのFieldのようなものと考えればいいでしょう。配列は、構造の中でターゲットを位置で指定しますが、Structは名前で指定します。

次の例の前半部分は、varString, varInt, varFloatという名前の三つの要素からなるstruct SOAPStructの、Schemaを使ったtypesでの定義です。この後半は、こうして定義されたstruct SOAPStructの配列、ArrayOfSOAPStructの定義です。

types要素
<types>
  <schema ……
    ……
    <complexType name="SOAPStruct">
      <all>
        <element name="varString" type="string" />
        <element name="varInt" type="int" />
        <element name="varFloat" type="float" />
      </all>
    </complexType>
    <complexType name="ArrayOfSOAPStruct">
      <complexContent>
        <restriction base="SOAP-ENC:Array">
          <attribute ref="SOAP-ENC:arrayType" wsdl:arrayType="s:SOAPStruct[]" />
        </restriction>
      </complexContent>
    </complexType>
 </schema>
</types>

先の例での、WSDLでのSOAPStructの定義は、次のようなJavaクラスの定義に自然に対応付けられます。いったん、このクラスができてしまえば、Javaの世界でその配列を定義するのは簡単なことです。このように、WSDLが与えられれば、それが定義しているSOAP-RPCの引数と返り値の型を、Javaのクラスに対応付けることが、容易に出来るのです。

public class SOAPStruct implements java.io.Serializable {
    private java.lang.String varString;
    private int varInt;
    private float varFloat;

    public SOAPStruct() {
    }
    ……
}

Structは、基本的に、その要素に名前でアクセスがあるわけですから、WSDLからJavaのクラスにマッピングする際に、Fieldだけでなく、次のように、各フィールドへのsetter / getterのペアのメソッドを定義するのも簡単に出来ると思います。このように、WSDLで定義された型は、JavaBeansにマッピングすることが可能です。

public class SOAPStruct implements java.io.Serializable {
    ……
    ……
    public java.lang.String getVarString() {
        return varString;
    }
    public void setVarString(java.lang.String varString) {
        this.varString = varString;
    }

    public int getVarInt() {
        return varInt;
    }
    public void setVarInt(int varInt) {
        this.varInt = varInt;
    }
    public float getVarFloat() {
        return varFloat;
    }
    public void setVarFloat(float varFloat) {
        this.varFloat = varFloat;
  ……
  ……
}

WSDL2Javaツール

先に単純な型の場合にWSDL2Javaを使ってWSDLから生成されるJavaクラスについては説明しました。複雑な型を持つ場合はWSDLにtypes要素を持つわけですし、前の節で、typesとJavaの対応を検討したわけですから、typesに対応したJavaクラスをWSDL2Javaが生成してくれることが期待されます。

実際、WSDL2JavaはWSDLのtypes要素で宣言したcomplexTypeの名前でJavaクラスを作ります。このクラスはたいていの場合JavaBeanになります。

AXISの%AXIS_HOME%\samples\jaxrpc\address\のWSDLをみてみましょう。

Address.wsdl
<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions targetNamespace="http://address.jaxrpc.samples"
 xmlns="http://schemas.xmlsoap.org/wsdl/" 
 xmlns:apachesoap="http://xml.apache.org/xml-soap"
 xmlns:impl="http://address.jaxrpc.samples"
 xmlns:intf="http://address.jaxrpc.samples"
 xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
 xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
 xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/soap/"
 xmlns:xsd="http://www.w3.org/2001/XMLSchema">
 <wsdl:types>
  <schema targetNamespace="http://address.jaxrpc.samples"
   xmlns="http://www.w3.org/2001/XMLSchema">
   <import namespace="http://schemas.xmlsoap.org/soap/encoding/"/>
   <complexType name="AddressBean">
    <sequence>
     <element name="street" nillable="true" type="xsd:string"/>
     <element name="postcode" type="xsd:int"/>
    </sequence>
   </complexType>
   <element name="AddressBean" nillable="true" type="impl:AddressBean"/>
  </schema>
  <schema targetNamespace="http://www.w3.org/2001/XMLSchema"
   xmlns="http://www.w3.org/2001/XMLSchema">
   <import namespace="http://schemas.xmlsoap.org/soap/encoding/"/>
   <element name="int" type="xsd:int"/>
   <element name="string" type="xsd:string"/>
  </schema>
 </wsdl:types>
   <wsdl:message name="updateAddressRequest">
      <wsdl:part name="in0" type="intf:AddressBean"/>
      <wsdl:part name="in1" type="xsd:int"/>
   </wsdl:message>
   <wsdl:message name="updateAddressResponse">
      <wsdl:part name="return" type="xsd:string"/>
   </wsdl:message>
   <wsdl:portType name="AddressService">
      <wsdl:operation name="updateAddress" parameterOrder="in0 in1">
         <wsdl:input message="intf:updateAddressRequest"
          name="updateAddressRequest"/>
         <wsdl:output message="intf:updateAddressResponse"
           name="updateAddressResponse"/>
      </wsdl:operation>
   </wsdl:portType>
   <wsdl:binding name="AddressSoapBinding" type="intf:AddressService">
      <wsdlsoap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>
      <wsdl:operation name="updateAddress">
         <wsdlsoap:operation soapAction=""/>
         <wsdl:input name="updateAddressRequest">
            <wsdlsoap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
             namespace="http://address.jaxrpc.samples" use="encoded"/>
         </wsdl:input>
         <wsdl:output name="updateAddressResponse">
            <wsdlsoap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
             namespace="http://address.jaxrpc.samples" use="encoded"/>
         </wsdl:output>
      </wsdl:operation>
   </wsdl:binding>
   <wsdl:service name="AddressServiceService">
      <wsdl:port binding="intf:AddressSoapBinding" name="Address">
        <wsdlsoap:address location="http://localhost:8080/axis/services/Address"/>
      </wsdl:port>
   </wsdl:service>
</wsdl:definitions>

このWSDLはAddressBeanという型のスキーマ定義を含んだType要素を含んでいます。

WSDLのtypes要素
 <wsdl:types>
  ……
   <complexType name="AddressBean">
    <sequence>
     <element name="street" nillable="true" type="xsd:string"/>
     <element name="postcode" type="xsd:int"/>
    </sequence>
   </complexType>
   <element name="AddressBean" nillable="true" type="impl:AddressBean"/>
  </schema>
  ……
 </wsdl:types>

このスキーマ定義から、AddressBean型をもつXMLのドキュメントとしては、例えば、次のような例が考えられることが分かります。

WSDLのtypes要素
<AddressBean>
    <street>稚内市若葉台2290-28</street>
    <postcode>970013</postcode>
</AddressBean>

このAddressBeanという型は、updateAddressRequestという名前を持つメッセージの二つのpart要素の第一のものの型であることが分かります。

WSDLのmessage要素
<wsdl:message name="updateAddressRequest">
    <wsdl:part name="in0" type="intf:AddressBean"/>
    <wsdl:part name="in1" type="xsd:int"/>
</wsdl:message>

このupdateAddressRequestは、PortType要素を見ると、updateAddresという名前のOperationの入力引数であることが分かります。

AddressService.java
<wsdl:portType name="AddressService">
    <wsdl:operation name="updateAddress" parameterOrder="in0 in1">
        <wsdl:input message="intf:updateAddressRequest"
             name="updateAddressRequest"/>
        <wsdl:output message="intf:updateAddressResponse"
             name="updateAddressResponse"/>
    </wsdl:operation>
</wsdl:portType>

このWSDLのPortTypeに対応したWSDL2Javaで生成されたJavaのインターフェース定義は次のようなものです。

WSDLのPortType要素
package samples.jaxrpc.address;
public interface AddressService extends java.rmi.Remote {
    public String updateAddress(AddressBean in0, int in1)
        throws java.rmi.RemoteException;
}

この形なら、はっきりと、AddressBeanという型は、updataAddressメソッドの第一引数の型であることが分かると思います。"AddressBean"型から次のAddressBean.javaが生成されます。

問題は、このインターフェース定義に出てきたAddressBeanのJavaでの定義がどういうものかということです。実は、WSDL2Javaツールが、WSDLのTypes句で定義されたXMLの型に対応するJavaの定義をソースの形で生成します。

WSDLが生成した、AddressBeanの定義は、次のようなものです。

AddressBean.java
package samples.jaxrpc.address;

public class AddressBean implements java.io.Serializable {
    private java.lang.String street;
    private int postcode;

    public AddressBean() {
    }

    public java.lang.String getStreet() {
        return street;
    }

    public void setStreet(java.lang.String street) {
        this.street = street;
    }

    public int getPostcode() {
        return postcode;
    }

    public void setPostcode(int postcode) {
        this.postcode = postcode;
    }
}

基本的には、XMLのAddressBeanを構成する、streetとpostcodeという二つのエレメントは、JavaのクラスAddressBeanのstreetとpostcodeという二つのフィールドとして解釈されていることが分かります。もっとも、これらのフィールドはprivateで、このフィールドにパブリックにアクセスするsetter,getterのペアが、フィールドの数だけ定義されることになります。こうしたスタイルのJavaクラスをJavaBeanと呼ぶのですが、WSDL2Javaは、Types要素のXMLスキーマでの型定義を対応するフィールドを持ったJavaBeanに変換すると考えても良いでしょう。

こうした補助的なクラスの助けを借りて、そのインスタンスを利用すれば、次のようなクライアントからのサービス呼び出しプログラムを書くことができます。

AddressClient.java
package samples.jaxrpc.address;

import javax.xml.namespace.QName;
import javax.xml.rpc.Service;
import javax.xml.rpc.ServiceFactory;
import java.net.URL;

public class AddressClient {
    public static void main(String[] args) throws Exception {
        URL urlWsdl = new URL("http://localhost:8080/axis/services/Address?wsdl");
        String nameSpaceUri = "http://address.jaxrpc.samples";
        String serviceName = "AddressServiceService";
        String portName = "Address";

        ServiceFactory serviceFactory = ServiceFactory.newInstance();
        Service service = serviceFactory.createService(urlWsdl, new
                QName(nameSpaceUri, serviceName));
        AddressService myProxy = (AddressService) service.getPort(new
                QName(nameSpaceUri, portName), AddressService.class);
        AddressBean addressBean = new AddressBean();
        addressBean.setStreet("55, rue des Lilas");
        System.out.println(myProxy.updateAddress(addressBean, 75005));
    }
}

このAxisの例はDynamic Proxy型の呼び出しプログラムです。ServiceLocatorを使ったプログラムの方がシンプルになります。)

Java2WSDL

今度は、最初からはWSDLを必要としないAXISのJAX-RPCのプログラミング・スタイルに挑戦してみましょう。AXISには、WSDL2Javaとは逆の、サービス定義インターフェースからWSDLを生成するJava2WSDLというツールがついています。このツールを使えば、次のようなインターフェース定義からWSDLを作り出すことが出来ます。

Hello.java
package samples.jaxrpc.hello2;
  public interface Hello extends java.rmi.Remote {
  public String sayHello(String name) throws java.rmi.RemoteException;
}

Java2WSDLのオプションで、binding名にHelloを指定すれば、サービスの実装クラスの名前はHelloImplとなり、次のようなコードで、サービスの実装が可能となります。

HelloImpl.java
package samples.jaxrpc.hello2;

public class HelloImpl implements Hello{
    public String sayHello(String name) throws java.rmi.RemoteException {
        return "This ServiceImpl was re-defined by " + name + "!" ;
    }
}

こうした設定を用いると、サービス・ロケータはHelloServiceLocator、サービスを獲得するのはgetHelloメソッド、StubへのキャストはHelloStubといったように、わかりやすい名前を使って、クライアントのプログラムを作ることが出来ます。

サーバ側が、Hello.javaとHelloImpl.javaで、クライアント側がこれらとMain.javaということになります。

Main.java
package samples.jaxrpc.hello2;

public class Main {

    public static void main (String[] args) throws Exception {
        Hello port = new HelloServiceLocator().getHello();
        try {
            String str = ((HelloStub) port).sayHello("Fujio Maruyama");
            System.out.println( str );
        } catch (java.rmi.RemoteException re) {
            System.err.println("Remote Exception caught: " + re );
        }
    }
}

Build

コードが楽になった分、buildファイルはちょっと複雑になっています。もっとも、複雑なのは、Java2WSDLのオプションの部分だけで、WSDLが出来てしまえばあとは先に見たWSDL2Javaの例と同じです。実行用のターゲットは省略しました。

オプションを通じた名前のコントロールについては、先に見た、WSDLの各エレメントと生成されるファイルの種類、ファイルの名前、メソッドの名前の対応表も参考にしてください。

Java <--> WSDLの対応関係こそ、JAX-RPCの本質なのです。

build.xml
<target name="compile2" depends="copy">
<!-- Java2WSDLで使うために、サービス定義インターフェースをコンパイルする-->
  <javac srcdir="${axis.home}" destdir="${build.dest}"
      debug="${debug}" fork="${javac.fork}">
    <classpath>
      <path refid="classpath"/>
    </classpath>
    <include name="samples/jaxrpc/hello2/Hello.java"/>
    <exclude name="samples/jaxrpc/hello2/ServiceImpl.java" />
    <exclude name="samples/jaxrpc/hello2/Main.java" />
  </javac>
<!-- Java2WSDLを使って、samples.jaxrpc.hello2.Helloから、WSDLを生成する-->
  <echo message="Generating WSDL"/>
  <java classname="org.apache.axis.wsdl.Java2WSDL" dir="./hello2" fork="yes">
    <classpath refid="classpath" />
<!-- -o 生成されるWSDLファイル-->
      <arg line="-o HelloWorld.wsdl" />
<!-- -l サービスのロケーションの指定-->
      <arg line="-l http://localhost:8080/axis/services/Hello" />
<!-- -n WSDLファイルのnamespace -->
      <arg line="-n http://hello2.jaxrpc.samples/" />
<!-- -p パッケージsamples.jaxrpc.hello2を、先のnamespaceにマッピングする-->
      <arg line="-psamples.jaxrpc.hello2=http://hello2.jaxrpc.samples/" />
<!-- -b binding名をHelloにする-->
      <arg line="-b Hello" />
<!-- このサービス定義インターフェースをWSDLにする-->
      <arg line="samples.jaxrpc.hello2.Hello"/>
  </java>
<!-- WSDL2Javaを使って、作業用ディレクトリにJavaコードを生成する-->
  <echo message="RE-Generating Java code"/>
  <wsdl2java url="${axis.home}/samples/jaxrpc/hello2/HelloWorld.wsdl"
    output="${build.dir}/work" serverSide="yes" testcase="no">
  </wsdl2java>
<!-- 作業用ディレクトリにその他のファイルをコピーする-->
  <echo message="Copy Java code to working directry"/>
  <copy todir="${build.dir}/work/samples/jaxrpc/hello2" overwrite="yes">
    <fileset dir="${axis.home}/samples/jaxrpc/hello2">
      <include name="*.java"/>
    </fileset>
  </copy>
<!-- 作業用ディレクトリのファイルをコンパイルする-->
  <echo message="Compile ...."/>
  <javac srcdir="${build.dir}/work" destdir="${build.dest}"
        debug="${debug}" fork="${javac.fork}">
  <classpath refid="classpath" />
    <include name="samples/jaxrpc/hello2/*.java" />
  </javac>
</target>

Bottom Up とTop Down

WSDLとJavaの変換について、二つのスタイルを見てきました。WSDLからJavaのインターフェースを生成するというスタイルは、TopDownと呼ばれることがあります。これとは逆の、JavaのインターフェースからWSDLを作成するやり方は、BottomUpと呼ばれます。

一見すると、プログラマにとっては、BottomUpのスタイルの方が簡単な様に思えるかもしれません。確かに、WSDLを記述するよりはJavaのインターフェースを記述するほうが簡単だと思います。しかし、システムの開発をする際に、どちらのスタイルが良いかを考えてみれば、必ずしも問題は簡単ではありません。すこし複雑なJavaの型をこのような変換ツールでXMLに変換する場合を考えて見ましょう。どのようなXMLが生成されるかは、必ずしも明確ではありません。もしも、異なるベンダーが提供するツールが存在した場合、生成するメッセージの相互運用性を保障するのは意外と面倒な仕事になるかもしれません。その点、WSDLでメッセージのワイアリング・フォーマットをきちんと定義しておけば、こうした紛れはなくなります。また、Javaだけでなく、C,C++など他の言語で書かれたシステムとやり取りすることも当然ありえますので、そうした場合には、WSDLをベースにするほうが便利でしょう。