Google App Engine for Java (GAE/J) で Bigtable CRUDを行う Flex アプリ作成法

週末、GoogleAppEngine for Java (GAE/J)で動作し BigtableCRUDを試せる簡易Flexアプリを作成してみた。
以下に作成法を紹介する。
RemotingにはBlazeDS を使用。
http://koichi-hamada.appspot.com/


作成において、次のサイトを参考にした。今回、参考サイトになかった Update、Deleteも追加している。
Flex Meets Google App Engine
Flex Remoting on Google App Engine

前提/環境

以下、GAE/J Eclipse plugin、Flex Builder plugin のEclipseインストールは完了している前提で進める。
インストールは次をご参考に:
Google App Engine for Javaを使ってみよう!(1)Google Plugin for Eclipse
Adobe Flex Builder 3.0.2 Professional Eclipse Plug-inのダウンロード


構築環境:
Java: 1.6.0_12
Eclipse: 3.4.2
Google App Engine Java SDK: 1.2.1
Google Plugin for Eclipse 3.4: 1.0.1
Google Web Tool Kit SDK: 1.6.4
Flex Builder Professional Eclipse plug-in: 3.0.2

作成手順

GAE/J Projectの作成

"Web Application Project"を作成する。


Server側 (Java)

gaeblog.server packageに次の3クラスを追加。

Content.java

Big Tableに永続化するEntity Class。Blog内容を表す。

package gaeblog.server;
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;
@PersistenceCapable
public class Content {
	@PrimaryKey
	@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
	Long id;
	@Persistent
	private String title;
	@Persistent
	private String comment;	
	//getters, setters...
}
BlogService.java

Big TableでのCRUDのService Interface。

package gaeblog.server;
import java.util.List;

public interface BlogService {
	public boolean addContent(Content content);
	public boolean updateContent(Content content);
	public boolean deleteContent(Integer id);
	public List<Content> getContentList();
}
BlogServiceImpl.java

BlogServiceの実装Class。

package gaeblog.server;
import java.util.List;
import javax.jdo.JDOHelper;
import javax.jdo.PersistenceManager;
import javax.jdo.PersistenceManagerFactory;
import javax.jdo.annotations.Transactional;

public class BlogServiceImpl implements BlogService{
	//Persistence Manager
	private static final PersistenceManagerFactory factory
	 = JDOHelper.getPersistenceManagerFactory("transactions-optional");
	//PersistenceManager pm;
	
	@Override
	@Transactional
	public boolean addContent(Content content) {
		PersistenceManager pm = factory.getPersistenceManager();
		try{
			if(content.getId().intValue()==0){
				content.setId(null);
			}
			pm.makePersistent(content);
			return true;
		}catch (Exception e){
			return false;
		}finally{
			pm.close();
		}
	}

	@Override
	@Transactional
	public boolean updateContent(Content content){
		PersistenceManager pm = factory.getPersistenceManager();
		try{
			Content persistenctContent = pm.getObjectById(Content.class, content.getId());
			persistenctContent.setTitle(content.getTitle());
			persistenctContent.setComment(content.getComment());
			return true;
		}catch (Exception e) {
			return false;
		}finally{
			pm.close();
		}
	}

	@Override
	@Transactional
	public boolean deleteContent(Integer id){
		PersistenceManager pm = factory.getPersistenceManager();
		try{
			Content persistenctContent = pm.getObjectById(Content.class, id);
			pm.deletePersistent(persistenctContent);
			return true;
		}catch (Exception e) {
			return false;
		}finally{
			pm.close();
		}
	}

	@Override
	@Transactional
	@SuppressWarnings("unchecked")
	public List<Content> getContentList() {
		PersistenceManager pm = factory.getPersistenceManager();
		String query = "select from " + Content.class.getName();
		return (List<Content>) pm.newQuery(query).execute();
	}
}

Client側(Flex)

次にClient(Flex)側の作成。


プロジェクト上で右クリックし、「Flexプロジェクトの特性」⇒「Flexプロジェクトの特性を追加」。


"Next"押下。


出力先は "war" にする。(ビルドされたファイルがサーバーにDeployされる)


SWFビルドエラーが出る。
エラーを右クリック⇒「HTMLテンプレートの再作成」を選択し解消する。

Content.as

gaeblog.client packageに、
Server側のContent.java とBindさせるAction Scriptを作成。

package gaeblog.client
{
	[Bindable]
	[RemoteClass(alias = "gaeblog.server.Content")]
	public class Content
	{
		public var id:int;
		public var title:String;
		public var comment:String;
		public function Content(){}
	}
}
BlazeDS Remoting設定

Remoting設定として、
war/WEB-INFフォルダで、web.xmlへの追加とwebAppContext.xmlの新規作成。

war/WEB-INF/web.xmlへの追加

Remoting用のSpring設定。
war/WEB-INFフォルダ内の web.xmlの web-app タグ内に設定追加。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>

 <!-- Remoting用 Spring設定追加:ここから -->
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>WEB-INF/webAppContext.xml</param-value>
	</context-param>
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>

	<servlet>
		<servlet-name>testdrive</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>

	<servlet-mapping>
    	<servlet-name>testdrive</servlet-name>
    	<url-pattern>/messagebroker/*</url-pattern>
 	</servlet-mapping>
 <!-- Remoting用 Spring設定追加:ここまで -->
 	
  <!-- Default page to serve -->
  <welcome-file-list>
    <welcome-file>GaeBlog.html</welcome-file>
  </welcome-file-list>
  
  <!-- Servlets -->
  <servlet>
    <servlet-name>greetServlet</servlet-name>
    <servlet-class>gaeblog.server.GreetingServiceImpl</servlet-class>
  </servlet>
  
  <servlet-mapping>
    <servlet-name>greetServlet</servlet-name>
    <url-pattern>/gaeblog/greet</url-pattern>
  </servlet-mapping>

</web-app>
war/WEB-INF/webAppContext.xmlの新規作成

BlazeDSでのchannel、destinationの設定。
上記のweb.xmlで Spring の ContextLoaderListenerに渡している webAppContext.xmlを次のように作成。
BlogServiceImplをSpring beanとし、BlogDestinationという名で、destinationに設定している。

<?xml version="1.0" encoding="utf-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:flex="http://www.springframework.org/schema/flex"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="
		http://www.springframework.org/schema/beans
		http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
		http://www.springframework.org/schema/flex
		http://www.springframework.org/schema/flex/spring-flex-1.0.xsd
		http://www.springframework.org/schema/context
		http://www.springframework.org/schema/context/spring-context-2.5.xsd">
		
	<!-- channel設定 -->
	<flex:message-broker>
		<flex:message-service
			default-channels="my-streaming-amf,my-longpolling-amf,my-polling-amf" />
	</flex:message-broker>

	<!-- Big Tableでの CRUD実装した Service Classを設定-->
	<bean id="blogBean" class="gaeblog.server.BlogServiceImpl"/>
	<flex:remoting-destination destination-id="BlogDestination" ref="blogBean" />
	
</beans>
war/WEB-INF/appengine-wab.xmlへの追加

GAE/JでSessionを使えるよう、appengine-web.xmlに、
trueを次のように追加。

<?xml version="1.0" encoding="utf-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
	<application>koichi-hamada</application>
	<version>1</version>
	
	<!--追加: Sessionを使えるようにする -->
	<sessions-enabled>true</sessions-enabled>
	
	<!-- Configure java.util.logging -->
	<system-properties>
		<property name="java.util.logging.config.file" value="WEB-INF/logging.properties"/>
	</system-properties>
	
</appengine-web-app>
設定

war/WEB-INF下にflexフォルダを作成しflex設定ファイルを置く。
これらの設定ファイルは参考サイトサンプルプロジェクトからも取得できる。

Flex UI作成:GaeBlog.mxml

GaeBlog.mxmlで次のUIを作成。

その内容は次。

RemoteObjectを、 destinationを上記の"BlogDestination"で設定。
endpointは、Local実行用に"http://localhost:8080/messagebroker/amf"で設定。

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" initialize="init()">
	<mx:Label x="40" y="23" text="title"/>
	<mx:Label x="40" y="49" text="comment"/>
	<mx:TextInput x="105" y="21" id="titleText" width="181" text="{grid.selectedItem.title}"/>
	<mx:TextInput x="105" y="47" id="commentText" width="181" text="{grid.selectedItem.comment}"/>
	<mx:Button x="294" y="10" label="insert" click="createContent()" width="66"/>
	<mx:Button x="294" y="36" label="update" click="updateContent()"/>
	<mx:Button x="294" y="60" label="delete" click="deleteContent()" width="66"/>
	<mx:DataGrid id="grid" x="43" y="90" dataProvider="{blogcontents}" width="317" height="177">
		<mx:columns>
			<mx:DataGridColumn headerText="Title" dataField="title"/>
			<mx:DataGridColumn headerText="Comment" dataField="comment"/>
			<mx:DataGridColumn dataField="id" visible="false"/>
		</mx:columns>
	</mx:DataGrid>

       <!-- For GAE Server
	<mx:RemoteObject id ="remoteObject" result="showContentList(event)" destination="BlogDestination" endpoint="http://koichi-hamada.appspot.com/messagebroker/amf"/>
		-->
       <!-- For Local	-->
	<mx:RemoteObject id ="remoteObject" result="showContentList(event)" destination="BlogDestination" endpoint="http://localhost:8080/messagebroker/amf"/>
	<mx:Script>
		<![CDATA[
			import mx.binding.utils.BindingUtils;
			import gaeblog.client.Content;
			import mx.collections.ArrayCollection;
			import mx.rpc.events.ResultEvent;
			import mx.controls.Alert;
			[Bindable]
			public var blogcontents:ArrayCollection;

			public function init():void{
				remoteObject.getContentList();
			}
			public function createContent():void{
				var content:Content=new Content();
				content.title=titleText.text;
				content.comment=commentText.text;
				remoteObject.addContent(content);
			}
			public function updateContent():void{
				if(null!=grid.selectedItem){
					var content:Content=new Content();
					content.id=grid.selectedItem.id;
					content.title=titleText.text;
					content.comment=commentText.text;
					remoteObject.updateContent(content);
				}
			}
			public function deleteContent():void{
				if(null!=grid.selectedItem){
					remoteObject.deleteContent(grid.selectedItem.id);
				}
			}
			public function showContentList(event:ResultEvent):void{
				if(event.result!=null){
					if(event.result==false){
						Alert.show("failed");
					}else if (event.result==true){
						Alert.show("successful");
						remoteObject.getContentList();
					}else{
						blogcontents=event.result as ArrayCollection;
					}
				}
			}
		]]>
	</mx:Script>
</mx:Application>
war/WEB-INF/libにJARの追加

war/WEB-INF/libに、
Spring BlazeDSに必要なJARを追加する。

こちらも、参考サイトサンプルプロジェクトから取得できる。

追加後の war/WEB-INF/libの中身は次:

antlr-3.0.1.jar
aopalliance.jar
appengine-api-1.0-sdk-1.2.1.jar
aspectjrt.jar
aspectjweaver.jar
backport-util-concurrent.jar
cglib-nodep-2.1_3.jar
commons-codec-1.3.jar
commons-httpclient-3.0.1.jar
commons-logging.jar
concurrent.jar
datanucleus-appengine-1.0.1.final.jar
datanucleus-core-1.1.0.jar
datanucleus-jpa-1.1.0.jar
flex-messaging-common.jar
flex-messaging-core.jar
flex-messaging-opt.jar
flex-messaging-proxy.jar
flex-messaging-remoting.jar
geronimo-jpa_3.0_spec-1.1.1.jar
geronimo-jta_1.1_spec-1.1.1.jar
gwt-servlet.jar
jackson-core-asl-0.9.9-6.jar
jdo2-api-2.3-SNAPSHOT.jar
org.springframework.aop-3.0.0.M3.jar
org.springframework.asm-3.0.0.M3.jar
org.springframework.aspects-3.0.0.M3.jar
org.springframework.beans-3.0.0.M3.jar
org.springframework.context-3.0.0.M3.jar
org.springframework.context.support-3.0.0.M3.jar
org.springframework.core-3.0.0.M3.jar
org.springframework.expression-3.0.0.M3.jar
org.springframework.flex-1.0.0.RC1.jar
org.springframework.jdbc-3.0.0.M3.jar
org.springframework.jms-3.0.0.M3.jar
org.springframework.transaction-3.0.0.M3.jar
org.springframework.web-3.0.0.M3.jar
org.springframework.web.servlet-3.0.0.M3.jar
spring-security-acl-2.0.4.jar
spring-security-catalina-2.0.4.jar
spring-security-core-2.0.4.jar
spring-security-core-tiger-2.0.4.jar
spring-security-taglibs-2.0.4.jar
xalan.jar

実行

ついに実行。
プロジェクト上で右クリック⇒「Run As」⇒「Web Application」でLocal実行できる。

GAE ServerへのDeploy前に行うべきこと

GAE ServerにDeployする前、GAE Server動作用に次の補正が必要。

GaeBlog.mxml

RemotingObjectのendpointを
Local実行用の"http://localhost:8080/"からGAE Serverのものに修正。
例:"http://koichi-hamada.appspot.com/の場合

<mx:RemoteObject id ="remoteObject" result="showContentList(event)" destination="BlogDestination" endpoint="http://koichi-hamada.appspot.com/messagebroker/amf"/>
war/WEB-INF/libに Jarを追加

GoogleAppEngine Server上での動作用に補正された flex-messagingのJARを追加。
次サイトからも取得できる。
flex-messaging-core-FIXED_1.jar

war/WEB-INF/flex/service-config.xml

services-config.xmlのsytemタグ内に、falseを追加

実際にDeployしたサンプルサイト:

http://koichi-hamada.appspot.com/


関連文献

Action Script, Adobe Flex のオススメ本

上記使用している Flex, MXML, Action Script, BlazeDS 等に関し今まで読んだ本で、オススメのものを以下に紹介する。初めての人でも分かりやすい入門本、および、応用に使える本。

ActionScript 3.0 プログラミング入門 - for Adobe Flash CS3 親切なAction Scriptの入門書。サンプルや例題が分かりやすく親切に載っており初学者でも読みやすい。
Flash Math & Physics Design:ActionScript 3.0による数学・物理学表現[入門編] ActionScriptで凝ったことをやり始めようとする人に最適。どのように使うかが具体的に丁寧に書かれている。ActionScriptの入門書を読んだ後に応用を学ぶのによい本。
Flex3プログラミング入門 MXMLの記述に詳しい。Flex Builderのインストール方法、設定など詳しく書かれており初学者でも読み進めやすい。
Adobe Flex 3 & AIRではじめるアプリケーション開発 Server側とFlexの具体的な連携方法が具体的に書かれている。インストール方法、設定方法から親切に書かれている。BlazeDSS2Daoを利用した通信方法、DBアクセスなど実際に使える方法。


※他参考・関連サイト:
AppEngine & Adobe BlazeDS (fix)
BlazeDSでFlexとJavaを通信させる必要最低限の設定
NEW UPDATE TO THE SPRING BLAZEDS INTEGRATION TEST DRIVE