2008年5月16日金曜日

Springフレームワークの続き(画面追加~挫折)

Springフレームワークのチュートリアルをベースに少し機能を追加してみます。
(注意:最後の一歩で頓挫しています。)
(5/23追記:Springフレームワークの続き(リベンジ)で一応成功しました)

チュートリアルでは価格を割増しするだけのビジネスロジックを作成しましたが、初期の価格を設定するロジックを追加します。
すでに存在するプロダクトのリストを取得してフォームとして画面に表示し、編集/入力された新規の価格をDBに反映するロジックです。

二つの画面が増えることになります。
1.最初の画面から変更対象選択画面への遷移
 編集画面に現在登録されているプロダクトを表示。
 変更対象選択画面では、チェックボックスで変更対象を選択することができます。
2.価格設定画面へ遷移
 '1'で選択された項目が表示されます。価格を設定することができます。

【手順概要】
Productsインターフェースに以下のメソッドを追加します。
public void setPrice(int id, int price);

SimpleProductManagerでsetPrice(int id, int price)を実装します。

SimpleProductManagerTestでsetPriceのテストを行います。

変更対象選択用Bean(SelectProduct)を追加します。

SelectInventoryコントローラを追加します。

変更対象選択画面を追加します。

springapp-servlet.xmlにエントリーを追加します。

メッセージを追加します。

リンクを追加します。

価格設定用Bean(SetPrice)を追加します。

バリデータ(SetPriceValidator)を追加します。

SetPriceFormControllerを追加します。

価格設定用画面を追加します。

springapp-servlet.xmlにエントリーを追加します。

【手順詳細】
Productsインターフェースに以下を追加します。
public void setPrice(int id, int price);

SimpleProductManagerを実装します。
 public void setPrice(int id, double price) {
List<Product> products = productDao.getProductList();
if (products != null) {
for (Product product : products) {
if (product.getId() == id) {
product.setPrice(price);
productDao.saveProduct(product);
break;
}
}
}
}

次にSimpleProductManagerTestです。
    private static int CHAIR_ID = 1;
private static int TABLE_ID = 2;
private static int POSITIVE_PRICE_INCREASE = 10;
private static int NEGATIVE_PRICE_INCREASE = 10;
(中略)
public void testSetPrice() {
productManager.setPrice(TABLE_ID, TABLE_PRICE_UPDATED);
productManager.setPrice(CHAIR_ID, CHAIR_PRICE_UPDATED);

List<Product> products = productManager.getProducts();

for (Product product: products) {
if (product.getId() == TABLE_ID) {
assertEquals(TABLE_PRICE_UPDATED, product.getPrice());
} else if (product.getId() == CHAIR_ID) {
assertEquals(CHAIR_PRICE_UPDATED, product.getPrice());
}
}
}

次に変更対象選択用Bean(SelectProduct)を追加します。
以下のサイトを参考にしました。LazyListは使っていません。(LazyListでは動きませんでした。なぜかは未調査)
<a href="http://mattfleming.com/node/134">Dynamic list binding in Spring MVC</a>
package springapp.service;

import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import springapp.domain.Product;

public class SelectProduct {
/** Logger for this class and subclasses */
protected final Log logger = LogFactory.getLog(getClass());
//logger.info("Descriptions set to "+strs);
private List<Product> products = null;
/*
LazyList.decorate(new ArrayList(),
FactoryUtils.instantiateFactory(Product.class));
*/

public List<Product> getProducts() {
return products;
}
public void setProducts(List<Product> products) {
this.products = products;
}
}

SelectInventoryFormControllerを追加します。
package springapp.web;

import org.springframework.web.servlet.mvc.SimpleFormController;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.RedirectView;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;

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

import springapp.service.ProductManager;
import springapp.service.SelectProduct;


public class SelectInventoryFormController extends SimpleFormController {
/** Logger for this class and subclasses */
protected final Log logger = LogFactory.getLog(getClass());

private ProductManager productManager;

protected Object formBackingObject(HttpServletRequest request) throws ServletException {
SelectProduct selectProduct = new SelectProduct();
logger.info("productManager:"+productManager.getProducts().size());
selectProduct.setProducts(productManager.getProducts());
logger.info("selectProd:"+selectProduct.getProducts().size());
return selectProduct;
}
public ModelAndView onSubmit(Object command)
throws ServletException {

logger.info("Executed.");

return new ModelAndView(new RedirectView(getSuccessView()));
}

public void setProductManager(ProductManager productManager) {
this.productManager = productManager;
}

public ProductManager getProductManager() {
return productManager;
}
}

変更対象選択画面(selectproduct.htm)を追加します。
<%@ include file="/WEB-INF/jsp/include.jsp" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<html>
<head><title><fmt:message key="title"/></title></head>
<body>
<h1><fmt:message key="selectproduct.heading"/></h1>
<h3>Products</h3>
<form:form method="post" commandName="setPrice">
<c:forEach items="${selectProduct.products}" var="prod" varStatus="row">
<input type="radio" id="id" name="id" value="<c:out value="${prod.id}"/>">
<c:out value="${prod.description}"/>
<i>$<c:out value="${prod.price}"/></i><br><br>
</c:forEach>
<input type="submit" align="center" value="Execute">
</form:form>
<br>
<a href="<c:url value="hello.htm"/>">back</a><br>
<br>
</body>
</html>

springapp-servlet.xmlにエントリーを追加します。
    <bean name="/selectproduct.htm" class="springapp.web.SelectInventoryFormController">
<property name="sessionForm" value="true"/>
<property name="commandName" value="selectProduct"/>
<property name="commandClass" value="springapp.service.SelectProduct"/>
<property name="formView" value="selectproduct"/>
<property name="successView" value="setprice.htm"/>
<property name="productManager" ref="productManager"/>
</bean>

メッセージを追加します。
selectproduct.heading=Select Product :: SpringApp

hello.jspにリンクを追加します。
<a href="<c:url value="selectproduct.htm"/>">Select product</a>

価格設定用Bean(SetPrice)を追加します。
package springapp.service;

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

public class SetPrice {
/** Logger for this class and subclasses */
protected final Log logger = LogFactory.getLog(getClass());
private int id;
private String description;
private double price;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
logger.info("id:"+id+" set.");
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
logger.info("description:"+description+" set.");
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
logger.info("description:"+description+" set.");
}

}

バリデータ(SetPriceValidator)を追加します。
package springapp.service;

import org.springframework.validation.Errors;
import org.springframework.validation.Validator;

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

public class SetPriceValidator implements Validator {

private int DEFAULT_MIN_PRICE = 1;
private int minPrice = DEFAULT_MIN_PRICE;

/** Logger for this class and subclasses */
protected final Log logger = LogFactory.getLog(getClass());

public boolean supports(Class clazz) {
return SetPrice.class.equals(clazz);
}

public void validate(Object obj, Errors errors) {
SetPrice ps = (SetPrice) obj;
if (ps == null) {
errors.rejectValue("price", "error.not-specified", null, "Value required.");
}
else {
logger.info("Validating with " + ps + ": " + ps.getPrice());
double price = ps.getPrice();
if (price <= minPrice) {
errors.rejectValue("price", "error.too-cheape",
new Object[] {new Integer(minPrice)}, "Value too low.");
}
}
}

public void setMinPrice(int i) {
minPrice = i;
}

public int getMinPercentage() {
return minPrice;
}

public void setMaxPercentage(int i) {
minPrice = i;
}

public int getMaxPercentage() {
return minPrice;
}

}

SetPriceFormControllerを追加します。
package springapp.web;

import java.util.Enumeration;

import org.springframework.beans.factory.parsing.ParseState.Entry;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.servlet.mvc.SimpleFormController;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.RedirectView;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;

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

import springapp.domain.Product;
import springapp.service.ProductManager;
import springapp.service.PriceIncrease;
import springapp.service.SetPrice;


public class SetPriceFormController extends SimpleFormController {
/** Logger for this class and subclasses */
protected final Log logger = LogFactory.getLog(getClass());

private ProductManager productManager;

public ModelAndView onSubmit(Object command)
throws ServletException {
int id = ((SetPrice) command).getId();
double price = ((SetPrice) command).getPrice();

productManager.setPrice(id, price);
logger.info(
"Update prices by " + price+ ". Id is "+id+"." );
logger.info(
"returning from PriceIncreaseForm view to " + getSuccessView());

return new ModelAndView(new RedirectView(getSuccessView()));
}

protected Object formBackingObject(HttpServletRequest request) throws ServletException {
// 結局ここで挫折しました。以下は挫折の痕跡です。
/*
SetPrice ret = new SetPrice();
Enumeration<String> enume = request.getAttributeNames();
while(enume.hasMoreElements()) {
String key = enume.nextElement();
logger.info(key+":"+request.getAttribute(key));
}
*/

logger.info(getFormSessionAttributeName(request));

String str = ServletRequestUtils
.getStringParameter(request, "id");

SetPrice ret = new SetPrice();
// 最後は決め打ちのハードコード(泣)
int i = 1;
for (Product product : productManager.getProducts()) {
if (product.getId() == i) {
ret.setId(product.getId());
ret.setDescription(product.getDescription());
ret.setPrice(product.getPrice());
break;
}
}
return ret;
}

public void setProductManager(ProductManager productManager) {
this.productManager = productManager;
}

public ProductManager getProductManager() {
return productManager;
}

}

価格設定用画面(setprice.jsp)を追加します。
<%@ include file="/WEB-INF/jsp/include.jsp" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<html>
<head>
<title><fmt:message key="title"/></title>
<style>
.error { color: red; }
</style>
</head>
<body>
<h1><fmt:message key="setprice.heading"/></h1>
<form:form method="post" commandName="setPrice">
<c:out value="${setPrice.description}"/>
<form:input path="price"/>
<form:errors path="price" cssClass="error"/>
<br>
<input type="submit" align="center" value="Execute">
</form:form>
<a href="<c:url value="hello.htm"/>">Home</a>
</body>
</html>


springapp-servlet.xmlにエントリーを追加します。
    <bean name="/setprice.htm" class="springapp.web.SetPriceFormController">
<property name="sessionForm" value="true"/>
<property name="commandName" value="setPrice"/>
<property name="commandClass" value="springapp.service.SetPrice"/>
<property name="validator">
<bean class="springapp.service.SetPriceValidator"/>
</property>
<property name="formView" value="setprice"/>
<property name="successView" value="hello.htm"/>
<property name="productManager" ref="productManager"/>
</bean>


以上ですが、残念ながら途中で頓挫してしまいました。

DAOと連携したDBの更新は成功するのですが(IDハードコードで確認済み)
一覧から更新対象を選択し、次の画面にIDを渡すことがどうしても出来ませんでした。
formBackingObject(HttpServletRequest request)で、requestからradioの値を引っ張ることができません。悔しいのですが二日間チャレンジして出来なかったので、これで諦めます。

#なんで "String str = ServletRequestUtils.getStringParameter(request, "id");"でPOSTされたidが取れないんだ!!!

(5/23追記)SimpleFormControllerはrequestをクリアしてしまうようですね。
"id"は前の画面のFormにちゃんと入っていました。これを生のHttpSessionに登録し、次画面で受け取ることができます。Spring的にキレイではないとは思いますが。

以上。

0 件のコメント: