2009年12月22日 星期二

swing Window 元件置頂


無論是 JFrame、JDialog 均可使用下列 method 將 window 置於最上層,


即使切換視窗也不會被 focus 的視窗所蓋到。


public final void setAlwaysOnTop(boolean alwaysOnTop)

2009年12月20日 星期日

debian 下格式化隨身碟為 vfat

必須使用 mkfs.vfat 來進行格式化
而 mkfs.vfat 預設沒有安裝,查詢他所存在的 deb 套件位置

$ apt-file search mkfs.vfat
dosfstools: sbin/mkfs.vfat

可以發現存在於 dosfstools 下
先安裝 dosfstools 再格式化隨身碟為 vfat:

sudo aptitude install dosfstools
sudo mkfs.vfat /dev/sdb1

2009年12月18日 星期五

自定以圖片為背景的 JPanel

繼承 JPanel 並 override paintComponent(Graphics gc) 就可以了:

public class ImageBackgroundJPanel extends JPanel {
private Image backgroundImg = null;

public ImageBackgroundJPanel(String imagePath) {
backgroundImg = new ImageIcon(imagePath).getImage();
}

@Override
protected void paintComponent(Graphics gc) {
gc.drawImage(backgroundImg, 0, 0, this);
}
}

使用 jgoodies 的 look and feel

jgoodies 有許多產品,下載最新版的 jgoodies libraries looks 就可以了,
目前使用 2.2.2 版,將 looks-2.2.2.jar 加到 classpath 下
在執行到 swing 的任何程式之前,先加入以下 code 設定 L&F (look and feel)


// 設定銀色主題,有多種可用,必須在設定 L&F 前設定
// com.jgoodies.looks.plastic.theme - 主題的 package
PlasticLookAndFeel.setPlasticTheme(new Silver());
// 設定 look and feel ,在 linux 下使用 Plastic 系列比較沒問題,三種可用
//com.jgoodies.looks.plastic.PlasticLookAndFeel
//com.jgoodies.looks.plastic.Plastic3DLookAndFeel
//com.jgoodies.looks.plastic.PlasticXPLookAndFeel
UIManager.setLookAndFeel(new PlasticXPLookAndFeel());

python 取得 os 的 path 分隔符號

使用 os moudle 取得有關 os 的資訊
import os

javaLibraryPath = "." + os.pathsep + "bundle" + os.pathsep + "lib"

windows 下 javaLibraryPath = .;bundle;lib
linux 下 javaLibraryPath = .:bundle:lib

help(os) 片斷說明如下
- all functions from posix, nt, os2, mac, or ce, e.g. unlink, stat, etc.
- os.path is one of the modules posixpath, ntpath, or macpath
- os.name is 'posix', 'nt', 'os2', 'mac', 'ce' or 'riscos'
- os.curdir is a string representing the current directory ('.' or ':')
- os.pardir is a string representing the parent directory ('..' or '::')
- os.sep is the (or a most common) pathname separator ('/' or ':' or '\\')
- os.extsep is the extension separator ('.' or '/')
- os.altsep is the alternate pathname separator (None or '/')
- os.pathsep is the component separator used in $PATH etc
- os.linesep is the line separator in text files ('\r' or '\n' or '\r\n')
- os.defpath is the default search path for executables
- os.devnull is the file path of the null device ('/dev/null', etc.)

2009年12月12日 星期六

2009年12月10日 星期四

JXTA p2p 協定介紹之資料傳送

啟動 JXTA 網路後可以得到一個 net peer group 實例

infrastructurePeerGroup = jxtaNetMan.startNetwork();

而 net peer group 提供了許多 service ,透過其中的 discover service 及 pipe service
即可讓 peer 互相傳遞訊息。

// 取得 discovery service
discoveryService = infrastructurePeerGroup.getDiscoveryService();
// 取得 pipe service
pipeService = infrastructurePeerGroup.getPipeService();

第一個要了解的概念是 PipeAdvertisement ,Pipe 是 JXTA 用來進行 peer 與 peer 溝通
的通道,可以想像成 Socket ,實作上底層應該也是脫離不了 Socket,而傳輸
也是透過 InputStream 與 OutputStream 進行資料傳送。
而 Advertisement 是 JXTA 用來表示資源的定義文件,是一個規範好的 xml 文件
JXTA 為網路中的各式各樣的資源都有定義屬於他的 Advertisement 。
以 PipeAdvertisement 來說,就是用來表示某一個 Pipe 的 Ad 文件。

因此,在資訊傳送前,需先由接收端的 peer 建立一個 PipeAdvertisement ,並且,
綁定一個 InputPipe 到 PipeAdvertisement 上,再由 discovery service 發佈到 JXTA
網路上,而發送端的 peer 再由 discovery service 查詢到 PipeAdvertisement,並綁定
OutputPipe 到同一個 PipeAdvertisement 上,等到綁定完成後,即可開始傳送資料。

建立 PipeAdvertisement
PipeAdvertisement pipeAd = (PipeAdvertisement)
AdvertisementFactory.newAdvertisement(PipeAdvertisement.getAdvertisementType());
pipeAd.setPipeID(IDFactory.newPipeID(PeerGroupID.defaultNetPeerGroupID));
pipeAd.setType(PipeService.UnicastType);
pipeAd.setName("APIPENAME");

建立 InputPipe bind 到 pipe ad 並設定 listener

pipeService.createInputPipe(pipeAd, pipeMsgListener);

listener 是一個 PipeMsgListener 的介面的實例,當完成對方建立了 OutputPipe 並
開始傳送資料時,PipeMsgListener 的 方法:

public void pipeMsgEvent(PipeMsgEvent event)

而傳進來的 event 物件會收到傳送過來的資料。
隨後即可將 pipeAd 發佈到 discover service 上,並設定 life time 及 expiration time

discoveryService.publish(pipeAd, pipeAdLifetime, pipeAdExpiration);
discoveryService.remotePublish(pipeAd, pipeAdExpiration);

而另一個要發送資料的 peer 則必須先以 discovery service 查詢前一個 peer
發佈的 pipeAd

discoveryService.getRemoteAdvertisements(
null, // 指定單一查詢的 peer id,null 則表示查詢整個網路
DiscoveryService.ADV, // 查詢的 type
"Name", // 指定查詢項目
"APIPENAME", // 查詢項目的值
1, // 一次要找多少個 ad
null); // 指定 discovery listener

這樣即會送出一個查詢到 JXTA 網路,最後一項參數的 listener 也可以使用

discoveryService.addDiscoveryListener(disclistener);

disclistener 是一個 DiscoveryListener 介面的實例,當查詢 ad 有結果後會觸發
listener 的 :

public void discoveryEvent(DiscoveryEvent event)

並可以 event 裡取得 ad 物件。因此發送端 peer 就可以得到 pipeAd 了
得到 pipeAd 後,發送端需要先建立一個 OutPipe 並綁定到 pipeAd 上

pipeService.createOutputPipe(pipeAd, outListener);

outListener 是一個 OutputPipeListener 當 OutPipe 綁定完成後,會觸發 listener 的

public void outputPipeEvent(OutputPipeEvent event)

並由 event 取得 OutputPipe 實例,開始傳送資料。

因此總結來說,傳送端在觸發 OutputPipeEvent 後可以開始傳送資料,
而接收端在觸發了 PipeMsgEvent 後可以開始接收資料,至於建立資料的方法則是,
建立一個 net.jxta.endpoint.Message 實例:

Message msg = new Message();

Message 是資料的包裝,裡面可以包含多筆 net.jxta.endpoint.MessageElement 實例
以最單純的 StringMessageElement 舉例:

StringMessageElement sme = new StringMessageElement(
"elemName", // element 的 key
"elemValue", // element 的 value
null // signature
);

將 element 加入到 Message 中,並以 OutPipe 傳送

msg.addMessageElement("msgNameSpace", sme);
event.getOutputPipe().send(msg);

而接收端則由 PipeMsgEvent 取得 Message,並由 namespace 和 element key 取值

Message msg = event.getMessage();
ElementIterator itMsg = msg.getMessageElements("msgNameSpace", "elemName");
while(itMsg.hasNext()){
MessageElement msgElem = itMsgElem.next();
System.out.println(msgElem.toString()); // 印出 "elemValue"
}

JXTA p2p 協定介紹之連線

JXTA 是 SUN 所提出的 p2p 標準協定,可使用各種語言完成,而由 java 完成的,
又可分為給 JavaSE 使用的 JXSE 及給 JavaME 使用的 JXME,也有其他語言的版本,
如: JXTA-C ,有空可以多加研究比較,而目前使用的版本為 JXSE,簡單介紹如下:

JXTA 是一個 p2p 的協定,規範了一個 p2p 網路上的所有 peer (節點) 的溝通標準,
有別於傳統的 client-server 架構,p2p 網路上有許多的新名詞,如:
peer、peer group、ad、RDV…等,在官方的說明文件中都有詳細說明,在此不多介紹,
JXTA 協定中定義了許多 peer 種類,以 EDGE 最常見,另外還有構成 JXTA 網路架構
的 RDV 節點,以及讓防火牆或 NAT 內的 EDGE 可以向外界RDV構通的 RELAY
節點,架構如下列官方文件裡的圖:









JXSE 下載下來解開有四個 jar:
jxse.jar、bcprov-jdk14.jar、javax.servlet.jar、org.mortbay.jetty.jar
其中似乎只要有 jxse.jar 即可 compile 而執行需要 jxse.jar 加 bcprov-jdk14.jar
javax.servlet.jar、org.mortbay.jetty.jar 目前的用處不明。

JXSE 程式以 net.jxta.platform.NetworkManager 類別來啟動及管理 JXTA 網路
建立 NetworkManager 時需指定 peer 種類及相關資訊:

jxtaNetMan = new NetworkManager(
NetworkManager.ConfigMode.EDGE,
FEELER_ENTITY_PEER_NAME,
new File(new File(JXTA_HOME), FEELER_ENTITY_PEER_NAME).toURI()
);

第一個參數為 peer 種類,第二個參數為 peer 名稱,第三個參數為 peer 資料暫存目錄
建立好 NetworkManager 之後可以進行一些設定:

jxtaNetMan.setConfigPersistent(true); // 啟動儲存設定功能
// 使用官方提供的預設 RDV ,但似乎有問題,無法連線
//會不斷連往 http://rdv.jxtahosts.net/cgi-bin/rendezvous.cgi?3 查詢 RDV
jxtaNetMan.setUseDefaultSeeds(true);
jxtaNetCfg = jxtaNetMan.getConfigurator(); // 取得設定物件,以進行更多設定
// 加入自定的 RDV 位址,範例: "tcp://www.bennu.tw:9701"
jxtaNetCfg.addSeedRendezvous(new URI(NetService.FEELER_RDV_URI));
jxtaNetCfg.save(); // 儲存設定到 peer 資料暫存目錄下的 PlatformConfig 檔案

設定之後,即可以 startNetwork() 啟動這一個 peer 的運作。

// startNetWork() 傳回 net group
infrastructurePeerGroup = jxtaNetMan.startNetwork();

每一個裝置或者jvm應該可以同時擁有多個節點,同時扮演多個角色,如:
可以既是某些 EDGE 的 RDV 又是其他 RDV 的 EDGE ,但是目前測試的結果,
無法在同一個 jvm 下啟動兩次 JXTA network

啟動完成後會回傳一個 peer group 物件,每一個 peer 最少都會加入一個 peer group
這個 peer group 稱之為 net peer group,可以想像成整個 JXTA 網路是一個 peer group
而如果要另外建立 peer group 也一定是此 net peer group 的子集,如下圖:

取得 net peer group 實例後,可以從 net peer group 取得許多種 service ,如:
discovery service、pipe service、rdv service、… 等,JXTA定義了許多 service
可以讓 peer 與 peer 進行資料傳遞,而不需在乎每個 peer 所在的網路環境。

其中需先注意 rdv service ,以 RDV peer 來說,除了在建立 NetworkManager 時,
第一個參數給予 NetworkManager.ConfigMode.RENDEZVOUS 仍然無法讓其他
EDGE peer 連上這一個 rdv peer ,還需在 startNetwork() 後啟動一下 rdv service:

jxtaNetMan.getNetPeerGroup().getRendezVousService().startRendezVous();

如此才可以開始提供 rdv 功能。
而 EDGE peer 要連上 RDV peer 則是使用 :

boolean connected = jxtaNetMan.waitForRendezvousConnection(timeout);

waitForRendezvousConnection 是一個 block 方法,會一直嘗試連線到成功或是
timeout 時間到為止,而回傳 boolean 表示連線成功(true)或失敗(false)。
目前 timeout 必須設定長一點,即使是在 local net 也需要 5 分鐘以上比較好。
可能是由於使用廣播的方式,導致連線效率不好,官網上可以看到已有改進的工作

除了 EDGE 可以連上 RDV,RDV 也可透過同一 method 連上其他 RDV,
以構成 RDV 主幹網路,而 EDGE peer 顧名思議就是散佈在 RDV 主幹網路的邊緣。

2009年12月3日 星期四

ivy 加入 java 的 maven 2 repository

直接在 <ivysettings> <resolvers> 下加入
<!-- maven 2 -->
<ibiblio name="java-net-maven2" root="http://download.java.net/maven/2/" m2compatible="true" />

這一個 ibiblio 節點
並在
<chain name="main" dual="true"> 裡補上
<resolver ref="java-net-maven2"/>

範例:
<?xml version="1.0" encoding="utf-8"?>
<ivysettings>
<resolvers>
<!-- spring EBR -->
<url name="com.springsource.repository.bundles.release">
<ivy pattern="http://repository.springsource.com/ivy/bundles/release/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]" />
<artifact pattern="http://repository.springsource.com/ivy/bundles/release/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]" />
</url>
<url name="com.springsource.repository.bundles.external">
<ivy pattern="http://repository.springsource.com/ivy/bundles/external/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]" />
<artifact pattern="http://repository.springsource.com/ivy/bundles/external/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]" />
</url>
<!-- maven 2 -->
<ibiblio name="java-net-maven2" root="http://download.java.net/maven/2/" m2compatible="true" />
<!-- main chain -->
<chain name="main" dual="true">
<resolver ref="shared"/>
<resolver ref="public"/>
<resolver ref="com.springsource.repository.bundles.release"/>
<resolver ref="com.springsource.repository.bundles.external"/>
<resolver ref="java-net-maven2"/>
</chain>
</resolvers>
</ivysettings>

2009年11月28日 星期六

java java.ext.dirs jvm 環境變數設定

在 java 運行時可以透過 -cp 去設定 classpath,
-cp 可以指定多個資料夾做為 *.class 檔的來源目錄,
同時也可以指定多個 jar 檔案做為 *.class 檔的來源,但是,
jar 檔通常很多,如果要一一的指定每一個 jar 非常不方便,
且,一般來說 jar 會被放在一個統一的目錄下:如 lib/ ,
這時可以透過 java -Djava.ext.dirs=lib 來指定 lib 下的所有 jar 檔,
一次就可以把所有的 jar 做為 classpath ,非常方便。

2009年11月19日 星期四

java 透過 PythonInterpreter 執行外部的 py 程式檔

先建立所需要的 java interface

package tw.bennu.feeler.jython.service;
public interface IJyCommand {
public String getName();
}

寫 src/jython/JyCommand.py 去實作 java interface
from tw.bennu.feeler.jython.service import IJyCommand
class JyCommandImpl(IJyCommand):

def __init__(self,cmd):
self.command = cmd

def getName(self):
ret = self.command
print ret
return ret
寫一個 Factory 物件,負責建立PythonInterpreter、讀取*.py、建立及回傳物件實例
public class JyFactory {
// 以 jython 實作的 classes 物件,可用來建立實例
private PyObject jyCommandClass = null;

public JyFactory() {
PyDictionary table = new PyDictionary();
PySystemState state = new PySystemState();
state.setClassLoader(JyFactory.class.getClassLoader());
PythonInterpreter interp = new PythonInterpreter(table, state);
// 讀入 src/jython/*.py
interp.execfile("src/jython/JyCommand.py");
jyCommandClass = interp.get("JyCommandImpl");
}

/**
* 建立一個 IJyCommand 介面的實例 (由JyCommand.py 實作介面內容)
*
* @param name
* @return
*/
public IJyCommand getJyCommand(String name) {
PyObject jyCommandObj = this.jyCommandClass.__call__(new PyString(name));
return (IJyCommand) jyCommandObj.__tojava__(IJyCommand.class);
}
}

JyFactory 解說:先以 interp.get("JyCommandImpl"); 取得 class 物件(只做一次),
再以 class.__call__ 建立實例 obj 物件,最後以 obj.__tojava__轉換回java 物件

public static void main(String[] args){
String name = jythonServImpl.getJyCommand("hello jython").getName();
System.out.println(name);
}

結果會印出 hello jython

jython Embedded 直譯器的 ClassLoader設定

jython 可以 embedded 一個直譯器到 java 的程式裡,叫作PythonInterpreter物件,
透過PythonInterpreter,我們可以寫出python語法的程式碼,同時又使用到java的物件,
PythonInterpreter 在建立時可以指定某個 classloader 做為他在讀取 java class的依據,
在某些Classloader 環境較特殊的情況可以使用,如OSGi的每個 bundle 都有自己的 classloader。

設定方式如下:

PyDictionary table = new PyDictionary();
PySystemState state = new PySystemState();
state.setClassLoader(XXX.class.getClassLoader());
PythonInterpreter interp = new PythonInterpreter(table, state);

其中的 table 是 python 的 key-value namespace 如:
a=1
則 table 裡會加入一筆 key=new PyString("a");value=new PyInteger(1) 的 pair,
傳入 table 可以當做 PythonInterpreter 建立時的預設參數,供 python 程式碼參考使用。

2009年11月17日 星期二

Felix 使用到 rt.jar 裡特殊的 package 設定

在OSGi的環境裡每個bundle都有自己的classloader,因此要使用別的bundle的package
必須透過Import、Export來使用。

Felix是一個java的OSGi實作,jre本身有提供許多官方的package可供使用,因此,
當Felix啟動時,必須啟動一個稱為System Bundle的 bundle,而這個System Bundle即
Export出許多 jre 所提供的 package,然而並非是"整個" jre 的 package 都Export 出來,
如 rt.jar 的一些 com.sun.* 的 package 即有缺少,可以在 Felix 的config.properties 檔裡
加上:
org.osgi.framework.system.packages.extra= \
com.sun.package.a,\
com.sun.package.b,\
....

來將需要的 package export 出來。

2009年11月12日 星期四

java MessageDigest 產生編碼資料

以 java.security.MessageDigest 產生 md5 編碼:
String password = "123";
MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.update(password.getBytes());
String digest = new String(md5.digest());

除了md5也有rsa等各種編碼詳見MessageDigest javadoc

virtual box 官方版本安裝

http://www.virtualbox.org/wiki/Linux_Downloads
轉載自官方wiki說明如下:

Debian-based Linux distributionsAdd
one of the following lines according to your distribution to your /etc/apt/sources.list:
deb http://download.virtualbox.org/virtualbox/debian karmic non-free
deb http://download.virtualbox.org/virtualbox/debian jaunty non-free
deb http://download.virtualbox.org/virtualbox/debian intrepid non-free
deb http://download.virtualbox.org/virtualbox/debian hardy non-free
deb http://download.virtualbox.org/virtualbox/debian gutsy non-free
deb http://download.virtualbox.org/virtualbox/debian dapper non-free
deb http://download.virtualbox.org/virtualbox/debian lenny non-free
deb http://download.virtualbox.org/virtualbox/debian etch non-free
deb http://download.virtualbox.org/virtualbox/debian sarge non-free
deb http://download.virtualbox.org/virtualbox/debian xandros4.0-xn non-free

The Sun public key for apt-secure can be downloaded here.
You can add this key with

sudo apt-key add sun_vbox.asc

or combine downloading and registering:

wget -q http://download.virtualbox.org/virtualbox/debian/sun_vbox.asc -O- | sudo apt-key add -

The key fingerprint is
AF45 1228 01DA D613 29EF 9570 DCF9 F87B 6DFB CBAE Sun Microsystems, Inc.
(xVM VirtualBox archive signing key)
To install VirtualBox, do

apt-get install virtualbox-3.0

2009年11月10日 星期二

Unit Test 使用 System.in 輸入的物件

有時我們會有程式是需要從 System.in 輸入資料來進行運算的,我們會這樣寫:
BufferedReader inputBr = new BufferedReader(new InputStreamReader(System.in));
String input = inputBr.readLine();
但在寫UnitTest時,不可能測試到一半還要去輸入一些資訊到 console,有違自動化的架構。
且在 ant 結合 junit 時,遇到這種狀況會直接測試失敗,這時我們可以透過 mock System.in 的方式,
完成這一個測試,首先待測物件不可以直接使用 System.in 而是在建構子上加上一個,
傳入 InputStream的建構子,而預設建構子則以 System.in 當作該 InputStream,如:

public LcanorusMain() {
this(System.in);
}
public LcanorusMain(InputStream in) {
this.systemIn = in;
}

真正執行時是使用預設建構子,而測試時就是使用有 InputStream 參數的建構子,
並且傳入假造的 mock InputStream 即可:

@Before
public void before() {
lcMain = new LcanorusMain(new SystemInMockInputStream());
}

假造的 mock InputStream 實作重點就是將所要回傳的資料轉成 byte 再一一由read()方法傳回。
且完成所有 byte 後要傳回 -1 表示結束。

private class SystemInMockInputStream extends InputStream {
private byte[] cmdBytes = "fexit\n".getBytes();
private int readIndex = 0;

@Override
public int read() throws IOException {
if (readIndex < cmdBytes.length) {
int ret = cmdBytes[readIndex];
readIndex++;
return ret;
} else {
return -1;
}
}
}

2009年11月5日 星期四

OSGi 的 Bundle-Classpath header

OSGi bundle 可以透過 Import-Package 及 Export-Package 去使用互相的 Class,
但前提是彼此都是 OSGi bundle 格式的 jar 並完成部署在 OSGi 平台上,如Felix、Equinox…,
如果只是單純要使用到非OSGi bundle的第三方 jar,必須要將 jar 整個放到 OSGi bundle 的 bundle space 裡。
所謂的 bundle space 就是 bundle jar 本身,及 bundle 裡的 META-INF/MANIFEST.MF所指定的位置。
MANIFEST.MF 有一個 Bundle-Classpath 的 Header 可以用來指定引用到的第三方 jar。
需注意,指定時要將 "." 目錄也加上去,這裡的 "." 目錄就是指 bundle jar 的根目錄,再
以 "," 分隔,一一加入所需要的第三方 jar,而jar的位置也是以bundle根目錄開始算起。
範例(加入sqlite-jdbc.jar):
Manifest-Version: 1.0
Spring-Context: META-INF/spring/db-context.xml,META-INF/spring/db-osgi-context.xml
Bundle-Name: [feeler] db service
Bundle-Description: provide db service
Bundle-Vendor: muchu@www.bennu.tw
Bundle-Version: 0.0.1
Import-Package: org.osgi.framework
Export-Package: tw.bennu.feeler.db.service
Bundle-ClassPath: .,
META-INF/lib/sqlite-jdbc-3.6.17.jar

因此這一個 bundle jar的裡面還有一個sqlite-jdbc-3.6.17.jar並放在META-INF/lib/ 下。
另外 Bundle-ClassPath 也可以指定含有 *.class 的資料夾做為 classpath。
用法和 java 的 -cp 完全一樣,只是改以"," 分隔。
而且不需再對Bundle-ClassPath的jar做Import-Package的宣告,Import-Package 是偵對
其他 bundle Export 出來的 Package 使用。有宣告反而會因找不到Export-Package而無法啟動 bundle。

2009年11月2日 星期一

easymock 官方範例

mock 是指當用來被 unit 測試對象的物件,需要相依某些其他物件才能正常運作時,
以一個"假冒"的 mock 物件實作被相依的物件之界面,設定給 unit 測試對象物件,
目的是隔離unit 測試對象物件與部署環境,並於測試後直接檢查mock物件的變化,
達到不需要部署測試對象物件到環境,也可以了解測試對象物件對環境的影響是否符合要求。

easymock api 方便我們建立 mock 物件,官方範例如下:
http://www.easymock.org/EasyMock2_5_2_Documentation.html
以一個被測試的 ClassUnderTest 物件,使用到一個 Collaborator 介面為例,
將要為 Collaborator 建立 mock ,並給 ClassUnderTest 做測試。
public class ClassUnderTest {
// ...
public void addListener(Collaborator listener){
// ...
}
public void addDocument(String title, byte[] document) {
// ...
}
public boolean removeDocument(String title) {
// ...
}
public boolean removeDocuments(String[] titles) {
// ...
}
}

public interface Collaborator {
void documentAdded(String title);
void documentChanged(String title);
void documentRemoved(String title);
byte voteForRemoval(String title);
byte[] voteForRemovals(String[] title);
}

測試的 TestCase 實作如下:

import static org.easymock.EasyMock.*;
import junit.framework.TestCase;
public class ExampleTest extends TestCase {
private ClassUnderTest classUnderTest;
private Collaborator mock;

protected void setUp() {
mock = createMock(Collaborator.class); // 1 建立 mock
classUnderTest = new ClassUnderTest();
classUnderTest.addListener(mock);
}

public void testAddDocument() {
mock.documentAdded("New Document"); // 2 設定 mock 物件將replay後該有的行為
replay(mock); // 3 切換 mock 為 replay 準備狀態
classUnderTest.addDocument("New Document", new byte[0]);
}
}

重點在 replay() 之前 mock 物件運作的行為不是 Collaborator 真正的行為,而是,
"預先告知" replay() 之後, mock 應該要有什麼 method 被呼叫,傳進來的參數是什麼…等。
而 replay() 之後, classUnderTest.addDocument("New Document", new byte[0]);
如果沒有觸發 mock 物件"預先告知"的行為,就會產生錯誤使得測試失敗。

ivy 官方範例

ivy 採用和 maven 相同的 lib 管理機置,也使用相同的 repository,
ivy 的 dependency 設定檔叫做 ivy.xml,範例:

<ivy-module version="2.0">
<info organisation="apache" module="hello-ivy"/>
<dependencies>
<dependency org="commons-lang" name="commons-lang" rev="2.0"/>
<dependency org="commons-cli" name="commons-cli" rev="1.0"/>
</dependencies>
</ivy-module>

對應於 maven 的 dependency
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.0</version>
</dependency>

groupId 對應到 org
artifactId 對應到 name
version 對應到 rev

完成 ivy.xml後將他放在和 ant build.xml 同目錄下,並在 build.xml 裡加上 resolve target:
<project xmlns:ivy="antlib:org.apache.ivy.ant" name="hello-ivy" default="run">

...

<!-- =================================
target: resolve
================================= -->
<target name="resolve" description="--> retrieve dependencies with ivy">
<ivy:retrieve />
</target>
</project>

執行 ant resolve 時,ivy 會自動到 maven repository 上找到 ivy.xml 裡定義的 library,
並將之下載到 ~/ivy2/cache 下儲存為本地函式庫,再 copy 到 build.xml 同目錄下的 lib 資料夾裡。

spring-DM webcontainer 啟動

spring-DM 以 extender bundle 去偵測install進來的 bundle中
的META-INF/MANIFEST.MF含有:

Spring-Context: xxxxx/xxxx.xml

這個 header 的 bundle 並以這個 xml 為這個 bundle 建立一個屬於他的
ApplicationContext.....

與 war 整合時,則透過類似相同的概念,不過不是用同一個 extender 偵測,
而是要另外再加上 org.springframework.osgi.web.extender 這個 bundle,
他一樣會偵測 install 進來的 war (war 也可以被當做 bundle install),
並看 war 裡面的 META-INF/MANIFEST.MF 的 Spring-Context
但是不為 war 建立 ApplicationContext 而是將 war 丟給指定的 WebContainer,
預設為 tomcat,tomcat當然也是一個 bundle,再由WebContainer去建立ApplicationContext。

tomcat WebContainer 有自己原本的啟勳方式,但現在被當成一個 bundle,
就必須以OSGi的方式啟動,也就是 Activator ,而tomcat bundle本身沒有這一個Activator,
因此,除了tomcat bundle以外,還要再加裝一個Spring-DM提供的catalina.start.osgi-1.0.0.jar
由這個 start bundle 來啟動 tomcat。這裡的 tomcat bundle 和 tomcat start bundle
都可以在 Spring-DM 的 zip 檔裡的 lib 目錄下找到,目前試過只有 Spring-DM 提供的bundle,運作正常。

除了 tomcat WebContainer ,也可以使用 jetty 做為 WebContainer ,只要以 OSGi 的 fragment 機致,
去修改 org.springframework.osgi.web.extender 的設定即可,Spring-DM 文件中有寫:
http://static.springsource.org/osgi/docs/1.2.0/reference/html-single/#web:configuration:changing-deployer
但由於目前使用的 Felix Osgi Framework 未支援 fragment 機致,因此只能先以 tomcat 做為 WebContainer。

jetty 同樣也需要 start bundle,jetty fragment 、 start bundle 在 Spring-DM zip 檔的 lib 裡都可以找到。