Hatena::ブログ(Diary)

hamadakoichi blog このページをアンテナに追加 RSSフィード

2009-06-11

[][] Google App Engine for Java (GAE/J) で Bigtable CRUDを行う Flex アプリ作成法 07:41  Google App Engine for Java (GAE/J) で Bigtable CRUDを行う Flex アプリ作成法 - hamadakoichi blog を含むブックマーク  Google App Engine for Java (GAE/J) で Bigtable CRUDを行う Flex アプリ作成法 - hamadakoichi blog のブックマークコメント

週末、GoogleAppEngine for Java (GAE/J)で動作し BigtableCRUDを試せる簡易Flexアプリを作成してみた。

以下に作成法を紹介する。

RemotingにはBlazeDS を使用。

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

f:id:hamadakoichi:20090609002548j:image


作成において、次のサイトを参考にした。今回、参考サイトになかった 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"を作成する。

f:id:hamadakoichi:20090609001113j:image

f:id:hamadakoichi:20090609001114j:image


Server側 (Java)

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

Content.java

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

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)側の作成。

f:id:hamadakoichi:20090610012015j:image

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

f:id:hamadakoichi:20090610012016j:image

"Next"押下。

f:id:hamadakoichi:20090610012017j:image

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


f:id:hamadakoichi:20090610012020j:image

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の新規作成。

f:id:hamadakoichi:20090611014006j:image

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>

 
	<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>
 
 	
  
  <welcome-file-list>
    <welcome-file>GaeBlog.html</welcome-file>
  </welcome-file-list>
  
  
  <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">
		
	
	<flex:message-broker>
		<flex:message-service
			default-channels="my-streaming-amf,my-longpolling-amf,my-polling-amf" />
	</flex:message-broker>

	
	<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に、

<sessions-enabled>true</sessions-enabled>を次のように追加。

<?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>
	
	
	<sessions-enabled>true</sessions-enabled>
	
	
	<system-properties>
		<property name="java.util.logging.config.file" value="WEB-INF/logging.properties"/>
	</system-properties>
	
</appengine-web-app>
設定

war/WEB-INF下にflexフォルダを作成しflex設定ファイルを置く。

これらの設定ファイルは参考サイトサンプルプロジェクトからも取得できる。

f:id:hamadakoichi:20090611022829j:image


Flex UI作成:GaeBlog.mxml

GaeBlog.mxmlで次のUIを作成。

f:id:hamadakoichi:20090610012022j:image

その内容は次。

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>

       
       
	<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実行できる。

f:id:hamadakoichi:20090610012023j:image


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タグ内に、<manageable>false</manageable>を追加


実際に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