目次


Webサービス・プログラミングのヒントと秘訣: JAX-RPCによる例外処理

サービス・エンドポイントから正しい例外を投げる

Comments

SOAP Webサービスの世界では、フォールトはサーバーからクライアントに向かって、SOAPフォールトの形で流れます。SOAPフォールトはフォールト・コードやフォールト・ストリング、それにオプションとしてのフォールト・アクター(fault actor)や詳細から成っています。JAX-RPC仕様では、Javaの例外からSOAPフォールト(サーバー側)に対して、また逆にSOAPフォールトからJavaの例外(クライアント側)に対して、どのようにマップすべきかを様々な規則で定義しています。

サーバーが投げる例外には4つの種類があります。

  • java.rmi.RemoteException
  • java.lang.RuntimeException
  • javax.xml.rpc.soap.SOAPFaultExceptionRuntimeExceptionのサブクラスで、特別なもの)
  • チェック済みでユーザー定義の例外(WSDLのwsdl:fault構成要素からマップしたもの)

クライアント側では下記のうちどれか一つの例外を受け取ります。クライアントはSOAPFaultException以外のRuntimeExceptionはどれも受け取れないことに注意してください。

  • java.rmi.RemoteException
  • javax.xml.rpc.soap.SOAPFaultException
  • チェック済みでユーザー定義の例外

この記事では、サーバーが様々な例外を投げる時に想定されるクライアントの振る舞いを説明し、次にチェック済み例外の使い方を詳しく説明します。

RemoteException

JAX-RPCは、サービス・インターフェース・エンドポイント(SEI)にある全てのリモート・メソッドが標準のjava.rmi.RemoteExceptionを投げることを要求しています。これによって通信や実行時の問題から発生する例外が、呼び出し側に伝達されて戻るようになります。ところがRemoteExceptionの特定なサブクラスを送信するための、可搬性を持った方法が無いのです。

アプリケーション自体もRemoteExceptionを投げることができるのですが、特定なRemoteExceptionを送信する可搬性を持った方法は無いので、クライアントは特定なRemoteExceptionを捉えることができないのです。サーバーから返ってきた、ある一つのSOAPフォールトに対して、異なるクライアント側JAX-RPCランタイムは異なる解釈をし、異なるRemoteExceptionsを生成します。この相互運用性の問題があるため、アプリケーションはRemoteExceptionsを投げるのを避けるべきなのです。

RuntimeException

RuntimeExceptionが投げられるような問題(例えばNullPointerException)がサーバー側JAX-RPCランタイムで起きると、その例外はクライアントに伝達されて戻りますが、SOAPフォールトとして戻されるのです。クライアント・ランタイムはSOAPフォールトをRemoteExceptionまたはSOAPFaultException(下で説明しています)のどちらかにマップします。ですからサービス・エンドポイントは、クライアントが常にそのRuntimeExceptionを捉えるものと期待してRuntimeExceptionを投げるべきではありません。クライアントはRemoteExceptionを受け取るかも知れないのです。

SOAPFaultException

一つ特別なRuntimeExceptionがあります。javax.xml.rpc.soap.SOAPFaultExceptionです。SOAPFaultExceptionRuntimeExceptionよりもより記述的で、クライアントに向かって流れるSOAPフォールト・メッセージを具体的にそのまま記述します。つまり、ランタイムであれアプリケーションであれ、このフォールトを投げたものがSOAPフォールト・レスポンスを制御するのです。ですからSOAPフォールトをどのようにして適切な例外にマップするかは本当にSOAPFaultExceptionの内容に依存し、SOAPフォールトはSOAPFaultExceptionにも、RemoteExceptionにも、チェック済みユーザー例外(Checked user exception)にさえマップされるのです。SOAPFaultExceptionはよくJAX-RPCハンドラーが使用します。JAX-RPCアプリケーション自体は通常、SOAPFaultExceptionを投げるのを避けるべきです。

チェック済みユーザー例外 (Checked user exception)

良いプログラミング習慣としては普通、インターフェース契約の一部として、チェック済みユーザー例外を明示的に定義しておくものです。JAX-RPCの世界では、プログラマーは最初にwsdl:operationの一部としてwsdl:faultsを定義する必要があります。Javaメソッドが複数の例外を許しているのと同様に、wsdl:operationも複数のwsdl:fault要素を許しています。各wsdl:faultはSEIの一部として、ユーザー例外にマップされます。大部分の場合、Javaの例外は複雑なデータ構造を持ちません。同様にwsdl:faultに関しても、wsdl:faultが定義するスキーマ定義はたいてい単純なものです。とは言ってもやはりプログラマーにとって、どの例外が投げられる可能性があるかをよく考えてから適切なwsdl:faultsを定義するのは重要なことです。

マップ規則

wsdl:inputwsdl:outputとは異なり、wsdl:faultが参照するメッセージには、単純タイプまたは複合タイプを参照する、単一のメッセージ・パートのみが許されます。パート要素がタイプ属性を持つ場合であれば、そのタイプが単純(例えばxsd:intやxsd:string等)か複雑かを直接判断できます。パート要素が要素属性を持つ場合であれば、もっと要素に近づいて、そのタイプが単純か複雑かをよく見る必要があります。

単純タイプに対するマップ規則

単純タイプの場合は、Java例外名がwsdl:message要素の名前属性からマップされます。wsdl:part名はゲッター・メソッドと、Java例外のコンストラクターにあるパラメーターにマップされます。例えばリスト1にあるWSDLのフォールト情報は、リスト2にあるJava言語の例外にマップされます。

リスト1. 単純フォールトでのWSDL定義
<definitions ...>
  <message name="empty"/>
  <message name="InsufficientFundsFault">
    <part name="balance" type="xsd:int"/>
  </message>
  <portType name="Bank">
    <operation name="throwException">
      <input message="tns:empty"/>
      <output message="tns:empty"/>
<fault name="fault" message="tns:InsufficientFundFault"/>
    </operation>
  </portType>
  ...
</definitions>
リスト2. リスト1のフォールトからのJavaの例外
public class InsufficientFundFault extends java.lang.Exception {
    private int balance;
    public int getBalance() {
        return this.balance;
    }
    public InsufficientFundFault() {
    }
    public InsufficientFundFault(int balance) {
        this.balance = balance;
    }
}

複雑タイプに対するマップ規則

complexTypesでは、Java例外名はcomplexTypesの名前(またはフォールト・メッセージのパートが要素を参照している場合には、その要素の名前)からマップされます。complexTypes内部の各要素は、Java例外のコンストラクターにあるパラメーターとゲッター・メソッドにマップされます。beanとは違ってセッター・メソッドが無いことに注意してください。そうしたフィールドを設定する唯一の方法は例外コンストラクターによるものです。例えばリスト3のフォールト情報は、リスト4のJava例外にマップされます。

リスト3. 複雑フォールトに対するWSDL定義
<definitions ...>
  <types>
    <schema xmlns="http://www.w3.org/2001/XMLSchema">
      <element name="InsufficientFundFault">
        <complexType>
          <sequence>
            <element name="balance" type="xsd:int"/>
            <element name="requestedFund" type="xsd:int"/>
          </sequence>
        </complexType>
      </element>
    </schema>
  </types>
			 <message name="empty"/>
  <message name="withdrawRequest">
    <part name="amount" type="xsd:int"/>
  </message>
  <message name="InsufficientFundFaultMessage">
    <part name="fault" element="tns:InsufficientFundFault"/>
  </message>
  <portType name="Bank">
    <operation name="withdraw">
      <input message="tns:withdrawRequest"/>
      <output message="tns:empty"/>
<fault name="fault" message="tns:InsufficientFundFaultMessage"/>
    </operation>
  </portType>
  ...
</definitions>
リスト4: リスト3のフォールトからのJava例外
   public class InsufficientFundFault 
    	extends java.lang.Exception 
    	implements java.io.Serializable {
    private int balance;
    private int requestedFund;

    public InsufficientFundFault(
           int balance,
           int requestedFund) {
        this.balance = balance;
        this.requestedFund = requestedFund;
    }

    public int getBalance() {
        return balance;
    }

    public int getRequestedFund() {
        return requestedFund;
    }
}

フォールト継承に対するマップ規則

ここで、リクエストを出しているクライアントの口座番号を運ぶためにAccountInsufficientFundFaultと呼ばれるInsufficientFundFaultのサブクラスを定義したいとしましょう(リスト5)。アプリケーションはこのサブクラス例外を投げることができ、クライアントはこの例外を受信します。非常に単純です。

リスト5: 継承のあるWSDLフォールト
  <wsdl ...>
    <types>
    <schema targetNamespace="http://example" ...>
      ...

      <complexType name="InsufficientFundFaultType">
        <sequence>
          <element name="balance" type="xsd:int"/>
          <element name="requestedFund" type="xsd:int"/>
        </sequence>
      </complexType>

      <complexType name="AccountInsufficientFundFaultType">
        <complexContent>
           <extension base="tns:InsufficientFundFaultType">
             <sequence>
               <element name="account" type="xsd:string"/>
             </sequence>
           </extension>
        </complexContent>
      </complexType> 
			     		    
      <element name="InsufficientFundFault" 
               type="tns:InsufficientFundFaultType"/>
      ...
    </schema>
    </types>
			 
    <message name="AccountInsufficientFundFaultMessage">
      <part element="tns:AccountInsufficientFundFault" name="fault"/>
    </message>
			
    <operation name="withdraw">
      <input message="tns:withdrawRequest" name="withdrawRequest"/>
      <output message="tns:withdrawResponse" name="withdrawResponse"/>
      <fault message="tns:InsufficientFundFaultMessage" 
                name="InsufficientFundFault"/>
    </operation>
    ...
  </wsdl>

これを実行することにより、生成されたSEIのメソッド「withdraw」は同じまま残ります(リスト6)。ただし一つ注意しておきたいのは、クライアントがその例外を受信するには、InsufficientFundFaultのサブクラスはどれも必ずクライアントのWSDLファイルに現れている必要があるということです。WSDLファイルの中にInsufficientFundFaultの新しいサブクラスを生成せずにサーバー側JavaコンポーネントにInsufficientFundFaultの新しいサブクラスを生成することはできません。WSDLは宣言型言語です。サービスが投げる可能性のあるフォールトはどれも必ず、明示的にXMLの中に定義されている必要があります。そうでないとクライアントにはフォールトが分からず、受け取ることができません。

リスト6. 上記WSDL定義からマップされたJava SEI
  public interface Bank extends java.rmi.Remote {
    public boolean withdraw(java.lang.String account, 
    int amount) throws java.rmi.RemoteException, example.InsufficientFundFaultType;
  }

SOAPフォールトの内容

JAX-RPCランタイムはユーザー例外を捉え、wsdl:faultのメッセージ部分が参照するスキーマ定義に基づいてXMLデータにシリアル化します。そうしたXMLデータはSOAPフォールトのdetail要素の中身を埋めるために使われます。リスト7は複雑なInsufficientFundFaultの例のSOAPメッセージです。simpleTypeフォールトに対するSOAPメッセージも、detail部分が異なることを除けば同様です。

リスト7. SOAPフォールトの例
  <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
    <soapenv:Body>
      <soapenv:Fault>
        <faultcode >...</faultcode>
        <faultstring>...</faultstring>
        <detail>
          <InsufficientFundFault xmlns="http://example">
            <balance>1000</balance>
            <requestedFund>2000</requestedFund>
          </InsufficientFundFault>
        </detail>
      </soapenv:Fault>
    </soapenv:Body>
  </soapenv:Envelope>

ここでの鍵は、detailの部分がwsdl:faultの参照するスキーマ定義に一致すべき内容を運ぶ、ということです。こうすることにより、クライアント・ランタイムはその内容がどのユーザー例外に対してマップされるかが分かります。また、Java例外で通常期待する通り、SOAPフォールトは例外スタック・トレースは運ばないことにも注意してください。ですからWebサービスのクライアントは、サーバー側からスタック・トレースが来ると期待すべきではありません。

まとめ

ユーザー定義のフォールトを導入するのは良いプログラミング習慣です。RemoteExceptionsやRuntimeExceptionsを使うのはあまりにも汎用すぎるだけでなく、どのベンダーもこれらを同様に扱うという保証が全くありません。

ユーザー定義のフォールトの導入を決めたのであれば、どんなフォールトを使うかを決める必要があります。つまり単純タイプのフォールトか、complexTypesのフォールトか、あるいは継承ツリーのフォールトかを決め、またこうしたフォールトがどのようにJavaプログラミングの作成物にマップされるかを理解しておく必要があります。


ダウンロード可能なリソース


関連トピック


コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=SOA and web services, XML
ArticleID=245195
ArticleTitle=Webサービス・プログラミングのヒントと秘訣: JAX-RPCによる例外処理
publish-date=02062004