wsdl 1.1 サポート

ドットコムブームのさなか猫も杓子も WSDL で記述されたSOAP サービスの一つや二つは書いていたのではないか。この WSDL ドキュメントを詳しくみてみると、実はメッセージのレイアウトを記述した XML Schema ドキュメントが埋めこまれており、それが結構な部分を占めいていることが分かる。残りは些細なものだ。scalaxb は XML Schema 部分は処理できるため、WSDL をサポートし始めるのは、時間の問題だったと言えるだろう。

使用例

  1. wsdl ドキュメントをローカル環境にダウンロードする。
  2. sbt-scalaxb を使っている場合はそれを src/main/wsdl に置く。

サンプルの設定例はこんな感じ:

import ScalaxbKeys._
 
val scalaXml = "org.scala-lang.modules" %% "scala-xml" % "1.0.2"
val scalaParser = "org.scala-lang.modules" %% "scala-parser-combinators" % "1.0.1"
val dispatchV = "0.11.1" // change this to appropriate dispatch version
val dispatch = "net.databinder.dispatch" %% "dispatch-core" % dispatchV
 
organization := "com.example"
 
name := "scalaxb-stockquote-sample"
 
scalaVersion := "2.11.1"
 
scalaxbSettings
 
packageName in (Compile, scalaxb) := "stockquote"
 
dispatchVersion in (Compile, scalaxb) := dispatchV
 
async in (Compile, scalaxb) := true
 
sourceGenerators in Compile <+= scalaxb in Compile
 
libraryDependencies ++= Seq(scalaXml, scalaParser, dispatch)

以下の9ファイルが生成される:

  • scalaxb/httpclients_async.scala
  • scalaxb/httpclients_dispatch_async.scala
  • scalaxb/scalaxb.scala
  • scalaxb/soap12_async.scala
  • soapenvelope12/soapenvelope12.scala
  • soapenvelope12/soapenvelope12_xmlprotocol.scala
  • stockquote/stockquote.scala
  • stockquote/stockquote_type1.scala
  • stockquote/xmlprotocol.scala

コード例:

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent._
import scala.concurrent.duration._
 
val service = (new stockquote.StockQuoteSoap12Bindings with
  scalaxb.SoapClientsAsync with
  scalaxb.DispatchHttpClientsAsync {}).service
val fresponse = service.getQuote(Some("GOOG"))
val response = Await.result(fresponse, 5 seconds)
println(response)

なにそれ?

その前に少し断っておきたいのは、ここではCake パターンが使われているので、何かよく分からない場合はリンク先を読んでほしい。

以下で実際に生成されたコードをみていこう。まずは、stockquote.scala:

// Generated by <a href="http://scalaxb.org/">scalaxb</a>.
package stockquote
 
import scala.concurrent.Future
 
trait StockQuoteSoap {
  def getQuote(symbol: Option[String]): Future[stockquote.GetQuoteResponse]
}

これは web サービスのインターフェイス部分を定義し、XML や SOAP といった実装から抽象化されている。次。

長いので、xmlprotocol.scala を全引用はしないが、XML の case class へのパーシング、および wsdl の場合は以下のような SOAP 1.2 バインディングを実装する:

  trait StockQuoteSoap12Bindings { this: scalaxb.SoapClientsAsync =>
    lazy val targetNamespace: Option[String] = Some("http://www.webserviceX.NET/")
    lazy val service: stockquote.StockQuoteSoap = new StockQuoteSoap12Binding {}
    def baseAddress = new java.net.URI("http://www.webservicex.net/stockquote.asmx")
 
    trait StockQuoteSoap12Binding extends stockquote.StockQuoteSoap {
      import scalaxb.ElemName._
      def getQuote(symbol: Option[String]): Future[stockquote.GetQuoteResponse] = 
        soapClient.requestResponse(scalaxb.toXML(stockquote.GetQuote(symbol), Some("http://www.webserviceX.NET/"), "GetQuote", defaultScope),
            Nil, defaultScope, baseAddress, "POST", Some(new java.net.URI("http://www.webserviceX.NET/GetQuote"))).transform({ case (header, body) => 
            scalaxb.fromXML[stockquote.GetQuoteResponse]((body.headOption getOrElse {body}), Nil) }, {
              case x: scalaxb.Fault[_] => x
              case x => x
            })
    }
  }

Cake パターンでは、StockQuoteSoap12Bindings はケーキの一切れのようなモジュールを表す。これは、soap12_async.scala 中のもう一つのモジュール scalaxb.SoapClientsAsync に依存していると宣言している。

trait SoapClientsAsync { this: HttpClientsAsync =>
  lazy val soapClient: SoapClientAsync = new SoapClientAsync {}
  def baseAddress: java.net.URI
 
  trait SoapClientAsync {
    implicit lazy val executionContext = scala.concurrent.ExecutionContext.Implicits.global
    import soapenvelope12.{Fault => _, _}
    val SOAP_ENVELOPE_URI = "http://www.w3.org/2003/05/soap-envelope"
 
    def requestResponse(body: scala.xml.NodeSeq, headers: scala.xml.NodeSeq, scope: scala.xml.NamespaceBinding,
                        address: java.net.URI, webMethod: String, action: Option[java.net.URI]):
        Future[(scala.xml.NodeSeq, scala.xml.NodeSeq)] = {
      val bodyRecords = body.toSeq map {DataRecord(None, None, _)}
      val headerOption = headers.toSeq.headOption map { _ =>
        Header(headers.toSeq map {DataRecord(None, None, _)}, Map())
      }
      val envelope = Envelope(headerOption, Body(bodyRecords, Map()), Map())
      buildResponse(soapRequest(Some(envelope), scope, address, webMethod, action))
    }
    ....
  }
}

これは、scalaxb の非同期な SOAP の実装で、内部では、scalaxb が生成した SOAP エンベロープ case class (soapenvelope12.scala と soapenvelope12_xmlprotocol.scala に定義されている)を使っている。しかし、大切なのは、scalaxb.SoapClientsAsync モジュールが 以下のscalaxb.HttpClientsAsync に依存していることだ:

package scalaxb
 
import concurrent.Future
 
trait HttpClientsAsync {
  def httpClient: HttpClient
 
  trait HttpClient {
    def request(in: String, address: java.net.URI, headers: Map[String, String]): Future[String]
  }
}

これは httpClient を実装していないので、抽象 trait となる。最後に、httpclients_dispatch_async.scala。

DispatchHttpClientsAsync

scalaxb.DispatchHttpClientsAsync は上記の scalaxb.HttpClientsAsync モジュールのレファレンス実装だ。

package scalaxb
 
import concurrent.Future
 
trait DispatchHttpClientsAsync extends HttpClientsAsync {
  lazy val httpClient = new DispatchHttpClient {}
 
  trait DispatchHttpClient extends HttpClient {
    import dispatch._, Defaults._
 
    val http = new Http()
    def request(in: String, address: java.net.URI, headers: Map[String, String]): concurrent.Future[String] = {
      val req = url(address.toString).setBodyEncoding("UTF-8") <:< headers << in
      http(req > as.String)
    }
  }
}

scalaxb は Dispatch 0.8、 0.10.x、および 0.11.x をサポートする。Dispatch のバージョンによって生成されるコードが微妙に違うため、dispatchVersion in (Compile, scalaxb) を設定する必要がある。現在のデフォルトは 0.11.1 だけどもこれは将来的に変更される。

val dispatchV = "0.11.1" // change this to appropriate dispatch version
val dispatch = "net.databinder.dispatch" %% "dispatch-core" % dispatchV
 
dispatchVersion in (Compile, scalaxb) := dispatchV
 
async in (Compile, scalaxb) := true // set to false for older version of dispatch
 
libraryDependencies ++= Seq(dispatch, ....)

ケーキを一切れ、二切れ

まとめよう。

  • 生成された stockquote.StockQuoteSoap12Bindings モジュールは ...
  • scalaxb.SoapClientsAsync モジュールに依存し、それは ...
  • scalaxb.HttpClientsAsync モジュールの一実装に依存する (DispatchHttpClientsAsyncを使う)

なんで、そんなヤヤコシイ事?答はモジュール性にある。例えるなら、ブレーキシステムが故障することを恐れずにカーステをアップグレードできるというようなことだ。何らかの理由で独自の http 処理がしたい?独自の HttpClientsAsync モジュールを書けばいい。パスワードを SOAP ヘッダに入れたい?scalaxb.SoapClientsAsync を拡張すればいい。

困ったら

何か質問や意見があれば英語のメーリングリストか、日本語で@scalaxb にツイートすると答えるかも。