JavaとFlexの連携その2(前準備)

前回のエントリーで、Merapiを使って、AIRJavaでメッセージをやり取りできた。
ただ諸々の理由(勘違いかもしれないけど)でMerapiの採用はやめた。


そもそもAdobeさんはFlexJavaを繋げられる機能を提供している。
それがAdobe LiveCycle Data Services(LCDS)
LCDSの機能限定版のBlazeDSの方が、なじみがあるかもしれない。

一般的にBlazeDSTomcatなどのサーブレットコンテナ上で動かすのだが、
今回はこれを使うためにJava側のプログラムはローカルWebサーバプログラムとしてつくり、
その上にBlazeDSを動作させることにした。


なので、今回はローカルWebサーバプログラムの作り方。
と言っても、待ち受けSocketを作って…、とWebサーバをゼロから作るのではなく、
JavaプログラムにWebサーバ機能を組み込む、と言うアプローチで作る。


Webサーバ機能を組み込む、と言えば、
そう、Jetty(流れが乱暴ですか?)。
Jettyと言えば、最近Javaが動くようになったGoogle App EngineサーブレットコンテナがJettyだったらしいので、話題としても旬だろう。


論より証拠ってことで、ソースコード

import java.util.HashMap;

import org.mortbay.jetty.Server;
import org.mortbay.jetty.nio.SelectChannelConnector;
import org.mortbay.jetty.servlet.Context;
import org.mortbay.jetty.servlet.ServletHolder;

public class HttpDeamon {

    public static void main(String[] args) {

        SelectChannelConnector connector = new SelectChannelConnector();

        // コネクタに待ち受けポート番号を設定
        // ここでは8888番ポートを指定した
        connector.setPort(8888);

        // サーブレットを作成
        // ここでは「HogeServlet」というクラスを使用
        ServletHolder hogeServletHolder = new ServletHolder(HogeServlet.class);

        // サーブレットに初期パラメータを渡す場合は、以下の処理でできる
        // まずはHashMapで初期パラメータを作成
        HashMap<String, String> servletInitMap = new HashMap<String, String>();
        servletInitMap.put(
            "parameter.name.1",
            "parameter.value.1",
        );
        servletInitMap.put(
            "parameter.name.2",
            "parameter.value.2",
        );
        // サーブレットに初期パラメータ用HashMapで設定
        hogeServletHolder.setInitParameters(servletInitMap);

        Server server = new Server();

        // Serverオブジェクトにコネクタを設定
        server.addConnector(connector);

        Context hogeContext = new Context(server, "/", Context.SESSIONS);

        // サーブレットを追加
        // "/hoge"というパスにhogeServletHolder(つまりHogeServlet)を割り当てる
        hogeContext.addServlet(hogeServletHolder, "/hoge/*");

        // Jetty起動
        server.start();
        server.join();
    }
}


以上の流れで、自分が作ったJavaプログラムにWebアプリケーション機能を組み込むことができた。


今回、これを作るにあたり調べたのが、サーブレットへの初期化パラメータの渡し方。
上のやり方だとサーブレットのweb.xmlをこう書いたのと同じ。

<web-app>

    <servlet> 
        <servlet-name>HogeServlet</servlet-name> 
        <servlet-class>HogeServlet</servlet-class> 
        <init-param> 
            <param-name>parameter.name.1</param-name> 
            <param-value>parameter.value.1</param-value> 
        </init-param> 
    </servlet> 

    <servlet-mapping> 
        <servlet-name>HogeServlet</servlet-name> 
        <url-pattern>/hoge/*</url-pattern> 
    </servlet-mapping> 
</web-app>

この後、BlazeDSをJettyに設定することになるが、また次に。
# Flexと連携してない…

参考サイト

JavaとFlexの連携その1

今作っているアプリは、

  1. JavaMXMLをビルドする
  2. AIRでSWFを表示する

というのが大雑把な流れだが(大雑把過ぎるか)、
1.が終わった後に2.を実行するために、JavaからAIRへメッセージを送る必要がある。
もっというと、(これは確定ではないが)MXMLを指定するUIをAIRで作るなら、
指定されたMXMLの情報(パスかMXMLそのもの)をJava側に送らなければいけない。


このAIRJavaとの間でメッセージをやり取りするのに、
Merapiというライブラリを使ってみた。


このライブラリの入手方法は、こちらの参考記事にお任せして(オイ!)、
早速Java/AIR双方のコードを。
まずJava側でメッセージを送受信するクラス。

package {
    public static void main(String[] args) {
         MessageBroker massageBroker = new MessageBroker("merapiConnection");
         massageBroker.sendMessage("Dear Flex, I'm Java");
    }
}

import merapi.Bridge;
import merapi.messages.Message;
import merapi.messages.IMessage;
import merapi.messages.IMessageHandler;

public class MessageBroker implements IMessageHandler {

    // 接続文字列
    private String connectionString="";
    // コンストラクタ
    public void MessageBroker(String connectionString) {
        // メッセージハンドラを登録
        this.connectionString = connectionString;
        Bridge.getInstance().registerMessageHandler(this.connectionString, this);
    }

    // メッセージを受信する
    public void handleMessage(IMessage message) {
        try {
            // メッセージを受け取って表示
            System.out.println("Get data from flex: " + ((Message)message).getData());
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

    // メッセージを送信する
    public void sendMessage(Object message) {
        // メッセージを作成して送信
        Message message = new Message(this.connectionString, message);
        try {
            Bridge.getInstance().sendMessage(response);
        } catch(Exception e) {
            e.printStackTrace();
        }
    }
}


次にAIR側のコード。

<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
    <mx:Script>
        <![CDATA[
            import merapi.BridgeInstance;
            import merapi.messages.Message;

            import mx.controls.Alert;
            import mx.events.FlexEvent;
            import mx.rpc.events.ResultEvent;

            protected var _bridgeInstance:BridgeInstance;
            private function onCreationComplete(event:FlexEvent):void {
                _bridgeInstance = new BridgeInstance();
                _bridgeInstance.addEventListener(
                    ResultEvent.RESULT,
                    bridgeInstanceResultHandler);
            }

            private function bridgeInstanceResultHandler(event:ResultEvent): void {
                // メッセージを受け取って表示
                var message:Message = event.result as Message;
                if (message.data != null) {
                    var data:Object = message.data
                    Alert.show(data, "受信メッセージ");
                }
            }

            private function sendMessage(messageString:String): void {
                // メッセージを作成して送信
                var message:Message = new Message("merapiConnection", messageString);
                bridgeInstance.sendMessage(message);
            }
        ]]>
    </mx:Script>
    <mx:Button click="sendMessage('Dear Java, I'm Flex');"/>
</mx:WindowedApplication>

これでAIRJavaでメッセージの送受信が出来るようになる。
# もちろん、両方起動した状態にしておかないといけないが。


ただし、実際はこのMerapiは使わなかった。
というのも、ライブラリに含まれるtools.jarが6MBもあり、これを含めたjarを作ると巨大なファイルになるため。
# JREに含まれるtools.jarで代用が利くかどうかは、未検証


結局、定番のBlazeDSを使用した。
それはまた別のお話。

wxJavaScriptの記事を見た

このwxJavaScriptの記事を見た。


作りたいのは、これのMXML版(ActionScript版)に当たるものなんだろうなぁ、と自分で思った。
AIRについても冒頭に触れられていて、

手軽さから見れば「本格的すぎる」印象が強く・・・

らしい。


なぜ本格的過ぎるんだろう?


今のAIRはまだガジェット/ウィジェットと呼ばれるほど、作り手/使い手双方にとっての手間が減ってない気がする。

作り手は開発環境(Flex Builder/FlashDevelop/Flex SDK/etc…)を用意しなくちゃいけないし、ソースを書いたらビルドして署名つけてパッケージして、と
配布できるまでぜんぜん手軽じゃない。

また使い手は、AIRファイルをダウンロードして、それをインストールして初めて使える。
使い手にとっては、ただのアプリケーションと手間は変わらない。
個人的な感想だが、この『インストール』という作業が嫌いだ。
少なくとも、インストール処理中待ってなくちゃいけないし、その処理時間が長いほど何かコンピュータの設定を変えているような気がしてくる。
実際、Windowsなら、(アンインストールの為とはいえ)レジストリを変更を加えている。


AIRBootという、AIRアプリをインストールせずに実行する為のAIRアプリ(わかりづら・・・)があるが、
あれは結構お気に入りで、使い手としての自分の不満を解消してくれた。
ただもちろんAIRファイルの配布が前提なので、使い手としての自分の不満はまだ残ったままだ。


なので、テキストエディタで書き終わったら即実行、みたいなことを
MXML(ActionScript)使いができる環境を作るのが、今作っているアプリだったりする。


というのを、最初のリンクを見て思ったので、とりとめもなく書いてみた。

AIRでタスクトレイアプリケーションを作る

今日はAIRネタにする。
AIRで作るタスクトレイアプリケーション、てことで。


結構いろんなサイトに出てるから、目新しいものではないけど、
ウィジェットマネージャとして動作するアプリなら、タスクトレイに常駐してるっぽいので、
自分用の備忘録として残しておく。


タスクトレイアプリとして、出来なくちゃいけないこと。

  1. タスクバーからメインウィンドウを消す
  2. タスクトレイにアイコンを入れる
  3. タスクトレイを右クリックしたら終了メニューなどが出る

あと出来たらアイコンを簡単に変更したい。
というわけで、いきなりサンプルコード。

package {

    import flash.desktop.NativeApplication;
    import flash.desktop.SystemTrayIcon;
    import flash.display.BitmapData;
    import flash.display.NativeMenu;
    import flash.display.NativeMenuItem;
    import flash.events.Event;
    import flash.events.MouseEvent;
    import flash.system.System;

    import mx.core.BitmapAsset;
    import mx.core.WindowedApplication;
    import mx.events.FlexEvent;
    import mx.styles.CSSStyleDeclaration;
    import mx.styles.StyleManager;

    // トレイアイコン用のスタイルを新たに定義
    [Style(name="trayIcon", type = "Class", inherit = "no")]

    public class TaskTrayApplication extends WindowedApplication {
        private var _trayIcon:Class;

        private var systemTrayIcon:SystemTrayIcon;
        private var systemTrayMenu:NativeMenu;
        private var iconChanged:Boolean = true;

        private var exitMenuItem:NativeMenuItem = new NativeMenuItem("終了");

        // デフォルトのアイコン
        // これは各自で用意する
        [Embed(source="assets/icons/DefailtTrayIcon.png")]
        private static var _defaultTrayIcon:Class;

        public function TaskTrayApplication() {
            super();
            // visibleプロパティをfalseに。
            // this.visibleはオーバーライドしたいので、super.visibleで。
            super.visible = false; // …a
            this.addEventListener(FlexEvent.CREATION_COMPLETE, applicationCreationCompleteHandler);

        }

        override public function set visible(value:Boolean):void {
            // visibleプロパティはfalseに固定したい
            // 派生クラスで変更できないようにオーバーライド
        }

        public function get defaultTrayIcon():Class {
            return _defaultTrayIcon;
        }

        private function applicationCreationCompleteHandler(event:FlexEvent):void {
            if (NativeApplication.supportsSystemTrayIcon) {
                systemTrayIcon = NativeApplication.nativeApplication.icon as SystemTrayIcon;    // …b-1

                // システムトレイのメニューは右クリックが基本らしいので、
                // 必要があれば左クリック時の処理を追加する
                systemTrayIcon.addEventListener(MouseEvent.CLICK, systemTrayIconClickHandler);
            }
            setTrayIcon();
            setMenuItems();
        }

        private function setTrayIcon():void {
            if (systemTrayIcon != null) {
                if (_trayIcon == null)
                    return ;

                // アイコンクラスを生成して、Bitmapデータを取得
                var iconBitmap:BitmapData = (new _trayIcon() as BitmapAsset).bitmapData;

                // 取得したBitmapデータをシステムトレイアイコンに設定
                systemTrayIcon.bitmaps = [iconBitmap];    // …b-2

                // ついでにツールチップも設定
                systemTrayIcon.tooltip = this.name; // これはアプリ名をそのまま使う例
            }
        }

        protected function setMenuItems():void {
            if (systemTrayIcon != null) {
                if (systemTrayMenu == null)
                    systemTrayMenu = new NativeMenu();
                systemTrayIcon.menu = systemTrayMenu;

                // 「終了」のメニューを追加
                systemTrayMenu.addItemAt(exitMenuItem, 0); // …c-1
                exitMenuItem.addEventListener(Event.SELECT, exitMenuSelectHandler); // …c-2
                
                // 以降、任意のメニューを追加していく
                
            }
        }

        private function exitMenuSelectHandler(event:Event):void {
            // アプリ自体を終了する
            NativeApplication.nativeApplication.exit(0);
        };

        protected function systemTrayIconClickHandler(event:MouseEvent):void {
            trace("tray_click");
        }

        override public function styleChanged(styleProp:String):void {
            super.styleChanged(styleProp);
            // トレイアイコン変更時の処理
            if (styleProp == "trayIcon") {
                iconChanged = true;
                invalidateProperties();
            }
            invalidateDisplayList();
        }

        override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void {
            if (iconChanged == true) {
                _trayIcon = this.getStyle("trayIcon");
                setTrayIcon();
                iconChanged = false;
            }
            super.updateDisplayList(unscaledWidth, unscaledHeight);
        }

        // スタイルデフォルト値設定用の静的変数と関数
        // http://livedocs.adobe.com/flex/3_jp/html/help.html?content=skinstyle_3.html
        private static var classConstructed:Boolean = classConstruct();
        private static function classConstruct():Boolean {
            var currentStyleDeclaration:CSSStyleDeclaration = StyleManager.getStyleDeclaration("TaskTrayApplication");
            // TaskTrayApplicationクラスのスタイル定義が既に存在するかどうかを確認
            if (currentStyleDeclaration == null) {
                // スタイルが定義されていない
                // ここでスタイルが作成され、trayIconスタイルのデフォルト値を設定
                var newStyleDeclaration:CSSStyleDeclaration = new CSSStyleDeclaration();
                newStyleDeclaration.setStyle("trayIcon", _defaultTrayIcon);
                StyleManager.setStyleDeclaration("TaskTrayApplication", newStyleDeclaration, true);
            }
            return true;
        }
    }
}


結構前に作ったものをベースに、参考サイトからの情報を付け加えていってるので、
無駄な処理もあるかも。


メインウィンドウを消す処理は、コンストラクタでやっているように、自身のvisibleプロパティをfalseに設定し(a)、
且つ、アプリケーション記述XMLの値にfalseにする。
これで、メインウィンドウは消え、タスクバー上にもウィンドウタイトルが表示されなくなる。


次にタスクトレイにトレイアイコンを入れる処理。
アプリケーションが起動した時に、SystemTrayIconクラスをNativeApplicationクラスから取得する(b-1)。
次に、取得したSystemTrayIconのbitmapsプロパティ(Array型)に、アイコンイメージを設定すればOK(b-2)。


またSystemTrayIconのmenuプロパティに、"終了"を表示するNativeMenuItemクラスを格納したNativeMenuクラスを設定すると(c-1)、
トレイアイコンを右クリックしたときに、"終了"メニューが表示される。
そのメニューが選択されたときの処理は、exitMenuSelectHandlerがNativeMenuItemにイベントハンドラーとして登録されている(c-2)。


あと、"trayIcon"という名前でスタイルを新規に追加して、TaskTrayApplicationのsetStyleメソッドで、アプリ実行中でもトレイアイコンが変更できるようにした。
例えば、Timerクラスを使って数秒毎にアイコンを(点滅っぽく)変更する、とかもできる。


ただスタイルを新規で作るとデフォルト値の扱いが問題になるらしく、
上記の例では予め用意しておく画像ファイル("assets/icons/DefailtTrayIcon.png")をデフォルトアイコンとしたかったので、
クラス定義の最後に、このクラスがnewされる最初だけ実行する静的変数/関数を定義して、トレイアイコンのデフォルト値を設定している。


このクラスでメインのアプリを作ってもいいし、メインのアプリのスーパークラスとして使ってもいいと思う。
# 後者の場合は、トレイアイコンをクリックしたときに出るメニューの追加には一手間いるかも

(4/27追記)
どうやら、ActionScriptで定義したApplication派生クラスから直接Flexアプリケーションはできないようなので、
やはり、上のクラスをスーパークラスにして、MXMLのクラスを作る必要があるみたい。
なので、setMenuItems()メソッドはprotectedに変更して、派生先のクラスでは、

        override protected function setMenuItems():void {
            super.setMenuItems();

            // 以降、任意のトレイアイコンメニューを追加
        }

とやるのがまともっぽいね。

参考サイト


# はてなシンタックスハイライトって、ActionScriptには対応してませんよね
# 上のサンプルコードはJavascriptを指定してけど、どれを指定するのが一番見やすいんだろう?

Dropboxが久々にバージョンアップ

結構前のエントリーで、Dropboxを使っていること書いたが、
こいつがなかなかバージョンアップしなかったが、久々にwebページの方にログインしてみると、
4月8日付で新バージョン(v0.6.507)がリリースされていたので、早速入れてみた。


それまではv0.6.402をずっと使っていたが、実はこいつ、起動しっぱなしにすると、CPU使用率が100%に迫る。
周りで使っている人も、何人かそうなっているらしい(なってない人もいる)。
なので、同期したいときだけ起動し、同期が終わったら必ず終了させていた。
# その事を手間と感じてたわけじゃないんだけど。


英語恐怖症なので、まともにForumのページを覗いたこともなく、この不具合が既知のものなのかもわかってないんだけど、
今回の新バージョンで直っていることを期待。
ただいまタスクマネージャをちら見しつつ耐久試験中。

(4/22 13:00追記)

起動しっぱなしで4時間以上経過したけど、
CPUの占有現象は一度も出ていない。
直ったのかな?よかった、よかった。

プログラム実行時にクラスパスを追加する

前のエントリーで、Flex SDKのCompiler APIを使ってMXMLをビルドしてSWFを生成できるようになった。


ここでビルドに使うFlex SDKはどこにあるのだろう。


一つは、アプリのパッケージにFlex SDK同梱して、同梱されたSDKのみを使うようにする。そうしたら、SDKの場所は特に悩まなくて済む。
でも同梱してしちゃうと、SDKのバージョンが上がるたびにパッケージしなおさなくちゃいけないし、
あとライセンス的に大丈夫なの?って気もする。
# 調べてないけど、考えるのが面倒なので。
あと、既にFlex開発をかじっている人(というか自分)にとって、さらにFlex SDKを追加されるのはあまりいただけない。結構でかいし。


もう一つは、ユーザがローカルディスク上に展開したFlex SDKを指定して使う。
Flex Builderの「インストールされているFlex SDK」という設定項目、あんな感じで。


今回のアプリでは、後者を選択した。
ただ、ユーザが任意のディレクトリのFlex SDKを追加/削除できちゃうと、
アプリからするとどこにSDKがあるかわからなくなって、予めクラスパスを通すのが難しくなる。
なのでComiler APIを使うために、Javaプログラムでクラスパスを動的に追加する必要がある。
というわけで、今回のテーマは「クラスパスを動的に追加する方法」です(前置き、ながッ…)。
# このアプリに限らないJavaの技のお話ですね。


具体的にはどうやるか。
ハイ、参考サイト
これをそのまんまやってみた。完璧!すごいなぁ。為になりました。

ただ、ディレクトリを指定すると、うまく動かないことがあったので、下のように若干修正した。

    public static void addFile(File f) throws IOException {
        if (f.isDirectory()) {
            File[] child = f.listFiles();
            for (int j = 0; j < child.length; j++) {
                addURL(child[j].toURL());
            }
        } else {
            addURL(f.toURL());
        }
    }

ディレクトリを指定するとその下にあるファイルを全部登録して、
且つ、配下のディレクトリに対し、再帰的に実行するようにした。


jarファイルだろうがなんだろうが、問答無用でクラスパスに登録しちゃってるけどね。


# イカン、早くもネタが尽き始めとるorz