カタカタブログ

SIerで働くITエンジニアがカタカタした記録を残す技術ブログ。Java, Oracle Database, Linuxが中心です。たまにRuby on Railsなども。

【Java】MetroでWSDLからSOAPクライアントのJavaソースコードを自動生成する

前回、WebLogic上で簡単にSOAPのWebサービスを公開する方法を検証した。

今回は公開されたWSDLだけから、そのSOAPサービスを利用するためのJavaクライアントコードを実装してみる。実装と言っても、SOAPクライアントの部分はWSDLから自動生成することができるので、難しいWSDLの定義や通信を意識する必要は全くない。WSDLからの自動生成にはMetroというツールを利用した。以下、詳しく見ていく。

MetroでSOAPクライアントを自動生成する

GlassFishの中で、Metroというオープンソースのツールがあり、これでWSDLからJavaコードを自動生成することができる。GlashFishサーバにも含まれているらしいが、今回はスタンドアロン版を入手した(記事執筆時点での最新版である2.3.1をダウンロード)。

  • Metro

https://metro.java.net/
f:id:osn_th:20150331074958p:plain

展開すると、binディレクトリにWSDLをインポートしJavaクラスを作成するwsimport.shがある。

$ unzip metro-standalone-2.3.1.zip
$ cd metro/bin/
$ ls
wsgen.bat  wsgen.sh  wsimport.bat  wsimport.sh

使うためには、METRO_HOME環境変数にzip展開先のパスを指定しておく。

$ export METRO_HOME=/home/user/metro

ソース出力先ディレクトリを作成し、wsimportを実行する。

  • keepオプションはソースコードを生成するオプションで、これを付与しないと.classファイルのみ作成される。また、-sオプションで、ソースコード出力先を指定している。
$ mkdir src
$ sh wsimport.sh -keep -s src http://vmora7:7001/SoapApp/CalculatorService?wsdl
parsing WSDL...
GConf Error: Client failed to connect to the D-BUS daemon:
Unable to autolaunch a dbus-daemon without a $DISPLAY for X11
GConf Error: Client failed to connect to the D-BUS daemon:
Unable to autolaunch a dbus-daemon without a $DISPLAY for X11
GConf Error: Client failed to connect to the D-BUS daemon:
Unable to autolaunch a dbus-daemon without a $DISPLAY for X11
GConf Error: Client failed to connect to the D-BUS daemon:
Unable to autolaunch a dbus-daemon without a $DISPLAY for X11
GConf Error: Client failed to connect to the D-BUS daemon:
Unable to autolaunch a dbus-daemon without a $DISPLAY for X11
GConf Error: Client failed to connect to the D-BUS daemon:
Unable to autolaunch a dbus-daemon without a $DISPLAY for X11
GConf Error: Client failed to connect to the D-BUS daemon:
Unable to autolaunch a dbus-daemon without a $DISPLAY for X11
GConf Error: Client failed to connect to the D-BUS daemon:
Unable to autolaunch a dbus-daemon without a $DISPLAY for X11
Generating code...
Compiling code...

何かエラーが出ているようだが、コード生成自体は問題なさそう。

$ find src
src
src/web
src/web/Add.java
src/web/AddResponse.java
src/web/Calc.java
src/web/Calculator.java
src/web/CalculatorService.java
src/web/ObjectFactory.java
src/web/Sub.java
src/web/SubResponse.java
src/web/package-info.java

とりあえずEclipseにて新規Javaプロジェクトを作成し、生成されたソースコードを配置する。ビルドも正常に完了するよう。
f:id:osn_th:20150331075002p:plain
さて、作られたソース・ファイルの中身を見ていく。前回Webサービスを公開したときに作ったコードと似たような名前のクラスができているが、中身はWebサービスを呼び出すための処理になっている。いろいろできているが、いくつかピックアップして見てみる。クラスの仕様は前回のWebサービスに準じているので、そちらの記事を参照のこと。

  • Calculator.java
package web;

import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import javax.xml.bind.annotation.XmlSeeAlso;
import javax.xml.ws.Action;
import javax.xml.ws.RequestWrapper;
import javax.xml.ws.ResponseWrapper;


/**
 * This class was generated by the JAX-WS RI.
 * JAX-WS RI 2.2.10
 * Generated source version: 2.2
 * 
 */
@WebService(name = "Calculator", targetNamespace = "http://web/")
@XmlSeeAlso({
    ObjectFactory.class
})
public interface Calculator {


    /**
     * 
     * @param arg0
     * @return
     *     returns int
     */
    @WebMethod
    @WebResult(targetNamespace = "")
    @RequestWrapper(localName = "sub", targetNamespace = "http://web/", className = "web.Sub")
    @ResponseWrapper(localName = "subResponse", targetNamespace = "http://web/", className = "web.SubResponse")
    @Action(input = "http://web/Calculator/subRequest", output = "http://web/Calculator/subResponse")
    public int sub(
        @WebParam(name = "arg0", targetNamespace = "")
        Calc arg0);

    /**
     * 
     * @param arg0
     * @return
     *     returns int
     */
    @WebMethod
    @WebResult(targetNamespace = "")
    @RequestWrapper(localName = "add", targetNamespace = "http://web/", className = "web.Add")
    @ResponseWrapper(localName = "addResponse", targetNamespace = "http://web/", className = "web.AddResponse")
    @Action(input = "http://web/Calculator/addRequest", output = "http://web/Calculator/addResponse")
    public int add(
        @WebParam(name = "arg0", targetNamespace = "")
        Calc arg0);

}
  • Calc.java
package web;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlType;


/**
 * <p>Java class for calc complex type.
 * 
 * <p>The following schema fragment specifies the expected content contained within this class.
 * 
 * <pre>
 * &lt;complexType name="calc">
 *   &lt;complexContent>
 *     &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
 *       &lt;sequence>
 *         &lt;element name="num1" type="{http://www.w3.org/2001/XMLSchema}int"/>
 *         &lt;element name="num2" type="{http://www.w3.org/2001/XMLSchema}int"/>
 *       &lt;/sequence>
 *     &lt;/restriction>
 *   &lt;/complexContent>
 * &lt;/complexType>
 * </pre>
 * 
 * 
 */
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "calc", propOrder = {
    "num1",
    "num2"
})
public class Calc {

    protected int num1;
    protected int num2;

    /**
     * Gets the value of the num1 property.
     * 
     */
    public int getNum1() {
        return num1;
    }

    /**
     * Sets the value of the num1 property.
     * 
     */
    public void setNum1(int value) {
        this.num1 = value;
    }

    /**
     * Gets the value of the num2 property.
     * 
     */
    public int getNum2() {
        return num2;
    }

    /**
     * Sets the value of the num2 property.
     * 
     */
    public void setNum2(int value) {
        this.num2 = value;
    }

}

Webサービス公開時には作っていなかったクラスの一つとして、CalculatorServiceというクラスができている。これは公開した<サービス名> + Serviceという名前になっていて、Webサービスに対するエンドポイントを保持している。実際の使い方としては、このCalcuratorServiceクラスからCalcuratorオブジェクトを取得してサービスを呼び出すことになる。

  • CalculatorService.java
package web;

import java.net.MalformedURLException;
import java.net.URL;
import javax.xml.namespace.QName;
import javax.xml.ws.Service;
import javax.xml.ws.WebEndpoint;
import javax.xml.ws.WebServiceClient;
import javax.xml.ws.WebServiceException;
import javax.xml.ws.WebServiceFeature;


/**
 * This class was generated by the JAX-WS RI.
 * JAX-WS RI 2.2.10
 * Generated source version: 2.2
 * 
 */
@WebServiceClient(name = "CalculatorService", targetNamespace = "http://web/", wsdlLocation = "http://vmora7:7001/SoapApp/CalculatorService?wsdl")
public class CalculatorService
    extends Service
{

    private final static URL CALCULATORSERVICE_WSDL_LOCATION;
    private final static WebServiceException CALCULATORSERVICE_EXCEPTION;
    private final static QName CALCULATORSERVICE_QNAME = new QName("http://web/", "CalculatorService");

    static {
        URL url = null;
        WebServiceException e = null;
        try {
            url = new URL("http://vmora7:7001/SoapApp/CalculatorService?wsdl");
        } catch (MalformedURLException ex) {
            e = new WebServiceException(ex);
        }
        CALCULATORSERVICE_WSDL_LOCATION = url;
        CALCULATORSERVICE_EXCEPTION = e;
    }

    public CalculatorService() {
        super(__getWsdlLocation(), CALCULATORSERVICE_QNAME);
    }

    public CalculatorService(WebServiceFeature... features) {
        super(__getWsdlLocation(), CALCULATORSERVICE_QNAME, features);
    }

    public CalculatorService(URL wsdlLocation) {
        super(wsdlLocation, CALCULATORSERVICE_QNAME);
    }

    public CalculatorService(URL wsdlLocation, WebServiceFeature... features) {
        super(wsdlLocation, CALCULATORSERVICE_QNAME, features);
    }

    public CalculatorService(URL wsdlLocation, QName serviceName) {
        super(wsdlLocation, serviceName);
    }

    public CalculatorService(URL wsdlLocation, QName serviceName, WebServiceFeature... features) {
        super(wsdlLocation, serviceName, features);
    }

    /**
     * 
     * @return
     *     returns Calculator
     */
    @WebEndpoint(name = "CalculatorPort")
    public Calculator getCalculatorPort() {
        return super.getPort(new QName("http://web/", "CalculatorPort"), Calculator.class);
    }

    /**
     * 
     * @param features
     *     A list of {@link javax.xml.ws.WebServiceFeature} to configure on the proxy.  Supported features not in the <code>features</code> parameter will have their default values.
     * @return
     *     returns Calculator
     */
    @WebEndpoint(name = "CalculatorPort")
    public Calculator getCalculatorPort(WebServiceFeature... features) {
        return super.getPort(new QName("http://web/", "CalculatorPort"), Calculator.class, features);
    }

    private static URL __getWsdlLocation() {
        if (CALCULATORSERVICE_EXCEPTION!= null) {
            throw CALCULATORSERVICE_EXCEPTION;
        }
        return CALCULATORSERVICE_WSDL_LOCATION;
    }

}

生成したコードを使ってSOAPサービスを呼び出す

生成されたコードを使ってSOAPサービスを呼び出してみる。以下のようなmainメソッドを持つクラスを作成し、実行する。

  • Main.java
package client;

import web.Calc;
import web.Calculator;
import web.CalculatorService;

public class Main {
	public static void main(String[] args) {
		CalculatorService service = new CalculatorService();
		Calculator calculator = service.getCalculatorPort();
		Calc calc = new Calc();
		calc.setNum1(7);
		calc.setNum2(3);
		int addResult = calculator.add(calc);
		int subResult = calculator.sub(calc);
		
		System.out.println("add=" + addResult);
		System.out.println("sub=" + subResult);
	}

}
  • 実行結果
add=10
sub=4

正しく計算結果が取得できている!これが、ローカルで計算しているのではなく、間違いなくWebサービスに問い合わせていることを確認するために、アプリケーションを停止して、Webサービスを止めた状態で再度実行してみる。

Exception in thread "main" javax.xml.ws.WebServiceException: Failed to access the WSDL at: http://vmora7:7001/SoapApp/CalculatorService?wsdl. It failed with: 
	Got Operation timed out while opening stream from http://vmora7:7001/SoapApp/CalculatorService?wsdl.
	at com.sun.xml.internal.ws.wsdl.parser.RuntimeWSDLParser.tryWithMex(RuntimeWSDLParser.java:173)
	at com.sun.xml.internal.ws.wsdl.parser.RuntimeWSDLParser.parse(RuntimeWSDLParser.java:155)
	at com.sun.xml.internal.ws.wsdl.parser.RuntimeWSDLParser.parse(RuntimeWSDLParser.java:120)
	at com.sun.xml.internal.ws.client.WSServiceDelegate.parseWSDL(WSServiceDelegate.java:258)
	at com.sun.xml.internal.ws.client.WSServiceDelegate.<init>(WSServiceDelegate.java:221)
	at com.sun.xml.internal.ws.client.WSServiceDelegate.<init>(WSServiceDelegate.java:169)
	at com.sun.xml.internal.ws.spi.ProviderImpl.createServiceDelegate(ProviderImpl.java:101)
	at javax.xml.ws.Service.<init>(Service.java:77)
	at web.CalculatorService.<init>(CalculatorService.java:42)
	at client.Main.main(Main.java:9)
Caused by: java.io.IOException: Got Operation timed out while opening stream from http://vmora7:7001/SoapApp/CalculatorService?wsdl
	at
	...(略)

今度はWebサービスのエンドポイントに接続できずにエラーとなった。このことから、先ほどは正しくWebサービスを呼び出しているよう!

まとめ

前回に簡単なクラスからWebサービスを公開し、今回はMetroというツールを用いてそのWebサービスのWSDLのみからクライアント側のソースコードを簡単に作成できることを検証した。WSDLやSOAPの仕様は複雑だが、複雑な部分はアノテーションや周辺ツール、サーバ側でいいかんじに隠蔽されており、使う分には割とシンプルに扱えることが分かった!

以上。