ant の scriptdef タスクがすごい

ant の scriptdef タスクを使うとタスクの実装を JavaScript で書くことができます。 それを使ってとある Web サービスを使う build.xml を書いてみました。

背景

mBaaS に分類される Kii Cloud というサービス を使うにあたって、ちょっとしたツールが必要になりました。テストに使うユーザを作成したり、テストが終わったら消したりするツールです。それらの操作には REST API を叩く必要があります。もちろん Kii Cloud には Android用とかiOS用のライブラリが提供されているのですが、今はテスト用ツールということでそれらのライブラリを作ってアプリを作るよりは、直接 REST API を叩いてしまったほうが良いという状況でした。

しかしそのために Python や Ruby なんかでスクリプトを書いても、このツールを使う人にそれらの言語をインストールさせるのも手間だなぁ、と。最近の開発者には OSX が人気で Python も Ruby でデフォルトで使えたりしますが Windows を使ってる人もまだまだ結構いるのです。

で、気がつきました。 Apache Ant だけでなんとかする方法はないんだろうかと。いま必要なツールは Android 向けのサンプルアプリに付属させるセットアップ用です。ですから ant がインストールされていると仮定するのは、悪い考えではなさそうです。

問題点と解決の糸口

調べてみると ant で REST API を叩くにあたって問題点は2つありました。

  • HTTP の POST メソッドを(antだけで)叩く方法が無い
  • JSON を解釈する(簡単な)方法が無い

しかしさらに調査してみるとどちらも scriptdef タスクを使うことで解決できそうなことが判明しました。

ant というのはタスクとタスク間の依存性を記述することで特定の手続きを表現します。この方法は ant の build.xml を少し書いたことがある人にならわかってもらえると思いますが、宣言的な書き方・考え方には適しているのですが逐次的な考え方にはとことん向いていません。そのあたりが原因の1つとなり maven に取って代わられ、更に gradle へと Java のビルド環境が変遷していくことになったと言っても過言ではないでしょう。

さて scriptdef というのはこの ant のタスクを JavaScript や他のスクリプト言語で書けるようにするものです。これにより逐次的な処理を記述しやすくなります。加えてそのスクリプト言語からは Java の豊富なクラスライブラリを利用できます。とうぜん POST アクセスが可能なクラスも存在します。しかも JavaScript であればダックタイピング的に開発でき、JSONの解釈もお手のものです。

コード解説

では 実際に書いたコード から、サンプルを抜き出して解説していきましょう。

build.xml

まずは build.xml のほうから scriptdef の定義 です。

<scriptdef name="kii-cloud" language="javascript" src="kii-cloud-ant.js">
  <attribute name="cmd"/>
  <element name="site" type="string" />
  <element name="app_id" type="string" />
  <element name="app_key" type="string" />
  <element name="username" type="string" />
  <element name="password" type="string" />
</scriptdef>

この要素は kii-cloud という新しいタスクを定義しています。タスクの実体、JavaScript は kii-cloud-ant.js というファイルに格納されています。こちらはあとで解説します。

attribute 要素は、この kii-cloud タスクには属性 cmd を指定できるという宣言になっています。なお JavaScript から属性の値を取得するには次のようなコードになります。

var value = attributes.get('cmd');

element 要素は kii-cloud タスク(要素)の内側の要素の宣言になっています。

<element name="app_id" type="string" />

という宣言は

<kii-cloud>
  <app_id>foobar</app_id>
</kii-cloud>

このように書けるようにし、しかも app_id 要素の値に

var appId = elements.get('app_id').get(0);

こんな風に JavaScript からアクセスできるようにしてくれます。

実際に kii-cloud タスクを使う時には login-user ターゲット にあるように

<kii-cloud cmd="loginUser">
  <site>${site}</site>
  <app_id>${app.id}</app_id>
  <app_key>${app.key}</app_key>
  <username>${username}</username>
  <password>${password}</password>
</kii-cloud>

プロパティ展開などが利用できます。

JavaScript側 (kii-cloud-ant.js)

次に JavaScript の方を見て行きましょう。

まずは POST を投げているところ を要約すると

function http2(url, headers, data) {
  var conn = new URL(url).openConnection();
  for (var name in headers) {
    conn.setRequestProperty(name, headers[name]);
  }
  conn.setDoOutput(true);
  conn.setRequestMethod('POST');
  var w = new OutputStreamWriter(conn.getOutputStream(), 'UTF-8');
  w.write(data);
  conn.connect();
  return conn;
}

こうなっています。Java ならば URL#openConnection() が返す型は URLConnection であるため、 HttpURLConnection#setRequestMethod() を呼び出すためにはダウンキャストが必要です。しかし JavaScript ですから、そんなのはお構いなしに var で setRequestMethod() を呼べてしまうあたり、とても興味深いです。

あ、このコードを正しく動かすためには先頭部分

importClass(java.io.OutputStreamWriter);
importClass(java.net.URL);

importClass を使って OutputStreamWriter と URL の両クラスを使える状態にしとかなければなりません。

また JSON を解釈する部分

function parseAsJson(stream) {
  var s = readAll(stream);
  return JSON.parse(s);
}

JSON.parse() を使ってスッキリですね。 org.json.JSONObject を使う必要すらありません。

結論

ant の scriptdef タスクを使うことで ant だけでWebサービスの REST API を利用できました。それはこの組み合わせの以下の性質によるものです。

  • スクリプト言語から Java の豊富なクラスライブラリにアクセスできる
  • スクリプト言語から build.xml の情報にアクセスできる
  • スクリプト言語では Java よりもダックタイピング的に気軽に書ける
  • スクリプト言語では build.xml よりも逐次的な処理を記述しやすい

ant は廃れゆく技術かなぁなんて考えていましたが、これは非常に有用、ant が大化けする可能性を感じさせてくれる使い方でした。

宣伝

ところで本記事で最初に少し触れた Kii Cloud (mBaaS) なんですが、モバイルに特化したバックエンドサービスです。サーバ側のコードを一切書かない&メンテしないでもオンライン機能をアプリに実装し利用できるという気の利いたモノです。この手のサービスによくあるように規模が十分に小さいアプリでなら無料で利用できるので、ちょっとしたオンラインアプリを書くときとかにはとても便利です。機会があったら使ってみてください。