SSH してみる Part 2

前回は SSH で サーバに接続して認証を行うところまで実装しましたが 今回はその続き...

今回利用している SSH 用ライブラリの JSch は 結構 多機能で

といったようなことができるらしいのだが ほとんどが コマンドラインで できることばかりなので JavaFX で実装しても 今一 面白みに欠ける...

前々回の Wake on LAN で紹介した SSH 経由でサーバを停止するのなんて まさにそう。
画面上のボタンをポチッと押しても ひっそりとサーバが停止するだけなので かなり寂しい感じ...

と言うことで...
bluepapa32 は考えた。いろいろ考えた。うんと考えた。
それから、とつぜん、bluepapa32 は さけんだ。

そうだ!!
サーバのモニタリングツールを作ろう!!

で...
今回作るものは大体こんな感じ。

  • SSHLinux サーバに接続して vmstat を実行する。
  • vmstat の実行結果を 1秒間隔で取得して CPU の使用率をいい感じで表示する。
いきなり凄いものはできないので 今回はこれだけ...

SSH しか接続できないような環境 (もちろん HTTP は別として....) は割と多い。
そういった環境では どうしても コマンド や テキスト の世界にならざるを得ない。
そんな世界をグラフィカルに変えられるのであれば、それはそれで面白い。

しかも うれしいことに JSch は SOCKS5 にも対応している。(個人的には かなりうれしい)
これなら プロキシ経由でしかアクセスできないようなサーバに対しても 使えるので かなり世界が広がる。

また、この方式は いろいろと応用が利くのもうれしい。
SSH で通信しているものの、リモートサーバのコマンドを実行して結果をテキストで取得する。
これはまさに RPC だ。
SSH で通信していることを除けば WEB サービスと何ら変わりはない。
入出力は全てテキストベースで単純だし、いろいろと応用もできる。
サーバのモニタリング以外にも

等を作っても面白いかもしれない。
JavaFX が得意とする? Chart で表示してあげるだけで えらい人たちは 皆 喜ぶに違いない。

更に HTML5 といったライバルたち?と競合しないのもうれしい。
何かと比較されてダメだしされている JavaFX だが、別に同じ土俵で戦わなくても...
WEB がすべてじゃないよ!!
既存のすばらしい資産を組み合わせて 少し足りない部分を JavaFX で埋め合わせるだけでも十分役に立つ。

と言うことで
実装は次回、興味があれば また...

SSH してみる (パスワード認証編)

前回 サーバを停止する際に利用した SSH がとっても面白そうだったので Wake on LAN は ちょっと置いておいて JavaFXSSH してみることに...

とりあえず、今回は SSH を使う上で絶対に必要になる 認証部分の実装から...
認証方式は 前回同様 一番手っ取り早い パスワード認証 です。
もちろん、今回は ユーザがパスワードを入力できるように ちゃんと認証画面も作ります。
折角なので CSS を使ってこんな感じに。


もちろん、今回も SSH 用のライブラリとして JSch を使っています。
JSch は EclipseNetBeans でも使われており GUI アプリケーションとしても実績があるようなので ちょっと安心ですね。

早速 画面デザインをいい感じで作成しましょう。
当然、今回も NetBeans JavaFX Composer のお世話になることに...
今回は次のように4つの状態を作成しました。

init : 初期状態 (何も表示しない)

auth : 認証要求 (認証ダイアログを表示)

login : 認証中 (プログレスインジケータを表示)

retry : 認証再要求 (ユーザ名変更不可の認証ダイアログを表示)

状態毎に作成した紙芝居だけでも それらしく見えてしまうからすごい。
前はこれをコードだけで作成していたから結構大変だったけど 今じゃ 何の説明もなくても これぐらいなら 5分もかからない...
これも NetBeans JavaFX Composer のおかげですね。

デザインができたところで いよいよ 今回のメインである SSH の認証処理の実装です。
まずは ユーザからの入力を JSch の認証処理に渡す部分を実装します。
それを実装するのが UserInfo インターフェースです。
今回の実装は パスワード認証だけなので

  • promptPassword(String): Boolean
  • getPassword(): String
だけを実装すればよいです。
実装はこんな感じ...
class MyUserInfo extends UserInfo {

    override function promptPassword(message : String) : Boolean {

        // 認証ダイアログでクリックされたボタンの値
        selectedValue = -1;    // OK: 1  キャンセル: 0

        // 認証ダイアログ 表示
        currentState.actual = currentState.findIndex("retry");

        // 認証ダイアログのボタンがクリックされるまで待機
        while (true) {
            Thread.sleep(100);
            if (selectedValue != -1) break;
        }

        // OK ボタンが押されたら true
        return selectedValue == 1;
    }

  // ユーザ名 テキストボックスの値を返す。
    function getUsername(): String { 
        usernameTextBox.text;
    }

    // パスワードボックスの値を返す。
    override function getPassword () : String {
        passwordBox.text;
    }

    // 今回は利用しない
    override function promptYesNo(message: String): Boolean { true; }
    override function showMessage(message: String): Void {}
    override function getPassphrase(): String { null; }
    override function promptPassphrase(message: String): Boolean { true; }
}

promptPassword(String): Boolean は 認証が失敗してもう一度 認証する際に呼び出されます。
このメソッドの戻り値により 認証するかキャンセルするかが決まります。

  • true なら getPassword() でパスワードを取得して認証。
  • false なら 認証処理はキャンセル。
今回は 状態を "retry" に変更して ボタンが押されるまで待機しているだけです。

次は 上記の UserInfo の実装を使って『SSH のセッションを取得してサーバに接続する』 処理の実装です。

// Generated Code


def host = "192.168.188.128";
def port = 22;

def jsch     = new JSch();
def userInfo = new MyUserInfo();

// 認証ダイアログでクリックされたボタンの値
var selectedValue = -1;     // OK: 1  キャンセル: 0

// 非同期で SSH のセッションを取得するためのタスク
ActionTask {
    action: function() {

        // 認証ダイアログ表示
        currentState.actual = currentState.findIndex("auth");

        // 認証ダイアログのボタンがクリックされるまで待機
        while (true) {
            Thread.sleep(100);
            if (selectedValue != -1) break;
        }

        // OK でなければ終了
        if (selectedValue != 1) {
            throw new JSchException("Auth cancel");
        }

        // セッション取得
        def session = jsch.getSession(userInfo.getUsername(), host, port);
        session.setConfig("StrictHostKeyChecking", "no");
        session.setPassword(userInfo.getPassword());
        session.setUserInfo(userInfo);

        // アプリケーション終了時にセッションを切断
        FX.addShutdownAction(function(): Void {
            session.disconnect();
        });

        // サーバに接続
        session.connect(5000);

        // 初期画面 表示
        currentState.actual = currentState.findIndex("init");
        Alert.inform("Auth success");
    }
    onException: function(e) {
        currentState.actual = currentState.findIndex("init");
        Alert.inform(e.getMessage());
    }
}.start();

前回は何も考えずに EDT (Event Dispatch Thread) で処理していましたが、 もちろん 非同期で処理しないと画面がフリーズします。

と言うことで...
今回は過去に実装した ActionTask を使って 非同期で処理することに...
ActionTask は JavaFX でとっても簡単に非同期処理を実現するための自作クラスです。
詳細については 過去の記事を参照してみてください。

SSH のセッションを取得してサーバに接続する処理は ActionTask の action 関数に実装しています。
画面の状態を遷移させるための実装を除けば 前回とほとんど同じです。
やっていることはとっても簡単で コード中にも書いてありますが

  • 状態を "auth" にして 認証ダイアログを表示する。
  • 認証ダイアログのボタンがクリックされるまで ひたすら待つ。
  • OK ボタンがクリックされたら セッションを取得してサーバに接続する。
  • 接続が成功したら 状態を "init" に変更してダイアログを表示する。
とたったこれだけです。

後は、認証ダイアログのボタンがクリックされた際に実行されるアクションを実装するだけ...
アクションといっても

  • OK ボタンがクリックされた時は "login" 状態へ
  • キャンセルボタンがクリックされた時は "init" 状態へ
それぞれ変更するだけなのです。
function loginButtonAction(): Void {
    selectedValue = 1;
    currentState.actual = currentState.findIndex("login");
}

function cancelButtonAction(): Void {
    selectedValue = 0;
    currentState.actual = currentState.findIndex("init");
}

今回のサンプルアプリケーションは SSH サーバを設定する機能がないため (コード直書きのため) 公開しても実行できないので とりあえず今回は説明だけ...

Wake on LAN してみる Part 3 (shutdown)

前回までで、とりあえず リモートで PC を起動できるようにはなったのだが...
今更ながら気づく。リモートで起動できるのに リモートでは停止できないことに...
これでは 必要な時だけサーバを起動しておくという当初の目的が達成できない。

もちろん、リモートログインすれば PC を停止できる。
でも、PC を停止するためだけに 毎回 リモートログインして shutdown コマンドを実行するのも面倒...
特に WindowsSSH クライアントが標準装備されていないし...

と言うことで...
今回は リモートで Linux サーバを停止するための処理を JavaFX で実装してみた。
ちなみに 今回 対象とする Linux ディストリビューションUbuntu だけ...
他のディストリビューションでも停止できるかどうかは不明。

JavaFX で ネットワーク上の Linux サーバを停止するのもそれほど難しくはない。
要は自分自身が行っている

  1. Linux サーバにリモートログインする。
  2. root 権限で shutdown コマンドを実行する。
といった手続きをプログラムするだけ...
当然 リモートログインは SSH で行います。
もちろん Telnet でもできますが 今時 Telnet でログインできるサーバなんてありえない...

と言うことで、
JavaFXSSH してみることに...

残念ながら Java の標準APISSH をサポートしていないので 別途ライブラリが必要になる。
SSH 用のライブラリはいくつかあるようだが 今回は 一番メジャーと思われる JSch を使うことにした。
まず、jsch-x.y.z.jar をダウンロードして クラスパスに追加しておく。

後は 以下のように実装するだけ...
例のごとく 例外処理等は全く考慮していません。

import java.io.ByteArrayInputStream;
import java.lang.*;

import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.JSch;

// サーバ情報
def host = "192.168.1.1";
def port = 22;

// ユーザ情報
def username = "hoge";
def password = "pass";

// shutdown コマンド
def command = "sudo -S -p '' /sbin/shutdown -h now";

def jsch = new JSch();
def session = jsch.getSession(username, host, port);

try {

    // SchException: UnknownHostKey を防ぐための設定
    session.setConfig("StrictHostKeyChecking", "no");
    session.setPassword(password);
    session.connect(5000);

    var channel = session.openChannel("exec") as ChannelExec;

    try {
        channel.setCommand(command);

        channel.setInputStream(new ByteArrayInputStream("{password}\n".getBytes()));
        channel.setOutputStream(System.out);
        channel.setErrStream(System.err);
        channel.connect();

        // コマンドが終了するまで待機
        while (not channel.isClosed()) {
            Thread.sleep(1000);
        }

    } finally {
        channel.disconnect();
    }

} finally {
    session.disconnect();
}

JSch の使い方は Examples を参照すれば大体わかるはず...

今回の実装は

echo "pass" | ssh hoge@192.168.1.1 "sudo -S -p '' /sbin/shutdown -h now"
のように ssh コマンドを実行しているのとほぼ同じです。
ちなみに、上記のコマンドは リモートサーバ上で sudo して root 権限で shutdown コマンドを実行しています。

通常 sudo コマンドを実行するとパスワードを要求してきますが、"-S" オプションを付けることで 標準入力からパスワードを渡すことができるので、 『echo "pass"』で出力したパスワードをパイプ ( | ) で渡しているのです。
で、プログラムでは このパスワードの渡し方を

channel.setInputStream(new ByteArrayInputStream("{password}\n".getBytes()));
で実現しています。
パスワードの最後に改行コードを付けるのを忘れないように注意しましょう。
この辺りの話は 『sudo をプログラムで使う際の注意点』 にまとめてありますのでよかったらどうぞ...

以上のように JavaFXSSH を利用するのは とても簡単です。
SSH には この他にも まだまだ いろいろな使い道がありそうですね。
サーバを操作したり、サーバの情報を参照したりする ちょっとしたクライアントツールが簡単にできちゃいます。

今回のサンプルも まだ画面がないので はっきり言えば JavaFX で実装する意味はほとんどないですが...
一応、コマンドラインから

javafxc -classpath jsch-x.y.z.jar Main.fx
javafx -classpath .:jsch-x.y.z.jar Main
と入力すれば実行できます。興味のある方は ぜひ試してみてください。

Wake on LAN してみる Part 2

今回も前回に引き続き Wake on LAN について...

前回は Wake on LAN で送信するマジックパケットJavaFX で表現してみましたが...
今回は 実際に Wake on LAN 対応の PC を起動するための処理を JavaFX で実装してみました。

Wake on LAN 対応の PC を起動するには

マジックパケット (前回の記事参照) を任意のポートにブロードキャストする
だけでよいので とっても簡単!! 実装もたったこれだけ...
もちろん、いつもの通り 例外処理等は全く考慮していません。

ちなみに 今回は 同じネットワーク内の PC だけを対象としているため ブロードキャストアドレスを 255.255.255.255 としています。
別のネットワークにブロードキャストする場合は 適宜ブロードキャストアドレスを設定すればよいはずです。

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.lang.Integer.parseInt;

// 起動する PC の NIC の MAC アドレス
def macAddress = "aa:bb:cc:dd:ee:ff";

// マジックパケット
def magicPacket = [ 
    for(i in [1..6]) { 0xff },
    for(i in [1..16], b in macAddress.split(":")) { parseInt(b, 16) } 
];

// 同一ネットワークへのブロードキャストアドレス (255.255.255.255)
def broadcastAddress = InetAddress.getByAddress([255, 255, 255, 255]);

// マジックパケットを任意のポートにブロードキャストするための UDPデータグラムパケット
def packet = new DatagramPacket(magicPacket, sizeof magicPacket, broadcastAddress, 5555);

// マジックパケット 送信
def socket = new DatagramSocket();
socket.send(packet);

UDP 通信するためのプログラムとしては 特筆するべき点もありませんが...
強いて書くなら JavaFX の特徴として 配列とシーケンスは同等で扱えるということぐらいでしょうか。

ブロードキャストアドレスを取得するための
InetAddress.getByAddress(byte[] b)

や DatagramPacket のコンストラク

DatagramPacket(byte[] b, int length, InetAddress address, int port)
は 引数に byte の配列を指定します。ところが JavaFX には 配列がない...
でっ... その代わりとしてシーケンスというデータ型が存在する訳なのです。
シーケンスは配列とは全く別ものなのですが、配列としても使える不思議なデータ型です。
その為、配列の引数に 何も考えず 指定できるのです。

このプログラムは まだ画面がないので はっきり言ってしまえば JavaFX で実装する意味はほとんどないです。
一応、コマンドラインから

javafxc Main.fx
javafx Main
と入力すれば実行できます。

Wake on LAN してみる

MacBook Pro を新調したことを機に 古い ノート PC に Linux を入れて Time Machine 用サーバとして使用することにしたのですが...
一日のほとんどを会社で過ごしているにも関わらず 常時 自宅で PC を立ち上げておくのも 何なので WOL (Wake on LAN) を使って 必要なときだけ リモートで起動することにしにてみた。

WOL を使って PC を起動するための アプリケーションは 世の中にいろいろとありますが、 WOL 自体 とっても簡単な仕様なので この機会に勉強も兼ねて JavaFX で自作することにしてみました。

WOL の仕組みは

起動したい PC がつなげられているネットワークに マジックパケットと呼ばれる特殊なパケットを ブロードキャストする
だけです。とっても簡単...
ところで マジックパケットとは
  • ブロードキャスト・アドレス(FF:FF:FF:FF:FF:FF)
  • 起動したい PC の NIC に割り当てられている MAC アドレス (AA:BB:CC:DD:EE:FF) × 16回
をつなぎ合わせたものです。こんなイメージ...
FFFFFFFFFFFFAABBCCDDEEFFAABBCCDDEEFF
AABBCCDDEEFFAABBCCDDEEFFAABBCCDDEEFF
AABBCCDDEEFFAABBCCDDEEFFAABBCCDDEEFF
AABBCCDDEEFFAABBCCDDEEFFAABBCCDDEEFF
AABBCCDDEEFFAABBCCDDEEFFAABBCCDDEEFF
AABBCCDDEEFFAABBCCDDEEFF
詳細な仕様は を参照するとよい。

マジックパケットJavaFX で表現すると

def macAddress = "AA:BB:CC:DD:EE:FF";

[ for (i in [1..6]) { 0xff as Byte },
  for (i in [1..16], b in macAddress.split(":")) { Integer.parseInt(b, 16) as Byte } ]
たったこれだけです。
たった 2行で マジックパケットを Byte[] で表現できてしまうのです。
リテラルで記述するよりも少ないコードで書けてしまうのは ちょっと感動ものです。
Java で書いたら 数十倍のコードが必要になるんだろうなぁ...

このコードはかなりトリッキーに見えますが JavaFX ではそれ程珍しいコードではありません。
JavaFX の一般的な言語仕様を使っているだけなので...
このコードには シーケンス (配列のようなもの) と for 式の主な特徴がほとんど含まれています。
これが理解できれば シーケンス と for式 が理解できたも同然ではないでしょうか。

JavaFX の for の特徴は 文 ではなく 式 であることです。
文と式の違いは結果を返すかどうかです。
JavaFX の for は シーケンスを返します。
次の式の結果は完全に等価です。
どちらも 0xff を 6個含む シーケンス を生成しています。

var broadcastAddress = [ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff ];
var broadcastAddress = for (i in [1..6]) { 0xff };

もう一つの特徴は 多重ループを一つの for で記述できるところです。
以下の 2つの for式は全く同じ処理を表します。
どちらも 16 × 6 回 HOGE を出力します。

for (i in [1..16]) {
    for (j in [1..6]) {
        println("HOGE");
    }
}

// 多重ループの場合 カンマ区切りで記述する
for (i in [1..16], j in [1..6]) { println("HOGE"); }

マジックパケットを表すコードをよく見ると シーケンスの中に ([ ] の間に) 2つの for 式がふくまれています。
for 式は シーケンスを返しますので、一見 2次元のシーケンスになりそうですが、シーケンスには多次元はありません。
シーケンスの中にシーケンスを入れると1次元のシーケンスとして展開されます。
従って 次のシーケンスは完全に等価です。

[ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF ] 
[ for (i in [1..6]) { 0xff }, [ 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF ] ]

次回は実際にこのマジックパケットを使って Wake on LAN してみたいと思います。
うまくいくか 乞うご期待...

XML-RPC してみる Part 3

今回も まだまだ引き続き XML-RPC について。

前回 NetBeans 6.9 を使って 簡単なアプリケーションを作成してみたものの...
実は そのアプリケーションには

XML-RPC の実行が終了するまで 何も操作できなくなる
という誰にも言えない問題があったのです。
もし、運悪く XML-RPC がレスポンスを返してくれなかったら 完全にフリーズしてしまいます。

この原因は XML-RPC の実行の仕方にあります。
JavaFX は基本的に 全ての処理を EDT と呼ばれる たった一つのスレッドで 実行しています。
もちろん 全ての処理には 描画処理やイベント処理等も含まれています。
前回のサンプルのように何も考えずにコーディングしてしまったら...
比較的 処理時間の長い XML-RPC が EDT で実行されてしまい、暫くの間 それ以外の処理は全てブロックされ アプリケーションはフリーズしてしまいます。
EDT は XML-RPC の返事をただ待っているだけなのに...
これは XML-RPC に限った話ではありません。
ファイルシステム、データベース、その他の WEBサービス等も同様です。

この問題を解決するのは とっても簡単...

XML-RPC は EDT 以外のスレッドで処理すれば良い
だけです。 方法は
  • XmlRpcClient#execAsync(String, Object[], AsyncCallBack) を使う。
  • javafx.async を使う。
の2つが考えられます。
javafx.async は 過去の日記 にも書きましたが ActionTask を使って とっても簡単に実装できます。
しかし 折角 ライブラリで非同期処理をサポートしているのに 使わない手はありません。
と言うことで 今回は XmlRpCClient#execAsync(String, Object, AsyncCallBack) を使って実装してみることに...

こちらも使い方はとっても簡単です。
前回との違いは buttonAction 関数だけです。
非同期で処理をするために executeAsync(String, Object, AsyncCallback) に 変更しています。
後は XML-RPC の処理が完了した際に呼び出されるコールバック関数として AsyncCallback インターフェースを実装するだけです。
JavaFX の場合 インターフェースの実装は 関数に override を付けるだけです。
更に JavaFX のいいところは 引数や戻り値の型を指定しなくても勝手にコンパイラが推測してくれるところです。

    function buttonAction(): Void {
        client.executeAsync("bookmark.getTotalCount", [ textBox.text ], AsyncCallback {
            override function handleResult(request, result) {
                label.text = "{result} users";
            }
            override function handleError(request, e) {
                e.printStackTrace();    // 今回は何も考えず スタックトレース出力
            }
        });
    }
実は このコードにもまだ問題があります。
JavaFX には忘れてはならないお約束が一つだけあるのですが
それは...
Node の属性は 必ず EDT で変更しよう
です。
このお約束を守らずに作られたアプリケーションは デッドロックを起こして フリーズしてしまいます。
この辺りの話も 既に 過去の日記 に書いてるので、そちらも参考になるかもしれません。

で、結局 buttonAction 関数は こんな感じになりました。
上記との違いは FX.deferAction(function():Void) で label.text の内容を変更しているところです。
この FX.deferAction(...) の引数に渡した関数は EDT で実行されるのです。

    function buttonAction(): Void {
        client.executeAsync("bookmark.getTotalCount", [ textBox.text ], AsyncCallback {
            override function handleResult(request, result) {
                FX.deferAction(function(): Void {
                    label.text = "(result} users";
                });
            }
            override function handleError(request, e) {
                e.printStackTrace();    // 今回は何も考えず スタックトレース出力
            }
        });
    }

上記のように ちょっとだけ注意すれば XML-RPC を利用するのは とっても簡単です。
ライブラリさえあれば 何も知らなくても誰でも利用できそうですね。
Apache XML-RPC には プロキシの仕組みも実装されているようなので、 それを使えば 完全に XML-RPC を意識することなく メソッドを実行できるようになるみたい...
いつか また時間があるときにまとめてみようかな...
最近は 完全に REST に押されている XML-RPC ですが まだまだ 利用できる仕組みだと思います。

実は REST よりも XML-RPC を先に試したのには理由があります。
それは Trac との連携がしたかったから...
自分は 仕事でもプライベートでも毎日のように Trac を利用しているのですが、 WEB のインターフェースはお世辞にも使い易いとは言えません。
それなら TracJavaFX を連携させちゃえばいいじゃんという安易なことを考えていたりして...

XML-RPC してみる Part 2

今回も 前回に引き続き XML-RPC について。
前回は 基本的な使い方として WEB サービスを実行して標準出力してみたが...
JavaFX で標準出力してどうすんの!!

と言うことで
今回は WEB サービスを実行して Window 上に結果を 表示してみることに...

今回作成するサンプルは

テキストボックスに URL を入力して ボタンをクリックすると ラベルに はてなブックマーク件数 を表示する
といった何の役にも立たないアプリケーション。

今回は最近流行の NetBeans 6.9 を使ってみることに...
サンプル程度のアプリケーションなら 5分たらずで作れてしまう...
とっても便利な開発支援ツールです。

まずは NetBeans 6.9 で 「JavaFx デスクトップビジネスアプリケーション」 プロジェクトを作成する。
プロジェクト名は何でもよいが、 とりあえず "Hatena" としてみた。

プロジェクトを作成すると...
"エディタ" ウィンドウが "デザイン" モードで表示されるので、 "パレット" ウィンドウから 適当に

  • テキストボックス
  • ボタン
  • ラベル
を ドラグ & ドロップで デザインエリアに貼付ける。
出来上がりはこんな感じ...


ちなみに NetBeans 6.9 の詳細な使い方は id:skrb さんの 「詳説JavaFX」第3回 変数と型、そしてJavaFX Composer がとっても役に立つ...

で、やっと ここからが今回の本題!!
ボタンがクリックされたときに実行されるアクションの実装です。

まず、"デザイン" エリア上の "OK ボタン" を選択して "プロパティー" ウィンドウの "アクション" の 鉛筆アイコンを クリックする。
コンテキストメニューから「生成:空の関数」を選択すると "エディタ" ウィンドウが 勝手に "ソース" モードに切り替わり

package hatena;

public class Main {

    // Generated Code

    function buttonAction(): Void {
        //TODO
    }

}

function run (): Void {
    var design = Main {};

    javafx.stage.Stage {
        title: "Main"
        scene: design.getDesignScene ()
    }
}
のようなコードが自動的に生成されます。
"ボタン" がクリックされたときに実行されるのが buttonAction 関数。
後は この buttonAction 関数を
package hatena;

import java.net.URL;
import org.apache.xmlrpc.client.XmlRpcClient;
import org.apache.xmlrpc.client.XmlRpcClientConfigImpl;

public class Main {

    // Generated Code...


    def config = new XmlRpcClientConfigImpl();
    def client = new XmlRpcClient();

    init {
        config.setServerURL(new URL("http://b.hatena.ne.jp/xmlrpc"));
        client.setConfig(config);
    }

    function buttonAction(): Void {

        // はてなブックマーク件数取得API 実行
        // 第一引数: メソッド名
        // 第二引数: パラメータ (配列)
        //   戻り値: はてなブックマーク件数(java.lang.Integer)
        var res = client.execute("bookmark.getTotalCount", [ textBox.text ]);

        label.text = "{res} users";
    }
}

function run (): Void {
    var design = Main {};
    
    javafx.stage.Stage {
        title: "Main"
        scene: design.getDesignScene ()
    }
}
のような感じで実装するだけで OK。
書かなければいけないコードは たったこれだけ...
前回のサンプルとほぼ同じコードなのに、アプリケーションぽくなってきた。

しかし!!
実は このサンプル アプリケーションは決定的な不具合を抱えているので、
絶対に真似してはいけない!!
それについては、また次回...