Apache SOAP の型マッピング: 第 1 回 Apache の直列化 API について

アプリケーションのデータ型を XML に変換する方法を学ぶ

SOAP では、アプリケーション・レベルのデータを転送するための簡単なワイヤー・プロトコルが定義されています。このプロトコルは、充実した広範な型システムを備えており、Java の任意の型を直列化された XML として簡単に伝送できます。Apache SOAP ツールキットにおける型システム・サポートについての 2 回シリーズの第 1 回である今回の記事で、Gavin Bong 氏は、SOAPの型システムに関する理論的な基礎を紹介します。また、SOAP における直列化と非直列化のプログラム的なサポートの詳細を学習し、最後にツールキット内部の仕組みを紹介します。これらのプロセスの動作原理をよく理解しておけば、独自の分散システムを開発する際に役に立ちます。

Gavin Bong (gavinb@eutama.com), Software engineer, eUtama Sdn. Bhd.

Gavin Bong 氏は、マレーシアのクアラルンプール出身の Java 開発者です。氏は、サービス指向のアーキテクチャーとワイヤレス Java に関心を持っています。同氏の連絡先は gavinb@eutama.com です。図 1 を完成するに当たって Ying Pee Eng 氏の協力に感謝しています。



2002年 4月 01日

電子的にデータを交換する際には、あらかじめ、対話パターン と型システム の2点について、交換に関係するエンドポイントの間で合意しておく必要があります。対話パターンは、通信チャネルのアーキテクチャーに関係する事がらです (ポイントツーポイントか多対一か、ブロッキングか非同期か、など)。一方、型システムは、メッセージのエンコードとデコードで使用される統一されたデータ・フォーマットです。

この記事では、Apache SOAPツールキットに適用されるSOAPの型システムについて説明します。現在のSOAPツールキットはメッセージングとRPCの両方の対話パターンをサポートしていますが、この記事ではRPCだけを取りあげます。特に指定がない限り、この記事で解説する機能は、2001年5月にリリースされたApache SOAPのバージョン2.2に対して適用されます (参考文献を参照)。必要に応じて、このリリース以降に行われたバグ修正またはインターフェースの変更に触れます。

用語についての余談として、このシリーズを通して使用するネームスペースのプレフィックス (SOAPとW3CのXMLスキーマの両方に対するもの) およびQName (修飾名) の規則を紹介しておきましょう。

  • SOAP 1.2のワーキング・ドラフトは2001年にリリースされましたが、この記事ではSOAP 1.1の仕様にのみ準拠しています。したがって、SOAP-ENV およびSOAP-ENC というプレフィックスは、それぞれ、http://schemas.xmlsoap.org/soap/envelope/ およびhttp://schemas.xmlsoap.org/soap/encoding/ というネームスペースを参照します。
  • 特に指定されていない限り、プレフィックスxsd およびxsi は、それぞれ、ネームスペースhttp://www.w3.org/2001/XMLSchema およびhttp://www.w3.org/2001/XMLSchema-instance を参照するものと見なされます。
  • 外部のURIを含むQNameは、次の形式で記述されます。
    {uri}localpart.

    例:{http://xml.apache.org/xml-soap}Map

SOAP と RPC

SOAPでRPC呼び出しを実行すると、Webサービスの要求発行者と提供者の間で2種類のペイロードが交換されます。

  • リモート・メソッドに対するパラメーター (RPC要求)
  • 戻り値 (RPC応答)

技術的には、SOAP添付とSOAPヘッダーもSOAP RPC呼び出しのペイロードを構成する場合がありますが、この記事に関してはこれらを無視してもかまいません。普通、ペイロードは、一般にSection 5 として知られる方式を使ってエンコードされます。SOAPの仕様においてSection 5がデフォルトのエンコード方式として指定されているわけではありませんが、現在利用できるSOAPフレームワークはすべて、このエンコード方式をサポートしています。

Section 5エンコードでは、オブジェクト・グラフをXMLに変換するための方法が記述されています。この方法に厳密に従えば、SOAP XMLを元の形式に再構成できます。別のエンコード方式としては、W3CのXML Schemaのようなスキーマ言語を使うことでペイロードを制限します。前者の方法では、トランザクションのすべての関係者は直列化の規則について合意するのに対し、後者の方法ではメッセージのリテラル形式について合意します。このシリーズの第2回では、スキーマによって制約されたSOAPの例を見ます。

リスト1 および2 は、Section 5エンコードを具体的に示したものです。リスト1Foo JavaBeanが、リスト2 では直列化されています。

リスト1. Foo JavaBean
class Foo{
  int i;
  String s;
  public Foo() {}
  ....  /* property mutators and accessors not shown for brevity */  
}
リスト2. FooオブジェクトのSOAP表現
  <SOAP-ENV:Body>
    <ns1:eatFoo xmlns:ns1="urn:ibmdw:myservice"
    SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
      <fooParam xmlns:ns2="http://foo/type" xsi:type="ns2:Foo">
        <i xsi:type="xsd:int">1000</i>
        <s xsi:type="xsd:string">Hello World</s>
      </fooParam>
    </ns1:eatFoo>
  </SOAP-ENV:Body>

リスト2のSOAP XMLインスタンスは、urn:ibmdw:myservice というURIで示されるWebサービスに対してメソッド呼び出しeatFoo をディスパッチするRPC呼び出しを表しています。fooParami、およびs の各要素はアクセサ と呼ばれ、値のコンテナーとして機能します。多重参照アクセサにはこの定義が適用されません。多重参照アクセサは、XML Pointer Language (詳細については後の参考文献を参照) のようなメカニズムを採用する空の要素で、実際の値を含む他の要素を参照するために使用します。

リスト2 の味付けになっているxsi:type 属性は、アクセサに対してサブタイプ化の機能を提供します。普通、Webサービスの提供者と要求発行者は、個々のRPC呼び出しにおけるパラメーターのデータ型を、あらかじめ統一しておきます。この事前合意があれば、xsi:type 属性がなくても、SOAP XMLインスタンスを正しく非直列化するには十分です。しかし、あるメソッドが文字列と整数の両方を受け取るような場合について考えてみましょう (java.lang.Object パラメーターを受け取るようにパラメーターの型が指定されている場合など)。このような場合、オブジェクトを正しく非直列化するには、xsi:type をはっきりと指定して、含まれている値の型を表明する必要があります。明示的なxsi:type 属性を含むアクセサのことを、ポリモアフィック・アクセサ (polymorphic accessor) と呼びます。

Apache SOAPは、すべてのアクセサをポリモアフィック (多形的) に扱うように設計されています。Version 2.2は、引き続きポリモアフィック・アクセサを使ってSOAP XMLインスタンスを生成しますが、ポリモアフィック・アクセサを使わないでアンマーシャルするようにプログラミングすることもできます。この方法については、後で簡単に説明します。

xsi:type 属性は、値としてQNameを受け取ります。xsi:type 属性では以下の値を指定できます。

XML Schema Part 2 のデータ型仕様で規定されている組み込みデータ型。

Apache SOAPは大部分の組み込み型をサポートしており、以下のものを含むSchema Part 2の全バージョンの型との間に下位互換性があります。

  • 勧告 (2001年)
  • 勧告候補 (2000年10月)
  • ワーキング・ドラフト (1999年)

正確には、Apache SOAPはすべての組み込み型をカバーしているわけではありませんが、最もよく使われる型はサポートしています。Apache SOAPでサポートされているSection 5エンコード型の詳細については、次の節で説明します。

ユーザー定義の型を表す任意の QName

リスト2 で使われているns2:Foo のような複合型を表すために使用します。この型の直列化された形式は、スキーマ文書を使って、またはApache SOAPの場合のようにカスタム・(デ) シリアライザーを使って定義できます。

SOAP 1.1 拡張型

これには、配列メンバーの順序付きシーケンスを表すSOAP-ENC:Array、およびbase-64エンコード文字列を表すSOAP-ENC:base64 が含まれます。

リスト2encodingStyle 属性は、使用する直列化方式を示すために使われています。たとえば、Section 5エンコードは、http://schemas.xmlsoap.org/soap/encoding/ というURIで表されます。Apache SOAPは、Section 5、XMI、およびリテラルXMLという3種類のencodingStyle を標準でサポートしています。カスタムencodingStyle もサポートしています。

ただし、Apache SOAPはSection 5エンコード方式のすべての機能をサポートしているわけではありません。例えば疎な配列、部分的に伝送される配列、多次元配列のサポートは、まだ含まれていません。

ここまでの解説では、データ交換のエンドポイントはSection 5でエンコードされた型を何らかの方法で直列化または非直列化できますが、この前提は、実際には、やり取りされるエンティティーのデータ・モデルに関する、伝送とは直接関係しない部分での合意に依存するものと考えてきました。これは、基本的に、エンドポイントが「既知の」型について合意する必要があることを意味します。ただし、これには代わりの方法があります。Section 5の制約を回避したいソフトウェア開発者は、スキーマ・ベースの直列化 を利用できます。この方式は、要求/応答メッセージに対するスキーマと共にSOAPサービスのインターフェースを公開することで機能します。Web Services Definition Language (WSDL) は、この目的に対する現時点での事実上の標準です。Apache SOAPはWSDLに対応していませんが、Apache SOAPの後継ツールキットであるAxis (参考文献を参照) は対応しています。


Section 5 エンコード

SOAPでは、Section 5エンコード方式で記述されているSOAP型に対する言語バインディングは定義されていません。SOAPの型は十分に一般的なので、代わりに、Javaプログラミング言語や他の大部分の主要プログラミング言語で使われている標準的なデータ型をモデル化できます。ここでは、Javaのプリミティブおよび任意のJavaクラスのエンコードとデコードに関するApache SOAPのサポートについて見ていきます。

ただし、最初に用語を定義しておきましょう。直列化 (serialization) とはJavaオブジェクトをXMLインスタンスに変換するプロセスで、非直列化 (deserialization) はXMLからJavaオブジェクトを再構成するプロセスです。Apache SOAPは、シリアライザー (serializer) およびデシリアライザー (deserializer) と呼ばれる構造を利用して、これらの処理を実行します。この記事の中では、デシリアライザーおよびシリアライザー を短縮して(デ) シリアライザー と記述します。

Apache SOAPツールキットに含まれている (デ) シリアライザーの基本セットでは、単純型 、複合型 、および特殊型 という3種類のデータ型グループを処理できます。単純型 および複合型 という用語はSOAP 1.1仕様からそのまま流用しており、このコンテキストにおける意味はSOAP 1.1仕様での意味と同じです。特殊型 という用語は、前の2つのグループにぴったり当てはまらないJava型を表すために著者が作ったものです。

単純型

Apache SOAPは、これらの型を、子としてのテキスト・ノードでのみアクセサとして直接直列化します。一般に、XMLインスタンスは次のような形式になります。

<accessor xsi:type="qname">
    <!-- data -->
</accessor>
表 1. Apache SOAP でサポートされる単純型
JavaSOAPシリアライザーデシリアライザー
Stringxsd:string組み込み*StringDeserializer*
Booleanxsd:boolean組み込み*BooleanObjectDeserializer*
booleanxsd:boolean組み込み*BooleanDeserializer*
Doublexsd:double組み込み*DoubleObjectDeserializer*
doublexsd:double組み込み*DoubleDeserializer*
Longxsd:long組み込み*LongObjectDeserializer*
longxsd:long組み込み*LongDeserializer*
Floatxsd:float組み込み*FloatObjectDeserializer*
floatxsd:float組み込み*FloatDeserializer*
Integerxsd:int組み込み*IntObjectDeserializer*
intxsd:int組み込み*IntDeserializer*
Shortxsd:short組み込み*ShortObjectDeserializer*
shortxsd:short組み込み*ShortDeserializer*
Bytexsd:byte組み込み*ByteObjectDeserializer*
bytexsd:byte組み込み*ByteDeserializer*
BigDecimalxsd:decimal組み込みDecimalDeserializer
GregorianCalendarxsd:timeInstant!CalendarSerializerCalendarSerializer
Datexsd:dateDateSerializerDateSerializer
QNamexsd:QNameQNameSerializerQNameSerializer
  • 組み込み:org.apache.soap.encoding.SOAPMappingRegistry でインプリメントされる内部クラス
  • *: Apache SOAP Version 2.1より
  • !: 2001年1 月にW3C XML Schemaデータ型仕様 (参考文献を参照) が提案勧告状態に移行した時点で、timeInstantdateTime に名前が変更されました。Apacheはxsd:dateTime をまだサポートしていません (参考文献を参照)。

先に進む前に、表1 に関する3つのポイントをさらに詳しく見ておくことにしましょう。第1に、鋭い読者はお気付きのことでしょうが、char がサポートされていません (参考文献を参照)。幸い、char 用のカスタム・(デ) シリアライザーの作成は難しくありません。第2に、BooleanDeserializer に対する更新によって、ブール・リテラルtrue を表すための値として { "1", "t", "T" } を、またfalse を表すための値として { "0", "f", "F" } を、それぞれ認識できるようになっています。第3に、RPC呼び出しの受信は、デフォルトでプリミティブに非直列化されます。例として、次のような単一のメソッドを公開するSOAPサーバーを考えてみます。

echoBoolean( Boolean b ) { .. }

Boolean パラメーターでechoBoolean を呼び出すと、SOAPの障害が発生します。

         Exception while handling service request:
         com.raverun.bool.Service.echoBool(boolean) -- no signature match

幸い、対応するラッパー・オブジェクトを返すデシリアライザー群 (xxxObjectDeserializer) があります。そのためには、デフォルトのデシリアライザーの動作をオーバーライドする必要があります。この方法については後で説明します。

複合型

Javaの観点からは、複合型とは構成要素を持つ型です。これらの構成要素を示すには、単なる名前 (複数のメンバー・プロパティーを持つJavaクラスなど) または序数位置 (ArrayVector のようなList データ構造の場合) を使用します。リスト3 は、複合型のXMLインスタンスです。

リスト3. java.util.Hashtableに対するXMLインスタンス
     <hash xmlns:ns2="http://xml.apache.org/xml-soap" xsi:type="ns2:Map">
     <item>
      <key xsi:type="xsd:string">2</key>
      <value xsi:type="xsd:string">two</value> </item>
     <item>
      <key xsi:type="xsd:string">1</key>
      <value xsi:type="xsd:string">one</value> </item>
     </hash>
表 2. 複合型に対する Section 5 エンコード
JavaSOAPシリアライザー/デシリアライザー
Java配列SOAP-ENC:ArrayArraySerializer
java.util.Vector
java.util.Enumeration
{http://xml.apache.org/xml-soap}Vector*VectorSerializer
java.util.Hashtable{http://xml.apache.org/xml-soap}MapHashtableSerializer
java.util.Map{http://xml.apache.org/xml-soap}MapMapSerializer#
任意のJavaBeanユーザー定義のQnameBeanSerializer
  • *: Apache 2.1のVectorSerializer は、SOAPの配列に直列化します。ただし、2.2では、独自のApache SOAPプロプラエタリー型として導入されました。
  • #:MapSerializer は、実際には、呼び出しをHashtableSerializer に委任します。

表2 を見るとわかるように、BeanSerializer を使えば、複雑なJavaクラスを自由に送信できます。ただし、直列化されたJavaクラスは、JavaBeansのデザイン・パターンに準拠していなければなりません。特に、直列化するすべてのプロパティーに対して、引数なしのパブリックなコンストラクターとgetter/setterメソッドを備えていなければなりません。getter/setterメソッドがJavaBeansの命名規則を満たしていない場合は、BeanInfo クラスを通してBeanSerializer に公開できます。たとえば、Foo クラスを直列化する必要がある場合は、java.beans.BeanInfo インターフェースをインプリメントするFooBeanInfo という名前のクラスを作成します。リスト4 の例を参照してください。

リスト4. FooBeanInfoクラス
import java.lang.reflect.*; 
import java.beans.*;

/*
 * Foo has a single String property by the name of "s".
 * And its setter and getter methods are called "_setS" and "_getS"
 * respectively, thus violating the JavaBean spec. Passing Foo to BeanSerializer
 * will cause this exception to be thrown:
 *        Error: Unable to retrieve PropertyDescriptor for property 's' 
 *               of class "Foo".
 */
public class FooBeanInfo extends SimpleBeanInfo{

  private final static Class c = Foo.class;

  public PropertyDescriptor[] getPropertyDescriptors()
  {
    try{
      Class[] sp = new Class[] {String.class};
      Method s_setter = c.getMethod( "_setS", sp );
      Method s_getter = c.getMethod( "_getS", null );
      PropertyDescriptor spd = new PropertyDescriptor("s", s_getter, s_setter);      
      PropertyDescriptor[] list = { spd };
      return list;
    }catch (IntrospectionException iexErr){
      throw new Error(iexErr.toString());
    }catch (NoSuchMethodException nsme){
      throw new Error(nsme.toString());
    }
  }
}

特殊型

これまでの2つのカテゴリーのどちらにも完全には当てはまらないJava型がいくつかあります。SOAPは、このような型も同じように扱うことができます。

ヌルとvoid
Apache SOAPは、ヌル値のオブジェクト参照を、xsi:null 属性を追加して"true" に設定することで直列化します。そのデフォルトの動作では、次に示すように、xsi:null属性に設定できる他のバリエーションは無視されます。

  • xsi:nil="true" (XML Schema Part 1の場合) (参考文献を参照)
  • xsi:null="1" (SOAP 1.1の場合) (参考文献を参照)

リスト5 は、null値を含むオブジェクト参照の直列化の例です。

リスト5. 疎ベクトルのSOAP表現
<myvec xmlns:ns2="http://xml.apache.org/xml-soap" xsi:type="ns2:Vector">
<item xsi:type="xsd:string">Hello World</item>
<item xsi:type="xsd:anyType" xsi:null="true"/>
</myvec>

Apache SOAP 2.1では、nullメンバーを含むベクトルを送信すると、ヌル参照を非直列化するデシリアライザーが存在しないため、SOAPException が発生しました。このような場合のために、UrTypeDeserializer が導入されました。VectorSerializer は、ヌル参照を、XML Schema組み込みデータ型階層のルート・クラスであるxsd:anyType にマップします (xsd:ur-type は2001 XML Schema勧告において非推奨になっています)。

文字列パラメーターを処理する際、アクセサがxsi:null 属性を持たない空の要素の場合は、Apache SOAPはヌル・オブジェクト参照に非直列化しません。たとえば、String パラメーターを必要とする次のようなリモート・メソッドについて考えてみます。

public void eat(String s) //1

そして、次のようにこの要求をWebサービスに送信するものとします。

<ns1:eat>                       
<s xsi:type="xsd:string"/>
</ns1:eat>

このような場合は、空のString に非直列化します。

バイナリー・データ
バイナリーのBLOBを転送する必要がある場合は、3つのオプションがあります。

  • バイト配列
  • 16進数ストリング
  • MIME添付
表3. BLOB の送信
JavaSOAPシリアライザー/デシリアライザー
バイト配列SOAP-ENC:base64#Base64Serializer
16進数ストリングxsd:hexBinaryHexDeserializer
javax.activation.DataSourcexsd:anyTypeMimePartSerializer
javax.activation.DataHandlerxsd:anyTypeMimePartSerializer
  • #:SOAPMappingRegistry の最新のバージョンは、非直列化の際にSOAP-ENC:base64 のスーパータイプである{http://www.w3.org/2001/XMLSchema}base64Binary をサポートします。

16進数ストリングをカプセル化するには、ラッパー・クラスのorg.apache.soap.encoding.Hex を使用します。これは、(非) 直列化されるクラスです。ラッパー・クラスは、a からf までの16進値として小文字と大文字の両方を受け取ります。16進数ストリングに偶数個の文字が含まれることだけに注意する必要があります。

Apache SOAPは、MIME/マルチパート関連メッセージ内へのSOAPエンベロープの埋め込みをサポートしています。この場合、バイナリー・データはMIME添付として搬送されます。これは、SOAP Messages with Attachments (SwA) 仕様に準拠しています (参考文献を参照)。

リテラル XML

XMLフラグメントをストリングとして送信する場合、組み込みストリング・シリアライザーは、要素の内容として不正なすべての文字を、あらかじめ定義されているストリング・エンティティーに置き換えることでエスケープします。たとえば、<&lt; に置き換えられます。SOAP RPC構造に埋め込まれているリテラルXMLを送信するには、2つのオプションがあります。第1の方法としては、XMLストリングをCDATA セクション内にラップします。この方法を使えば、形式が整っていないXMLでも送信できます。第2の方法は、XMLフラグメントをDOMにロードし、ドキュメント要素をパラメーターとして送信するものです。Apache SOAPは、XMLParameterSerializer を使ってorg.w3c.dom.Element インスタンスを直列化します。


型マッピングのパターン

Apache SOAPでSOAPクライアントとSOAPサーバーをプログラミングする際には、表4 で示されているようなメッセージと共にjava.lang.IllegalArgumentException が発生することがよくあります。

表4. 一般的な型マッパー・エラー・メッセージ
メッセージ
1No Serializer found to serialize a 'xxx' using encoding style 'yyy' (エンコード・スタイル 'yyy' を使用する 'xxx' を直列化すためのシリアライザーが見つかりません)
2No Deserializer found to deserialize a ':Result' using encoding style 'yyy' (エンコード・スタイル 'yyy' を使用する ':Result' を非直列化するためのデシリアライザーが見つかりません)
3No mapping found for 'xxx' using encoding style 'yyy' (エンコード・スタイル 'yyy' を使用する 'xxx' に対するマッピングが見つかりません)

Apache SOAPにおける直列化と非直列化の仕組みは、レジストリーで定義されている型マッピング (type mapping) の利用可能性に依存しています。型マッピングでは、Javaクラス (またはプリミティブ) からXMLへの (およびその逆の) 変換方法が定義されています。レジストリーは、org.apache.soap.encoding.SOAPMappingRegistry クラスのインスタンスです。デフォルトでは、SOAPクライアントとSOAPサーバーは両方とも、同じ型マッピングのセットを継承します。

図1. あるコンテキストにおける直列化/非直列化
図1. あるコンテキストにおける直列化/非直列化

以下の説明では、SOAPメッセージ処理の方向を示すためにインバウンド (inbound) およびアウトバウンド (outbound) という用語を使用します。たとえば、SOAPサーバーのコンテキストでは、直列化は、アウトバウンド・メッセージを生成する仕組みを示しています。この簡単なモデルでは、中間にあるSOAPサーバーを無視しても、説明の内容が損なわれることはありません。図1 は、説明の基になっているアーキテクチャーを示したものです。

型マッピング・レジストリーと対話するためのプログラムの構文を説明する前に、レジストリーの内部的なインプリメンテーションについて少し理解しておくと役に立ちます。開発者が対話するレジストリーは、実際には、4種類の内部的なHashtable を外から見たものです (次のリストを参照)。

  1. シリアライザー用レジストリー
  2. デシリアライザー用レジストリー
  3. Java2XML 用レジストリー
  4. XML2Java 用レジストリー

直列化と非直列化は対称的な機能なので、直列化の型マッピングが格納される方法についてだけ説明します。非直列化については、この説明から類推してもらえれば幸いです。直列化の型マッピングは、前記のリストで示されている3つのハッシュ・テーブルA、C、およびDのエントリーによって表されます。例として、Foo.class オブジェクトをSOAPの型"{http://someuri}foo" に直列化するシリアライザーFooSerializer について考えます。この型マッピングに対する固有キーを表すストリングk を考えた場合、直列化のためには次のようなマッピングが作成されます。

  • ハッシュ・テーブルA:kFooSerializer.class にマップされる
  • ハッシュ・テーブルC:k{http://someuri}foo にマップされる

ハッシュ・テーブルAとCはどちらも、Javaランタイム型とencodingStyleURI を連結することで生成される共有ストリングをキーとして使用します。ハッシュ・テーブルAはorg.apache.soap.util.xml.Serializer インターフェースをインプリメントするJavaオブジェクトの集合を保持し、ハッシュ・テーブルCはQNameの集合を管理します。直列化プロセスの過程では、2つのAPI呼び出しによってレジストリーへの問い合わせが行われます。

  Serializer querySerializer( javaType, encodingStyleURI )
  QName queryElementType (javaType, encodingStyleURI )

Apache SOAPは、Foo インスタンスを直列化するように指示されると、querySerializer メソッドを呼び出して、FooSerializer を受け取ります。その後、オブジェクトをマーシャルするためにFooSerializer が呼び出されます。このとき、queryElementType を呼び出してxsi:type 属性の値を取得します。

querySerializerSerializer を返すのに失敗した場合は、表4 のエラー・メッセージ1が返ります。一方、queryElementType が対応するQNameを返さない場合は、エラー・メッセージ3が返ります。

SOAP クライアントでの型マッピングの定義

SOAPMappingRegistry インスタンスでmapTypes() メソッドを呼び出すことで、特殊型を処理するようにレジストリーを変更できます。メソッドのシグニチャーは次のとおりです。

  mapTypes( String, QName, Class, Serializer, Deserializer );

マッピングをハッシュ・テーブルCとDに同時に登録する場合は、QName パラメーターとClass パラメーターを必ず指定する必要があります。リスト6 は、Apache SOAPでのデフォルトの型マッピングの例です。

リスト6. クライアントの mapTypes の例
HashtableSerializer hashtableSer = new HashtableSerializer();
mapTypes("http://schemas.xmlsoap.org/soap/encoding/", 
         new QName("http://xml.apache.org/xml-soap", "Map"),
         Hashtable.class, 
         hashtableSer, 
         hashtableSer);

mapTypes("http://schemas.xmlsoap.org/soap/encoding/", 
         new QName("http://schemas.xmlsoap.org/soap/encoding/", "Array"),
         null,
         null,
         new ArrayDeserializer());

mapTypes( encStyleURI, Qname, Foo.class, null, null );

リスト6Hashtable マッピングは、インバウンド処理とアウトバウンド処理の両方が登録される対称マッピング (symmetric mapping) の例です。2番目のマッピングは非対称マッピング (asymmetric mapping) で、ハッシュ・テーブルBへのArrayDeserializer の登録だけが行われます。配列の直列化はSOAPMappingRegistry.querySerializer() の中で行われます。このメソッドは、イントロスペクションを使用してJavaの型が配列かどうかを明らかにして、直ちにArraySerializer の新しいインスタンスを返します。最後のマッピングは単なる関連付けです。

SOAP サーバーでの型マッピングの定義

サーバー・サイドでは、型マッピングは配置記述子 (deployment descriptor) と呼ばれるXMLファイルで定義されます。実際には、Apache SOAPは、配置記述子の内容からSOAPMappingRegistry インスタンスを作成します。XML文法に対するスキーマは、この記事と共に提供されるソースで利用できます (参考文献を参照)。リスト7 はスキーマからの抜粋で、map 要素に対する属性が示されています。この属性は、SOAPMappingRegistrymapTypes メソッドのパラメーターを反映しています。唯一の違いは、map に対してはqname が必須である点です。

リスト7. Apache SOAP配置記述子におけるマップ要素に対するスキーマ・フラグメント
    <complexType name="mapType">
      <attribute name="encodingStyle" type="xsd:anyURI"/>
      <attribute name="qname" type="xsd:string"/>
      <attribute name="javaType" type="xsd:string" use="optional"/>
      <attribute name="java2XMLClassName" type="xsd:string" use="optional"/>
      <attribute name="xml2JavaClassName" type="xsd:string" use="optional"/>
    </complexType>

コンパイル済みのカスタム型マッピング

型マッピングをSOAPMappingRegistry のサブクラス内にまとめることにより、面倒な手作業で型マッピングを宣言しなくて済みます。このサブクラスは、クライアントでもサーバーでも使用できます。リスト8 は、独自の配列 (デ) シリアライザーを提供するSOAPMappingRegistry サブクラスのコード・サンプルです。

リスト8. SOAPMappingRegistryを拡張するパブリック・クラスMySmrのサブクラス化
  public MySmr()
  { super(); registerMyMappings();
  }
  public MySmr(String schemaURI)
  { super(schemaURI); registerMyMappings();
  }
  private void registerMyMappings()
  {
    MyArraySerializer arraySer = new MyArraySerializer();
    mapTypes(Constants.NS_URI_SOAP_ENC,
             new QName(Constants.NS_URI_SOAP_ENC, "Array"), null,
             arraySer,
         arraySer);
  }
}

呼び出し側でこのカスタムSOAPMappingRegistry を使用するには、Call オブジェクトのsetSOAPMappingRegistry() メソッドにパラメーターとしてSOAPMappingRegistry を渡します。プロバイダー側では、個別のマップ要素をすべて削除し、代わりに、配置記述子をリスト9 で示すように変更します。

リスト9. 配置記述子の変更
<isd:service ...>
  // other elements here
  <isd:mappings defaultRegistryClass="com.raverun.array.MySmr" />
</isd:service>

Apache SOAP が異なるスキーマ・ネームスペースを扱う方法

リスト9 を見ると、SOAPMappingRegistry クラスにはString パラメーターを受け取る多重定義されたコンストラクターがあることがわかります。このパラメーターは、実際には、W3CのXML Schema言語の特定のバージョンを示すURIです。Apache SOAPが受け入れたバージョンは、org.apache.soap.Constants における定義済みの最終版です。

  • Constants.NS_URI_1999_SCHEMA_XSD =http://www.w3.org/1999/XMLSchema
  • Constants.NS_URI_2000_SCHEMA_XSD =http://www.w3.org/2000/10/XMLSchema
  • Constants.NS_URI_2001_SCHEMA_XSD =http://www.w3.org/2001/XMLSchema

引数なしのコンストラクターでSOAPMappingRegistry をインスタンス化すると、Apache SOAPは、デフォルトで、1999ネームスペースに対する型を直列化します。多重定義されたコンストラクターを呼び出すことで実際にオーバーライドされるのは、xsd ネームスペースだけです。SOAPエンベロープで宣言されているxsi ネームスペースは変更されないで残ります。これは、エンベロープはネームスペースのプレフィックスのマッピングをConstants.NS_URI_CURRENT_SCHEMA_XSDConstants.NS_URI_CURRENT_SCHEMA_XSI から直接取得し、それが1999ネームスペースを指しているためです。この問題は、CVSソースの最新のリリースにおいて解決されています (参考文献を参照)。ただし、非直列化の観点からは、Apache SOAPは、示されている3つのバージョンのどのスキーマ・タイプも問題なく扱うことができます。

エンコード・スタイルと型マッピング

型マッピング・レジストリーの内部についてわかっていることから判断して、直列化と非直列化の処理方法の決定においてencodingStyleURI が大きな役割を演じていることは明らかです。このセクションでは、encodingStyleURI の使用に影響を与えるプログラミングに関する問題に焦点を当てます。

まず、SOAP 1.1の仕様では、「SOAPメッセージに対してデフォルトのエンコード方式は定義されていない」と明確に示されています。そのため、Apache SOAPでは、デフォルトのencodingStyleURI はヌルです。Call オブジェクトまたは各パラメーターの宣言の中でencodingStyleURI を何らかの値に初期化しない場合は、SOAPMappingRegistrysetDefaultEncodingStyle メソッドを呼び出すことで初期化できます。これは、すべてのパラメーター・アクセサにローカル・スコープのencodingStyleURI 属性を追加することと同じです。

次に、SOAP応答とSOAP要求で異なるencodingStyle が必要であるような状況について考えます。解説のため、リスト10countKids() メソッドを使用します。インバウンドとアウトバウンドのencodingStyles は、それぞれ、リテラルXMLとSection 5です。

リスト10. サンプルのSOAPサーバーのコード・フラグメント
public int countKids( Element el ){
  return( DOMUtils.countKids(el, Node.ELEMENT_NODE) );
}

リスト11 のコードでcountKids() を呼び出そうとすると、java.lang.IllegalArgumentException: I only know how to serialize an 'org.w3c.dom.Element' というSOAP障害が返ります。

リスト11. RPC呼び出しに対するコード・フラグメント
Call call = new Call();
Vector params = new Vector();
params.addElement( new Parameter("xmlfile", Element.class,
                                 e,
                                 Constants.NS_URI_LITERAL_XML) ); 
call.setParams( params );
call.invoke ( url, "" );

このエラーが発生する理由を理解するには、アウトバウンド・メッセージで使用するencodingStyle を決定するために (SOAPサーバー上の) Apache SOAPが使用するアルゴリズムを理解する必要があります。

  1. 利用できる場合は、メソッドのラッパー・アクセサのencodingStyle 属性を使用します。
  2. この属性がない場合は、最初のパラメーターからencodingStyle を導き出します。

したがって、メソッドが複数のパラメーターを受け取る場合で、CallencodingStyle 属性を明示的に設定していない場合は、パラメーターの順序の変更が必要になることがあります。Call オブジェクトのencodingStyle を、リモート・メソッドの戻り値の型に対応するように設定しておけば、この問題は起きません。

SOAP メッセージにおける encodingStyle の混合

まず第一に、SOAP仕様では、1つのパラメーターの中でencodingStyle を混合することは禁止されていません。encodingStyle 属性のスコープは、ネームスペースの宣言のように動作します。このような混合が有効な場合は、SVGフラグメントの配列です。最初は、Section 5を使って配列を直列化し、リテラルXMLを使って配列のSVGメンバーを直列化すればいいように見えるかもしれません。残念ながら、すべてのSection 5シリアライザーはSection 5のencodingStyle で登録された型でのみ動作するようにハードコーディングされているので、この方法はうまくいきません。言い換えると、Section 5でエンコードされたXMLストリーム内のすべてのものは変更できず、異なるencodingStyle を使ってエンコードされたXMLフラグメントを含めることはできません。また、最近行われたバグ修正 (参考文献を参照) により、ArrayHashtableVector などの複合データ構造内の値はSection 5以外のencodingStyle を想定できないようになりました。

型マッピングに関するその他の問題

この記事の締めくくりとして、SOAPの型マッピングで遭遇する可能性のあるいくつかの問題と癖を紹介しておきます。

非直列化の際のxsi:typeの緩和
MicrosoftのSOAPツールキットで生成されたインバウンド・メッセージを処理する際には、次のエラーが発生する可能性があります。

No Deserializer found to deserialize a ':Result' using encoding style 'yyy'

このよく見かけるインターオペラビリティーに関するエラーは、アクセサにxsi:type 属性がないために発生します。Apache SOAP 2.1リリースでインプリメントされているソリューションでは、次のルールに従ってxsi:type の値が動的に作成されます。パラメーターに対するアクセサがどのネームスペースのスコープにもない場合は、次のQNameを使用します。

{""}/X

X はアクセサのtagName を表し、"" は空のURIを表します。アクセサがある場合は、空のURIの代わりに、非直列化されるアクセサのネームスペースのURIを使用します。このように適切な順序で、特別なQNameに対応する型マッピングが (デ) シリアライザーに対して生成されます。リスト12 は、クライアントとサーバーの両方に対する型マッピングのサンプルを示したものです。

リスト12. RPC呼び出しに対するコード・フラグメント
    [Client]
    SOAPMappingRegistry smr = new SOAPMappingRegistry ();
    StringDeserializer sd = new StringDeserializer ();
    smr.mapTypes (Constants.NS_URI_SOAP_ENC,
                  new QName ("", "Result"), null, null, sd);
    [Server]
    <isd:mappings>
    <isd:map encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
             xmlns:x="" qname="x:Book"
             javaType="com.raverun.Book"
             xml2JavaClassName="org.apache.soap.encoding.soapenc.BeanSerializer"/>
    <isd:map encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
             xmlns:x="" qname="x:price"
             xml2JavaClassName="org.apache.soap.encoding.soapenc.FloatObjectDeserializer"/>
    <isd:map encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
             xmlns:x="" qname="x:isbn"
             xml2JavaClassName="org.apache.soap.encoding.soapenc.StringDeserializer"/>
    </isd:mappings>

型マッピングは、次の規則に従う必要があります。

  • プリミティブ型とそのラッパー・オブジェクト (intInteger など) の場合は、型マッピングにおいてjavaType を指定してはならない。
  • 複合型では、デシリアライザーが処理を実行するときにjavaType についての知識を必要とする場合は (org.apache.soap.encoding.soapenc.BeanSerializer など)、javaType を指定してもかまわない。

この動作の詳細については、Sanjiva Weeraweenaによるsoap-devメーリング・リストへの投稿、およびJim Snellによる記事を参照してください (どちらのリンクについても参考文献を参照)。

多重参照型
多重参照型は新しい型のグループではありませんが、単純型と複合型の面を考慮する必要があります。多重参照型は、複数のパラメーターの間で識別情報を保持するのに役に立ち、特にDCEスタイル [ptr] のセマンティクスを公開するパラメーターに対して有効です。Apache SOAPは、多重参照型を非直列化することはできますが、多重参照型に直列化することはできません。

多重参照型は、パラメーター・アクセサ (空の要素) と値アクセサ という2つの部分に直列化されます。リスト13 では、varString 要素がパラメーター・アクセサで、greeting 要素が値アクセサです。値アクセサはメソッド・ラッパー・アクセサns1:echoString に対する直接の兄弟要素であることに注意してください。Apache SOAPでは直列化グラフのルート (メソッド・ラッパー・アクセサ) はSOAP-ENV:Body の直接的な子であると見なされるので、これは重要なことです。値アクセサが最初にあると、Server.BadTargetObjectURI の障害が発生します。SOAPの仕様では、要素が直列化ルートでないことを示すための属性SOAP-ENC:root が設けられていますが、Apache SOAPはこの属性を認識しません。

リスト13. 多重参照型パラメーターの例
<SOAP-ENV:Body>
  <ns1:echoString 
    xmlns:ns1="http://soapinterop.org" 
    SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
    <varString href="#String-0" />
  </ns1:echoString>
  <greeting 
    xsi:type="xsd:string" 
    id="String-0">Hello World</greeting>
</SOAP-ENV:Body>

XMIのエンコード
XMI (XML Metadata Interchange) は、アプリケーション間でUMLモデルを共有するためのOMG標準です。XMIを最もよく使用するのは、XMLファイルとの間でモデルのエクスポートやインポートを行うUMLモデリング・アプリケーションです。IBM XMI FrameworkのJava Object Bridge (JOB) を使用すれば、JavaオブジェクトをXMI形式に (非) 直列化できます。したがって、XMIはSection 5エンコードに対する代替手段と考えることができますが、使われることはあまりありません。


まとめと予告

この記事では、Javaの型の (非) 直列化に対するApache SOAPの標準的なAPIサポートの大部分について説明しました。また、他のJavaベースまたは非JavaベースのSOAPツールキットとのインターオペラビリティーを阻害する可能性のあるいくつかの特異性にも触れました。

次回の記事では、独自の (デ) シリアライザーを作成する際のガイドとなる解説書を紹介する予定です。また、スキーマ言語を利用して非Section 5エンコードを扱う方法も紹介します。

参考文献

  • Apache SOAP は、Javaプログラミング言語用のSOAPツールキットで、オープン・ソースとして提供されています。
  • Apache Axis は、Apache SOAPに取って代わる次世代のツールキットです。
  • Brendan MacmillanのJava Serialization to XML (JSX) ツールキットには、役に立つツールが含まれています。
  • Sunが提供するJava Architecture for XML Binding (JAXB) の早期アクセス・リリースをダウンロードしてください。
  • Castor は、オープン・ソースのJavaプログラミング言語用データ・バインディング・フレームワークです。
  • Schema2Java は、Creative Science Systems製の市販XMLデータ・バインディング製品です。
  • このシリーズに付属するサンプル・コード(PurchaseOrder.zip またはPurchaseOrder.tar.gz) をダウンロードしてください。サンプル・コードについては次回の記事で詳しく解説しますが、このパッケージに含まれるXML文法用スキーマはすぐに調べてみたいと思うかもしれません。
  • XMI (XML Metadata Interchange) についてさらに学習するには、IBM XMI Framework を参照してください。
  • SOAP 1.1の仕様は、W3C Technical Note で入手できます。
  • Sanjiva Weerawaranaによるこの投稿では、Apache SOAPにおけるxsi:type 要件の緩和について解説されています。
  • Apache SOAPと他のツールキットのインターオペラビリティーについては、James SnellのWeb servicesインサイダー: 第3回 (developerWorks、2001年5月) で触れられています。
  • W3Cノートの "SOAP Messages with Attachments" では、MIMEにSOAPを埋め込む方法が解説されています。
  • W3C XML Schema, Part 1 では、XML Schemaの中心概念と構文が解説されています。
  • W3C XML Schema, Part 2 では、XML Schemaでサポートされているデータ型が解説されています。
  • Apache SOAPバグ・レポート#2865 では、char プリミティブの非サポートについて解説されています。
  • Apache SOAPバグ・レポート#3000 では、timeInstant のバグについて解説されています。
  • Apache SOAPバグ・レポート#2388 では、古いXML SchemaネームスペースURIの使用について解説されています。
  • Apache SOAPバグ・レポート#2470 では、混合SOAPとリテラルXMLのエンコード・スタイルに関するHashtableSerializer の問題について解説されています。

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=SOA and web services, Java technology
ArticleID=294716
ArticleTitle=Apache SOAP の型マッピング: 第 1 回 Apache の直列化 API について
publish-date=04012002