GAE/JでYUI2.7.0+Struts1.3をつかって、データストアからエンティティを取得してDataTableで表示する

前回のログでは、Google App Engine/Java(GAE/J)に配備したCSVファイルから、

  • Emailアドレス
  • 日本語名
  • 住所
  • 役割(ロール)

の情報を取得して、YUI2.7.0のDataTableで表示してみた。

今回は、GAE/Jのデータソース(Data Source)のエンティティーとしてこれらの情報を永続化し、同様にYUI2.7.0でDataTableで表示してみる。サンプルの画面は、前回と同様に以下となる。


以下の作業は、GAE/JプラグインがインストールされたGanymede(Eclipse3.4)で行った。JDK1.6.0_16が出ているので、JDKJREのアップデートを行った(GAE/Jのバージョンは、以前のログに記載したように1.6.0_13)。

プロジェクトの作成

前回作成したプロジェクトをコピーして、新規のWeb Applicationプロジェクトを作成する。必要に応じてバージョン、もしくはアプリケーションを変更する。

エンティティの作成

以前のログ「DWR2+SpringframeworkでGEA/Jのデータストアを使う(その2)」と同様に、以下の2つのエンティティーをtest.entitiesパッケージに作成する。

RootTO ルート・エンティティー。エンティティー・グループを規定する。
AppUser Email、日本語名、住所、役割(ロール)などをプロパティーとしてもつエンティティー。RootTOの小エンティティーとなって、同一のエンティティー・グループに属するようにする。

RootTO.java

package test.entities;

import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;

import com.google.appengine.api.datastore.Key;

/*********************************
 * 
 * エンティティーグループを定義するRootエンティティー
 * 
 * @author
 *     tetsuya_odaka (EzoGP) <br>
 *********************************/

@PersistenceCapable(identityType=IdentityType.APPLICATION)
public class RootTO {

    /*
     * rootエンティティーのキーはKeyか、String
     */
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    private Key key;
    
    @Persistent
    private String name;
    
    @Persistent
    private Long count;

    /**
     * コンストラクタ
     * 
     */
    public RootTO(String name, Long count) {
        this.name = name;
        this.count = count;
    }

    public Key getKey() {
        return key;
    }

    public void setKey(Key key) {
        this.key = key;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Long getCount() {
        return count;
    }

    public void setCount(Long count) {
        this.count = count;
    }
}


AppUser.java

package test.entities;

import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;

import com.google.appengine.api.datastore.Key;

/**
 * AppUserを表すオブジェクト。
 * 
 */
@PersistenceCapable(identityType=IdentityType.APPLICATION)
public class AppUser{

    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    private Key key;

    @Persistent
    private Long number;

    @Persistent
    private String email;

    @Persistent
    private String name;

    @Persistent
    private String address;

    @Persistent
    private String role;

    /**
     * クラスPersonのオブジェクトを構築します。
     * 
     */
    public AppUser() {
    }

    public AppUser(String email, String name, String address, String role) {
        this.email = email;
        this.name = name;
        this.address = address;
        this.role = role;
    }
    /**
     * Setter/Getter
     */

    public Key getKey() {
        return key;
    }

    public void setKey(Key key) {
        this.key = key;
    }
    
    public Long getNumber() {
        return number;
    }
    
    public void setNumber(Long number) {
        this.number = number;
    }

    public int getIntNumber() {
        return number.intValue();
    }
   
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
    
    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getEmail() {
		return email;
	}

	public void setEmail(String email) {
		this.email = email;
	}

	public String getRole() {
		return role;
	}

	public void setRole(String role) {
		this.role = role;
	}

	/**
     * プロパティーを文字列に連結して返す
     */
    public String toString(){
        return this.key.toString() + this.email + 
        		this.name + this.address + this.role;
    }
}

DAOの作成

AppUserエンティティーをmanipulateするためのDAOを作成する。PMF(Persistent Manager Factory)は、以前のログ「Google App Engine/JavaのData Storeを使ってみる」で用いたサンプルで使用したものを使用する。

package test;

import java.util.ArrayList;
import java.util.List;

import javax.jdo.PersistenceManager;
import javax.jdo.Query;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;

import test.entities.AppUser;
import test.entities.RootTO;

/**
 * AppUserエンティティーのData Access Object
 * 
 */
public class AppUserDao{
    private static final Log log = LogFactory.getLog(AppUserDao.class);
    
    /**
     * コンストラクタ
     */
    public AppUserDao() {
    }
    
    @SuppressWarnings("unchecked")
    private List<AppUser> loadAppUser(){

        List<AppUser> users =
            new ArrayList<AppUser>();

        PersistenceManager pm 
                = PMF.get().getPersistenceManager();

        /**
         * AppUserエンティティーに対するクエリの実行
         */
        Query query = pm.newQuery(AppUser.class);
        query.setOrdering("email asc");

        try{
            List<AppUser> list = (List<AppUser>)query.execute();
            for(AppUser usr : list){
                users.add(usr);
                log.debug("person loaded: "+usr.toString());
            }
        }finally{
            query.closeAll();
            pm.close();
        }
        return users;
    }

    /*
     *  AppUserのリストを返却する。 
     */
    public List<AppUser> getAll() {
        return this.loadAppUser();
    }

    /*
     *  AppUserを登録、更新する。 
     */
    public List<AppUser> setAppUser(AppUser usr) {
        
        log.debug("setAppUser id : " + usr.getNumber());
        
        if(usr.getNumber().intValue() != -1){
            this.updateAppUser(usr);
        }else{
            this.registerAppUser(usr);
        }
        
        return this.loadAppUser();
    }

    /*
     *  AppUserを登録する。 
     */
    private void registerAppUser(AppUser usr) {
        
        PersistenceManager pm 
            = PMF.get().getPersistenceManager();
        long count = 0;
        try{
            pm.currentTransaction().begin();
            RootTO root = pm.getObjectById(RootTO.class, "key");
            count = root.getCount()+1;
            root.setCount(count);
            pm.currentTransaction().commit();
            log.debug("root count updated to: " + new Long(count).toString());
        }finally{
            if(pm.currentTransaction().isActive()){
                // ロールバック
                pm.currentTransaction().rollback();
            }
            pm.close();
        }

        if(count > 0){        
            AppUser storePer =
                new AppUser(usr.getEmail(),
                			usr.getName(),
                			usr.getAddress(),
                			usr.getRole());
         
            KeyFactory.Builder kb = new KeyFactory.Builder(RootTO.class.getSimpleName(), "key");
            kb.addChild(AppUser.class.getSimpleName(), 
                    AppUser.class.getSimpleName()+ new Long(count).toString());
            Key key = kb.getKey();
               
            storePer.setKey(key);
            storePer.setNumber(count);
            
            pm = PMF.get().getPersistenceManager();
            try{
                pm.makePersistent(storePer);
                log.debug("AppUser entered: "+storePer.toString());
            }finally{
                pm.close();
            }
        }
        return;
    }

    /*
     *  AppUserを更新する。 
     */
    private void updateAppUser(AppUser usr) {
        
        PersistenceManager pm 
            = PMF.get().getPersistenceManager();

        try{
            // キーの生成
            KeyFactory.Builder kb = new KeyFactory.Builder(RootTO.class.getSimpleName(), "key");
            kb.addChild(AppUser.class.getSimpleName(), 
                AppUser.class.getSimpleName()+usr.getNumber().toString());
            Key key = kb.getKey();

            // オブジェクトの取得
            AppUser storePer = pm.getObjectById(AppUser.class, key);
            storePer.setEmail(usr.getEmail());
            storePer.setName(usr.getName());
            storePer.setAddress(usr.getAddress());
            storePer.setRole(usr.getRole());
            log.debug("AppUser updated: "+storePer.toString());
        }finally{
            pm.close();
        }
        return;
    }

    /**
     *  AppUserを削除する。 
     */
    public List<AppUser> deletePerson(AppUser usr) {
        PersistenceManager pm 
        = PMF.get().getPersistenceManager();

        try{
            // キーの生成
            KeyFactory.Builder kb = new KeyFactory.Builder(RootTO.class.getSimpleName(), "key");
            kb.addChild(AppUser.class.getSimpleName(), 
                AppUser.class.getSimpleName()+usr.getNumber().toString());
            Key key = kb.getKey();

            // オブジェクトの取得
            AppUser storePer = pm.getObjectById(AppUser.class, key);
            pm.deletePersistent(storePer);
            log.debug("AppUser deleted: "+key.toString());
        }finally{
            pm.close();
        }
        return this.loadAppUser();
    }
    
    /**
     *  Personを一括削除する。 
     */
    @SuppressWarnings("unchecked")
    public void deleteAll() {
        PersistenceManager pm 
        = PMF.get().getPersistenceManager();

        Query query = pm.newQuery(AppUser.class);
        try{
            pm.currentTransaction().begin();
            List<AppUser> list = (List<AppUser>)query.execute();
            pm.deletePersistentAll(list);
            pm.currentTransaction().commit();
            log.debug("AppUser all deleted.");
        }finally{
            if(pm.currentTransaction().isActive()){
                // ロールバック
                pm.currentTransaction().rollback();
                log.debug("person all-delete failed.");
            }
            query.closeAll();
            pm.close();
        }
        return;
    }

コンテキストリスナーの作成

RootTOとAppUserエンティティーの初期化は、コンテキストリスナーで行うことにした。今回のサンプルでは、AppUserの追加処理を実装しない。コンテキスト起動時に、AppUserエンティティーは一旦全て削除し、2つのエンティティーを追加することにした(ソースコードを参照)。

CreateEntitiesContextListner.java

package test;

import javax.jdo.PersistenceManager;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

import test.entities.AppUser;
import test.entities.RootTO;

import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;

public class CreateEntitiesContextListner implements ServletContextListener {

    @Override
    public void contextDestroyed(ServletContextEvent arg0) {
    }

    /**
     * DataStoreの初期設定を行います。
     * 
     */
    @Override
    public void contextInitialized(ServletContextEvent arg0) {
        /*
         * Rootエンティティーの初期設定
         *  カウンターを0にリセットします。
         */
        RootTO to = new RootTO("root",new Long(0));
        Key key =KeyFactory.createKey(RootTO.class.getSimpleName(), "key");
        to.setKey(key);
        
        PersistenceManager pm = PMF.get().getPersistenceManager();
        // rootエンティティーを登録
        try{
            pm.makePersistent(to);
        }finally{
            pm.close();
        }
        
        /*
         * AppUserエンティティーの初期設定
         *  AppUserエンティティーを登録します。
         */
        
    	// AppUserのDAOを生成する
        AppUserDao pdao = new AppUserDao();

        // 一旦全部削除
        pdao.deleteAll();
        
        // エンティティーの登録(その1)
        AppUser usr = new AppUser();
        usr.setNumber(new Long(-1));
        usr.setEmail("tetsuya.odaka@gmail.com");
        usr.setName("小高");
        usr.setAddress("東京都");
        usr.setRole("admin");
        // 登録
        pdao.setAppUser(usr);
        
        // エンティティーの登録(その2)
        usr.setEmail("dummy@dummy.com");
        usr.setName("山田");
        usr.setAddress("北海道");
        usr.setRole("user");
        // 登録
        pdao.setAppUser(usr);
        
    }
}

Actionクラスの作成

前回のサンプルのアクションクラス(UserForwardAction.java)を元にして、Actionクラスを作成する。このプログラムは、YUIのDataSourceオブジェクトから非同期に呼び出される。前回のサンプルでは、CSV形式のファイルから(永続化された)データを読み込んだが、今回はData Storeから読み込む。クライアントへのレスポンスは、前回と同様にCSV形式で行う。

UserForwardAction.java

package test;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.List;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;

import test.entities.AppUser;

public class UserListForwardAction extends Action {

    public ActionForward execute(ActionMapping mapping,
        ActionForm form,
        HttpServletRequest request,
        HttpServletResponse response)
    {
    	// AppUserのDAOを生成する
        AppUserDao pdao = new AppUserDao();
    	// AppUserのListを取得する
        List<AppUser> usrList = pdao.getAll();

        StringBuffer sb = new StringBuffer();
        byte[] bStr;

    	// AppUserのListをCSV形式に整形する
        for(AppUser usr : usrList){
            sb.append("\n");
            sb.append(usr.getEmail());
            sb.append(",");
            sb.append(usr.getName());
            sb.append(",");
            sb.append(usr.getAddress());
            sb.append(",");
            sb.append(usr.getRole());
            sb.append("\n");
        }

        try{
        	// CSV形式に整形したListをクライアントにレスポンスする。
        	bStr = sb.toString().getBytes("UTF-8");
            response.setContentType("text/html; charset=UTF-8"); 
            ServletOutputStream outputStream;
            outputStream = response.getOutputStream();
            outputStream.write(bStr);
            outputStream.flush();

        } catch (UnsupportedEncodingException e){
			e.printStackTrace();
        } catch (IOException e) {
			e.printStackTrace();
		}
        return null;
    }
}
||


**struts-config.xmlの変更
Actionクラスの名前を変更したので、struts-config.xmlを修正する。以下は修正後のstruts-config.xml。
>|xml|
<?xml version="1.0" encoding="utf-8" ?>
<!--
    Licensed to the Apache Software Foundation (ASF) under one or more
    contributor license agreements.  See the NOTICE file distributed with
    this work for additional information regarding copyright ownership.
    The ASF licenses this file to You under the Apache License, Version 2.0
    (the "License"); you may not use this file except in compliance with
    the License.  You may obtain a copy of the License at
   
         http://www.apache.org/licenses/LICENSE-2.0
   
    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
-->

<!DOCTYPE struts-config PUBLIC
          "-//Apache Software Foundation//DTD Struts Configuration 1.3//EN"
          "http://struts.apache.org/dtds/struts-config_1_3.dtd">

<struts-config>

<!-- ================================================ Form Bean Definitions -->
<form-beans>
    <form-bean
		name="dummyForm"
		type="org.apache.struts.action.DynaActionForm" >
	</form-bean>
</form-beans>

<!-- ========================================= Global Exception Definitions -->
    <global-exceptions>
    </global-exceptions>

<!-- =========================================== Global Forward Definitions -->
    <global-forwards>
        <!-- Default forward to "Welcome" action -->
        <forward
            name="welcome"
            path="/index.hml"/>
    </global-forwards>

<!-- =========================================== Action Mapping Definitions -->
    <action-mappings>
		<action path="/forwardText"
			type="test.UserListForwardAction"
			name="dummyForm"
			scope="request">
		</action>
    </action-mappings>

<!-- ======================================== Message Resources Definitions -->
    <message-resources parameter="MessageResources" />

<!-- =============================================== Plug Ins Configuration -->

  <!-- ======================================================= Tiles plugin -->

  <!-- =================================================== Validator plugin -->
   
</struts-config>

index.htmlの変更

Action名の変更がstrutsで吸収されているので、index.xmlは変更しなくてよい。以下は全文。

<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>Ajax+Strutsのサンプル</title>
<link rel="shortcut icon" href="../images/egp-favicon.ico" >

<style type="text/css"> 
body {
	margin: 2px;
	padding: 3px;
}
</style> 
 
<link rel="stylesheet" type="text/css" href="./scripts/lib/yui/build/fonts/fonts-min.css" /> 
<link rel="stylesheet" type="text/css" href="./scripts/lib/yui/build/paginator/assets/skins/sam/paginator.css" /> 
<link rel="stylesheet" type="text/css" href="./scripts/lib/yui/build/datatable/assets/skins/sam/datatable.css" /> 
<script type="text/javascript" src="./scripts/lib/yui/build/utilities/utilities.js"></script> 
<script type="text/javascript" src="./scripts/lib/yui/build/paginator/paginator-min.js"></script> 
<script type="text/javascript" src="./scripts/lib/yui/build/datasource/datasource-min.js"></script> 
<script type="text/javascript" src="./scripts/lib/yui/build/datatable/datatable-min.js"></script> 

<script>
XHRDataTableSample = function() {
	var myDataSource;
	var myDataTable;
	
	return{
		init:function() {

			// DataTable用:列定義
    		var myColumnDefs = [
             	{	key:"email",
                 	label:"e-mail",
             		parser:"text",
                 	width:250,
                 	resizeable:true,
                 	sortable:true},
                {	key:"jname",
                    label:"名前",
             		parser:"text",
                    width:100,
                    resizeable:true,
                    sortable:true},
               	{	key:"addr",
                    label:"住所",
              		parser:"text",
                    width:100,
                    resizeable:true,
                    sortable:true},
               	{	key:"role",
                    label:"役割",
               		parser:"text",
                    width:100,
                    resizeable:true,
                    sortable:true}
    		];

 		   	// DataTable用:コンフィグ属性
    		var myConfigs = {
            sortedBy:{key:"email",dir:YAHOO.widget.DataTable.CLASS_ASC},
            paginator: 
                new YAHOO.widget.Paginator({
                	rowsPerPage: 10,
                	template: YAHOO.widget.Paginator.TEMPLATE_ROWS_PER_PAGE,
                	rowsPerPageOptions: [10,25,50,100],
                	pageLinks: 5
            	}),
           	caption:"users",
			// 列のDrag and Drop
           	draggableColumns:true,
           	// 行の選択は1つだけ
           	selectionMode:"single"
    		};

    		// DataSourceのインスタンス化;struts actionを呼ぶ
        	myDataSource = new YAHOO.util.DataSource('/forwardText.do');
	        	myDataSource.responseType = YAHOO.util.DataSource.TYPE_TEXT;
    		myDataSource.responseSchema = {
   			// 行区切り
            	recordDelim: '\n',
            	// フィールド区切り
            	fieldDelim: ',',
        		fields: ['email','jname','addr','role']
    		};

        	// DataTableのインスタンス化
	        myDataTable = new YAHOO.widget.DataTable("output", 
	    	    myColumnDefs, myDataSource, myConfigs);

			/*
			* 行の選択補助。
			*/
			// クリックでハイライトするようにハンドラを設定
        	myDataTable.subscribe("rowClickEvent",
            	myDataTable.onEventSelectRow);
        	myDataTable.subscribe("rowMouseoverEvent", 
       			myDataTable.onEventHighlightRow);
        	myDataTable.subscribe("rowMouseoutEvent", 
       			myDataTable.onEventUnhighlightRow);

		}, // initの終わり
	   	oDS: myDataSource,
   		oDT: myDataTable
	};
}();


//DOMが完全にloadされたら、サンプルを初期化する。
YAHOO.util.Event.onDOMReady(
	//DomReadyイベントで発火するハンドラ
	XHRDataTableSample.init,
	//ハンドラに渡すオブジェクト(関数)
	XHRDataTableSample,
	//ハンドラは、上記のオブジェクトのスコープをもつ。   
	true
);

</script>
</head> 
 
<body class="yui-skin-sam"> 

<h2>YUI2.7 ConnectionManager,DataTable + Struts1.3</h2>

GAE/Jサーバーにあるcsvファイルを、struts経由で取得します。
<br>
<br>

<div id="output">
</div>

</body>
</html>

web.xml

コンテキスト・リスナーでエンティティーの初期化を行うので、以下の定義を加える。

<?xml version="1.0" encoding="utf-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">

  <display-name>GAE/J Sample Application</display-name>

  <listener>
  	<listener-class>
   	test.CreateEntitiesContextListner
  	</listener-class>
  </listener>

  <!-- Standard Action Servlet Configuration -->
  <servlet>
    <servlet-name>action</servlet-name>
    <servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
    <init-param>
      <param-name>config</param-name>
      <param-value>/WEB-INF/struts-config.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>

  <!-- Standard Action Servlet Mapping -->
  <servlet-mapping>
    <servlet-name>action</servlet-name>
    <url-pattern>*.do</url-pattern>
  </servlet-mapping>

  <!-- The Usual Welcome File List -->
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
  </welcome-file-list>

</web-app>

テストとデプロイ

Data Storeを使うので、ローカルサーバーで一通り動かしてから、GAE/Jサーバーにデプロイする。