星期日, 4月 24, 2005

Simplied Struts DispatchAction

Struts 的 Dispatch Action 已經行之有年了,有 Dispatch, Lookup, MappingDispatch... 好幾種可以用。但設定方法都很囉嗦 (網頁,程式,struts-config三者都要 hard-code String 在上面),而且適用的了 A ,就不能用在 B。例如雖然 DispatchAction 可以寫在 URL 上: /foo.do?method=saveOrder ,但是遇到一個 form 需要利用多個 submit button dispatch 時,就不行了。你得轉用 LookupDispatch,可是 LookupDispatch 又需要在 Action 定義一個很醜的 getKeyMethodMap() (裡面全部是 hard-code string)。接下來要分享一個通用 URL/submit button/struts-config.xml 三者設定的 SimpleDispatchAction:

package org.bioinfo.util.struts;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

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

import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.springframework.web.struts.ActionSupport;
/**
* 簡化的 DispatchAction,並支援Spring的 ActionSupport (如不需要可以換回 Action)
*
* 使用方法:
*
* 繼承此 class,並定義各個 action method,比方說有兩個 method 分別做儲存和刪除:
*
* <code>
* public ActionForward save(ActionMapping mapping, ActionForm form,
* HttpServletRequest request, HttpServletResponse response)
* throws Exception {
* // orderService.save(....)
* }
*
* public ActionForward delete(ActionMapping mapping, ActionForm form,
* HttpServletRequest request, HttpServletResponse response)
* throws Exception {
* // orderService.delete(....)
* }
* </code>
*
* 有三種方式可以設定 dispatch 到哪個 method 上。
*
* (1) submit button 法,直接寫在 submit 的 property 上:
*
* <code>
* <html:form action="/some/work">
* .... some thing ....
*
* <html:submit property="dispatch=save" value="儲存"/>
* <html:submit property="dispatch=delete" value="刪除"/>
* </html:form>
* </code>
*
* 注意 property 裡面的值是 'dispatch=xxxx' 記得要寫等號與 method 名稱,
* 而且大小寫要對,不能空白。當網頁按下 "儲存" 時,則會執行 /some/work.do
* 的 save(...) 的 method。 如果按下 "刪除" 則執行 delete(...),
*
* 建議 -- 這種寫法通常是用在一個 Action 有多個 dispatch method,而每個 method
* 都共用同個 ActionForm
*
* (2) URL 法,接在 URL 後面:
*
* <code>
* <html:form action="/some/work?dispatch=save">
* 或是用 link 也可以
* <html:link action="/some/work?dispatch=save" />
* </code>
*
* 建議 -- 通常用在不需要 ActionForm 的 Action,或者是要將 submit button 法
* 寫成 url 時使用。
*
* (3) struts-config 法,直接寫死在 parameter='dispatch=foo' 上
*
* <code>
* <action
* path="/saveOrder"
* name="SaveOrderForm"
* type="antar.order.web.OrderDispatchAction"
* parameter="dispatch=save" >
* </action>
* <action
* path="/deleteOrder"
* name="DeleteOrderForm"
* type="antar.order.web.OrderDispatchAction"
* parameter="dispatch=delete" >
* </action>
* </code>
*
* 建議 -- 這種寫法通常是為了讓 Action 中每個 dispatch method 使用不同
* 的 ActionForm。一旦寫死在 struts-config 裡,該 mapping 的 path 就不能
* 與 URL 法 或是 submit button 法同時使用。
*
* 最後請注意同一個 request 上,URL 法不能與 submit button 法同時使用:
*
* <code>
* ...........錯誤範例...........
* <html:form action="/some/work?dispatch=save">
* .... some thing ....
* <html:submit property="dispatch=delete" value="刪除"/>
* </html:form>
* </code>
*
* @author ingram
*
*/
public abstract class SimpleDispatchAction extends ActionSupport {

private static final String KEY_VALUE_SEPERATOR = "=";

private final static String KEY = "dispatch";

private static final String VALID_PARAMETER_NAME_PATTERN = KEY + "\\"
+ KEY_VALUE_SEPERATOR + "[a-zA-Z0-9_]+";

private Class clazz = this.getClass();

private Class[] argTypes = new Class[] { ActionMapping.class,
ActionForm.class, HttpServletRequest.class,
HttpServletResponse.class };

private Map dispatchMethods = new HashMap();

public final ActionForward execute(ActionMapping mapping,
ActionForm form, HttpServletRequest request,
HttpServletResponse response) throws Exception {

String methodName = getMethodName(request, mapping);

Method method = null;
try {
Object[] args = { mapping, form, request, response };
method = obtainDispatchMethod(methodName);
return (ActionForward) method.invoke(this, args);
} catch (NoSuchMethodException e) {
throw dealWithMethodProblem(methodName, e);
} catch (IllegalAccessException e) {
throw dealWithMethodProblem(methodName, e);
} catch (InvocationTargetException e) {
throw dealWithMethodProblem(methodName, e);
}

}

private RuntimeException dealWithMethodProblem(String methodName,
Exception e) {
return new RuntimeException(
"can not access dispatching method:["
+ methodName
+ "]. ", e);
}

private Method obtainDispatchMethod(String methodName)
throws NoSuchMethodException {
Method method = (Method) dispatchMethods.get(methodName);
if (method == null) {
method = clazz.getMethod(methodName, argTypes);
dispatchMethods.put(methodName, method);
}
return method;
}

static String getMethodName(HttpServletRequest request,
ActionMapping mapping) {

final List gatherAllMethodNames = new ArrayList();
for (Enumeration e = request.getParameterNames();
e.hasMoreElements();) {
String parameterName = (String) e.nextElement();
addMatchedParameter(gatherAllMethodNames, parameterName);
}

final String[] values = request.getParameterValues(KEY);
if (values != null) {
for (int i = 0; i < values.length; i++) {
gatherAllMethodNames.add(values[i]);
}
}

if (mapping.getParameter() != null) {
addMatchedParameter(gatherAllMethodNames
, mapping.getParameter());
}

if (gatherAllMethodNames.isEmpty()) {
throw new IllegalArgumentException(
"no 'dispatch=methodName' found in parameter");
} else if (gatherAllMethodNames.size() > 1) {
throw new IllegalArgumentException(
"\nMultiple dispatch parameter: " + gatherAllMethodNames
+ " Only one parameter is allowed.");
} else {
return (String) gatherAllMethodNames.iterator().next();
}
}

private static void addMatchedParameter(List gatherAllMethodNames,
String parameterName) {
if (parameterName.matches(VALID_PARAMETER_NAME_PATTERN)) {
gatherAllMethodNames
.add(parameterName.split(KEY_VALUE_SEPERATOR)[1]);
}
}
}
嘿嘿,有了這個統合的 Dispatch,日子就好過多啦!我也另外用同樣的邏輯寫了個 SimpleDispatchActionForm,搭配起來用不錯。

0 Comments:

張貼留言

<< Home