2009年12月21日 星期一

[Struts2]設定上傳文件大小上限

在處理一份檔案時報錯了
得到訊息如下
the request was rejected because its size exceeds the configured maximum
原因就是檔案的SIZE太大了

兩種解決方式
1.設定所有檔案的大小上限
編輯struts.properties
修改struts.multipart.maxSize=16777216
或是
編輯struts.xml
加入
<constant name="struts.multipart.maxSize" value="16777216" />

2.設定單一檔案的大小上限
在要執行上傳的action中
加入interceptor
<interceptor-ref name="fileUpload">
<param name="maximumSize">5242880</param>
</interceptor-ref>

2009年12月20日 星期日

[SuperCSV]讀取csv檔案

Super CSV是個操作csv檔案的Open Source

由於我需要的是讀取csv檔案資料
所以Code example也只先看這一部份了

實作上可以分做兩種情況
已知檔案格式和未知檔案格式

在已知的情況下
就它的範例
File Header的部份為以下
username, password, date, zip, town
Klaus, qwexyKiks, 17/1/2007, 1111, New York
Oufu, bobilop, 10/10/2007, 4555, New York

便可以做出對應的bean
public class UserBean {
String username, password, street, town;
int zip;

public String getPassword() { return password; }
public String getStreet() { return street; }
public String getTown() { return town; }
public String getUsername() { return username; }
public int getZip() { return zip; }
public void setPassword(String password) { this.password = password; }
public void setStreet(String street) { this.street = street; }
public void setTown(String town) { this.town = town; }
public void setUsername(String username) { this.username = username; }
public void setZip(int zip) { this.zip = zip; }
}

再來
可以透過processor做Header各欄位的格式檢查
final CellProcessor[] processors = new CellProcessor[] {
new Unique(new StrMinMax(5, 20)),
new StrMinMax(8, 35),
new ParseDate("dd/MM/yyyy"),
new Optional(new ParseInt()),
null
};

最後就是讀取的部份
class ReadingObjects {
public static void main(String[] args) throws Exception{
ICsvBeanReader inFile = new CsvBeanReader(new FileReader("foo.csv"), CsvPreference.EXCEL_PREFERENCE);
try {
final String[] header = inFile.getCSVHeader(true);
UserBean user;
while( (user = inFile.read(UserBean.class, header, processors)) != null) {
System.out.println(user.getZip());
}
} finally {
inFile.close();
}
}
}

雖然沒實際去測試
如果header的字眼有空白,特殊符號之類
這樣bean的方法是不是就不能用了?

第二類是未知格式
這部份在官網只寫了
You accomplish this using the CsvListReader.read() method
果然也是很簡單
我把方法寫在一起
可以做個對照
public static void main(String[] args) throws Exception {
ICsvBeanReader inFile = new CsvBeanReader(new FileReader("foo.csv"), CsvPreference.EXCEL_PREFERENCE);
CsvListReader r = new CsvListReader(new FileReader("foo.csv"), CsvPreference.EXCEL_PREFERENCE);
try {
final String[] header = inFile.getCSVHeader(true);
for(int j=0;j<header.length;j++){
System.out.println(header[j]);
}
UserBean user;
while( (user = inFile.read(UserBean.class, header, userProcessors)) != null) {
System.out.println(user.getZip());
}
List l;
while( (l = r.read()) !=null ){
System.out.println(l.get(0));
}
} finally {
inFile.close();
}
}

2009年12月9日 星期三

[SEO]meta tag

工作以來也碰了不少SEO的東西
雖然不是專門
但也實驗性(?)的做了不少改變
只為了讓那排名往上再爬一點...

雖然網路上SEO的資料已經多到逛不完
但也是時候做個自己的心得筆記了

meta tag的成員還不少
目前自己只有使用到其中三個
1.
<meta name="keywords" content="keyword放這裡" />
用來讓search engine知道網頁的關鍵字是哪些
若關鍵字沒確實地在body的text中出現
會帶來反效果

2.
<meta name="Description" Content="網頁描述" />
用於描述網頁

3.
<META NAME="distribution" CONTENT="global">
用來說明此網頁為世界性的

meta tag對於Google來說
已不再那麼重要
Google does not use the keywords meta tag in web ranking

2009年11月16日 星期一

[POI]取得有公式的cell的計算值

1.
double dCellValue =  cell.getNumericCellValue();
2.
HSSFFormulaEvaluator evaluator = new HSSFFormulaEvaluator(sheet,workbook);
double dCellValue = evaluator.evaluate(row.getCell((short)col)).getNumberValue();
第一種方法
大部分的時候都成功
但有遇過
如果沒有refresh檔案
就只會取到零
這是跟excel的版本有關
(關閉檔案時會彈出訊息:
當開啟Excel舊版本所儲存的檔案時,Microsoft Office Excel會重新計算公式
要儲存才算refresh)

第二種方法
POI的版本需求較高(3.x)
詳細的版本我也沒記了...

2009年10月23日 星期五

[Struts2]<s:actionmessage/>

最近用了<s:actionmessage/>來做訊息的傳遞
使用上也相當簡單

java code
addActionMessage("success");

jsp code
<s:if test="hasActionMessages()">
<s:iterator value="actionMessages">
<script language="JavaScript">
alert("<s:property escape="false"/>");
</script>
</s:iterator>
</s:if>

或是直接透過tag輸出
<s:actionmessage/>

因為<s:actionmessage/>是由ul,li組成的
所以在顯示的字前面會有一個點(dot)
可以自定義template
或是修改ul,li的css屬性
加入list-style:none
來修正這個問題

2009年10月11日 星期日

[Struts2]一次產生多個textfield

java
private String threeTextfield[]={"","",""};

public String[] getThreeTextfield() {
return threeTextfield;
}

public void setThreeTextfield(String[] threeTextfield) {
this.threeTextfield = threeTextfield;
}
jsp
<s:iterator value="threeTextfield" status="rowstatus" id="id">
<tr>
<td><s:textfield name="threeTextfield" value="%{id}" /></td>
</tr>
</s:iterator>

透過這樣的方式
優點是jsp的撰寫與後端的接收都快了不少
缺點是無法指定textfield的個數

2009年10月9日 星期五

[CSS]避免表格因過長字串撐開

如果<td>裡是一長串連續的英文字
表格可能因此撐開變形

解決的方法是
table layout固定住
table {
  table-layout: fixed;
}

td加入word-wrap:break-word 屬性來強制換行
td {
word-wrap:break-word;
}

2009年9月30日 星期三

[Google Analytics]追蹤 AJAX 應用程式

工作上的網站除了有自行開發的瀏覽紀錄模組
還用了Google Analytics來做分析工具

最近想追蹤一個使用者行為
便設了這個動作為目標

幾天過去
目標的數值卻一直沒有變動增加
與我在網站記錄的數據不一致
看來是目標設定的出了問題
後來懷疑是此目標是AJAX事件
Google Analytics可能會偵測不到

後來查到了一篇
如何追蹤 AJAX 應用程式?
在基本的 HTML 網頁中,您可以使用 URL 來區別多個網頁檢視。 但是在 AJAX 應用程式中,向伺服器提出要求時並不會變更網頁的 URL,因此難以追蹤。

不過,您可以呼叫 _trackPageview 函數來為任何 AJAX 事件指派網頁檔名

所以在AJAX事件succes後加入
pageTracker._trackPageview("/pagefilename1" );//pagefilename1可替換名稱 
應該就可以了

過幾天再看看
有沒有成功!

Updated by 2009/10/07
將目標頁面改成trackPageView所指定的名稱後
有看到目標成功的轉換了!

2009年9月15日 星期二

[Struts2]TextProvider

在後端使用resource bundle
Struts的話我是用messageResources.getMessage(locale, key)來處理
Struts2的話之前還沒用到過
便查了一下API
找到了TextProvider
以下節錄一段敘述
public interface TextProvider

Provides access to ResourceBundles and their underlying text messages. Implementing classes can delegate TextProviderSupport. Messages will be searched in multiple resource bundles, startinag with the one associated with this particular class (action in most cases), continuing to try the message bundle associated with each superclass as well. It will stop once a bundle is found that contains the given text. This gives a cascading style that allow global texts to be defined for an application base class.

而且ActionSupport已經Implemented了

所以在Struts2里的用法更簡單
在有extends ActionSupport的class下
super.getText("keyYouType")
這樣就可以取出對應的值了!

2009年9月3日 星期四

[Solaris]Apcahe整合Tomcat配置虛擬主機

延續上一篇Apache整合Tomcat
環境為Solaris 8+Apache+tomcat+mod_jk2

要配置虛擬主機
需修改Apache底下的httpd.conf和Tomcat底下的server.xml

假設專案是在/opt/app/tomcat/webapps/myApp
而網域名為www.testapp.com

httpd.conf加入
NameVirtualHost *:80
<VirtualHost *:80>
Documentroot /apache/htdocs
</VirtualHost>
<VirtualHost *:80>
DocumentRoot /opt/app/tomcat/webapps/myApp
ServerName www.testapp.com
DirectoryIndex index.jsp
ErrorLog logs/dummy-ecom-test.com-error_log
CustomLog logs/dummy-ecom-test.com-access_log common
</VirtualHost>

server.xml加入
<Host name="www.testapp.com" debug="0" appBase="webapps"
unpackWARs="true" autoDeploy="true"
xmlValidation="false" xmlNamespaceAware="false">
<Context path="" docBase="myApp" debug="0"
reloadable="true" crossContext="true"/>
<Logger className="org.apache.catalina.logger.FileLogger"
directory="logs" prefix="localhost_log." suffix=".txt"
timestamp="true"/>
</Host>

主要是目錄跟網域名在這兩個檔案的設定要一致
這樣就完成虛擬主機的設定了

2009年9月1日 星期二

[Solaris]Apache整合Tomcat

之前有寫一篇在Windows下Apache整合Tomcat的文章
而這篇是在solaris 8下做整合
還有一個差別是這篇是用jk2(Windows那篇是jk)

Tomcat跟JDK已安裝
所以就不記錄了

安裝Apache
tar -xvf httpd-2.0.63.tar
cd httpd-2.0.63
./configure --prefix=/usr/local/apache --enable-modules=most --enable-so
make
make install

安裝JK2
tar -xvf jakarta-tomcat-connectors-jk2-src-current.tar.gz -C /tomcat
cd /tomcat/jakarta-tomcat-connectors-jk2-2.0.4-src/jk/native2/
./configure --with-apxs2=/usr/local/apache2/bin/apxs
make
cd ../build/jk2/apache2/
/usr/local/apache/bin/apxs -n jk2 -i mod_jk2.so

測試Apache是否可啟動
必須先修改httpd.conf
ServerName  yourIpAddress
Group nogroup

確定可正常啟動後
在httpd.conf加入
LoadModule jk2_module modules/mod_jk2.so
加入這行是要載入jk2的意思

在apache/conf下新增workers2.properties
[logger.apache2]
level=DEBUG

# Shared memory handling. Needs to be set.
[shm]
file=/usr/local/apache/logs/shm.file
size=1048576

# Example socket channel, explicitly set port and host.
[channel.socket:localhost:7009]
tomcatId=localhost:7009
port=7009
host=127.0.0.1

# define the worker
[ajp13:localhost:7009]
channel=channel.socket:localhost:7009

[status:status]

# Uri mapping

[uri:/jkstatus/*]
worker=status:status

[uri:/*]
worker=ajp13:localhost:7009

啟動tomcat及apache
若能在80 port看到應有程式的畫面
就代表成功了!

後記:
在solaris環境下安裝的跌跌撞撞
而且玩了兩台
兩台都出不一樣的狀況

不過也學到一點小東西
例外安裝Apache需要先安裝gcc套件
安裝gcc套件需要較大的暫存空間
如果/var/tmp的空間不夠
可在環境變數裡新增$TMPDIR指定暫存目錄

改天在補上
Apache跟Tomcat如何設定虛擬主機

2009年8月13日 星期四

[SQL]查詢欄位是否有重複資料

SELECT 欄位名稱
FROM 表格名稱
GROUP BY 欄位名稱
HAVING (COUNT(欄位名稱) > 1)

2009年8月11日 星期二

Tomcat shutdown不完全的問題

自從整合quartz後
在solaris環境下shutdown tomcat
下ps指令還可以看到程序的process
必須手動kill掉

由於已確定是整合quartz後才發生
自然朝這個方向研究
就觀察結果猜測跟quartz thread不是daemon類型有關
如果shutdown完quartz thread還在使用
就可能造成這樣的情形

當下解決的想法有兩個
1.在tomcat shutdown前先將quartz scheduler shutdown
2.將quartz thread停止

著手實驗後
將scheduler shutdown
再關掉tomcat
就沒有發生這種情形了!

順便紀錄一下
關於這次如何在tomcat shutdown前執行
1.ServletContextListener
2.shutdown hook
3.quartz的shutdownHook plugin
最後是選擇用ServletContextListener來處理

過程中發現shutdown hook
似乎在solaris下執行.sh腳本來shutdown tomcat時無法使用
而window下直接在工作管理員關閉也無法偵測到
可能shutdown hook只針對syste.exit()和Ctrl+c這兩種狀況吧!?

推薦一個Blog 隔夜黃鶯
Quartz的文件翻譯的不錯
也跟他請教過不少問題

2009年8月10日 星期一

Control character in cookie value, consider BASE64 encoding your value

在例行性的查看log過程中看到這個exception
便開始著手解決這個問題

在網上搜索了一下
得到原因是
值含有控制字元所以無法保存到cookie裡
可能是中文字在編碼時出現亂碼導致有控制字符的出現

解決的方法就是存值跟取值都進行編碼的動作
public void addCookie(String name, String value)
{
int maxAge = 3600 * 24 * 30;
try{
if (value == null) {
maxAge = 0;
value = "";
}else{
value = URLEncoder.encode(value,"UTF-8");
}
}catch(Exception e){
}
Cookie cookie = new Cookie(name, value);
cookie.setMaxAge(maxAge);
cookie.setPath("/");

response.addCookie(cookie);
}

public String getCookie(String name)
{
String value = "";
Cookie[] cookies = request.getCookies();

if (cookies != null) {
for (int i = 0; i < cookies.length; i++) {
Cookie c = cookies[i];
if (c.getName().equals(name)) {
try{
value = URLDecoder.decode(c.getValue(),"UTF-8");
}catch(Exception e){
}
return value;
}
}
}

return value;
}

2009年7月24日 星期五

[JavaScript]用replace()達到replaceAll的效果

如果想替換字串中所有的某個字段
javascript中沒有直接提供類似java裡的replaceAll可以用
而javascript裡的replace()也僅能替換第一個符合的字段
後來在網上看到一個不錯的方式

如下
var str = "aabb";
str = str.replace(/\aa/g,'AA');//replace(/\要替換的字段/g,'替換後的字段')
alert(str);

可以把字串中'aa'的部份替換成'AA'

2009年7月21日 星期二

[jQuery]Dialog

有別於傳統的對話框
jQuery Dialog可做的變化也較多
其它屬性,函式就去官方文件看吧!

下面是官方文件上的Example
<!DOCTYPE html>
<html>
<head>
<link type="text/css" href="http://jqueryui.com/latest/themes/base/ui.all.css" rel="stylesheet" />
<script type="text/javascript" src="http://jqueryui.com/latest/jquery-1.3.2.js"></script>
<script type="text/javascript" src="http://jqueryui.com/latest/ui/ui.core.js"></script>
<script type="text/javascript" src="http://jqueryui.com/latest/ui/ui.draggable.js"></script>
<script type="text/javascript" src="http://jqueryui.com/latest/ui/ui.resizable.js"></script>
<script type="text/javascript" src="http://jqueryui.com/latest/ui/ui.dialog.js"></script>
<script type="text/javascript">
$(document).ready(function(){
$("#dialog").dialog();
});
</script>
</head>
<body style="font-size:62.5%;">

<div id="dialog" title="Dialog Title">I'm in a dialog</div>

</body>
</html>
按下面的Demo可以看效果!
Demo

2009年7月20日 星期一

[jQuery]DOM 物件與jQuery 物件之間的轉換

透過jQuery選取的元素變成jQuery的物件後
只能使用jQuery 的方法

剛開始還真的有點不習慣
等到debug看到xxx is not defined之類的訊息
才意會到自己又忘了

jQuery 物件->DOM 物件
$("#elementID").get(0)
先取得元素的集合,在從中挑出第X個
get(0)的0是索引值
這例子即是第一個

DOM 物件 → jQuery 物件
$(document.getElementById("elementID"))
傳入$()就可以了

2009年7月13日 星期一

[Solaris]Inconsistent definitions for partition type

用format指令對硬碟做完新的分割後
後來想再check時
鍵入format後
發生Inconsistent definitions for partition type 'home'的訊息

網上搜尋了一下大概知道原因了
我在name partition的時候
兩次都給予相同的名稱'home'而造成

解決的方法就是
把/etc/format.dat裡partition = "home"
重複的地方
留下新的那段就好了

2009年7月12日 星期日

[Solaris]修改hostname

可以透過sys-unconfig指令重新設定

如果只想修改這一項的話
就修改以下六個檔案
/etc/hosts
/etc/hostname.hme0
/etc/nodename
/etc/net/ticlts/hosts
/etc/net/ticots/hosts
/etc/net/ticotsord/hosts

2009年7月8日 星期三

Windows下Apache與Tomcat的整合

這篇是參考網上文章加自己實際測試的過程

測試環境為Windows 2003
需準備Apache, Tomcat,mod_jk.so

分別安裝好Apache和Tomcat
就可以開始設定整合所需的檔案

Step 1
將mod_jk.so放到/Apache/modules下

Step 2
修改/Apache/conf/httpd.conf
加入以下
#mode_jk Settings
Include conf/mod_jk.conf
加不加註解就看個人習慣了

Step 3
/Apache/conf下新增mod_jk.conf
內容為
#Load module mod_jk
LoadModule jk_module modules/mod_jk.so

# Where to find workers.properties
JkWorkersFile conf/workers.properties

# Where to put jk logs
JkLogFile logs/mod_jk.log

# Set the jk log level [debug/error/info]
JkLogLevel info

# Select the log format
#JkLogStampFormat "[%a %b %d %H:%M:%S %Y] "

# JkOptions indicate to send SSL KEY SIZE,
#JkOptions +ForwardKeySize +ForwardURICompat -ForwardDirectories

# JkRequestLogFormat set the request format
#JkRequestLogFormat "%w %V %T"

#close Lookup
#HostnameLookups Off

#what's request should be send to tomcat
JkMount /* controller
其中最後一項比較重要
是設定tomcat負責處理哪些請求
而controller是要做load balance是要用的
不需要的話可以拿掉

Step 4
/Apache/conf下新增workers.properties
內容為
worker.list = controller,tomcat1,tomcat2

#========tomcat1========
worker.tomcat1.port=7009 #ajp13 端口號,在tomcat下server.xml配置,默認8009
worker.tomcat1.host=localhost #tomcat的主機地址,如不為本機,請填寫ip地址
worker.tomcat1.type=ajp13
worker.tomcat1.lbfactor = 5 #server的加權比重,值越高,分得的請求越多
#========tomcat2========
worker.tomcat2.port=9009
worker.tomcat2.host=localhost
worker.tomcat2.type=ajp13
worker.tomcat2.lbfactor = 3
#========controller(load balance)========
worker.controller.type=lb
worker.controller.balanced_workers=tomcat1,tomcat2 #指定分擔請求的tomcat
worker.controller.sticky_session=1
worker.list設定所要整合的tomcat
名稱可以自訂
但port的部份要跟tomcat的server.xml裡一致

Step 5
修改/tomcat/conf/server.xml

<!-- Define an AJP 1.3 Connector on port 8009 -->
<Connector port="7009"
enableLookups="false" redirectPort="7443" protocol="AJP/1.3" />
這裡的port跟workers.properties裡的一致

找到以下
<!-- You should set jvmRoute to support load-balancing via AJP ie : -->
<Engine name="Standalone" defaultHost="localhost" jvmRoute="tomcat1">
jvmRoute=tomcat1這邊也是名稱要一致

找到以下
<Cluster className="org.apache.catalina.cluster.tcp.SimpleTcpCluster"
managerClassName="org.apache.catalina.cluster.session.DeltaManager"
expireSessionsOnShutdown="false"
useDirtyFlag="true"
notifyListenersOnReplication="true">
.
.
.
<ClusterListener className="org.apache.catalina.cluster.session.ClusterSessionListener"/>
</Cluster>
拿掉原本的註釋
其中tcpListenPort要注意是否被用了

這樣就改好workers.properties裡的tomcat1了
tomcat2的server.xml也是一樣的修改

Step 6
兩個tomcat修改完後分別啟動
先測試在各自的port下localhost是否能連
然後再啟動Apache
測試是否直接在80 port下
localhost就可以連到那兩個tomcat
因為有設定load balance的關係
所以要多連幾次才能看出是否兩台都有通!

2009年7月7日 星期二

swfobject設置flash透明

使用swfobjec在網頁上嵌入flash後
遇到了一個小問題
flash上方有個下拉式選單
下拉後重疊的部分被flash蓋住了

後來在網上搜尋了一番
發現在將flash設置為透明即可

swfobjec.embedSWF的形式如下
swfobject.embedSWF(swfUrl, replaceElementId, swfWidth, swfHeight, flashVersion
,expressInstallUrl, flashvars, params, attributes);
其中params是設置參數的部份
透明是設定參數 wmode : "transparent"

設定後flash就沒有蓋住下拉選單了!

[Solaris]無法透過ftp連上server

使用的是root帳號
可以telnet連上server
但ftp失敗
錯誤訊息為: 530 Login incorrect

解決方法為
修改/etc/ftpusers
mark root(#root)

2009年7月6日 星期一

[Solaris]root透過telnet登錄

剛安裝完solaris
root帳號還沒有辦法透過telnet登錄

解決方法為
修改/etc/default/login文件
找到以下
# If CONSOLE is set, root can only login on that device.
# Comment this line out to allow remote login by root.
CONSOLE=/dev/console

然後改成
# If CONSOLE is set, root can only login on that device.
# Comment this line out to allow remote login by root.
#CONSOLE=/dev/console
mark第三行就可以了

前兩行已經說明了有沒有設定CONSOLE的差別

2009年7月2日 星期四

[Solaris]重新設定config的指令

鍵入指令
sys-unconfig
系統reboot後
就可以重新設定ip,netmask...等等

2009年7月1日 星期三

[jQuery]noConflict mode

如果頁面上同時使用jQuery和其他javascript library
可能會發生衝突
這時可透過
<script type="text/javascript">
jQuery.noConflict();
</script>
或是
var $j = jQuery.noConflict();
使用$j取代jquery的$
來避免這種情形

若以上兩種方法使用後還會有衝突的錯誤訊息
那就先去掉上面兩種方法
然後把所有$的地方以jQuery取代掉
應該就可以避免了

2009年6月25日 星期四

[Struts2]整合Open Flash Chart 2

基本上對於使用strust2的人來說
這是一個非常好上手的例子

Open Flash Chart 2官網download檔案後
java-stuts-2這個資料夾裡就是一個整合在struts2的範例

三支java檔
Bar.java是action
Element.java/Title.java是chart element

struts.xml
<package name="json-chart" namespace="/json-chart" extends="json-default">
<action name="bar" class="action.Bar">
<result type="json"/>
</action>
</package>
由於ofc2返回的數據都是json格式
所以必須加入json plugin
這裡也只有extends="json-default"result type="json"比較不一樣

index.jsp
<%@ taglib prefix="s" uri="/struts-tags"%>
<html>
<head>
<script type="text/javascript" src="<s:url value="/js/swfobject.js"/>"></script>
<script type="text/javascript">
swfobject.embedSWF("<s:url value="/open-flash-chart.swf"/>"
, "my_chart", "1024", "300", "9.0.0"
, "expressInstall.swf"
, {"data-file":"<s:url value="/json-chart/bar.action"/>"});
</script>
</head>
<body>
<div id="my_chart"></div>
</body>
</html>
data-file是透過bar.action在server端產生的

這裡別忘了加入swfobject.js
並且把open-flash-chart.swf放到根目錄

就可以很順利的看到圖了!

[Struts2]<s:url>的includeParams屬性

<s:url>的includeParams屬性共有三種值可設定
none, get, all
default為none(版本Struts 2.1.3之後)

none是不帶任何參數
get是帶著HTTP-GET所帶著的參數
all則是帶著HTTP-GET/HTTP-POST所帶著的參數

要預設includeParams為none的話
可以在struts.properties文件中設置
struts.url.includeParams=none

[Struts2]多個默認值的下拉選單

官網的Example

<s:select label="Pets"
name="petIds"
list="petDao.pets"
listKey="id"
listValue="name"
multiple="true"
size="3"
required="true"
value="%{petDao.pets.{id}}"
/>

%{petDao.pets.{id}}的值有出現在listKey的id裡
就會被auto-selected

[jQuery]元素名為變數時的選取

一般來講jQuery的id selector的形式為
$("#elementID");//取得id為elementID的元素

不過當id為變數時 就得改成
$("#"+variableElementID)

2009年6月22日 星期一

[Struts2]interceptor

Strust2 default interceptor
<default-interceptor-ref name="defaultStack"/>
<interceptor-stack name="defaultStack">
<interceptor-ref name="exception"/>
<interceptor-ref name="alias"/>
<interceptor-ref name="servletConfig"/>
<interceptor-ref name="prepare"/>
<interceptor-ref name="i18n"/>
<interceptor-ref name="chain"/>
<interceptor-ref name="debugging"/>
<interceptor-ref name="profiling"/>
<interceptor-ref name="scopedModelDriven"/>
<interceptor-ref name="modelDriven"/>
<interceptor-ref name="fileUpload"/>
<interceptor-ref name="checkbox"/>
<interceptor-ref name="staticParams"/>
<interceptor-ref name="params">
<param name="excludeParams">dojo\..*</param>
</interceptor-ref>
<interceptor-ref name="conversionError"/>
<interceptor-ref name="validation">
<param name="excludeMethods">input,back,cancel,browse</param>
</interceptor-ref>
<interceptor-ref name="workflow">
<param name="excludeMethods">input,back,cancel,browse</param>
</interceptor-ref>
</interceptor-stack>

如果有多個自定義的interceptor
使用interceptor stack的形式來處理比較方便
<interceptors>
<interceptor-stack name="myStack">
<interceptor-ref name="i18n" />
<interceptor-ref name="params" />
<interceptor-ref name="static-params" />
<interceptor-ref name="LoginInterceptor" /> <!--自定義的-->
</interceptor-stack>
<interceptor name ="LoginInterceptor" class ="myClass" />
</interceptors>

直接引用interceptor stack就不用一個一個加了
<action name="myAction" class="myClass">
<interceptor-ref name="myStack"/>
...
</action>

[Quartz]JobListener, TriggerListener

透過JobListener, TriggerListener
可以監控job和trigger的運作情況

JobListener code
public class JobMonitor implements JobListener {
static Logger logger = Logger.getLogger(JobMonitor.class);

public String getName() {
return getClass().getSimpleName();
}

public void jobToBeExecuted(JobExecutionContext context) {
String sJobName = context.getJobDetail().getName();
LogUtil.logDebug(logger, "The job "+sJobName+" will be execute!");
}

public void jobExecutionVetoed(JobExecutionContext context) {
String sJobName = context.getJobDetail().getName();
LogUtil.logDebug(logger, "The job "+sJobName+" was Vetoed by trigger!");
}

public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {
String sJobName = context.getJobDetail().getName();
LogUtil.logDebug(logger, "The job "+sJobName+" was executed!");
}
}

TriggerListener code
public class TriggerMonitor implements TriggerListener {
static Logger logger = Logger.getLogger(TriggerMonitor.class);

public String getName() {
return getClass().getSimpleName();
}

public void triggerFired(Trigger trigger, JobExecutionContext context) {
String sTriggerName = trigger.getName();
LogUtil.logDebug(logger, sTriggerName + " was fired");
}

public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) {
String sTriggerName = trigger.getName();
LogUtil.logDebug(logger, sTriggerName + " was not vetoed");
return false;
}

public void triggerMisfired(Trigger trigger) {
String sTriggerName = trigger.getName();
LogUtil.logDebug(logger, sTriggerName + " misfired");
}

public void triggerComplete(Trigger trigger, JobExecutionContext context, int triggerInstructionCode) {
String sTriggerName = trigger.getName();
LogUtil.logDebug(logger, sTriggerName + " is complete");
}
}

然後在scheduler加入這兩個listener
JobListener jobMonitor = new JobMonitor();
scheduler.addGlobalJobListener(jobMonitor);
TriggerListener triggerMonitor = new TriggerMonitor();
scheduler.addGlobalTriggerListener(triggerMonitor);
實際跑了一下
監看的流程如下
TestQjobTrigger was fired
->TestQjobTrigger was not vetoed
->The job TestQjob will be execute!
->The job TestQjob was executed!
->TestQjobTrigger is complete

[Quartz]interrupt job

如果想中斷執行中的job
在Quartz官網的FAQ有提到解決的辦法
Q:How do I stop a Job that is currently executing?
A:See the org.quartz.InterruptableJob interface
, and the
Scheduler.interrupt(String, String) method.

透過Scheduler.interrupt(String jobName, String groupName)
則Scheduler會調用job的interrupt method

這裡有個前提
job必須implements InterruptableJob
否則會出現下面兩行的錯誤訊息
org.quartz.UnableToInterruptJobException: Job 'TestQjob' of group 'TestJob,
can not be interrupted, since it does not implement org.quartz.InterruptableJob

2009年6月21日 星期日

[prototype]AJAX物件

Ajax
屬性:
activeRequestCount
函式:
getTransport()

Ajax.Responders
這是個Gobal物件,故無論在程式中使用了多少個Ajax.Request或是Ajax.Updater物件,都可以用同一份回應函式來處理結果。
函式:
register():註冊新的回應函式
unregister():取消已註冊的回應函式。

Ajax.Base
屬性:
method
parameters
asynchronous
requestHeader
postBody
onxxxxxxx:onUninitialization, onLoading, onLoaded, onInteractive, onComplete
onSuccess,onFailure
onException
函式:
setOptions()
responseIsSuccess()
responseIsFailure()

Ajax.Request
屬性:
transport
url
函式:
建構子(url, options)

Ajax.Update
屬性:
evalScripts:設為True時,Ajax.Updater物件會自動執行任保回傳內容中script區塊
函式:
建構子(container,url,options)

Ajax.PeriodicalUpdater
函式:
建構子(container,url,options)
start()
stop()















參考書目:Ajax網頁程式設計 Google成功背後的技術

[AJAX]XMLHttpRequest

依據不同的瀏覽器,取得 XMLHttpRequest 物件
function createAJAX() {
if (window.ActiveXObject) { //IE
try {
return new ActiveXObject("Msxml2.XMLHTTP");
} catch (e) {
try {
return new ActiveXObject("Microsoft.XMLHTTP");
} catch (e2) {
return null;
}
}
} else if (window.XMLHttpRequest) { //FF,safari
return new XMLHttpRequest();
} else {
return null;
}
}

XMLHttpRequest的函式
void open(string method, string url, boolean asynch, string username, string password)
開啟對伺服端的連結;method為請求方式(GET、POST);url是目標URL;asynch為非同步設定,預設是true。
void send(content)
對伺服端傳送請求,content以放XML、輸入串流、字串、JSON格式的內容,放進去會放在POST本體中發送。
void setRequestHeader(string header, string value)
為HTTP請求設定一個給定的 header 設定值。
void abort()
用來中斷請求。
string getAllResponseHeaders()
傳回一個字串,其中包含HTTP請求的所有回應標頭。
string getResponseHeader(string header)
傳回一個字串,其中包含指定的回應標頭值。

XMLHttpRequest 的屬性
onreadystatechange
readyState每次改變時,都會呼叫onreadystatechange所參考的函式。

readyState

會有0到4的數值,分別表示不同的請求狀態:
0 = 未初始化的連線(uninitialized),還沒呼叫open()
1 = 載入中(loading),呼叫open(),還沒呼叫send()
2 = 已載入(loaded),呼叫send(),請求header/status準備好
3 = 互動中(interactive),正在與伺服器互動中
4 = 請求完成(completed),完成請求

responseText

伺服器傳來的請求回應文字,會設定給這個屬性。

responseXML

伺服器傳來的請求回應如果是XML,會成為DOM設定給這個屬性。

status
伺服器回應的狀態碼,例如200是OK,404為Not Found

statusText
伺服器回應的狀態文字。

參考書目:《Ajax網頁程式設計》旗標出版社

2009年6月19日 星期五

[SQL]序列

建立序列
SQL>CREATE SEQUENCE SEQ_NAME
MINVALUE 1 最小值
MAXVALUE 99999999 最大值
INCREMENT BY 1 遞增或遞減值
NOCYCLE 是否循環
CACHE 200;

查詢序列下一個值
SELECT SEQ_NAME.nextval FROM DUAL

查詢目前序列號
SELECT SEQ_NAME.currval FROM DUAL

查詢所有序列
SELECT* FROM USER_SEQUENCES

刪除序列
DROP SEQUENCE SEQ_NAME

2009年6月18日 星期四

[CSS]IE/FireFox 對width屬性的解讀

.link {
padding:5px
width: 100px;
}

IE的解讀是寬度為100px,width的100px包含padding的5px
FF的解讀是寬度為105px,width+padding

下面是三種兼容的寫法
1.
.link {
padding:5px
width: 95px !important;
width: 100px;
}
在FF要用的width後面加上!important(FF認識這個字-"提升優先權")
而且要放在另一個width前面
因為有重複的屬性IE只抓最後一個

2.
link {
padding:5px
width/**/: 95px;
width: 100px;
}
FF要用的加上/**/
IE看到/**/只會把那行註釋掉

3.
.link {
padding:5px
width: 100px; > width: 95px;
}
IE認識 '>'
所以會用比較大的那個

[quartz]cron expression

0 0/5 * * * ?

從左到右以空白格隔後有六個值
分別代表著
1.Seconds (0-59)
2.Minutes (0-59)
3.Hours (0-23)
4.Day-of-Month ( 0-31,但是要注意的是每月的天數有所不同)
5.Month (0-11或是用 JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV and DEC)
6.Day-of-Week (1-7或是 SUN, MON, TUE, WED, THU, FRI and SAT)
7.Year (optional field)

而特殊符號代表的意思如下
/ -> Minutes 設成 '0/15' ,表示從0分鐘開始,每15分鐘執行一次,如果設成 '3/20',則表示從3分鐘開始,每20分鐘執行一次(也就是說 3,23,43分的時候)
* ->所有的,放在月份,表示每個月都要,1,2,3同理
? ->可以用在 day-of-month 和 day-of-week 的設定,表示沒有去特別限定
# ->The '#' is used to specify "the nth" XXX. For example, day-of-week 如果設為 "6#3" 或是 "FRI#3" ,則表示這個月的第三個星期五
L -> 表示最後一個,如果 day-of-month 設成L ,一月就表示 31號,二月指的就是28或29號;若day-of-week 設成L,則表示星期六或是一個禮拜的最後一天.另外,若設成 "6L" 或是 "FRIL",就表示這個月的最後一個星期五
W -> 表示最接近的WeekDay(星期一到星期五),For example,如果 day-of-month 設成15W,則表示這個月中最接近15號那天的WeekDay

2009年6月17日 星期三

ROME

ROME是個可以產生,分析RSS的開源的java tool
我除了下載ROME還下載了ROME Fetcher
這樣多了一些函式可以用比較方便

抓取RSS
FeedFetcher feedFetcher = new HttpURLFeedFetcher();
SyndFeed feed = feedFetcher.retrieveFeed(new URL(feedUrl));//feedUrl是RSS的來源

分析RSS
第一種寫法
for (java.util.Iterator iter = feed.getEntries().iterator(); iter.hasNext(); ) {
SyndEntry entry = (SyndEntry) iter.next();
System.out.println(entry.getLink() + entry.getTitle() + entry.getPublishedDate());
}

第二種寫法
List entryList = feed.getEntries();
for(int i=0;i<entryList.size();i++)
{
entry = (SyndEntry) entryList.get(i);
System.out.println(entry.getLink() + entry.getTitle() + entry.getPublishedDate());
}
差異在使用不同的物件類型而已

ROME的使用也很直覺
getLink()就是取得RSS裡<link>內的值
getTitle()也是同樣的道理

大部分網站發佈的格式都差不多
有不一樣的地方的話大概是在<pubDate>的時間格式
ROME已經將大部分的時間格式都加進去了
如果發生因為時間格式不符合而產生錯誤的話
可以增加一個rome.properties在classpath底下
內容加入datetime.extra.masks=EEE dd MMM yyyy HH:mm:ss z
這裡的日期格式就是不符合內建的時間格式
有多個話就用分隔線(|)隔開

整合JCaptcha

JCaptcha是個用來產生圖形驗證碼的開源組件




Captcha class
主要是字型,出現的字元,驗證碼的字數等初始化設定
public class Captcha extends ListImageCaptchaEngine
{
protected void buildInitialFactories() {

WordGenerator wgen = new RandomWordGenerator("abcdefghijklmnopqrstuvwxyz123456789");
RandomRangeColorGenerator cgen = new RandomRangeColorGenerator(
new int[] { 0, 100 }, new int[] { 0, 100 },
new int[] { 0, 100 });

TextPaster textPaster = new RandomTextPaster(new Integer(4),
new Integer(4), cgen, true);

BackgroundGenerator backgroundGenerator = new FunkyBackgroundGenerator(
new Integer(150), new Integer(50));

Font[] fontsList = new Font[] { new Font("Arial", 0, 10),
new Font("Tahoma", 0, 10), new Font("Verdana", 0, 10), };

FontGenerator fontGenerator = new RandomFontGenerator(new Integer(30),
new Integer(30), fontsList);

WordToImage wordToImage = new ComposedWordToImage(fontGenerator,
backgroundGenerator, textPaster);
this.addFactory(new GimpyFactory(wgen, wordToImage));
}
}
servlet
public class DynaImageServlet extends HttpServlet {
public static class CaptchaServiceSingleton {
private static DefaultManageableImageCaptchaService instance =
new DefaultManageableImageCaptchaService(
new FastHashMapCaptchaStore(),
new Captcha(),
180,
100000,
75000);

public static ImageCaptchaService getInstance() {
return instance;
}
}

public void init(ServletConfig servletConfig) throws ServletException {
super.init(servletConfig);}

protected void doGet(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse) throws ServletException, IOException {

byte[] captchaChallengeAsJpeg = null;
ByteArrayOutputStream jpegOutputStream = new ByteArrayOutputStream();
try {
String captchaId = httpServletRequest.getSession().getId();
BufferedImage challenge = CaptchaServiceSingleton.getInstance().getImageChallengeForID(captchaId,httpServletRequest.getLocale());
JPEGImageEncoder jpegEncoder = JPEGCodec.createJPEGEncoder(jpegOutputStream);
jpegEncoder.encode(challenge);
} catch (IllegalArgumentException e) {
httpServletResponse.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
} catch (CaptchaServiceException e) {
httpServletResponse.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
return;
}
captchaChallengeAsJpeg = jpegOutputStream.toByteArray();
httpServletResponse.setHeader("Cache-Control", "no-store");
httpServletResponse.setHeader("Pragma", "no-cache");
httpServletResponse.setDateHeader("Expires", 0);
httpServletResponse.setContentType("image/jpeg");
ServletOutputStream responseOutputStream = httpServletResponse.getOutputStream();
responseOutputStream.write(captchaChallengeAsJpeg);
responseOutputStream.flush();
responseOutputStream.close();
}
}
然後在web.xnl裡加入這個servlet的設定


至於比對使用者所填入的驗證碼是否正確的部份
boolean isResponseCorrect = false;
String captchaId = ServletActionContext.getRequest().getSession().getId();
String sImageNumber = dp.getAttribute("imageNumber", "");
isResponseCorrect = CaptchaServiceSingleton.getInstance().validateResponseForID(captchaId, sImageNumber);

captchaId是session的id
主要是用來比對使用者送出表單時跟產生驗證碼兩者之間的session是否一致
sImageNumber是使用者輸入的值

然後就可以藉由isResponseCorrect的boolean值得知
使用者輸入的跟產生驗證碼是否一樣了!

2009年6月16日 星期二

[dom4j]寫xml

建立document和root節點
Document doc = DocumentHelper.createDocument();
Element root = doc.addElement("root");
doc.setRootElement(root);

在root加入一名為name的子節點,其值為william
Element Type = root.addElement("name").addText("william");

產生檔案
public void createFile(Document doc) throws Exception{
OutputFormat format = OutputFormat.createPrettyPrint();
format.setEncoding("UTF-8");//加個編碼可避免掉亂碼的問題
String filename = "F://test123.xml";
FileOutputStream fos = new FileOutputStream(filename);
XMLWriter writer = new XMLWriter(fos, format);
writer.write(doc);
writer.close();
}

2009年6月15日 星期一

[dom4j]讀xml

由url取得xml
public Document readxml(URL url) throws MalformedURLException,DocumentException {
SAXReader reader = new SAXReader();
Document document = reader.read(url);
return document;
}


由file取得xml
public Document readxml(URL url) throws MalformedURLException,DocumentException {
SAXReader reader = new SAXReader();
Document document = reader.read(url);
return document;
}

2009年6月14日 星期日

[Struts2]<s:if>對於型態的判斷


<s:if test="itsString=='Yes'">
這樣寫沒問題
<s:if test="itsString=='Y'">
這樣寫會有java.lang.NumberFormatException: For input string: "Y" 的訊息

先撇開itsString是塞Yes還是塞Y給它(這不是重點...)
而是Struts2似乎是認為Y是Char而不是String

解決的方法是加個toString()來避開這個錯誤
<s:if test="itsString=='Y'.toString()">

2009年6月12日 星期五

[Struts2]檔案上傳

利用tag s:file和library commons-io,commons-fileupload
便可以輕鬆的運用加以完成此功能!

JSP code:
<s:file id="file" name="file">
form要加入屬性 enctype="multipart/form-data"

JAVA code:
public File getFile() {
return file;
}
public void setFile(File file) {
this.file = file;
}
如果遇到jxl.read.biff.BiffException: Unable to recognize OLE stream的Exception
可能是因為在form裡除了file外
還包含了其它像是text,textarea之類的tag element