FrontPage

2008/01/24からのアクセス回数 11605

Cart問題

Agile Web Development with Railsの例題と同じ問題をSpringを使って実装を試みたときの メモです。

もう一つの目的は、Spring-MVCプラグインがどの程度実際の問題解決に役立つかを検証することです。

プロジェクトの作成

mavenを使ってプロジェクトを生成します。

とします。ecliseでプロジェクトを管理できるようにeclipseプラグインも起動します。

mvn archetype:create \
	-DgroupId=example.cart \
	-DartifactId=cart \
	-DarchetypeArtifactId=spring-mvc-archetype \
	-DarchetypeGroupId=jp.co.pwv.spring-mvc-archetype \
	-DarchetypeVersion=1.1.1

cd cart

mvn eclipse:eclipse -DdownloadSources=true

データベースは、HsqlDBのサーバを使用するため、db.propertiesの内容を修正します。

db.url=jdbc:hsqldb:hsql://localhost

最後にeclipseでcartプロジェクトをimportし、CVSに登録します。

session scopeについて

長くなったので別タイトルにしました。

Product(製品の)の管理

最初にProductを管理するページを作成します。

さしあたり、管理機能として

Productドメインモデルの作成

最初にProductのドメインモデルを作成します。 ドメインモデルは、example.cart.domainパッケージ内に定義します。

eclipseで以下のように入力した後、getter/setterを自動生成してください。

package example.cart.domain;

public class Product {
	private Integer	id;
	private String	title;
	private String	description;
	private String	image_url;
}

GenMVCプラグインの起動

GenMVCプラグインのscaffoldゴールを指定して、ProductのDao、 Controller、 View、データベーステーブル を自動生成します。

その前に、GenMVCプラグインは、Productのクラスファイルを見に行くので、mavenのpackageを実行します。

mvn package

mvn GenMVC:scaffold

このコマンドで、

[INFO] ------------------------------------------------------------------------
[INFO] Building Unnamed - example.cart:cart:war:1.0-SNAPSHOT
[INFO]    task-segment: [GenMVC:scaffold]
[INFO] ------------------------------------------------------------------------
[INFO] [GenMVC:scaffold]
[INFO] pkgName:example.cart
[INFO] runtime.classpath:/Users/take/Documents/workspace/cart/target/classes
[INFO] cls: example.cart.domain.Member
[INFO] template fullpath:velocity/IDao.vm
[INFO] template fullpath:velocity/Dao.vm
[INFO] template fullpath:velocity/edit_stub.vm
[INFO] template fullpath:velocity/list_stub.vm
[INFO] template fullpath:velocity/hbm.vm
[INFO] cls: example.cart.domain.Product
[INFO] template fullpath:velocity/IDao.vm
[INFO] template fullpath:velocity/Dao.vm
[INFO] template fullpath:velocity/Manager.vm
[INFO] template fullpath:velocity/EditController.vm
[INFO] template fullpath:velocity/OpsController.vm
[INFO] template fullpath:velocity/edit.vm
[INFO] template fullpath:velocity/edit_stub.vm
[INFO] template fullpath:velocity/list.vm
[INFO] template fullpath:velocity/list_stub.vm
[INFO] template fullpath:velocity/hbm.vm
[INFO] template fullpath:velocity/servlet-stub.vm
[INFO] template fullpath:velocity/sql.vm
[INFO] template fullpath:velocity/applicationContext.vm
[INFO] template fullpath:velocity/form-messages.vm
[INFO] template fullpath:velocity/validation.vm

と出力され、必要なファイルがすべて生成されます。 再度、mvn packageを実行してtarget/cart.warをtomcatのwebappsにコピーします。

これだけで、Productのリスト表示、編集の画面が生成されます。

product_list.jpg

属性の追加

scaffoldの後にProductに属性を追加したくなることはよくあります。

Product の属性を変更したときの手順は以下の通りです。

通常は、これで十分ですが、以下のファイルを修正した場合にはバックアップを取ってください。

今回は、自動生成されたファイルを全く変更していないので、テーブルの削除だけを行います。

テーブルの削除

開発の途中ではデータベースのテーブルを変更したり、値を参照します。このような用途に便利なのが Ecl,ipseのプラグインDbEditです。 DbEditのインストール方法はhttp://www.pwv.co.jp/take_public_html/DevTool/DevTool_c9.html#doc1_589 を参照してください。

DbEditのTableタグを開くと以下のようにT_MEMBERとT_PRODUCTの2つのテーブルが作られています。

DbEdit.jpg

GenMVCプラグインでは、クラス名の前にT_を付けたテーブルが作成されます。 T_PRODUCTを削除するには、 T_PRODUCTで右マウスクリックから削除を選択してください。

Productクラスの変更

Productに価格(price)を追加します。

以下のように属性priceを追加し、getter/setterを自動生成するだけです。

	private Double	price;

日本では価格に小数点はないのですが、ここでは例としてDouble型を使いました。

それでは、先ほどと同様にGenMVCプラグインを起動します。

mvn package

mvn GenMVC:scaffold

画面(Velocityテンプレート)の変更

GenMVCプラグインが生成する画面は、属性の出力順がProductクラスの定義順に並んでいないので、実際には手で修正する必要があります。

ProductのVelocityテンプレートは、main/webapp/WEB-INF/velocity/productops/以下にあります。

が一覧を表示するテンプレートです。

list.vmを見ると

parse ( "productops/list_stub.vm" )

だけです。 これは、GenMVCプラグインがlist.vmを変更しないようするためにlist_stub.vmをインクルードする 2段階で処理しています。

従ってユーザvelocityテンプレートを変更する場合には、list_stub.vmをlist.vmにコピーして編集します。 以下に順序を入れ替えたlist.vmを示します。

<html>
  <head>
  	<title>Product</title>
  </head>
  <body>
  <h1>Listing product</h1>
    <table>
		<tr>
		<td>id</td>
	        <td>title</td>
	        <td>description</td>
	        <td>image_url</td>
	        <td>price</td>
		</tr>	
#foreach (${product} in ${productList})
		<tr>
		<td>${product.id}</td>
	        <td>${product.title}</td>
	        <td>${product.description}</td>
	        <td>${product.image_url}</td>
	        <td>${product.price}</td>
			#set( $editLink = "/editproduct.htm?id=${product.id}" )
	        <td><a href="#springUrl(${editLink})">[edit]</a></td>
			#set( $deleteLink = "/productops/delete.htm?id=${product.id}" )
	        <td><a href="#springUrl(${deleteLink})">[delete]</a></td>
        </tr>
#end
    </table>
    <a href='#springUrl("/editproduct.htm")'>add</a>
  </body>
</html>

同様に編集画面も順序を変え、Descriptionをtextareaに変えてます。

<html>
<head>
	<title>Products</title>
</head>

<body>
Edit Product
	<form method="post" action="#springUrl("/editproduct.htm")">
		#springFormHiddenInput( "product.id" "" )
		<table>
	        <tr>
	            <td>title:</td>
	            <td>#springFormInput( "product.title" "size='35'" )</td>
	            <td>
				#springBind("product.title")
				<font color="red">${status.errorMessage}</font>
	            </td>
	        </tr>
	        <tr>
	            <td>description:</td>
	            <td>#springFormTextarea( "product.description" "rows='4' cols='40'" )</td>
	            <td>
				#springBind("product.description")
				<font color="red">${status.errorMessage}</font>
	            </td>
	        </tr>
	        <tr>
	            <td>image_url:</td>
	            <td>#springFormInput( "product.image_url" "size='35'" )</td>
	            <td>
				#springBind("product.image_url")
				<font color="red">${status.errorMessage}</font>
	            </td>
	        </tr>
	        <tr>
	            <td>price:</td>
	            <td>#springFormInput( "product.price" "size='10'" )</td>
	            <td>
				#springBind("product.price")
				<font color="red">${status.errorMessage}</font>
	            </td>
	        </tr>
	        <tr>
	            <td colspan="3">
	                <input type="submit" value="Save Changes"/>
	            </td>
	        </tr>
		</table>
	</form>
</html>
</body>

Validationの変更

現在の入力フォームは、各フィールドが必須だけのチェックしかしていません。 priceに文字を入力して、Save Chageボタンを押すと

validation_error1.jpg

が出力されます。

これでは、エラーの原因が分かりづらいので、validation.xmlを修正してpriceをdoubleとしてふさわしい値になるようにしようと、

			<field property="price" depends="required">
				<arg0 key="product.price" />
			</field>

			<field property="price" depends="required,double">
				<arg0 key="product.price" />
			</field>

としたが、ダメでした。

原因は、Validationが行われる前に、Productの値がHTTPのパラメータからセットされるためです。

Validationエラーへの対応

ソースをトレースした結果、bindAndValidationを使用する場合、最初にHTTPリクエストから値をセットするcommandオブジェクトへのbind操作が先行します、ここでStringからDoubleへの変換に失敗するため、その後のValidationのエラーチェックでは値がセットされていないので、requiredのエラーが追加されますが、表示されません。

対応策としては、ProductionのPriceをDoubleからStringに変えるという方法しかありません。 これでは、GenMVCプラグインを起動すると間違ったCreate table文が生成されてしまいます。

commandクラスの追加

そこで、domainクラスに対応するcommandクラスをGenMVCプラグインで生成し、その属性をすべてString型としました。

更に、EditProductControllerでcomanndオブジェクトとdomainオブジェクトの値を変換するメソッドcommandToDomain, domainToCommandを追加しました。こんなに簡単にデータの設定ができるのは、CustomDataBinderの威力です。

	protected void bind(Object target, Object source)  throws Exception {
		CustomDataBinder binder = new CustomDataBinder(target, source);
		this.prepareBinder(binder);
		binder.bind();				
	}
	
	protected Object commandToDomain(Object source) throws Exception {
		Object object = new Product();
		bind(object, source);
		return (object);
	}

	protected Object domainToCommand(Object source) throws Exception {
		Object object = new ProductCommand();
		bind(object, source);
		return (object);
	}

これでようやく、priceのValidationが正常に行えるようになりました。

validation_error2.jpg

スタイルシートの設定

最後にスタイルシート使って衣装替えをします。 スタイルシートについては、詳しくないのでAgile Web Development with Railsの例題のスタイルを借用します。

スタイルシートを追加したlist.vmは、以下の通りです。

<html>
  <head>
  	<link rel="stylesheet" href="#springUrl('/css/cart.css')" type="text/css">
  	<title>Product</title>
  </head>
  <body>
<div id="product-list">
  <h1>Listing product</h1>
  
    <table cellpadding="5" cellspacing="0">
#foreach (${product} in ${productList})
#if($velocityCount %2 == 1)
		<tr valign="top" class="list-line-odd">
#else
		<tr valign="top" class="list-line-even">
#end
			<td>
				<img class="list-image" src="#springUrl(${product.image_url})" />
			</td>
			<td width="60%">
				<span class="list-title">${product.title}</span><br/>
#if ($product.description.length() > 80)
	#set($size = 80)
#else
	#set($size = $product.description.length())
#end
				$product.description.substring(0,$size)
			</td>
			<td class="list-action">
				#set( $editLink = "/editproduct.htm?id=${product.id}" )
				<a href="#springUrl(${editLink})">[edit]</a><br/>
				#set( $deleteLink = "/productops/delete.htm?id=${product.id}" )
				<a href="#springUrl(${deleteLink})">[delete]</a>
			</td>
        </tr>
#end
    </table>
    <a href='#springUrl("/editproduct.htm")'>New product</a>
</div>
  </body>
</html>

ここで、スタイルシートの指定を

  	<link rel="stylesheet" href="#springUrl('/css/cart.css')" type="text/css">

でしているところと、説明文を一部カットするためにStringのsubstringメソッドをテンプレートから呼び出しているところに注意してください。このようにVelocityを使うとテンプレートからjavaのメソッド呼び出しができます。

スタイルシートの出力結果は、以下の通りです。

styled_list.jpg

カタログページの作成

次にカタログ表示ページを作成します。Agile Web Development with Railsではカタログの表示にstoreという新しいコントローラを作成していますが、ここではProductOpsControllerを借用します。 その理由は、ProductOpsControllerがProductを扱うコントローラであり、MultiActionControllerのサブクラスなのでメソッドと同じテンプレートを作成するだけでカタログページが作れるとメリットを示すためです。

メソッドとテンプレートの追加

手順は以下の通りです。

    public ModelAndView catalog(HttpServletRequest request, HttpServletResponse response) throws Exception {
        return new ModelAndView().addObject(manager.findAll());
    }

catalog.vmは以下の通りです。

<html>
<head>
	<link rel="stylesheet" href="#springUrl('/css/cart.css')" type="text/css">
  	<title>Store</title>
</head>
<body id="store">
	<h1>Your wine catalog</h1>
  
#foreach (${product} in ${productList})
	<div class="entry">
		<img src="#springUrl(${product.image_url})" />
		<h3>${product.title}</h3>
		$product.description <br>
		<span class="price">$numberTool.format("##0", $product.price)</span>
	</div>
#end
</body>
</html>

カタログページは、次のように表示されます。

catalog1.jpg

カートへの追加ボタンの追加

最後にカートへの追加ボタンを入れます。

価格の後に次の行を挿入します。

		<form method="post" action="#springUrl("/cartops/add.htm")">
			#springFormHiddenInput( "product.id" "" )
			<input type="submit" value="Add to Cart"/>
		</form>

トップ   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
SmartDoc