Spring WS(WebService)でSOAPリクエストを受け取るための設定


Spring WS (WebService)を使ったSOAPで死ぬほどハマったのでまとめておきます。

----

Spring WS (2.2.2)でSOAPリクエストを受け取る環境をつくる。
以下のような構成の既存WEBアプリケーションへの組込みです。

- Spring Framework 3.2.8
- Spring MVC
- Java7
- Tomcat7


### pom.xml

依存関係を追加

```xml
<dependency>
    <groupId>org.springframework.ws</groupId>
    <artifactId>spring-ws-core</artifactId>
    <version>2.2.2.RELEASE</version>
</dependency>
```

### web.xml 

`{context-root}/ws/*` のアクセスがMessageDispatcherServletに流れるようになる。

```xml
<servlet>
  <servlet-name>spring-ws</servlet-name>
  <servlet-class>org.springframework.ws.transport.http.MessageDispatcherServlet</servlet-class>
  <init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/META-INF/spring-ws-servlet.xml</param-value>
  </init-param>
  <init-param>
    <param-name>transformWsdlLocations</param-name>
    <param-value>true</param-value>
  </init-param>
  <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
  <servlet-name>spring-ws</servlet-name>
  <url-pattern>/ws/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
  <servlet-name>spring-ws</servlet-name>
  <url-pattern>*.wsdl</url-pattern>
</servlet-mapping>
```


### spring-ws-servlet.xml

WEB-INF配下にwsdlファイルを配備しておく

```xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:sws="http://www.springframework.org/schema/web-services"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                      http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
                      http://www.springframework.org/schema/web-services
                      http://www.springframework.org/schema/web-services/web-services-2.0.xsd
                      http://www.springframework.org/schema/context
                      http://www.springframework.org/schema/context/spring-context-3.2.xsd">

  <context:component-scan base-package="jp.xxx.*" />
  <sws:annotation-driven />
  <sws:static-wsdl id="hoge" location="/WEB-INF/hoge.wsdl"/>
</beans>
```

### wsdl/xsdからjavaクラスを生成

wsdl/xsdが用意できる場合は便利。

mavenを利用し、pom.xml に以下を記述する。

- wsdlの場合

   WEB-INF配下のwsdlファイル参照を想定

```xml
<plugin>
  <groupId>org.codehaus.mojo</groupId>
  <artifactId>jaxb2-maven-plugin</artifactId>
  <version>1.6</version>
  <executions>
    <execution>
      <id>xjc</id>
      <goals>
        <goal>xjc</goal>
      </goals>
    </execution>
  <configuration>
  <wsdl>true</wsdl>
  <xmlschema>false</xmlschema>
  <schemaDirectory>${basedir}/src/main/webapp/WEB-INF</schemaDirectory>
  <schemaFiles>hoge.wsdl</schemaFiles>
  <outputDirectory>${basedir}/src/main/java</outputDirectory>
  <clearOutputDir>false</clearOutputDir>
  </configuration>
</plugin>
```

- xsdの場合

  resource配下のxsdファイルの参照を想定

```xml
<plugin>
  <groupId>org.codehaus.mojo</groupId>
  <artifactId>jaxb2-maven-plugin</artifactId>
  <version>1.6</version>
  <executions>
    <execution>
      <id>xjc</id>
      <goals>
        <goal>xjc</goal>
      </goals>
    </execution>
    <configuration>
      <schemaDirectory>${basedir}/src/main/resource/xsd</schemaDirectory>
      <outputDirectory>${basedir}/src/main/java</outputDirectory>
      <clearOutputDir>false</clearOutputDir>
    </configuration>
</plugin>
```

mavenでタスク `clean jaxb2:xjc` を実行すると、wsdl/xsd の記述に基づいて
`${basedir}/src/main/java`配下にjavaソースが出力される。

（IDEの機能でも同じことができるかも。）


### Endpointの作成

spring-ws-servlet.xmlに記載した
component-scanの対象パッケージに作成する。
HogeRequest/HogeResponseが上述のmavenタスクで自動生成されたクラス。

```java
@Endpoint
public class HogeEndPoint {
	private static final String NAMESPACE_URI = "http://example.com/"; // wsdlのtargetNamespaceとあわせる

	@PayloadRoot(localPart = "Hoge", namespace = NAMESPACE_URI)
	@ResponsePayload
	public JAXBElement<HogeResponse> hoge(@RequestPayload JAXBElement<HogeRequest> request) {
		return new JAXBElement<>(
				new QName(NAMESPACE_URI, "HogeResponse"),
				HogeResponse.class,
				new HogeResponse()
		);
	}
}
```


### @RequestPayload 内のフィールドをunmarshal

@RequestPayloadで `xs:anyType` を Object型で受け取った場合、
そこからさらにunmarshalする場合は以下手順でいけた。

```java
Node node = (Node)request.getValue().getPiyo();

JAXBContext jaxb = JAXBContext.newInstance(Piyo.class);
JAXBElement<Piyo> piyo = jaxb.createBinder().unmarshal(node.getFirstChild(), Piyo.class);
```

### ログ出力

logbackを利用していますが、
ログの出力レベルをtraceまで許可してやると、

- どこまでSOAPリクエストが届いてるのか？
- EndPointがちゃんとマッピングされてるのか？

などの情報も確認できるため、トラぶったときにはオススメ。

---

<br>

まとめてみると大した記述量でもないですね・・・

ほとんどRESTでしか通信することが無かったので、
SOAP自体への理解度が低いということに気付いた今日この頃。