wsdl 1.1 サポート
ドットコムブームのさなか猫も杓子も WSDL で記述されたSOAP サービスの一つや二つは書いていたのではないか。この WSDL ドキュメントを詳しくみてみると、実はメッセージのレイアウトを記述した XML Schema ドキュメントが埋めこまれており、それが結構な部分を占めいていることが分かる。残りは些細なものだ。scalaxb は XML Schema 部分は処理できるため、WSDL をサポートし始めるのは、時間の問題だったと言えるだろう。
使用例
- wsdl ドキュメントをローカル環境にダウンロードする。
- 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 にツイートすると答えるかも。