JSP版本系統開發流程
我們現在舉一個登入系統的畫面及其開發流程來做為一個簡單的範例說明,使用者輸入使用者名稱及密碼後,按下登入的按鈕,在Client端將會先檢查使用者帳號和密碼是否有輸入,若否則顯示錯誤訊息,並要使用者重新輸入。檢查之後會將內容送往Server,執行LOGIN這個流程的設定,若登入成功,則畫面將轉入系統的操作畫面,否則仍停留在登入畫面,並顯示錯誤訊息。
在範例內讀者將會學會下列幾項能力:
- 運用jBrick所提供的Taglib及JavaScript來設計畫面。
- 編輯流程所要執行的程序。
- 編寫程序中存取資料庫的程式碼。
- 處理查詢資料後的結果。
- 依處理後的結果做頁面的轉換。
1. JSP畫面的開發
由於整個JSP原始檔還包含元件排列位置及美工圖片,在以下說明僅節錄部份程式碼說明,完整程式碼請讀者自行下載。
- 宣告所要使用的JavaScript
<ecity:script function="validator"/>
在jbrick有提供基本的Validator或Event,當要引用這些Function時要透過Script的Tag來引用,function所傳的參數就是所要引用的JavaScript的檔名,這些檔案都會放在function這個目錄下。 - 放置一個Form的Tag
<ecity:Form id="MAIN"
name="MAIN"
sourcefilename="/login/login.jsp"
targetcomponent=""
executecommand="LOGIN_CHECK">
…
</ecity:Form>
在HTML中當UI的資料要傳至Server去時,要用form的tag來將所要傳送的資料欄位包起來,在jbrick中因為需要額外再傳送一些資訊,所以提供了Form的tag lib來替代原本HTML中所提供的form。- name與id:設定這個Form Tag的name和id以使JavaScript可以取得這個Form的Object。
- executeCommand:設定當使用者按下submit按鈕時,在Application Server上所要執行的流程名稱。
- targetComponent:如果此一執行的流程會將查詢結果往Client送時,設定送回來後所要承接資料的元件名稱(可以是一個Group)。
- sourceFileName:設定此一jsp檔案所在的路徑及檔名。
- 放置輸入的元件(TextField和Password)
首先放置一個TextField來供使用者輸入使用者帳號,其程式碼如下:
<ecity:TextFieldX name="USERID"
datafield="USERID"
styleclass="input"
required="true"
datatype="Require"
errormessage="Please Input UserName"/>
以下為屬性說明:- name:設定這個欄位傳至Application Server時,要將值放至那個名稱內。
- dataField:當這個欄位會接收Application Server回傳回來的結果時,所要讀取的值的名稱。
- styleClass:當使用CSS來設定畫面上元件的Style時,可用這個參數來設定所要讀取的CSS設定的class。
- required:設定此一元件是否必須輸入(不可為空白)。
- dataType:設定一資料的種類,可有datatime、email、phone…等種類,Validator會依據這?的設定來檢查資料的格式。
- errorMessage:當Validator檢查資料有錯誤時,所要顯示的錯誤訊息。
再來放置一個Password的元件來供使用者輸入密碼,其程式碼如下:
<ecity:PasswordX name="PASSWORD"
datafield="PASSWORD"
styleclass="input"
required="true"
datatype="Require"
errormessage="Please Input Password"/>
屬性設定說明如TextField之說明。 - 放置按鈕
放置一個按鈕,顯示的標題為”登入”,當使用者按下時會執行Submit。
<ecity:ButtonX name="submit"
title="登入"
styleClass="button"
inputType="submit"
onClick="Validator.Validate('MAIN',1,0);"/>
以下為屬性設定的說明:- name:設定這個欄位傳至Application Server時,要將值放至那個名稱內。
- title:這個按鈕所要顯示的標題內容。
- styleClass:當使用CSS來設定畫面上元件的Style時,可用這個參數來設定所要讀取的CSS設定的class。
- inputType設定按鈕的類別,可有button、submit、reset。
- onClick:當使用者按下按鈕時所要執行的Function(JavaScript的Function名稱)。
放置一個按鈕,顯示的標題為”取消”,當使用者按下時會將輸入欄位清空。
<ecity:ButtonX name="reset"
title="取消"
styleClass="button"
inputType="reset"/>
其屬性說明如"登入"之按鈕。 - 放置顯示錯誤訊息的元件
當Server執行登入的程序有問題時,將會回傳錯誤訊息,而這個元件將會把錯誤訊息顯示在畫面上。
<ecity:LabelX dataField="ERROR_MSG"
styleClass="error_message"/>
以上為屬性設定之說明:- dataField:當這個欄位會接收Application Server回傳回來的結果時,所要讀取的值的名稱。
- styleClass:當使用CSS來設定畫面上元件的Style時,可用這個參數來設定所要讀取的CSS設定的class。
2. Workflow Configure的編寫
我們就以上圖的Flow來做為一個登入流程的範例。在流程的第一個節點我們做帳號和密碼的檢查,檢查帳號是否存在,若不存在則回傳"使用者不存在"的錯誤訊息,再來檢查密碼是否正確,若不正確則回傳"密碼錯誤"的錯誤訊息。當這些檢查都通過時,則以使用者所能使用的功能來做使用者選單的初使化。
以下是LOGIN_CHECK.xml的內容
<workflow>
<start-node>LOGIN_CHECK</start-node>
<end-node>DISPATCH_PAGE</end-node>
<resource-bundle>com.hclife</resource-bundle>
<database>HCLIFE</database>
<node>
<name>LOGIN_CHECK</name>
<type>com.jbrick.workflow.DecisionNode</type>
<command>com.hclife.login.LoginCheck</command>
<contain-node>OK~INIT_MENU;NG~DISPATCH_PAGE</contain-node>
</node>
<node>
<name>INIT_MENU</name>
<type>com.jbrick.workflow.ProcessNode</type>
<command>com.hclife.login.InitMenu</command>
<child>DISPATCH_PAGE</child>
</node>
<node>
<name>DISPATCH_PAGE</name>
<type>com.jbrick.workflow.ProcessNode</type>
<command>com.ecity.workflow.PageDispatcher</command>
<child></child>
</node>
<dispatch-page>
<value>OK</value>
<page>/module/mainform.jsp</page>
</dispatch-page>
<dispatch-page>
<value>NG</value>
<page>/login/login.jsp</page>
</dispatch-page>
<session-variable>
<name>USERID</name>
<name>PASSWORD</name>
</session-variable>
</workflow>
以下就設定的內容說明(更詳細之說明會於Workflow的說明中詳述)
- 流程資訊設定
設定內容:
<start-node>LOGIN_CHECK</start-node>
<end-node>DISPATCH_PAGE</end-node>
<resource-bundle>com.hclife</resource-bundle>
<database>HCLIFE</database>
以下為本段設定之說明:- start-node
設定整個流程的起始節點,此為節點名稱 - end-node
設定流程的結束節點,當整個流程有做Transaction Managememt時,當中間節點執行錯誤時,會跳至此節點,然後將整個Transaction Rollback。 - resource-bundle
設定Resource所在的目錄(如Database.properties, CommandMapping.properties...等) - database
本流程所要使用的資料庫的資源設定(包含資料庫名稱,使用者帳號,使用者名稱,連接方式...等)
- start-node
- 決策節點設定
節點內容:
<node>
<name>LOGIN_CHECK</name>
<type>com.jbrick.workflow.DecisionNode</type>
<command>com.hclife.login.LoginCheck</command>
<contain-node>OK~INIT_MENU;NG~DISPATCH_PAGE</contain-node>
</node>
以下為屬性說明:- name
節點名稱,整個流程執行時會由此一名稱取得所要執行的命令 - type
節點的類別,有決策節點(DecisionNode),執行節點(ProcessNode),平行執行節點(ParallelNode)。 - command
此一節點所要執行的命令(符合ServerCommand介面的Class均可被加入) - contain-node
當此節點為決策節點時,則設定執行的結果之後所要執行的節點,在這個例子中,如果回傳值為OK時則執行INIT_MENU這個節點,如果為NG時,則執行DISPATCH_PAGE這個節點
- name
- 執行節點設定
節點內容:
<node>
<name>INIT_MENU</name>
<type>com.jbrick.workflow.ProcessNode</type>
<command>com.hclife.login.InitMenu</command>
<child>DISPATCH_PAGE</child>
</node>
以下為屬性說明:- name
節點名稱,整個流程執行時會由此一名稱取得所要執行的命令 - type
節點的類別,有決策節點(DecisionNode),執行節點(ProcessNode),平行執行節點(ParallelNode)。 - command
此一節點所要執行的命令(符合ServerCommand介面的Class均可被加入) - child
此節點執行完後,下一個所要執行的節點名稱
- name
- 頁面重導設定
節點內容:
<dispatch-page>
<value>OK</value>
<page>/module/mainform.jsp</page>
</dispatch-page>
<dispatch-page>
<value>NG</value>
<page>/login/login.jsp</page>
</dispatch-page>
以下為屬性說明:- value
檢查執行後的回傳值是否符合這一個節點的設定值,若符合則將頁面導向page所設定的頁面。
在本範例中,若回傳值為OK時則會導向/module/mainform.jsp - page
所要導向的頁面(包含路徑及檔名)
- value
- 變數保存設定
當此一流程執行之後,有某些變數是要被保存下來以供其他頁面使用時,則在此做設定,若無則可忽略此一部份
節點內容:
<session-variable>
<name>USERID</name>
<name>PASSWORD</name>
</session-variable>
說明:
在本範例中,會將USERID及PASSWORD保存在Session中,以供其他頁面使用。
3.Server Command的編寫
在此舉LoginCheck這個命令為範例(為說明此一介面的Method,將一些流程的內容寫入此一程式碼,在更具彈性的寫法應將判斷使用者存不存在及密碼正不正確編寫在Workflow Configure內)。DatabaseCommand為一個套用Templete Pattern(GoF Design Pattern)的一個類別,在execute的方法中會依續執行beforeExecute, generateSQL, executeSQL, afterExecute四個方法。
package com.hclife.login;
import java.sql.Connection;
import com.hclife.cfg.HCLIFE;
import com.ecity.cmd.QueryCommand;
import com.ecity.model.Model;
public class LoginCheck extends QueryCommand
{
/* 當不使用Workflow Engine所提供的Transaction Management時,
會套用此一建構式 */
public LoginCheck()
{
super("HCLIFE");
}
/* 當使用Workflow Engine所提供的Transaction Management時,
會套用此一建構式,並且將自動結束Connection設定為false,
Workflow Engine會在流程結束時結束Connection */
public LoginCheck(Connection con)
{
super(con);
setAutoCloseConnection(false);
}
/* 檢查使用者帳號是否為空白或空值, 若是則返回此一命令並不往下執行 */
public int beforeExecute(Model model)
{
if(model.getValue("USERID") == null ||
model.getValue("USERID").equals(""))
return RETURN_TO_CLIENT;
return CONTINUE_PROCESS;
}
/* 將所要執行的SQL Statement寫在這一個方法內,
SQL Statement寫法與一邊資料庫的寫法相同, 只是變數名稱會以冒號開頭
DatabaseCommand會將:Variable所對應至model內的值,
來組合SQL Statement, 有加單引號表示為字串類別, 否則為數字 */
public void generateSQL(Model model)
{
String strSQL;
strSQL = "SELECT userid, password FROM " + HCLIFE.MBR_USER +
" WHERE userid = ':USERID'" +
" AND activeflg = 'Y'";
addSQL(getTarget(model,"USER"),
strSQL,
DATA_MODEL);
}
public int afterExecute(Model model)
{
/* 首先檢查執行結果所回傳的資料筆數, 若為0筆則代表使用者並不存在,
此時將回傳錯誤訊息, 並將使用者帳號和密碼清空,
並將回傳值設為NG */
if(model.getRowCount("USER.USERID") == 0)
{
model.setValue("ERROR_MSG","User Name not Exist");
model.setValue("USERID","");
model.setValue("PASSWORD","");
model.setReturnValue("NG");
return CONTINUE_PROCESS;
}
/* 檢查執行結果所回傳的密碼是否與使用者輸入的一致,
若否則代表密碼錯誤,
此時將回傳錯誤訊息, 並將使用者帳號和密碼清空,
並將回傳值設為NG */
if(!model.getValue("USER[0].PASSWORD").toString().equals(model.getValue("PASSWORD").toString()))
{
model.setValue("ERROR_MSG","Password Error");
model.setValue("PASSWORD","");
model.setReturnValue("NG");
return CONTINUE_PROCESS;
}
/* 檢查均無問題, 則將回傳值設定為OK */
model.setReturnValue("OK");
return CONTINUE_PROCESS;
}
}