2009年11月21日 星期六

[Eclipse] 好用的 Task List

哇~好久沒有寫文章了!最近實在太忙碌了!不過忙碌之中也發現一些好用的技巧可以提供工作的效率喔!這也許你已經知道了,但容許我在說一次!

正式開始工作後才發現,使用者需求如雪片般飛來,在加上手邊又有很多系統要弄,一堆事情夾雜在一起就會發現,修了這個系統卻忘了另一個系統要改哪些,導致工作效率就下降了~荷包就跟著縮水,年終相對減少,經濟壓力就變大....扯太多了!

有太多工作要作,我們可以用傳統的紙筆來紀錄,那...紙髒了、掉了、濕了、字看不清楚了...工作效率就下降了~...進入一種無窮迴圈!接下來提供的方法也許不是最好,不過對我來說卻很方便 - Task List,先說好以下的前提是:針對程式碼的部份~

當我們使用 Eclipse 編輯程式時,我們可以寫註解(廢話~),不過有一些註解對於 Eclipse 來說是有特別處理的,例如:TODO、FIXME 等。當我要開始修改或開發某些程式片段前,我都會先註記 TODO,也就 class 之前或 method 之前加上 //TODO ,當某個程式我現在沒時間思考問題點在哪,但是就是有問題時,我就會註記 //FIXME ,這樣的紀錄 Eclipse 可以幫我們整理出來 workspace 中所有 project 的註記,這樣我們就可以很快的回想自己的工作清單,從 Window -> Show View -> Others 來開啟 Tasks View,如右圖:


從 Tasks View 中我們可以看到一堆已經被註記的工作清單,如左圖。直接對 task 點兩下就可以直接開啟對應的程式碼片段,這樣我們就可以很容易的紀錄我們之前是作到哪了!

這工具對我來說挺不錯用的!尤其在工作越來越多的時候更是一個好得工作利器!與大家分享之~

2009年10月11日 星期日

[Struts2] Other UI Tags

在前兩篇的文章中,我們提到了許多的 UI tags,包含基本的Collection-based,在這裡我將一些比較不屬於前兩種,但是也是有用的 tags 加以介紹,這應該算是 bonus!以下將介紹三種 tags:1) Label tag、2) Hidden tag 與 3) Double Select tag。

1) Label Tag
Label tag 主要是用來顯示某項資料,並且讓顯示的結果與其他的 UI tags 有相同的格式,也就是以同樣的 theme 來顯示,雖然我們可使用 property tag 來幫助我們顯示,不過這樣就無法與其他 UI tags 使用相同的 theme 了!
在使用 Label tag 時,我們只需要設定 name 與 label attributes 就可以簡單的將 data 帶出!不過因為 Label tag 只是顯示資料,所以資料只會被帶出,不會被帶入!以下就是簡單的例子:
<s:label name="user.name" label="Your Name" />

由於 Label tag 的使用是非常簡單的,我們就不再多說了!

2) Hidden Tag
Hidden tag 的使用也很簡單,此 tag 最主要是希望將某些資料被 form 送出但不要讓使用者知道,在某些情況下是很好用的,例如我們希望使用者填寫 form 的時間不要超過 10 分鐘,我們可以使用 Hidden tag 來紀錄時間,當使用者送出該表單時,後端在將目前時間與 Hidden tag 所送來的時間進行判斷。
由於 Hidden tag 使用方式容易,以下就舉一個例子示範:
<s:hidden name="timestamp" />

3) Double Select Tag

Double Select Tag 算是一個很神奇的 UI tag,因為他提供了一種很特別的功能。讓我們想像一個情況,當我們在註冊某個網站時,我們應該常常會遇到要填寫住址,比較好得網站會給我們兩個下拉選單,一個選擇縣市,另一個則是顯示該縣市對應的鄉鎮市區!對於 programmers 來說,要達到這樣的功能,不外乎採用的是 Javascript 的技術,不過是 synchronize 或 asynchronize,我們都要依靠 Javascript 的幫助,但是有時候我們又不擅長撰寫 Javascript,所以 Double Select tag 就因應而生。
Double Select tag 提供了兩個下拉選單,第一個選擇後,第二個選單會根據第一個選單的選擇來產生第二個選單的內容,就如同我在上面所描述的情況一樣!對於 Double Select tag 來說,list, listKey 與 listValue attributes 就相同於 Select tag 的使用,只是 Double Select tag 多了以下的一些 attributes:




























AttributeTypeDescription
doubleNameString儲存第二個下拉選單的選擇值
doubleListCollection-based第二個下拉選單
doubleListKeyString第二個下拉選單所產生 option 的 key
doubleListValueString第二個下拉選單所產生 option 的顯示

除了原先在 Select tag 中我們就使用過得 list, listKey 與 listValue 之外,所有想要設定 Double Select tag 的第二個下拉選單,attributes 的開頭都是以 double 為首。上面中比較重要的是 doubleName attribute,因為他是必填的選項,doubleName attribute 的目的在於儲存使用者選擇第二個下拉選單的內容,就如同 name attribute 是儲存第一個下拉選單的結果一樣。
那我們要如何讓第二個下拉選單可以動態呢?有兩種方法,第一種是將第二個下拉選單,透過 OGNL 方式寫在 double select tag 中:
<s:doubleSelect name="first"
list="beanList"
listKey="beanId" listValue="beanValue"

doubleName="second"
doubleList="%{top.beanId==1?{'1.1','1.2'}:{'2.1','2.2'}}"
/>
這種方式就必須將第二個下拉選單的內容寫死在頁面上,如果我們需要增加不同的內容,就會顯得礙手礙腳!除非內容很單純,也不會有太多的變化,否則我是不太建議使用這樣的方式來作。第二種方式就顯得有彈性了,我們將第二個下拉選單到 Action 中取得,剩下的就交由 Struts2 framework 幫我們用 javascript 來自動切換:
<s:doubleSelect name="first"
list="beanList"
listKey="beanId" listValue="beanValue"

doubleName="second"
doubleList="nextList(top.beanId)"
doubleListKey="nextId"
doubleListValue="nextValue"
/>
這種方式就代表我們的 Action 中有一個 nextList() method,接受 beanId 的資料型態。也就是如果 beanId 是一個 String,則此 method 就會是 nextList(String next)。當然,我們在這裡回傳的第二個下拉選單內容是以一個 List 來包裝一堆的 JavaBean。透過 nextList,我們可以根據不同的 parameter 內容來回應不一樣的下拉選單內容,達到動態的效果。
如果你仔細觀察就會發現,double select 看似有 AJAX 的動態取內容的效果,其實卻不然,當 double select tag 被執行時,framework 會先迭代 list (也就是第一個下拉選單的內容),並且逐一取得所有的第二個下拉選單內容,再將這些內容組織到 javascript 中!也就是如果第一個下拉選單中有 10 個項目,那我們的 nextList() method 就會先被呼叫 10 次!
另外,讓我們回到上面的範例中,你應該有發現 OGNL 中的 top 關鍵字吧!這是用來儲存第一個下拉選單所選到的物件,就如同我們的範例,我們每一個物件都是一個 JavaBean,所以我們就可以用 OGNL 的特性,將 bean 中的 property 傳遞給 nextList() method!

在這裡我介紹了三個很特殊,但不一定常用到的 tags,Struts2 framework 提供了很多種神奇的 tags,讓 programmer 可以很容易的撰寫出複雜的表單畫面,不過關方網站的說明實在是很少,所以要使用這些 tags 的代價就是必須自己先 survey 過!

P.S. 這篇內容原本要在 8/11 完成,沒想要拖了整整 2 個月,中間歷經了當兵,現在又忙於工作,閒暇之餘趕緊完成~希望大家多多指教~

2009年7月29日 星期三

[Struts2] Collection-based UI Tags

除了之前比較簡單的 UI tags 之外,Struts2 framework 當然也會提供一些比較複雜的 tags,不然就無法符合使用者的需求了!在這裡我們將會介紹三種以 Collection 為基礎的 UI tags:1) select tag、2) radio tag 與 3) checkbox list tag。

這裡所謂的 Collection-based 就是在 Java 端的 property 是以 Collection 為主,如 List、Map 等,而這些 property 要如何在頁面上顯示,就是這裡的重點了!同樣的,在這裡我只會介紹每個 tag 特殊的 attributes,而共用的 attributes 就請參考 Simple UI Tags 的共同 attributes。

1) Select Tag
Select Tag 對於 Collection-based UI Tags 來說,應該是最常使用到的 tag!因為下拉式選單是最常見到的顯示方式,下面是最簡單的範例:
<s:select name="user.name" list="{'Silver','Brian','Mike'}" />

我們使用到 OGNL 的 List 型態,並且由 OGNL 幫我們快速建立。name attribute 就是該選單選到的值要被儲存到的目標 property,而 list 則是我們要顯示在頁面的 Collection-based 物件。以下就是 select tag 的 attributes:
















































AttributeTypeDescription
listCollection-based type, List, Map, Array or Iterator用來產生 select tag 的 option list
listKeyString根據 list attribute 中,指定 property 用來產生 option tag 的 key
listValueString根據 list attribute 中,指定 property 用來產生 option tag 的 value
headerKeyString類似於 listKey,產生在 Collection 之前
headerValueString類似於 listValue,產生在 Collection 之前
emptyOptionBoolean指定是否自動產生空的 option 在 header 之前
multipleBoolean是否可以多重選擇
sizeInteger指定一次顯示的筆數,會有 scroll bar 輔助多餘的 option


上述的 attributes 中,比較需要注意的是 list, listKey 與 listValue 這三個,list 是用來設定我們要顯示的 Collection-based property,在這裡要注意的是 list attribute 的型態只能接受 Collection, Array, List, Map 與 Iterator,如果型態不對就會出現錯誤!另外,listKey 則是根據 list 中所存放物件的某個 property,例如:
private List<UserBean> userList;

userList 中存放了一堆的 UserBean 物件,每個 UserBean 物件都有 name 與 age 的 property,那我們的 select tag 撰寫成:
<s:select list="userList" listKey="age" listValue="name" />

就是指定每一個 select 之下的 option 都會以 UserBean 物件中的 age 作為 key (也就是作為 option tag 中的 value attribute),而 name property 則為 option 顯示的內容。也就會變成如下:
<select>

<option value="10">Silver</option>
<option value="24">Su</option>
<option value="50">Jason</option>

</select>

從上面的結果我們也可以知道,我們的 userList 中存有三個 UserBean 物件。

而比較特別的是 headerKey 與 headerValue 這兩個 attributes,就目前為止我也搞不清楚這兩個 attributes 有什麼特別的用處,如果我們指定的 list 物件中需要增加一項資料,但我們不想動到 list 物件,那我們可以使用 headerKey 與 headerValue 來增加一項 option。而 emptyOption attribute 則是指定我們是否需要 Struts2 framework 幫我們自動產生一個空白的 option。size attribute 則是用來指定一次顯示的數量,如果我們不指定就會是一次顯示一項,這樣會比較像是下拉式選單,如果顯示數量大於 1 又小於 list 的總數量,則會多了上下的 scroll bar 幫助我們選擇。

這幾個 attribute 中比較有趣的是 multiple,以往的下拉式選單都是多選一,如果我們指定 multiply="true",選單就可以呈現多選,在不指定 size attribute 之下,多選的選單就會將整個 list 展開。如果我們採用多選的選單,選單送出的 property (也就是 name attribute 所只到的 property) 必須是一個 Collection-based property,這點應該很直觀,既然可以多選,就表示我們需要用 Collection-based 物件來接收,否則就失去多選的意義了!

2) Radio Tag
使用 radio tag 就如同使用 select tag 一樣,只是少了許多 attributes 可以使用罷了:























AttributeTypeDescription
listCollection-based type, List, Map, Array or Iterator用來產生 select tag 的 option list
listKeyString根據 list attribute 中,指定 property 用來產生 radio 的 key
listValueString根據 list attribute 中,指定 property 用來產生 radio 的 value


由於使用的方式大同小異,所以我就不舉例了!而要注意的是,radio tag 一定是單選,所以 name attribute 指到的 property 可以是 primitive type 或 Collection-based type。

3) Checkbox List Tag
使用 checkboxlist tag 跟 select 也是類似,只是要注意的是,不要將 checkbox 與 checkboxlist 搞混了!checkbox 是單選,checkboxlist 是多選!而 checkboxlist 提供的 attributes 有:























AttributeTypeDescription
listCollection-based type, List, Map, Array or Iterator用來產生 select tag 的 option list
listKeyString根據 list attribute 中,指定 property 用來產生 checkbox 的 key
listValueString根據 list attribute 中,指定 property 用來產生 checkbox 的 value


不過在這裡要注意的是,因為 checkboxlist 一定是多選,所以 name attribute 指到的 property 一定是要 Collection-based type。

最後我想探討的是如何將 list 的選項初始化,由於 name attribute 是指定該元件在表單送出後的目的地,如果我們要讓某個 select tag 中的 option 被預先選起來,我們就要在相對應的 property 事先給予值,例如我們有一個 select tag 如下:
<s:select name="myFavorite" list="favoriteList"
listKey="key" listValue="value" />
這個 select tag 的目的地是 myFavorite property,如果我們希望 list 中某個 option 先被選起來,我們就將 favoriteList 物件中的該項目的 key 填入 myFavorite property,這樣 Struts2 framework 就會幫我們選起該 option 了!總之記得,資料怎麼來就怎麼去!

2009年7月16日 星期四

[Struts2] Simple UI Tags

之前我們介紹了一些 tags,主要都是輔助 programmers 完成頁面上資料的存取,在這裡我將開始描述關於顯示在頁面上與使用者互動的表單 (form)。不過,我先簡介一些共同有的 attributes,因為這些 attributes 如果在介紹 tag 時重複敘述,感覺過於累贅!

共同 attributes

共同的 attributes 就是所有的 tags 都會擁有的,為了之後將重點放在各個 tags 上,我們就將這些 attributes 分開來介紹,這裡要注意的是,如果某個 attribute 的 type 屬於 String,那就表示該 attribute 的 value 只能接受字串,如果想要將 OGNL expression language 寫入,就必須在 expression 加上 %{} 符號 - %{ expression } - 這樣 Struts2 framework 就可以正確解析 OGNL 了!如果該 type 屬於 Object,那你就可以大方的將 OGNL expression 置入,無須加上 %{} 符號了!

































































AttributeTypeDescription
nameString用來設定 form 元件,並且會依照 name 所設定的值,將其對應到 Action 中的 property
valueObject用來設定 form 元件中所包含的值
keyStringi18n 用
labelString設定某個 form 元件顯示的字串
labelpositionString設定 label 顯示的位置:left 或 top
requiredBoolean如果設定為 true,則該 form 元件的 label 會出現 * 符號
idString相同於 HTML 的 id 屬性,通常用於 java script 與 CSS
cssClassString相同於 HTML 的 css 屬性
cssStyleString相同於 HTML 的 style 屬性
disabledBoolean相同於 HTML 的 disabled 屬性
tabindexString相同於 HTML 的 tabindex 屬性


Simple UI tags


上面描述的是共同的 attribute,以下將介紹一些 UI 相關並基礎的 tags,對於共同的 attribute 就不會重複提及。

1) Head Tag
對於任何的 HTML 或 JSP 來說,要產生一個網頁通常都是以 <head> 開頭,對於 Struts2 提供的 <head> 來說,卻不同於一般的 HTML head tag,如果你試過 Struts2 提供的 head tag,你應該會發現,tag 本身並不作任何事情,而是在網頁中默默的幫我們加入一些檔案連結,這些檔案的連結目的在於支援其他的 tag 完成設定。在使用 Struts2 提供的 head tag 時,一定要放置於 HTML 的 head tag 之下,否則自動產生的檔案連結會放到不正確的位置而導致無法使用某些功能!
Struts 的 head tag 本身並沒有其他的 attribute 需要設定,我們可以簡單的使用:

<s:head />

這樣的撰寫就會自動幫我們產生一些檔案的連結。

2) Form Tag
Form tag 對於 UI 來說是最重要的 tag,因為他是所有 UI tag 的上層元件,如果對於 HTML 熟悉的話,form tag 的使用應該不會陌生,而且我們之前就看過蠻多的範例了!下表是一些 form tag 的 attributes (除了共同 attributes 之外):









































AttributeTypeDescription
actionString指定該 form 送出的目標 Action
namespaceString目標 Action 的 namespace,預設值是目前的 namespace
methodString相同於 HTML 的 method attribute,預設值是 POST
targetString相同於 HTML 的 target attribute
enctypeString若要使用檔案上傳,則填入 multipart/form-data
validateBoolean設定該 form 是否使用 javascript 驗證,當使用 Validation Framework 時,此 attribute 要設定為 true,驗證功能才能正常運作

在上述眾多 attributes 中,最重要的莫過於 action 了!因為他主導整個 form 的目標 action,在這裡設定 action attribute 時要注意,我們無須填入 .action 的縮寫,因為 form 元件會幫我們完成,如果目標的 action 與現在是相同的 namespace,我們就無須填寫 namespace,否則我們就需要 namespace attribute 幫助我們找到 Action。每當 form 元件被執行時會有以下三種可能發生的狀況:
  1. 如果 action attribute 沒有被設定,則 form 的目標就會指向目前的 Action
  2. 如果有指定 action attribute,form 就會根據 Action 的位置配合上 namespace 來設定目標。如果 namespace 沒有被指定,就會使用目前的 namespace。當我們在設定 action attribute 時,我們無須加上 .action 的延伸網址
  3. 如果 action attribute 所指定的值不屬於一個 Action,form 元件的目標就會直接將該值作為目標。這種情況下,我們就需要自己加上延伸網址 (如:.jsp, .html 等),並且我們的相對位址需要以 / 作為開頭,例如:/myApp/b.jsp。另外要注意的是,如果我們採用此種方法,即使我們指定 namespace attribute,form 元件也會自動忽略!
3) Textfield Tag
這個 tag 對於你來說也是無可避免的!因為沒有此 tag,你就無法取得使用者的資料,所以這個 tag 也算是最常使用的!不過要注意的是 textfield tag 的 name 與 value attribute,一般來說,我們無需要指定 value attribute,因為我們只需要指定 name attribute 讓 OGNL 幫我們去 ActionContext 中取得對應的值來填入,當然,我們也可以填寫 value attribute,但是要注意的是,如果我們自己有填寫 value attribute,那 name 指定的 OGNL 所取到的值就不會顯示在 textfield tag 上了,這點要注意喔!以下是一些 textfield tag 的 attributes:


























AttributeTypeDescription
maxlengthString指定 textfield 的可輸入最大長度
readonlyBoolean如果設定為 true,則該 textfield 就無法輸入字串
sizeStringtextfield 的長度

4) Password Tag
這個 tag 跟 textfield 幾乎是一樣的,不過當使用者輸入字串時,所有的文字都會被包裝成其他的符號,以防資料外洩!這個 tag 的使用其實跟 textfield 是相同的,所以我就不多說了!以下是 password tag 的 attributes:































AttributeTypeDescription
maxlengthString指定 textfield 的可輸入最大長度
readonlyBoolean如果設定為 true,則該 textfield 就無法輸入字串
sizeStringtextfield 的長度
showPasswordBololean使否將 OGNL 取到的值顯示在 value 中,預設 false

上面列出的 attributes 中,幾乎跟 textfield 是一樣的,不過多了一個 showPassword attribute,這個 attribute 是讓我們設定是否要將 name attribute 中指定的 OGNL 取到的值顯示,在這裡我所謂的顯示不是大剌剌的用文字方式顯示,當然是有被遮蔽的顯示,不過從原始碼中還是可以看到原來的值,所以 showPassword attribute 預設值為 false,為的就是不要產生一些 security issues 來困擾 programmers,在這裡我也建議你不要將此 attribute 設定為 true!

5) Textarea Tag
同樣的,textarea 從開發角度上來說,與 textfield 並沒有太大的差別,主要就是提供使用者多行的輸入欄位!以下就是一些 textarea tag 的 attributes:































AttributeTypeDescription
colsInteger指定 textarea 的行數
rowsInteger指定 textarea 的列數
readonlyBoolean如果設定為 true,則該 textarea 就無法輸入字串
wrapString相同於 HTML 的 wrap attribute

6) Checkbox Tag
這裡的 checkbox 元件如果你認為跟 HTML 的 checkbox 一樣的話,那你就錯了!因為在 Struts2 提供的 checkbox 元件是一個單一 HTML 的 checkbox,這是什麼意思呢?一般的 HTML checkbox 提供了多個選項讓使用者選擇零或多個,但是這裡的 checkbox 只提供使用者一個選項,也就是選或不選。所以這個 checkbox 不同於 HTML 的 checkbox。這個 checkbox 只提供 Boolean 的功能,也就是說其對應的 property 一定要是一個 boolean property,如果要像 HTML 提供的 checkbox 有相同功能的話,要改使用 checkboxlist tag,現在不會提到!
以下是 checkbox tag 的 attribute:





















AttributeTypeDescription
fieldValueString這裡所填寫的 value 是真正會被傳送到 Action 中的值,主要是 true 或 false,預設值為 true
valueString這裡是用來判斷此 checkbox 是否會被勾選

看到上面的 attribute 你一定會感到很奇怪,value attribute 已經是共同的 attribute 了,為何在這裡還會在重新點出呢?原因就在於 checkbox 元件還有一個 fieldValue 這會與 value attribute 搞混!所以在這裡要特別點名。
fieldValue attribute 中的值代表當此 form 被送出後,fieldValue 中填寫的值會真正被送到 Action 中對應的 property,而 value attribute 只是用來判斷此 checkbox 是否需要被勾選。當 OGNL 取得 ValueStack 中的值,會將此值寫入 value attribute 中 (任何一個元件都是這樣),使用者可以看到此 checkbox 是否有被選起來。所以 fieldValue 與 value attribute 之間不要弄混了!
另外,checkbox 所指定的 name attribute,其對應到的 property 需要是一個 boolean 型態,否則會無法運作!

在這裡列出了六種簡單的 UI tag 給你參考,這裡列出的都是針對單一的 property,之後我將介紹關於 Collection-based 相關的 UI tags。

2009年7月8日 星期三

[Struts2] 探討 OGNL 運算功能

關於 OGNL (Object-Graph Navigation Language),我們之前已經提過相當的多了!不過那些都只是在 navigate 的部份,也就是如何將資料在 String-based HTTP 與 Java-type 之間做轉換與存取。熟悉 JSP 的 Expression Language (EL) 的 programmers 來說,EL 不僅僅提供了存取資料的功能,也提供好用的運算功能!身為比 EL 更強大的 OGNL 來說,這也是基本的配備!

OGNL and Collection

之前我們有提過 OGNL 如何在 Collection 型態的物件進行 navigate,但不只這樣喔~對於 Collection 型態的物件,OGNL 提供了一些好用的功能:1) 取得資訊、2) 快速建立 與 3) Filter and Project。

1. 取得資訊
如果是要取得 Collection 中的資料,我在內建 OGNL Type Converter 中就有提過,不過我在這裡想要說得是,如果我們想要知道 Collection 中的大小,那我們是不是要將 Collection 的大小先儲存到某個 property 中?答案當然是不用!OGNL 怎麼不會提供這樣的功能勒~不僅僅是 Collection 大小,就連判斷是否為 empty 都有支援。
如果我們要取得 Array 的大小,我們就要使用 array.length;如果是 list,則是 list.size;而 map 的話就與 list 是相同的。而 empty 的判斷只有 list 和 map 有提供,使用都是 list.isEmpty。

2. 快速建立
在 JSP 頁面中所使用的 Collection 型態物件,不見得都要屬於 Action 的 property,OGNL 提供了快速建立 Collection 物件的機制,讓 programmers 可以容易的建立 list 或 map,這對於在頁面上提供一個 select 下拉式選單來說是很方便的。不過目前我不想提到關於 select 選單的部份,主要先考慮如何快速建立 list 或 map,對於下列的 Java code 你應該不陌生:
public List<String> createList() {
List<String> list = new ArrayList<String>();
list.add("Hello");
list.add("Silver");
list.add("Welcome");
return list;
}
這段程式碼主要就是建立一個 list 並且容納我們想要的字串物件,對於 OGNL 來說,要建立一個一樣的 list,簡直是易如反掌:

{"Hello", "Silver", "Welcome"}

別懷疑自己的眼睛,沒錯!就是這麼簡單~我們只要短短的 OGNL 就可以幫我們實現出一串的 Java code。現在的你是否也想要知道 map 的建立呢?別急,我們先看看一段程式碼:
public Map<Integer, String> createMap() {
Map<Integer, String> map = new HashMap<Integer, String>();
map.put(1,"Hello");
map.put(3,"Silver");
map.put(100,"Welcome");
return map;
}
這同樣也是你很熟悉的程式碼,我想我就直接告訴你這樣的 OGNL 會是:
#{1: "Hello", 3: "Silver", 100:"Welcome"}

看到這裡,連我自己都不禁懷疑,到底 OGNL 在哪裡需要 # 符號,在哪裡又不需要呢?目前我還看不透,不過自己在使用時就記住,list 不用 # 而 map 需要~

3. Filter and Project
OGNL 除了上面兩種功能外,另外還提供了過濾 (filter) 的功能與投影 (Project),這看起來匪夷所思,實際上卻是很好用的東西。Filter 就是 OGNL 提供讓 programmers 針對 Collection 撰寫 rule 來取出我們想要的東西;Project 就是將某個 Collection 指取出部份的資料並投影到另一個 Collection 之中。這兩個東西乍看之下好像很類似,其實不然,直接舉個例子應該會比較清楚。假設我們有一個 User 物件:
public class User {
private String name;
private int age;
//getter and setter methods
}
然後,我們有一個 List 儲存一堆的 user 為 users,這次我們先展示 filter 功能,如果我們將 OGNL 撰寫成:
users.{? #this.age > 18}

則 OGNL 會幫我們將 users 中的所有 User 物件的 age 進行過濾,並且依照我們所指是的 rule 完成,而 rule 中的 #this 代表每一次 OGNL 迭代所指向的目標物件 (類似於 iterator.next())。
接下來是 project 的部份,同樣的我們使用 users 來示範,如果我們的 OGNL 為:
users.{name+' '+age}

則 OGNL 會幫我們將 users 中的所有 User 物件的 name 與 age 取出,並用一個空白字串組成新的字串,在存成一個新的 list 之中。當然,我們可以結合 filter 與 project:

users.{? #this.age > 18}.{name+' '+age}

這個結果就是上面兩個 OGNL 的結合。不知道你是否有注意到一個規則,當我們在使用 filter 時,我們的撰寫方式為:

collectionName.{? expression }

反之,當我們使用 project 時,我們的 OGNL 撰寫為:

collectionName.{ expression }

也就是 filter 的 expression 之前有加 ? 符號,而 project 沒有。

Other Functions

OGNL 不僅提供上述的功能,另外提供對於運算方面的功能以及一些特殊的功能:1) 運算功能、2) 呼叫 method 與 3) 存取 static method 與 field。

1) 運算功能
OGNL 提供了多種型態的變數讓 programmers 可以快速的宣告:
  • char - 'a'
  • String - "a" 或 'silver'
  • boolean - True, False
  • int - 123
  • BigDecimal - 123b
  • BigInteger - 123h
另外就是關於運算的部份,就如同 Expression Language(EL) 一樣,OGNL 提供了許多的運算:
  • add: 1+2, "hello"+"silver"
  • subtract: 2-1
  • multiply: 2*1
  • divide: 6/3
  • modules: 5%2
  • increment: a++, ++a
  • decrement: a--, --a
  • equality: a==b
  • less than: a<b
  • greater than: a>b
2) 呼叫 method
對於熟悉 EL 的 programmer 來說,最希望的一件事就是 EL 能夠呼叫 method,不過很可惜的,目前為止,EL 還無法做到~不過別氣餒,OGNL 身為 EL 的強化版,對於 programmer 多年的渴望終於有了回應,OGNL 提供 programmers 可以呼叫 method 的功能,而且使用上相當的簡單。假設我們的 User class 有一個 method 為 toString(),如果我們在 OGNL 想要呼要的話,只要簡單的撰寫成:

user.toString()

沒錯!就是這麼簡單,就如同我們在 Java 中撰寫呼叫 method 一樣~

3) 存取 static method 與 field
OGNL 除了針對 instance 的 field 與 method 做呼叫外,對於 static 的 field 與 method 也是可以呼叫的:

@fullClassName@[field 或 method]

不過要注意的是,我們在 struts.xml 檔案中需要在設定一個 constant:
<constant name="struts.ognl.allowStaticMethodAccess"
value="true" />

這樣我們才可以開始使用 OGNL 呼叫 static method。假設我們在 User class 中有一個 static int age,我們在 OGNL 中就可以這樣呼叫:

@silver8250.bean.User@age

要注意的部份只有 static 呼叫是要用 @ 符號來呼叫,這點記住就好了!其他的呼叫都是一樣的,當然我們可以取得某個 static 物件後,再採用原來的呼叫方式取得更進一步的 field 等:

@silver8250.bean.User@nextUser.name

NOTE:
請盡量保持 JSP 頁面的簡單,雖然 OGNL 擁有運算的能力,不過我還是建議你將這些運算移到 action 中執行,畢竟 JSP 頁面屬於 View,他只負責將 data 顯示給使用者,我們不應該將運算的動作在這裡執行,這樣會破壞 MVC 原先美好的本意!除非有必要這樣做,否則請饒了日後 maintain 的人吧~

2009年6月28日 星期日

[Struts2] Miscellaneous Tags

之前已經提到兩種類型的 Tags:Control TagsData Tags。在這裡我們所提到的 Tags 都不屬於前面所提到的兩種類型,所以我特別放在雜項 (miscellaneous) 討論:1) include tag, 2) URL tag 與 3) param tag。

Include Tag
如果你熟悉 JSP 的話,include tag 你應該不陌生,因為在 JSTL 中也有提供類似的 tag:<jsp:include>,不過,Struts2 提供的 include tag 擁有更多更好的功能,也具備了 JSTL 的 include tag 所沒有提供的能力。
之前我們有提過 Action tag,與現在的 include tag 有些許的不同:include tag 可以引用任何的網路 resources,包含其所屬的 web app 或是外部的 resources 等;而 action tag 只能引用自己本身所屬的 web app 中的 actions,對於外部的 actions 則無法引用。
Include tag 只有一個 attribute 可以使用:value,在 value 中我們可以指定 URL、也可以是某個 action 等,如果我們要傳送 parameter 給 include 的頁面,我們可以配合 param tag 使用 (稍後提到):
<s:include value="MyAction.action">
<s:param name="id" value="myID" />
</s:include>

URL Tag
對於 Struts2 framework 來說,轉換 URL 到目標的 action 是主要的工作,因為 URL 是瀏覽器需要解析的,而背後的工 action 對應就由 Struts2 framework 幫我們完成!在 Struts2 中提供了這樣的 URL tag 幫助我們轉換 URL,任何的 resources 我們都可以交由 URL tag 幫我們轉換。
URL 提供的 attributes,我們透過 URL tag 幫我們將 action 轉換成正確可執行的 URL,然後我們在嵌入 <a> 中:
<a href='<s:url action="myAction" />'>My Action</a>

如果我們要傳送 parameter 給目標的 URL,我們可以結合 param tag 並透過 var attribute 幫我們將 URL 暫存起來,最後在透過 property tag 幫我們取出暫存的 URL:
<s:url action="MyAction" var="tmpURL">
<s:param name="id" value="myID"/>
</s:url>
<a href='<s:property value="#tmpURL"/>'>My Action</a>

Param Tag

這個 tag 在之前就已經有稍微看過了,目的在於將 parameter 做傳送的動作,在這個 tag 中只有兩個 attributes:1) name 與 2) value。
我們可以想像就是將 key-value pattern 傳送給某個地方,就像我們在上面就提過的,我們可以和 include tag 或 URL tag 配合,產生更好用的功能!

在這裡我們討論到三個會用到但又不屬於特殊功能的 tags,這三個 tags 都算常用,而且很好用!

2009年6月27日 星期六

[Ontology] Ontology and OWL

有人可能問我,怎麼會突然想要寫點不一樣的東西勒?畢竟我以前都沒有紀錄過關於 Ontology相關的文章,我怎麼會想要轉寫 Ontology 呢?其實,昨天剛領到碩士畢業證書,回想起在碩士生涯的兩年裡,我主要學的就是 Ontology,想想我怎能不紀錄起來呢?這樣好像有違交了我兩年的指導教授,所以在這裡我決定紀錄起來!

-----以上都是廢話 以下才是正文-----

Ontology 是什麼?是一種概念,試圖將人類腦中與生活中的知識加以描述成電腦可以瞭解的東西,而這些知識都是相關於某個領域 (domain),並且定義出該 domain 中的概念 (concept) 與這些 concepts 之間的關聯 (relationship) 形成一個 domain ontology。所以,ontology 是一種知識的表示方式,在 AI 領域中試圖將這些知識,透過嚴謹的定義,讓電腦可以具有人類腦中的知識,提昇電腦的判斷能力。

目前針對 ontology 的描述方法不只一種,不過現在最普遍也最標準的方法是採用 Web Ontology Language (OWL),別問我為甚麼不是 WOL 而是 OWL。在我的碩士論文中,提出一套監控系統,採用 OWL 建立受監控的環境,讓電腦瞭解目前環境中的知識,在配合 Semantic Web Rule Language (SWRL) 進行語意式推論,提供更有效也更精準的危險偵測。

雖然 OWL 是以 XML 的形式儲存,而 XML 強調是一種人類與電腦都方便閱讀的格式,但如果其中包含了複雜的 tags,對於人類的閱讀也不是很方便!目前有一套圖形化工具 Protege,提供方便使用者修改 OWL,並且很容易的 maintain。透過這類型工具的協助下,我們可以很容易的撰寫出 OWL!

OWL 使知識具有語意,是來自於其本身提供的 restriction,使用 OWL 中定義的 restriction,使得我們撰寫出的 ontology 能具有嚴謹的關係,例如:inverseOf、disjoinWith 等,電腦可以透過 restriction 推導出語意關係。舉例來說:人在房間,房間屬於房子的一部分,房子與家等價,所以人在家中。這類型的推導來自於 description language (DL) 的定義,但是這樣的推導關係僅限於在現有的知識中,對於產生新的知識卻是沒有辦法的!所以又衍生出新的推論語言 SWRL,提供 first-order logic (FOL) 也就是一階邏輯,補足 OWL 中無法自行產生新知識的不足,SWRL 針對 OWL 提供比前身 RuleML 更好的結合性,讓 rule 與 knowledge 容易的結合,使得 maintain OWL+SWRL 可以更容易達成。

SWRL 其實也是一種 XML-based 的表示方法,也因為要直接撰寫出 XML 實為不易,Protege 也提供了 SWTLTab plug-in 協助使用者開發,使用者在 Protege 撰寫 SWRL 是以 FOL 形式撰寫,例如:father(x,y) ^ brother(x,z) -> uncle(z,y) 表示 x 是 y 的 father,x 與 z 是 brother,所以推論出 z 是 y 的 uncle。

在這裡我只想要淺淺的帶過 ontology 的概念,以後有機會在深入探討,不過還是提供一些有用的 reference:

Ontology Development 101: A Guide to Creating Your First Ontology
這篇論文主要描述如何設計與開發 ontology,對於初學者很有幫助,文章中並以簡單的範例來帶領我們開發一個 domain ontology。

2009年5月13日 星期三

[Struts2] Struts2 Control Tags

先前我們已經看過了 Struts2 framework 所提供的 Data tags,主要針對資料在 ActionContext 與頁面之間的移動。在這裡我們將要看到的是 Control tags,顧名思義就與控制流程相關的 tags。官方網站中列出了 9 種的 Control tags,不過在這裡我只會介紹其中的 3 種:1) iterator, 2) if 與 3) else tag。

iterator tags
iterator 對於你來說應該不陌生,這個顧名思義就是用來進行迭代的 tag。iterator tag 可以針對 Collection, Map, Enumeration, Iterator 與 array 進行迭代,並且提供目前迭代的 status 讓 programmer 可以針對狀況進行特殊行為,而此 status 會儲存在 ActionContext 中讓 programmer 呼叫使用。例如 programmer 可以根據目前迭代的 status 讓 Collection 的顯示出現單數與奇數列的資料有不同的背景顏色等等功能。下面的範例中示範了所有 iterator tag 與 status 的狀態:
<s:iterator value="list" status="listStatus">
Count:<s:property value="#listStatus.count"/><br />
Index:<s:property value="#listStatus.index"/><br />
isEven:<s:property value="#listStatus.even"/><br />
isOdd:<s:property value="#listStatus.odd"/><br />
isFirst:<s:property value="#listStatus.first"/><br />
isLast:<s:property value="#listStatus.last"/><br />
Value:<s:property/><br />
<hr />
</s:iterator>

iterator tag 透過 value attribute 取得該集合,我們並且指定將目前迭代的 status 儲存為 listStatus 變數,並且放置於 ActionContext。而 iterator status 有哪些狀態可以顯示呢?我們可以查詢 IteratorStatus class,因為這是 status 的實際物件。
上面的範例中,我們的集合內儲存的是 String 物件,所以迭代時無須指定 property,如果你的集合內儲存的是某個 JavaBean 物件,那每一次迭代的 property 就直接呼叫該 property 即可!

if, elseif and else tags

if, elseif 與 else tags 其實也很簡單,其目的就像是我們在程式語言中的 if-elseif- else 語句一樣,話不多說,直接給個範例:
<s:if test="age > 10">You are old</s:if>
<s:elseif test="age < 10">You are young</s:elseif>
<s:else>You are 10 years old</s:else>

我們在 test attribute 中利用 OGNL expression 進行條件測試,OGNL 跟 EL 一樣有提供 boolean operation 的功能。上面的範例就是測試 age property。

在這裡我們簡介了兩個 Struts2 的 Control tags,這裡的 tags 有些用法跟 JavaServer Page Standard Tag Library(JSTL) 其實是很類似的!所以有寫過 JSTL 的人應該會感到很熟悉也很容易上手。

2009年5月12日 星期二

[Struts2] Struts2 Data Tags

在之前的文章裡,我們已經看過了 Action, Interceptor 與 OGNL 等。這些主要是整個 Struts2 framework 中的核心 components,而且在 MVC 中主要 focus 在 Model 與 Controller 的角色。現在我們要切換 MVC 角色中不可或缺,對於使用者來說是很重要的 View 部份!
Struts 2 framework 中提供了很多的 tags 可以使用,我將這些 tags 中我們在這裡先介紹跟 data 相關的部份,而所謂跟 data 相關是指這些 tags 讓我們可以很方便的將資料由 ValueStack 中搬進與搬出,以下將簡介 5 個重要的 data tags:1) property tag, 2) set tag, 3) push tag, 4) bean tag 與 5) action tag。
在進入正題之前,我還是要提醒一下,要使用 Struts2 framework 的 tags 之前,我們的 JSP 頁面要先撰寫 tag directive 告知 JSP 我們要使用 Struts2 tags:
<%@ taglib uri="/struts-tags" prefix="s" %>


property tag
property tag 在之前我們已經使用過很多次了,而這個 tag 應該是整個 Struts2 framework 中最常使用到的 tag 之一。property tag 提供了一種很快速很方便的機制,讓 programmer 可以很容易的將資料由 ActionContext 中移動,而資料的移動是根據 OGNL 中撰寫的 expression 來決定,資料也會根據 OGNL 的 Type Converter 進行轉換型態。不過有一點要注意的是,如果 OGNL 指到的 Java type object 沒有對應的 converter 的話,OGNL 預設是呼叫該物件的 toString() method。
關於 property tag 中的相關設定請參考 Struts2 官方網站,在此就不贅述了!
以下是一個簡單的使用 property tag 範例:
<s:property value="user.age" />

上述的 tag 中我們撰寫 OGNL 要求將 ValueStack 中的 user.age 取出,並且轉換成 String 顯示!

set tag
對於 set 來說,意義在於將某個物件給予另一個新的名稱,有一些理由讓這個 tag 存在,例如我們如果用一個 OGNL expression 來取出資料時,expression 很長很容易撰寫錯誤時,這個 tag 就可以適時的發揮功用,減輕 programmer 的一些負擔。因為 set tag 可以將一些複雜又常用到的 OGNL 先給予新的 reference,而 programmer 在 set tag 之後就可以根據新的 reference 撰寫相對簡結的 OGNL expression。而且我們可以新的 reference 指定在 ActionContext 中的不同位置,這也可以將 data 複製到新的位置!透過 set tag 的 scope attribute 我們可以指定新的 data 要被存放在 JSP 中就有的 scope (application, session, request, and page) 或是 Struts2 中特定的 action scope。
其餘的 attribute 在這。以下就給個範例,不過我們沒有複雜的 OGNL:
<s:set name="myUser" value="user"></s:set>
Your Name:<s:property value="#myUser.name"/>

我們將 ValueStack 的 user 物件重新存放,並且給予 myUser 的變數名稱,因為我們的 scope 是採用預設的 action,所以之後的 OGNL 要使用的話,就必須以 # 開頭!在之前的深入探討 OGNL 中我們有提到,如果要在 OGNL 中取得 session 的 data 就必須是 #session 開頭,在這裡有去的是,如果 set tag 將新的 data 除存在 action scope 的話,意思是 myUser 物件與 session 是相同的高度,也就是 ActionContext 中的最上層!不過要注意的是,如果我們將 data 放至於 action scope,其生命週期跟 request 是一樣的喔!如果要更長的生命週期,那就選擇使用 session 或 application 囉~

push tag
set tag 讓你可以將 data 建立一份副本,而且可以設定放置在哪個 scope;而 push tag 也有類似的功能,不過是將 data 放置於 ValueStack 中,這個 tag 也是很有用的,而且可以有更短的生命週期、更簡便的 OGNL expression。完整的 tag reference
<s:push value="user">
<s:property value="name"/>
</s:push>

上面的範例顯示了 push tag 在內的生命週期,也就是說,在 push tag 中我們可以不用重新撰寫 user 為起始的 OGNL expression,而我們可以直接取 user object 中的 name property,當然,離開 push tag 之後,我們就不能使用了,所以 push tag 有暫存變數的功能,並且讓我們的 OGNL expression 更簡潔有力!

bean tag
bean tag 看上去就像是 set 與 push tag 的混合版,主要的不同在於我們可以不用再現存的 data 上進行運作,我們可以自己創造一個新的 data,而且可以存放在 ValueStack 或是 ActionContext 中。預設是存放在 ValueStack 中,就如同 push tag 的範例一樣,是在 tag 的開始與結尾之間的 scope 使用!如果我們我要將 data 存放在 ActionContext 中,我們就要指定 id attribute:
<s:bean name="silver8250.bean.HelloBean" id="myBean">
<s:param name="content">Ya!</s:param>
<s:param name="name">Silver</s:param>
<s:property value="sayHello()"/>
</s:bean>
<br />
<s:property value="#myBean.content"/>

上面的範例中,我們建立一個 HelloBean 並且包含 content 與 name properties,還有一個 sayHello() method。透過 bean tag 我們建立出新的 HelloBean 物件命名為 myBean,接著透過 param tag 將 name 與 content properties 賦予 value,並且在 bean tag 生命週期中呼叫 sayHello() method,最後,我們透過 property tag 呼叫儲存在 ActionContext 中的 myBean。

action tag

action tag 讓我們在頁面中呼叫不同 Action。使用 action tag 中最重要的 executeResult attribute,如果我們將 executeResult 設定為 true,則 action tag 就不只是幫我們執行 Action,還會將執行結果列印在畫面上!不過不用擔心,executeResult 預設是 false。一開始可能會覺得 action tag 不是那麼實用,不過當我們有一些 data 放至於某個 Action 中,並且是用來初始畫頁面的 data,那 action tag 就會非常的實用!以下就給個簡單的範例:
<s:action name="Included"></s:action>
<s:property value="#request.message"/>

我們採用 action tag 呼叫 IncludedAction,此 Action 會將一個 message 儲存在 request 中,不過我們現在不將結果印出來,所以採用 executeResult 的預設。使用 action tag 後,IncludedAction 會被執行,然後我們就可以由 request 取得 data。

在這裡我們簡介了 Struts2 framework 中重要的 data tags,當然,還有其他的 tags 我在之後會慢慢的介紹。

2009年4月30日 星期四

[Struts2] 深入探討 OGNL

在之前我們已經知道 OGNL expression 可以幫助我們將 data 在網頁上與後端 Javabean 之間作搬移與轉換型態,而且我們也瞭解到我們的 Actions 會被放在 ValueStack 中讓 OGNL 可以存取。現在我們要告訴你,OGNL 可以存取任何集合的物件,而 ValueStack 只是其中一個集合物件而已!更完整的來說,OGNL 是用來存取 ActionContext 中的 data,而 ActionContext 中包含了 ValueStack。

ActionContext
ActionContext 儲存了 Struts2 framework 中所有的 data,這些 data 包含 ValueStack、Request、Session 與 Application 等。而所有的 OGNL 都會到 ActionContext 中查詢 data,不過如果我們沒有指定 OGNL 要到哪裡存取資料的話,OGNL 預設是到 ValueStack 中進行存取。所謂沒有指定的意思是,如果我們撰寫的 OGNL 為:user.age,這就是採用 OGNL 預設的存取位置。如下圖。
在這裡所謂的 Context 不是指環境,而是一種有 container 概念的物件,主要是蒐集整個 framework 在執行時所需要的 data 與 resources。所有的 OGNL 都會先選擇所要存取的初始地點,如果沒有指定,就是到 ValueStack 中存取。如果我們將之前的 User 物件儲存在 session 中,那我們要存取 session 中 user 的 age,那我們就要改用:#session['user'].age。我們採用 # 開頭並且指定我們要存取的位置為 session,因為 session 是一個 Map 物件(回想我們之前所使用 SessionAware interface 中,session 物件是以 Map 方式被宣告!),我們要採用之前在內建的 OGNL Type Converter 中提到的 Map property 存取方式,所以就會是 #session['user'],取得 user 物件後我們就可以操作內部的 properties!
那我們還有哪些存取位置可以使用呢?下面就列出可用的位置:
  • parameters - 此 request 中的 parameter map
  • request - 如同 JSP 中的 request
  • session - 如同 JSP 中的 session
  • application - 如同 JSP 中的 application
  • attr - 根據 page, request, session 與 application 的順序查詢第一個找到的 data
  • ValueStack - 預設的存取位置
ValueStack
之前我們已經討論過很多關於 ValueStack 的資訊,現在我們要深入的討論 ValueStack。當 Strus2 framework 接收到使用者的 request 後,會先建立一個 ActionContext、ValueStack 與使用者呼叫的 action 物件,並且將 action 中的 properties 儲存到 ValueStack 中。這樣使用者就可以透過 OGNL 存取 ActionContext 中的 data。不過這裡有一點需要注意的是,在 ValueStack 中的 properties 只會出現最上層的 properties,也就是說如果有兩個 properties 是相同的變數名稱,只有最上層的 property 可以被存取。
你可能會好奇為什麼會有這樣情形發生呢?一個 class 中不可能有兩個變數是相同的名稱阿!沒錯!這的確有點弔詭,不過確實是有可能發生的,當我們的 Action 中的 Javabean property 採用 ModelDriven 的方式就會有可能:
public class ModelDrivenAction extends ActionSupport implements ModelDriven<User>
{
private User user = new User();
private String name;
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
@Override
public User getModel()
{
return this.user;
}
@Override
public String execute() throws Exception
{
System.out.println("This.name = "+this.name+"\n User.name="+this.user.getName());
return SUCCESS;
}

}

上面的 ModelDrivenAction 中我們採用 ModelDriven 方式讓 user property 直接存取,然後我們在 execute method 中印出使用者在 textfield 中輸入的值會存在哪個 property 之下,回想一下我們如果使用 ModelDriven 方式,我們的 OGNL 就可以直接寫成:name 而不是 user.name,從 console 中可以發現,ModelDrivenAction 自己定義的 name property 被隱藏了!所以使用者輸入的值只會被除存在 user property 下的 name property!所以在 ValueStack 中會如下:
所以我在將物件作為 Action property 中建議採用 Object-backed Javabean property 方式,這樣可以減少日後維護的問題!

2009年4月21日 星期二

[Struts2] 建立自訂的 OGNL Type Converter

Struts2 framework 中的 OGNL Type Converter 已經滿足了我們大部分的情況,基本上我們可以不用自己建立自訂的 Type Converter,不過 Struts2 framework 還是提供了讓我們可以自訂 Type Converter 的機制!在這裡我將示範要如何建立一個自訂的 OGNL Type Converter!範例中主要是提供一個 UserTypeConverter:將頁面上的 User data 轉換成 Action 中的 User property。

Implement a type converter
Struts2 framework 中,所有的 OGNL Type Converter 都必須 implements TypeConverter interface,這個 interface 提供了 programmers 在任何兩種類型的 data 進行轉換,不過在 Web application 中,我們只需要在 String 與 Object 之間作轉換就可以了!所以,我們自訂的 Type Converter 就採用 Struts2 framework 定義的 StrutsTypeConverter。StrutsTypeConverter 是一個 Abstract class,我們的 Type Converter extends StrutsTypeConverter 之外,還需要實做出兩個 abstract methods:
public abstract Object convertFromString(Map context,
                      String[] values, Class toClass)
public abstract String convertToString(Map context, Object o)
這兩個 methods 從 method name 就可以很清楚的瞭解其目的,convertFromString 主要是將頁面上 String-based data 轉換成 Java type property;而 convertToString 就是將 Java type property 轉換成 String-based data。

Convert between Strings and User
接下來就整個重頭戲了!我們要實際的撰寫我們的 Type Converter:
public class UserTypeConverter extends StrutsTypeConverter
{
  @Override
  public Object convertFromString(Map context, String[] values, Class toClass)
  {
     String name = values[0];
     String password = values[1];
     User user = new User();
     user.setName(name);
     user.setPassword(password);
     return user;
  }
  @Override
  public String convertToString(Map context, Object o)
  {
     User user = (User) o;
     return "User:name="+user.getName()+", password="+user.getPassword();
  }
}

首先,就如我們之前提到的,我們的 Type Converter 要 extends StrutsTypeConverter class,然後實做出兩個 abstract methods。將字串轉換成物件的 method 中,我們主要是將使用者在頁面上輸入兩個字串,而這兩個字串會分別變成 User 物件中的 name 與 password!在這裡沒有什麼邏輯可言,因為我們只是要示範 Type Converter 罷了!反之,將物件轉換成字串時,我們就是將 User 物件顯示成一個字串,這就有點像是 toString() method 一樣,純粹將物件中的資訊轉換成字串!

Configure our type converter
撰寫完我們自訂的 Type Converter 之後,那我們要如何讓 Struts2 framework 知道呢?我們需要一個設定檔!在 Struts2 framework 中提供了兩種方式讓我們可以設定我們的 Type Converter :1) Property-specific 與 2) Global type converter。

1. Property-specific
這種方式的設定檔只能針對某個 Action 的某個 property 給予我們的 Type Converter!首先,我們要先建立一個設定檔,檔案的命名規則為:{ActionName}-conversion.properties。假設我們有一個 Action 為 UserTypeAction,此 Action 中當然要有 User property,然後我們就必須建立一個設定檔名為:UserTypeAction-conversion.properties,而這個 properties 檔案要跟 UserTypeAction 在同一個資料夾中!在設定檔中我們要撰寫如下:
user=silver8250.type_converter.UserTypeConverter

其中,先告知 Struts2 framework 我們要進行轉換的 property 為 user,然後在等號右邊給予我們的 Type Converter!這樣就完成了設定檔案!
這種設定檔到底有什麼好處呢?一開始你應該會很困惑,因為我們不可能為每一個 Action 中有使用 User property 都增加一個設定檔,我們一定是會使用等等會談到的 Global 設定檔!不過在某一種情況下會有用處!假設我們有兩個 Type Converters,第一個是 Global 的,所以我們採用 Global 的設定讓大多數的 property 都使用,不過如我們有某個 Action 的某個 property 要使用我們設計的第二個 Type Converter,這時候 property-specific 就派上用場了!因為 Struts2 framework 會先以 property-specific 設定優先!

2. Global type converter
另一種設定檔就是 Global 型態的 Type Converter,我們只需要一個設定檔為:xwork-conversion.properties 檔案,並且放在 WEB-INF/classes 之下就可以!內容如下:
silver8250.bean.User=silver8250.type_converter.UserTypeConverter

等號左邊是告訴 Struts2 framework 是哪一個 Object 要執行 Type Conversion,右邊則是要使用哪個 Type Converter 進行轉換!這樣就完成了 Global 的設定檔!大多數的情況下我們都是以 Global 設定檔為主!

Use our type converter
最後就是我們在頁面上要如何撰寫呢?其實沒有任何的差別!例如:我們的 Type Converter 是要讓頁面上的兩個字串轉換到 User 中的 name 與 password,所以我們在頁面上就要設計兩個 textfield 讓使用者輸入:
<s:form action="UserType" method="GET">
   <s:textfield name="user.name" label="User Name"></s:textfield>
   <s:textfield name="user.password" label="User Password"></s:textfield>
   <s:submit></s:submit>
</s:form>

你沒看錯!真的沒有差別!真的~

在這裡我們示範了要怎樣設計自己的 Type Converter,不過大多倏地行況下,我們不需要這樣作!

2009年4月15日 星期三

[Struts2] 內建的 OGNL Type Converter

我們已經了解到 OGNL 具備有自動的 data transfer 以及 type converter 的功能,現在我們就要介紹 OGNL 中內建的 type converter。之前我們 Introduce OGNL 中就有簡單的使用過將頁面上 String-based data 轉換成 Javabean 中的 int type data。讓我們來看看這樣的轉換機制到底有哪些其他的功能!

All converters
首先我們先看看 Struts2 framework 中到底有哪些內建的 converters:
  • String - 字串,幾乎是不用進行轉換,因為在 client 端與 server 端的型態就是一樣的!
  • boolean/Boolean - true 或 false 的字串會被轉換
  • char/Character - 可以想像是一個單位的字串
  • int/Integer, float/Float, double/Double, long/Long - 原始的格式被轉換成字串型態,你可以想像就像是使用 String.valueOf() 來進行轉換!
  • Date - 根據使用者目前的 Locale 而被轉換成 SHORT 格式的字串,例如:28/02/97
  • array - 每一個 array 中的 element 將會被轉換成 String 物件做處理
  • List - 預設設定的 Element 以 String 為主
  • Map - 預設設定的 Element 以 String 為主
上述的各種就是 OGNL 內建的 converters,基本上 Primitive type(例如:String, int 等) 是比較好處理也比較可以理解其運作原理;而 Colleciton type(例如:array, List 與 Map) 在轉換上比較複雜,但是對於 programmers 來說還是很直觀的!至於我們要怎樣讓 OGNL 可以使用適當得 type converter,這點我們就無須擔心,只要我們將要取的 data 放置到 ValueStack 中並且在 view-layer 中撰寫適當的 OGNL expression language 就可以了,OGNL 會自動知道使用哪個 type converter!
以下我們將所有的 type converter 分成兩類加以討論:1) Primitive type mapping 與 2) Collection type mapping。

Primitive type mapping
基本型別的轉換是最簡單的,也是很直觀的!我們就直接給個範例:
<s:form action="Register">
<s:textfield name="user.name" label="Name" />
<s:textfield name="user.password" label="Password" />
<s:textfield name="user.age" label="Age" />
<s:submit />
</s:form>

上面所顯示的 form 當使用者按下 submit 之後,就會根據 action attribute 中的 value 將表單傳送給 struts.xml 中設定的 Register action,透過 OGNL 的 expression language 會將各個欄位的值交由 param interceptor 找尋 mapping destination,在由 OGNL 個別進行 type conversion。由上述的 OGNL 中我們可以知道 Register action 中必定會有一個 User object 作為 property:
public class RegisterAction extends ActionSupport
{
private User user;
public void setUser(User user)
{
this.user = user;
}
public User getUser()
{
return this.user;
}
}

我們現在是透過 Object-back Javabean property 的方式(將物件作為 Action property 中提到)存取 User property。而 User object 中一定會有 name, password 與 age 的 property。所以 OGNL 會先透過 RegisterAction 取得 User 物件後,在依照 expression 中不同的 property 去進行 type conversion,例如:age 在 User 物件中屬於 int 型態,則會啟動 int type converter 將使用者輸入的 String type 的 age 轉換成 int type 的 age。
至於為甚麼 HTTP 中的 form 欄位一定都是 String 呢?這點就要牽涉到 HTTP 本身的設計,因為我們傳送的 form 欄位都會以 parameter 的方式傳送,只是有 GET 跟 POST 的差別,這兩種差別在於 GET 會將這些 key value pairs 顯示在 URL 上;而 POST 不會!但是最終都是以 parameter 的方式傳送!也正因為如此,Java EE 中設計的 HttpServletRequest 物件有一個 getParameter() method 就是用來取得使用者在頁面上所傳送的資料。而這個 method 回傳的資料都是 String,所以 OGNL 必須將這些 String-based data 進行 type conversion 了!
同樣的,如果我們要取得某個 property value,我們在頁面上就可以使用 property tag 來取值:
<s:form action="Register">
<s:textfield name="user.name" label="Name" />
<s:textfield name="user.password" label="Password" />
<s:textfield name="user.age" label="Age" />
<s:submit />
</s:form>

這個 OGNL 在後端工作時會轉換成:getUser().getAge();
另外,當 OGNL 在進行 type conversion 時也會進行 validation 的作業!舉個例子來說,如果使用者在 Age 欄位中輸入了非數字的字串,OGNL 在 type conversion 時就會出現錯誤,並且顯示錯誤訊息在使用者所輸入的欄位上,這樣的錯誤機制有點像我們在深入實做 Action 中提到的驗證失敗的訊息。

Collection type mapping
對於上面所提到的 primitive type conversion 的確比較直觀,不過對於 programmer 來說,要讓 String-based 的 HTTP data 能夠 mapping 到 Collection 類的 Java-type 的確比較棘手!好在 OGNL 已經提供了這樣的功能,讓 programmer 不必擔心這類型的轉換工作!
在 Struts2 framework 中提供了將 multivalued request parameters 轉換到有變化性的 Collection 型態 property,而且也包含了原始的 array 型態,畢竟 array 是所有 Collection-type 的基礎型態!以下將分成三種不同型態的 Java-type 分別介紹:1) array, 2) List 與 3) Map。

1. array
Array 其實也算是基本的 Java 型態,只是收集了一堆相同型態的資料罷了!Struts2 framework 提供了這樣的轉換功能,也就是如果我們的 Action 中宣告了 array property(又稱為 indexed Javabeans property),我們可以很輕鬆的完成由網頁上的資料轉換到 Javabean 中的 array。這樣的功能源自於 OGNL 的 navigate(導覽) 能力,因為具有導覽的功能,我們可以期望 OGNL 在 Collection-type 中幫我們找到某個 elements。話不多說,我們就看以下的範例吧:
<s:form action="Regist" method="get">
<s:textfield name="age" label="age"></s:textfield>
<s:textfield name="age" label="age"></s:textfield>
<s:textfield name="age" label="age"></s:textfield>

<s:textfield name="name[0]" label="Name"></s:textfield>
<s:textfield name="name[2]" label="Name"></s:textfield>
<s:textfield name="name[3]" label="Name"></s:textfield>

<s:textfield name="ageInt" label="age int"></s:textfield>
<s:textfield name="ageInt" label="age int"></s:textfield>
<s:textfield name="ageInt" label="age int"></s:textfield>

<s:submit />
</s:form>

首先是頁面的部份,在上面我們宣告了一個 form,不過我們將資料傳送的方式設定為 GET 模式,因為這樣我們就可以觀察 array 型態的資料是怎樣被安排被傳送的。接下來就是重頭戲了!我們有三種 properties,age, name 跟 ageInt。在 age property 中我們測試不要給 index ,看看資料會怎樣被安排;另外就是 name property,這裡的寫法很像是在 Java 中對 array 給值的寫法,我們在這裡故意跳過 index=1 的 array!至於 ageInt 我等等會解釋!
接著就是 Javabean,我們看看 age 跟 name properties 要怎樣在 Javabean 中撰寫:
public class RegistAction extends ActionSupport
{
private Integer[] age;
private String[] name = new String[4];
private int[] ageInt;
public Integer[] getAge()
{
return age;
}
public void setAge(Integer[] age)
{
this.age = age;
}
public String[] getName()
{
return name;
}
public void setName(String[] name)
{
this.name = name;
}
public int[] getAgeInt()
{
return ageInt;
}
public void setAgeInt(int[] ageInt)
{
this.ageInt = ageInt;
}
@Override
public String execute() throws Exception
{
for (int i=0,n=this.age.length;i<n;i++)
System.out.println(this.age[i]);

for (int i=0,n=this.name.length;i<n;i++)
System.out.println(this.name[i]);

for (int i=0,n=this.ageInt.length;i<n;i++)
System.out.println(this.ageInt[i]);

return SUCCESS;
}
}

首先是 age,我們在這裡將他宣告為 Integer 的物件陣列,而 ageInt 則是宣告為 int 的原始型態陣列,等等我們會看到再取值時的差別!再來就是 name property,我們在這裡採用 String 型態,由於 String 不屬於 primitive type 或是 primitive type wrapper 所以我們要自行先宣告陣列大小!這點需要注意一下~否則在 assign value 時 console 會出現錯誤訊息!
在這裡使用陣列跟一般的 property 沒兩樣,我們不必擔心說 assign 具有 index 的 value,因為 OGNL 會幫我們處理,我們只要如同往常提供 getter/setter method 就可以!接下來就是要觀察一下這些 parameters 會怎樣傳送?我們就將 form submit 出去,由 URL 上方可以看到這些 parameters 的結果:
http://localhost:8080/HelloStruts2/Regist.action?age=1&age=2&age=3&name[0]=4&name[2]=5&name[3]=6&ageInt=7&ageInt=8&ageInt=9
由於我們的 form 中有兩種類型的 textfield:一種是有給 index,另一種則是沒有!我們會看到沒有 index 的 value 會被照順序的安排,並且使用相同的 key(如上的 age 與 ageInt),而有給 index 的 value 則會按照我們的 index 作為 key(如上的 name[2])!在 Action 接收後,我們特別在 execute() method 中將這些 array values 印出來觀察,我們發現到 name[1] 確實是沒有值!
最後我們就要看看怎樣將這些 array values 顯示在頁面上:
Age:<s:property value="age"/><br />
Name:<s:property value="name"/><br />
Age Int:<s:property value="ageInt"/><br />

Age[1]:<s:property value="age[1]"/><br />
Name[1]:<s:property value="name[1]"/><br />
Name[2]:<s:property value="name[2]"/><br />
Age Int[1]:<s:property value="ageInt[1]"/><br />

我們分成兩個部份,第一個部份是不給予 index 看看值會怎樣被取出來!第二部份就是給予 index 取出某個 element 的值!第二個部份很好猜測,就和我們先前將 value 寫回 action 中的方式一樣!不過第一部份就比較難猜了!我就直接執行看看結果吧!
Age:1
Name:ognl.NoConversionPossible
Age Int:7, 8, 9
Age[1]:2
Name[1]:
Name[2]:5
Age Int[1]:8
第二部份的結果我就不多說了!只是提醒一下如果是 NULL 的值,回傳到頁面上就會是空白的內容。現在我們就探討一下第一部份的結果。首先,如果我們在 Action 中採用 Primitive type wrapper 物件(例如:Integer, Double 等)宣告陣列,當我們想要印出所有陣列中的元件,我們就要自行 iterate array,如同我們的 age property;如果我們是使用 primitive type 宣告陣列,Struts2 framework 就會自動幫我們 iterate array;另外,如果我們的 array 不屬於 primitive type 或 primitive type wrapper 的話,OGNL 就無法幫我們顯示內容了!

2. List
List 也是在 Struts2 framework 中有支援 navigate 功能,我們可以將上面的範例簡單的修改為 List:
public class RegistAction extends ActionSupport
{
private List<Integer> age;
private List<String> name;
public List<Integer> getAge()
{
return age;
}
public void setAge(List<Integer> age)
{
this.age = age;
}
public List<String> getName()
{
return name;
}
public void setName(List<String> name)
{
this.name = name;
}
}

頁面的部份我們不必修改,因為在 Struts2 framework 中對於 List 與 array 的處理方式其實是類似的,上面的例子中我們使用 Primitive type 作為 List 中的 Element,我們也可以使用非 Primitive type,只要我們使用 J2SE 5.0 的重要特性-Generic 就可以輕鬆完成!如下的 Action 讓我們可以在 List 放我們自己定義的物件:
public class RegistAction extends ActionSupport
{
private List<User> users;
public List<User> getUsers()
{
return users;
}
public void setUsers(List<User> users)
{
this.users = users;
}
}

在頁面上我們就可以採用這樣的 OGNL expression language:users[1].name

3. Map
最後一種 OGNL 可以進行轉換的就是 Map,Map 跟使用 List 上沒有太大的差別,主要差別在於 Map 是以物件作為 key,而 List 是以 index 作為 key。
private Map<String, User> users;
private Map<Integer, User> otherUsers;
public Map<String, User> getUsers()
{
return users;
}
public void setUsers(Map<String, User> users)
{
this.users = users;
}
public Map<Integer, User> getOtherUsers()
{
return otherUsers;
}
public void setOtherUsers(Map<Integer, User> otherUsers)
{
this.otherUsers = otherUsers;
}

上面的 Action 中我們宣告了兩個 Map properties,第一個是用 String 作為 Map 的 Key,第二個則是用 Integer 作為 Map 的 Key。而這兩種 Map 都是存放 User 物件。而我們在頁面存取這兩種 Map 時就要撰寫成如下:
<s:form action="Regist" method="get">
<s:textfield name="users['silver'].name" label="Silver Name"></s:textfield>
<s:textfield name="users.kent.name" label="Kent Name"></s:textfield>

<s:textfield name="otherUsers['0'].name" label="Other User Name"></s:textfield>
<s:submit />
</s:form>

我們存取 Map 的方式可以用 [] 將 Key 寫在裡面,不過要加上 ' ' 符號!或者我們可以直接指定 Key(如:user.kent) 方式,但是當我們的 Key 採用 Integer 時,我們就只能使用第一種表示法存取了!

在這裡我們討論了進階的 OGNL 的 type conversion 機制,從 primitive type 到 collection-based type,如:array, List 與 Map。OGNL 讓 programmers 可以不必煩惱如何將 String-based 的 HTTP value 轉換成 Java-type based 的 Javabean,甚至我們在撰寫 OGNL expression language 時也是很輕鬆容易的!