spring_logo01

SOAPサーバの作成(Gradle + Spring Boot + JAXB)

本記事で想定するパターンは、すでにSOAP通信を定義したWSDLおよびXSDがある状態。

そこからWSDLオブジェクトのJavaクラスを自動生成し、Spring Bootに組み込みます。
Spring Bootは組み込みTomcatでサーブレットサーバを実現していますので
楽にSoapサーバを構築することができます。

まずは、真っさらなGradleプロジェクトを用意しました。

ここからプロジェクトを編集していきます。

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:1.5.2.RELEASE")
    }
}


// Apply the java plugin to add support for Java
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'org.springframework.boot'
apply plugin: 'application'

task genJaxb {
	// 自動生成ソース出力先
    ext.sourcesDir = "src/main/java"
    ext.schema = "src/main/resources/MashpoteSoapService.xsd"
    doLast() {
       project.ant {
           taskdef name: "xjc", classname: "com.sun.tools.xjc.XJCTask",
                   classpath: configurations.jaxb.asPath
           mkdir(dir: sourcesDir)
           xjc(destdir: sourcesDir, schema: schema) {
               arg(value: "-wsdl")
               produces(dir: sourcesDir, includes: "**/*.java")
           }
        }
    }
}

configurations {
    jaxb
}

jar {
    baseName = 'soap-server'
    version =  '0.1.0'
}

repositories {
    mavenCentral()
}

dependencies {
    //--- JAXB ---//
    compile("org.springframework.boot:spring-boot-starter-web-services")
    testCompile("org.springframework.boot:spring-boot-starter-test")
    compile("wsdl4j:wsdl4j:1.6.1")
    jaxb("org.glassfish.jaxb:jaxb-xjc:2.2.11")
    compile("org.springframework.boot:spring-boot-gradle-plugin:1.5.2.RELEASE")
}

上記のようにbuild.gradleを作成し、
プロジェクトを右クリック > Gradleプロジェクトの更新を実行。
必要なライブラリを読み込みましょう。

次に、/src/main/resources フォルダを作成しWSDLファイル、XSDファイルを配置します。

XSDファイルは以下のように定義されています。

<?xml version="1.0" encoding="UTF-8"?><!--Generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, (build IBM 2.2.3-07/07/2014 12:56 PM(foreman)-) 
See <a href="http://java.sun.com/xml/jaxb">http://java.sun.com/xml/jaxb</a> -->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://soap.test.mashpote.net/" version="1.0" targetNamespace="http://soap.test.mashpote.net/">

  <xs:element name="getStatus" type="tns:getStatus"></xs:element>
  <xs:element name="getStatusResponse" type="tns:getStatusResponse"></xs:element>
  <xs:element name="jobCancel" type="tns:jobCancel"></xs:element>
  <xs:element name="jobCancelResponse" type="tns:jobCancelResponse"></xs:element>

  <xs:complexType name="getStatus">
    <xs:sequence>
      <xs:element name="arg0" type="tns:jobStatusInput" minOccurs="0"></xs:element>
    </xs:sequence>
  </xs:complexType>

  <xs:complexType name="jobStatusInput">
    <xs:sequence>
      <xs:element name="jobId" type="xs:string" minOccurs="0"></xs:element>
    </xs:sequence>
  </xs:complexType>

  <xs:complexType name="getStatusResponse">
    <xs:sequence>
      <xs:element name="return" type="tns:jobStatusOutput" minOccurs="0"></xs:element>
    </xs:sequence>
  </xs:complexType>

  <xs:complexType name="jobStatusOutput">
    <xs:sequence>
      <xs:element name="apiResult" type="xs:string" minOccurs="0"></xs:element>
      <xs:element name="jobSpec_all" type="xs:int"></xs:element>
      <xs:element name="jobSpec_executed" type="xs:int"></xs:element>
      <xs:element name="jobSpec_not_executed" type="xs:int"></xs:element>
    </xs:sequence>
  </xs:complexType>

  <xs:complexType name="jobCancel">
    <xs:sequence>
      <xs:element name="arg0" type="tns:jobCancelInput" minOccurs="0"></xs:element>
    </xs:sequence>
  </xs:complexType>

  <xs:complexType name="jobCancelInput">
    <xs:sequence>
      <xs:element name="jobId" type="xs:string" minOccurs="0"></xs:element>
    </xs:sequence>
  </xs:complexType>

  <xs:complexType name="jobCancelResponse">
    <xs:sequence>
      <xs:element name="return" type="tns:jobCancelOutput" minOccurs="0"></xs:element>
    </xs:sequence>
  </xs:complexType>

  <xs:complexType name="jobCancelOutput">
    <xs:sequence>
      <xs:element name="apiResult" type="xs:string" minOccurs="0"></xs:element>
      <xs:element name="jobId" type="xs:string" minOccurs="0"></xs:element>
      <xs:element name="jobManagementCode" type="xs:string" minOccurs="0"></xs:element>
    </xs:sequence>
  </xs:complexType>

</xs:schema>

さきほどのbuild.gradleの中でこのXSDを参照しています。(下記)

task genJaxb {
// 自動生成ソース出力先
ext.sourcesDir = “src/main/java”
ext.schema = “src/main/resources/MashpoteSoapService.xsd”

作成できたら、build.gradleを右クリックして実行の構成を選択、
GradleのオプションをgenJaxbに指定して実行しましょう。

上記のようにGradleビルドがうまく通ればsrc/main/java配下に
自動生成ソースが出力されます。
※Eclipseプロジェクト上に表示されない方はプロジェクトをリフレッシュしてみてください。

ようやく前準備が整いました。
ではSoapサーバを動かすためのJavaクラスを実装していきます。

まずはSpring Bootの起動クラスとなるApplication.javaを作成します。

package net.mashpote.soapapp.web.server;

import org.springframework.boot.*;
import org.springframework.boot.autoconfigure.*;

@SpringBootApplication
public class Application {

    public static void main(String[] args) throws Exception {
        SpringApplication.run(Application.class, args);
    }
}

さらにWSDL/XSDを読み込むため、以下のようにConfigクラスを作成します。

package net.mashpote.soapapp.web.server;

import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.ws.config.annotation.EnableWs;
import org.springframework.ws.config.annotation.WsConfigurerAdapter;
import org.springframework.ws.transport.http.MessageDispatcherServlet;
import org.springframework.ws.wsdl.wsdl11.DefaultWsdl11Definition;
import org.springframework.ws.wsdl.wsdl11.SimpleWsdl11Definition;
import org.springframework.ws.wsdl.wsdl11.Wsdl11Definition;
import org.springframework.xml.xsd.SimpleXsdSchema;
import org.springframework.xml.xsd.XsdSchema;

@EnableWs
@Configuration
public class WebServiceConfig extends WsConfigurerAdapter {
	@Bean
	public ServletRegistrationBean messageDispatcherServlet(ApplicationContext applicationContext) {
		MessageDispatcherServlet servlet = new MessageDispatcherServlet();
		servlet.setApplicationContext(applicationContext);
		servlet.setTransformWsdlLocations(true);
		return new ServletRegistrationBean(servlet, "/sws/*");
	}

	// XSDファイル利用
	@Bean(name = "MashpoteSoapService")
	public Wsdl11Definition getJobAdministrationService() {
        SimpleWsdl11Definition wsdl11Definition = new SimpleWsdl11Definition();
        wsdl11Definition.setWsdl(new ClassPathResource("MashpoteSoapService.xsd"));
        return wsdl11Definition;
	}

	// WSDLファイル利用
	@Bean(name = "MashpoteSoapServiceWSDL")
    public Wsdl11Definition defaultWsdl11Definition() {
        SimpleWsdl11Definition wsdl11Definition = new SimpleWsdl11Definition();
        wsdl11Definition.setWsdl(new ClassPathResource("MashpoteSoapService.wsdl"));
        return wsdl11Definition;
    }
}

肝心のWSDLファイルを載せていませんでしたね。
こんな感じです。

<?xml version="1.0" encoding="UTF-8"?>
<definitions name="JobManageService" targetNamespace="http://soap.test.mashpote.net/" xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:wsp="http://www.w3.org/ns/ws-policy" xmlns:tns="http://soap.test.mashpote.net/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:wsp1_2="http://schemas.xmlsoap.org/ws/2004/09/policy" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns:wsam="http://www.w3.org/2007/05/addressing/metadata">
  <types>
    <xsd:schema>
      <xsd:import namespace="http://soap.test.mashpote.net/" schemaLocation="/sws/MashpoteSoapService.wsdl"/>
    </xsd:schema>
  </types>
  <message name="getStatus">
    <part name="parameters" element="tns:getStatus">
    </part>
  </message>
  <message name="getStatusResponse">
    <part name="parameters" element="tns:getStatusResponse">
    </part>
  </message>
  <message name="jobCancel">
    <part name="parameters" element="tns:jobCancel">
    </part>
  </message>
  <message name="jobCancelResponse">
    <part name="parameters" element="tns:jobCancelResponse">
    </part>
  </message>
  <portType name="JobManage">
    <operation name="getStatus">
      <input message="tns:getStatus" wsam:Action="http://soap.test.mashpote.net/JobManage/getStatusRequest"/>
      <output message="tns:getStatusResponse" wsam:Action="http://soap.test.mashpote.net/JobManage/getStatusResponse"/>
    </operation>
    <operation name="jobCancel">
      <input message="tns:jobCancel" wsam:Action="http://soap.test.mashpote.net/JobManage/jobCancelRequest"/>
      <output message="tns:jobCancelResponse" wsam:Action="http://soap.test.mashpote.net/JobManage/jobCancelResponse"/>
    </operation>
  </portType>
  <binding name="JobManagePortBinding" type="tns:JobManage">
    <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
    <operation name="getStatus">
      <soap:operation soapAction=""/>
      <input>
        <soap:body use="literal"/>
      </input>
      <output>
        <soap:body use="literal"/>
      </output>
    </operation>
    <operation name="jobCancel">
      <soap:operation soapAction=""/>
      <input>
        <soap:body use="literal"/>
      </input>
      <output>
        <soap:body use="literal"/>
      </output>
    </operation>
  </binding>
  <service name="JobManageService">
    <port name="JobManagePort" binding="tns:JobManagePortBinding">
      <soap:address location="/sws/JobManageService.wsdl"/>
    </port>
  </service>
</definitions>

※schemaLocationのパスがXSDへのリクエストパスになっていることを確認してください。
Soapリクエストの中で内部的にWSDL→XSDへと参照が行われますのでここが間違っているとうまく動きません。

それではApplicaiton.javaを右クリック>Javaアプリケーションとして実行からSpring Bootを起動してみましょう。
デフォルトは8080ポートなので、ポートを変更する場合は適宜Spring Bootの仕様に従ってプロパティファイルなどで対応してください。

配置したXSD、WSDLへブラウザからアクセスしてみます。

①http://localhost:8080/sws/MashpoteSoapService.wsdl

②http://localhost:8080/sws/MashpoteSoapServiceWSDL.wsdl

これでSOAPアクセスの準備が整いました。
次はSOAPリクエストの受け口となるエンドポイントクラスをJavaで実装していきましょう。

package net.mashpote.soapapp.web.server;

import javax.annotation.PostConstruct;
import javax.xml.bind.JAXBElement;
import javax.xml.namespace.QName;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ws.server.endpoint.annotation.Endpoint;
import org.springframework.ws.server.endpoint.annotation.PayloadRoot;
import org.springframework.ws.server.endpoint.annotation.RequestPayload;
import org.springframework.ws.server.endpoint.annotation.ResponsePayload;

import net.mashpote.test.soap.GetStatus;
import net.mashpote.test.soap.GetStatusResponse;

@Endpoint
public class JobManageService
{
	// ロガー
	private static final Logger logger = LoggerFactory.getLogger(JobManageService.class);
	// WSDLのtargetNamespaceと合わせる
	private static final String NAMESPACE_URI = "http://soap.test.mashpote.net/";

	@PostConstruct
    public void postConstruct() {
        logger.info("JobManageService created.");
    }

	// getStatusの部分-> 小文字大文字区別するので注意
	@PayloadRoot(namespace = NAMESPACE_URI, localPart = "getStatus")
	@ResponsePayload
	public JAXBElement<GetStatusResponse> getStatus(@RequestPayload JAXBElement<GetStatus> request)
	{
		logger.info("getStatus : start");
		
		// 入力パラメータ
		GetStatus getStatus = request.getValue();
		
		QName q1 = request.getName();
		
		// レスポンスオブジェクト
		GetStatusResponse response = new GetStatusResponse();
		
		// getStatusResponseの部分-> 小文字大文字区別するので注意
		JAXBElement<GetStatusResponse> res =
			    new JAXBElement(new QName(q1.getNamespaceURI(), "getStatusResponse"), GetStatusResponse.class, response);

		logger.info("getStatus : end");
		
		return res;
	}
}

とりあえず、getStatusリクエストのみ受け口を作成しました。
必要になったら、他のリクエスト分もこのクラスに追加していけます。

さて、ここからは動作テストです。
Soapリクエスト/レスポンスのテストは、HTTPのPOSTリクエストを送る必要があります。
curlコマンドなどで通信データをPostする手段もありますが
Windowsだとちょっと不便。

そこでオススメするのが無料で利用可能なSoapUIというツールです。
https://www.soapui.org/

インストール後、左側パネルのProjectを右クリックから「New Soap Project」でWSDLファイルを読み込みます。

読み込むWSDLファイルはSpring Boot上のファイルと同じですが、WSDLからXSDファイルを参照する等の関係上、うまいことパスを調整してください。

右クリックから上記WSDLをインポート。

Spring Boot起動中にWDSLへのリクエストURLを入力し、その左側の緑色の再生ボタンを押します。

うまくいけば、期待したレスポンスが返ってきます。
サーバ側のテストはこれで容易にできますね♪
期待したレスポンスが返ってこない場合は、画面下のログメニューを利用して状況を細かく確認しながらデバッグしましょう。

とりあえず今回はここまでと。

ソースは以下のGitリポジトリにて公開しておきます。ご参考ください。
https://github.com/Rizworks-Ta-fuji/SoapServer.git

いずれSoapクライアントのJava実装についても書きたいと思います。

RESTに比べてSoapは使う機会が少ないのであまり情報が少ない気がします。
とはいえ、色んなプロジェクトを経験しているとたまに利用する機会が出てきますね。

当方も今回実装にはなかなか戸惑いました。
その一方でSpring Bootの良い勉強になったのでやってみてよかったです♪

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です