StaxでBlazeDSを始めてみる

f:id:voidy21:20090130032544p:image
http://www.stax.net/
Staxは簡単に言えばGoogle App EngineJava版みたいなものです。
今のところ無料でJavaサーバサイドの環境で開発できちゃいます。
情報があんまり無いのでこれからちょっとずつ書いていこうと思います。

Staxで開発を始める前に

まず、上のリンクの"Apply for the Beta"をクリックしてメールを送ります。
すると招待コードが送られてくるのでメールのリンクからユーザ登録をします。
あとはここからSDKをダウンロードしてホームディレクトリにでも展開しましょう。


Unix系の場合はパスを通す必要があります。例えば僕が使っているzshの場合は~/.zshrcに

export STAX_HOME=~/stax-sdk-0.2.14
PATH=$STAX_HOME:$PATH

と追加しました。Windowsの場合はコンソールへのリンクがあるのでそれを使うとパスが通っているので使う必要がありません。
他のコマンドプロンプトから実行したい場合はUnix系と同様にpathを通す必要があると思います。

BlazeDSを使ってみる

こちらのページはアプリケーションコンソールとしてログインすると表示されますが、
アプリケーションを作りたい場合は左上の"Create App"をクリックします。アプリケーション名を決めて、開発フレームワークを決定します。
僕は一度試してみたかったのでBlazeDSを選択しました。
f:id:voidy21:20090130020300p:image
あとはアプリケーションコンソール上に書いてあるように、先ほどのSDKのディレクトリから

stax getapp -a ユーザ名/アプリケーション名 -u ユーザ名 -p パスワード

とすると、自動的に必要なライブラリやファイル等がダウンロードされます。
今後はこれを使って開発していきます。

まずサンプルを動かしてみよう

stax compile

でJavaとFlexのファイルがコンパイルされます。
そして、

stax run

とするとアプリケーションがローカルサーバhttp://localhost:8080/で立ち上がります。
今回はこのアプリケーションを少しだけ改造してみます。

BlazeDSは全体的にどうなってんだ

まず大事なファイル等について以下に書いておきます。
(色々と間違ってるかもしれないので注意)

ソースファイル
srcディレクトリとflex-srcディレクトリ
  • それぞれJavaFlexのソースファイルを格納する
  • 先ほどのコンパイルではこれらのファイルをコンパイルしている
  • 生成されるclassファイルとswfファイルはwebappディレクトリに出来る
web.xml
webapp/WEB-INFディレクトリ
  • 設定ファイルのトップとなる
  • サーブレットやリソースとかを定義
services-config.xml
webapp/WEB-INF/Flexディレクトリ
  • 設定ファイルの中間職みたいな存在
  • サービスを定義
  • Long Pollとかの設定はここで行う
  • BlazeDSでCometを構築したいときは設定が必要。
  • 今回はデフォルトのままで大丈夫
remoting-config.xml
webapp/WEB-INF/Flexディレクトリ
  • 設定ファイルでは下っ端だが大事
  • FlexからJavaを呼ぶ際のJavaのクラスへの対応の定義を書いたりする
  • だからJavaのクラスが増えたらちゃんと追記しないといけない
  • 今回はデフォルトのままで大丈夫
stax-application.xml
confディレクトリ
  • 実はこれが一番大事な設定ファイルかもしれない
  • データベースを使うときに追記する必要がある

他にもいろいろありますが、基本的にこれらのファイルに対して処理していく流れになります。

データベースを使ってみよう!!

StaxではデータベースとしてMySQLを使っています。
ここではJavaから処理してみたいと思います。(JDBCを使う)


まず、データベースをアプリケーションコンソールで作成しましょう。
左上の"Create DB"をクリックすると作成できます。ユニークなデータベース名、パスワードなどを入れます。
すると中央の"Cribsheet Snippets"という場所で使用方法が載っているので、それを参考にファイルに追記していきます。
このときパスワードとかが丸見えなので注意しましょう。(それもどうかと思うが…)
f:id:voidy21:20090130020301p:image
ここではデータベース名をhogehogeとしておきました。
上の部分をstax-application.xmlに、下の部分をweb.xmlに追加しておきます。


これでデータベースを使用する準備ができたので、実際に使用する場合はCode samplesを参考にするといいと思います。
僕の作ったサンプル改造も置いておきます。


/src/example/HelloWorldService.java

package example;
import java.util.HashMap;

import javax.naming.*;
import javax.sql.*;
import java.sql.*;

public class HelloWorldService {
      //これはサンプルそのまま
	public HashMap<String, String> getData(String message)
	{
		HashMap<String, String> data = new HashMap<String, String>();
		data.put("clientMessage", message);
		data.put("serverResponse", "Hello from HelloWorldService");
		return data;
	}
	//データベースを作成する例
	public void createDB(){
	    try {
	            Context ctx = new InitialContext();
	            DataSource ds = (DataSource)ctx.lookup("java:comp/env/jdbc/hogehoge");
	            Connection conn = ds.getConnection();
	            Statement stmt = conn.createStatement();
	            
	            String sql = "CREATE TABLE  testTable" 
	                  + "(hoge INTEGER PRIMARY KEY, "
	                  + "fuga varchar(50)," 
	                  + " piyo varchar(50) )";
	            stmt.execute(sql);
	            sql = "INSERT INTO  testTable" 
	                     +"(hoge,fuga,piyo) " 
	                     + "VALUES(1,'hoga','hogera')";
	           stmt.executeUpdate(sql);
	           sql = "INSERT INTO  testTable" 
	                     +"(hoge,fuga,piyo) " 
	                     + "VALUES(2,'bar!!','foo!!')";
	            stmt.executeUpdate(sql);
	             sql = "INSERT INTO  testTable" 
	                     +"(hoge,fuga,piyo) " 
	                     + "VALUES(3,'太郎','花子')";
	            stmt.executeUpdate(sql);
	            conn.close();
	            System.out.println("created database.");
	        }catch(Exception e){
	            System.out.println(e.toString());
	        }
	}
	//データを参照する例
	public HashMap<String, String> viewDB(){
	    HashMap<String, String> data = new HashMap<String, String>();
	    try {
	  	    Context ctx = new InitialContext();
	            DataSource ds = (DataSource)ctx.lookup("java:comp/env/jdbc/hogehoge");
	            Connection conn = ds.getConnection();
	            Statement stmt = conn.createStatement();
	            ResultSet rst = stmt.executeQuery("select hoge, fuga, piyo from testTable");
	             int num = 0;
	             while(rst.next()) {
	                    int hoge = rst.getInt(1);
	                    String fuga= rst.getString(2);
	                    String piyo = rst.getString(3);
	                    num++;
	                    data.put("hoge"+num,Integer.toString(hoge));
	                    data.put("fuga"+num,piyo);
	                    data.put("piyo"+num,fuga);
	             }
	      	     conn.close(); 
	        }catch(Exception e){
	            data.put("error",e.toString()); 
	        }
	        return data;
	}
}


/flex-src/main.mxml (ほぼサンプル通り)

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" creationComplete="init()">
	<mx:Script>
		<![CDATA[
			import mx.messaging.Channel;
			import mx.messaging.ChannelSet;
			import mx.messaging.channels.AMFChannel;
			import mx.rpc.events.ResultEvent;
			import mx.managers.BrowserManager;
			import mx.managers.IBrowserManager;
			import mx.utils.URLUtil;
			
			public function init():void
			{
				//initialize the RemoteObjects with the channel
				//TODO: is there away to avoid this step?
				
				var browserManager:IBrowserManager = BrowserManager.getInstance();
				browserManager.init("", "Welcome!");
				var baseURL:String = null;
				var protocol:String = URLUtil.getProtocol(browserManager.url); 
				if(protocol == "file")
					baseURL = "http://localhost:8080";
				else
					baseURL = protocol + "://" + URLUtil.getServerNameWithPort(browserManager.url);

				var cs:ChannelSet = new ChannelSet();
				var customChannel:Channel = new AMFChannel("my-amf", baseURL + "/messagebroker/amf");
				cs.addChannel(customChannel);
				
				helloRO.channelSet = cs;
			}
			
			private function getDataResultHandler(event:ResultEvent):void
			{
				resultText.text = dump(event.result);
			}
			private function viewDBResultHandler(event:ResultEvent):void
			{
			    resultText.text = dump(event.result);
			}
			private function dump(obj:Object):String
			{
				var dumpStr:String = "";
				for(var key:String in obj)
				{
					dumpStr += key + " = " + obj[key] + "\n";
				}
				dumpStr += "";
				return dumpStr;
			}
		]]>
	</mx:Script>
	<mx:Button label="Send Message" click="helloRO.getData(clientMessage.text)" y="33" right="78"/>
	<mx:Button label="Create Data" click="helloRO.createDB()" y="60" right="88"/>
	<mx:Button label="View Data" click="helloRO.viewDB()" y="60" right="0"/>
	<mx:RemoteObject id="helloRO" destination="HelloWorldService">
		<mx:method name="getData" result="getDataResultHandler(event)"/>		
		<mx:method name="createDB"/>
		<mx:method name="viewDB" result="viewDBResultHandler(event)"/>
	</mx:RemoteObject>	
	<mx:TextInput y="33" id="clientMessage" text="Hello World" left="78" right="192"/>
	<mx:Label y="75" text="Result from server" left="78"/>
	<mx:TextArea id="resultText" y="94" height="186" left="78" right="78" editable="false"/>
</mx:Application>

ポイントは

  • RemoteObjectを作るところ
  • Flex(Flash)からJavaのメソッド呼び出してる!

ところです

作ったものをアップしたい

stax deploy -u ユーザ名 -p パスワード

これでアップできます!!
今回作ったのは
http://testblazeds.voidy21.staxapps.net/
です。

最後に

  • BlazeDSはJavaFlexSQLとか触っていないとややきついかもしれない
  • そのうちもっとアプリケーションらしいものを作っていきたい(Cometでのチャットとか)
  • AMF(Action Message Format)による通信はJSONを使うよりも(バイナリな分)高速なので使ってみたい
  • コンパイルの時間どうにかならんのか
  • アップロードに時間かかりすぎ
  • 上げても動きが重いような…
  • でもこれだけのことが無料でできるのはよいことだと思う