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 裡都可以找到。