Tomute’s Notes

2009-02-26

[][]JAX-WSを使ってSOAPヘッダに独自のヘッダを付け足す方法(続編)

以前に「 JAX-WSを使ってSOAPヘッダに独自のヘッダを付け足す方法 - Tomute’s Notes」という記事に書いたのだが、JAX-WSを使ってWebサービスクライアントを作成する際に、送信するSOAPリクエストに対して独自ヘッダを付け足したい場合には以下のようにする(下記の例は<simpleHeader>stringValue</simpleHeader>というようなヘッダを付け足す場合)。

import com.sun.xml.ws.developer.WSBindingProvider;
import com.sun.xml.ws.api.message.Headers;

HelloPort port = helloService.getHelloPort();
WSBindingProvider bp = (WSBindingProvider)port;

bp.setOutboundHeader(
    Headers.create(new QName("simpleHeader"),"stringValue")
);

しかし、上記の方法はJAX-WS 2.1.3以降でサポートされたものであり、現時点で最新のJDK 1.6(Update 12)に含まれているJAX-WS 2.1.1では利用する事が出来ない。

したがってJAX-WS 2.1.3より以前のバージョンを使って、SOAPヘッダに独自のヘッダを付け足す場合には、SOAPメッセージハンドラを利用した、より複雑な方法を取る必要がある。

具体的には以下のような実装となる。

import javax.xml.ws.Binding;
import javax.xml.ws.BindingProvider;
import javax.xml.ws.handler.Handler;

HelloPort port = helloService.getHelloPort();
// メッセージハンドラの登録処理
Binding binding = ((BindingProvider)port).getBinding();
List<Handler> handlerList = binding.getHandlerChain();
handlerList.add(new ClientHandler());
binding.setHandlerChain(handlerList);

作成するメッセージハンドラのサンプルは以下。

import javax.xml.soap.*;
import javax.xml.ws.handler.*;
import javax.xml.ws.handler.soap.*;

public class ClientHandler implements SOAPHandler<SOAPMessageContext> {
    public Set<QName> getHeaders() { return Collections.EMPTY_SET; }

    public boolean handleMessage(SOAPMessageContext context) {
        try {
            Boolean outboundProperty =
              (Boolean)context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
            if (outboundProperty.booleanValue()) {
                // Outbound
                SOAPMessage msg = context.getMessage();
                SOAPPart part = msg.getSOAPPart();
                SOAPEnvelope envelope = part.getEnvelope();
                SOAPHeader header = envelope.addHeader();
                QName xmlName = new QName("simpleHeader");
                SOAPHeaderElement headerElement =
                    (SOAPHeaderElement)header.addChildElement(xmlName);
                headerElement.addTextNode("stringValue");
            } else {
                // Inbound
            }
        } catch (SOAPException ex) {
            return false;
        }
        return true;
    }
    
    public boolean handleFault(SOAPMessageContext context) { return true; }

    public void close(MessageContext context) {}
}

ちなみにJAX-RPCを使う場合には、更に少し実装が異なり、以下のような形になる。

import javax.xml.rpc.Stub

HelloPort port = helloService.getHelloPort();
HelloPort_Stub stub = (HelloPort_Stub)port;

// メッセージハンドラの登録処理
stub._getHandlerChain().add(new ClientHandler());

作成するメッセージハンドラのサンプルは以下。

import javax.xml.rpc.handler.*;
import javax.xml.rpc.handler.soap.*;
import javax.xml.soap.*;

public class ClientHandler implements Handler {
    public void destroy() {}

    public QName[] getHeaders() { return null; }

    public void init(HandlerInfo arg0) {}

    public boolean handleFault(MessageContext arg0) { return false; }

    public boolean handleRequest(MessageContext arg0) {
        try {
            SOAPMessageContext mc = (SOAPMessageContext) arg0;
            SOAPMessage msg = mc.getMessage();
            SOAPPart part = msg.getSOAPPart();
            SOAPEnvelope envelope = part.getEnvelope();
            SOAPHeader header = envelope.addHeader();
            QName xmlName = new QName("simpleHeader");
            SOAPHeaderElement headerElement =
                (SOAPHeaderElement)header.addChildElement(xmlName);
            headerElement.addTextNode("stringValue");
        } catch (SOAPException ex) {
            ex.printStackTrace();
            return false;
        }
        return true;
    }

    public boolean handleResponse(MessageContext arg0) { return false; }
}

参考:

2008-06-03

[][] wsimportコマンド実行時に出る「src-resolve: Cannot resolve the name…」という警告について

JAX-WSのwsimportコマンドを使って、WSDLからJavaソースコード、及びクラスを生成した際に、以下のような警告メッセージが出力された(この警告はwsimportコマンドをJDK6を利用して実行すると出力されるのだが、JDK5の場合出力されない)。

[WARNING] src-resolve: Cannot resolve the name 'ns1:xxxxx'
            to a(n) 'type definition' component.
line 100 of http://localhost/Boo?wsdl#types?schema4

generating code...

compiling code...

WSDLを調べてみた結果、どうもXML Schemaを使って「メッセージのフォーマットを定義する際に使用する型」を定義している部分(types要素)で、"ns1:xxxxx"という型を定義する前に使用している事が原因の模様(例は以下)。

<wsdl:types>
  <xs:complexType name="AAA">
    <xs:sequence>
      <xs:element minOccurs="0" name="bbb" type="ns1:xxxxx"/>
    </xs:sequence>
  </xs:complexType>
      (snip)
  <xs:simpleType name"xxxxx">
    (snip)
  </xs:simpleType>
  (snip)
</wsdl:types>

上記は文法としては問題ないと思われるし、警告は出るものの問題なくJavaソースコード、及びクラスが生成出来ているため、JDK6に付属するJAXP 1.4の不具合ではないだろうか?


なお、WSDLをローカルファイルとして保存(拡張子は.wsdl)してから、wsimportコマンドで読み込ませると警告は出なくなる。しかし、拡張子を.xmlとして保存すると何故か警告は消えない。

更に拡張子を.xmlとしても、上記の"xxxxx"の定義を使用している個所より上に持ってくると、警告は出なくなる。


いまいち根本的な原因が良く分からないのだが、とりあえず大きな問題では無さそうなので、調査は一旦終了。


参考:

Error 404 Not Found

2008-04-25

[] wsimportコマンドで非同期クライアント用クラスを生成する方法

JAX-WSでは非同期プログラミング・モデルがサポートされているのだが、wsimportコマンドを使用して自動生成したJavaクラスには、デフォルトでは非同期のインタフェースが含まれていない。


しかし、以下のような定義ファイルを準備し、wsimportコマンドの-bオプションで読み込ませる事で、非同期のインタフェースを生成する事が可能となる。

<bindings
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
    wsdlLocaption="http://localhost/hoge/moge?WSDL"
    xmlns="http://java.sun.com/xml/ns/jaxws">
    <bindings node="wsdl:definitions">
        <package name="hoge.client"/>
        <enableAsyncMapping>true</enableAsyncMapping>
    </bindings>
</bindings>

また、上記の定義は全てのインタフェースを非同期にするという指定であるが、特定のインタフェースだけを非同期にしたい場合には、以下のような定義ファイルにする*1

<bindings
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
    wsdlLocaption="http://localhost/hoge/moge?WSDL"
    xmlns="http://java.sun.com/xml/ns/jaxws">
    <bindings node="wsdl:definitions/
                    wsdl:portType[@name='getData']/
                    wsdl:operation[@name='getBooData']">
        <package name="hoge.client"/>
        <enableAsyncMapping>true</enableAsyncMapping>
    </bindings>
</bindings>

参考:no title


なお、NetBeans IDE等では、非同期Webサービスを容易に開発する機能がサポートされているため、上記のようなマニュアル作業は不要である。NetBeans IDEを使った具体的な方法については、以下のページで分かりやすく説明されている。


参照:no title

*1:戻り値がvoidのインタフェースに関しては非同期に出来ず、「[ERROR] wsimport failed to generate async response bean for operation: xxxxx」というエラーが出る。

2008-02-26

[] JAX-WS RIのダウンロード場所のメモ

JAX-WSのReference Implementationの最新版はno titleからダウンロード可能なのだが、旧バージョンのRIに関してはno titleからダウンロードできる。


例えばJAX-WS 2.0をダウンロードしたい場合には、ページ中の「2.0 Final (5)」というリンクを選択し、JAX-WS 2.0 RI FCSをダウンロードすれば良い。


[][] JBoss WS 関連メモ

オープンソースのアプリケーションサーバであるJBoss Application Server (JBoss AS) には、JAX-WSに準拠*1したWebサービス開発用ライブラリであるJBoss Web Services (JBoss WS) が含まれている。このJBoss WSを利用する事で、JBoss AS上で動作するWebサービスの開発を簡素化する事が可能となる。


JBoss WSにはJAX-WSのコマンドライン・ツールであるwsgen(Web サービスの配備/実行を行うために必要なファイルを生成するツール)とwsimport(Web サービスのクライアント・プログラムを作成するときに利用するツール)に相当する物が存在し、それぞれwsprovidewsconsumeという名称である。また、作成したWebサービスクライアントを簡易に実行させる事が出来る、wsrunclientというツールも提供されている。


なお、上記のツールはJAX-WSに対応したツールであるが、旧規格にあたるJAX-RPCベースで開発を行う場合にはwstoolsを利用する。


参照:

no title

*1:一部互換性に問題がある模様

2008-02-15

[][] wsmonitorでSOAPメッセージをモニタする

トラフィックモニタを使わずにSOAPメッセージをモニタする方法 - Tomute’s Notes」で書いたように、JAX-WSを使っている場合にはシステムプロパティを設定する事で、トラフィックモニタを利用しなくてもSOAPメッセージをモニタする事が出来る。

ただ、JAX-WS以外を使っている場合にはこの方法は使えないようなので、GlassFishプロジェクトで開発されているオープンソースのWebサービスモニタのwsmonitorを使ってみる。その使い方は以下。

  1. wsmonitorをダウンロード
  2. 以下のコマンドを実行してjarファイルを展開(Windowsの場合はjarファイルをダブルクリックして展開可能)
    java -jar wsmonitor-installer-XX.jar
  3. 設定ファイル(wsmonitor\etc\config.xml)を自分の環境に合わせて編集する
    デフォルトではwsmonitorはポート4040でSOAPメッセージを受信し、ポート8080に転送するように設定されている。受信ポートを変更したい場合にはlistenPort、転送先のポートを変更したい場合にはtargetPortのパラメータをそれぞれ変更する。
  4. wsmonitor\bin\wsmonitor.bat*1を実行する
    config.xmlを修正した場合には、引数にそのconfig.xmlを指定する。
    詳細なログメッセージを出力する場合には更に-verboseオプションを指定する。

f:id:tomute:20080216081145j:image


なお、SOAPクライアントが送信するSOAPメッセージも、wsmonitorが待ち受けているポートの方に送信する必要が出てくるが、JAX-WSを使っている場合には以下のようなコードで送信先を変更できる。

(以下はエンドポイントをhttp://localhost:4040/endpointに変更する場合の例)

import javax.xml.ws.BindingProvider;

HelloPort port = helloService.getHelloPort();
BindingProvider bp = (BindingProvider)port;

Map<String, Object> reqContext = bp.getRequestContext();
reqContext.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY,
 "http://localhost:4040/endpoint");

参照:no title

*1:事前にJAVA_HOMEの設定が必要。またJavaをC:\Program Files配下のように空白を含むパスにインストールしている場合には、ダブルクォーテーションで囲む必要がある