星期日, 8月 20, 2006

NetBeans Java 6(aka Mustang)備忘記

以下為 Java SE 6 的目標:
提高兼容性及穩定性
增強診斷,監察及管理能力
提高開發效率
Enterprise desktop
XML and web services
Transparency


以下為 Java SE 6 新特性:
‧ JSR 105: XML Digital Signature
‧ JSR 173: Streaming API for XML
‧ JSR 181: Web Services Metadata
‧ JSR 199: Java Compiler API
‧ JSR 202: Java Class File Specification Update
‧ JSR 221: JDBC 4.0
‧ JSR 222: JAXB 2.0
‧ JSR 223: Scripting for the Java Platform
‧ JSR 224: Java API for XML-Based Web Services (JAX-WS) 2.0
‧ JSR 250: Common Annotations
‧ JSR 269: Pluggable Annotation Processing API


官方網 Java SE 6 新特性:
http://java.sun.com/javase/6/webnotes/features.html
http://java.sun.com/javase/6/webnotes/compatibility.html


本備忘記主要介紹使用 NetBeans 實作一些本人感興趣的 Java SE 6 新特性.


開始備忘記:
[1]
安裝 Java SE 6
[2] 測試 Java 6 Scripting
[3] 安裝 NetBeans IDE 5.0
[4] 建立第一個 NetBeans web project
[5] NetBeans 建立 Java 6 project (Scripting and JSR 223)
[6] NetBeans 建立 Java 6 project (Java Compiler API)
[7] NetBeans 建立 Java 6 project (web service)
[8] NetBeans 建立 Java 6 project (XML)
[9] NetBeans 建立 Java 6 project (Other)
[10] NetBeans 常用 Hot-Key


[1] 安裝 Java SE 6:
下載 jdk-6-beta2-windows-i586.exe
http://java.sun.com/javase/downloads/ea.jsp
http://192.18.108.229/ECom/EComTicketServlet/BEGIN8806B36D55D38D5324C7FB81ED8D1453/-2147483648/1629557847/1/737498/737330/1629557847/2ts+/westCoastFSEND/jdk-6-beta2-oth-JPR/jdk-6-beta2-oth-JPR:3/jdk-6-beta2-windows-i586.exe


雙擊 jdk-6-beta2-windows-i586.exe 進行安裝
選擇安裝目錄至 D:\jdk1.6.0\
如下圖所示


以下是官方 Java SE 6 安裝教學:
http://java.sun.com/javase/6/webnotes/install/jdk/install-windows.html
http://java.sun.com/javase/6/webnotes/install/index.html



[2] 測試 Java 6 Scripting:
進入目錄 D:\jdk1.6.0\bin
執行指令 D:\jdk1.6.0\bin>jrunscript -? 顯示指令資料


執行指令 D:\jdk1.6.0\bin>jrunscript -q 顯示 support 的 scripting engine
輸出為
Language ECMAScript 1.6 implemention "Mozilla Rhino" 1.6 release 2


執行指令 D:\jdk1.6.0\bin>jrunscript
輸入 js> var output = "Hello World";
輸入 js> println(output);
輸出為
Hello World


輸出如下圖所示



[3] 安裝 NetBeans IDE 5.0:
下載 netbeans-5_0-windows.exe
http://www.netbeans.info/downloads/download.php
http://us1.mirror.netbeans.org/download/5_0/fcs/200601251500/netbeans-5_0-windows.exe


雙擊 netbeans-5_0-windows.exe 進行安裝
安裝目錄 Directory Name: D:\netbeans-5.0
如下圖所示


J2SE JDK Home Directory: D:\jdk1.6.0
如下圖所示


啟動 NetBeans 後
NetBeans: Tools -> Server Manager
如下圖所示

NetBeans 內置了 tomcat
NetBeans 內置 tomcat 目錄 D:\netbeans-5.0\enterprise2\jakarta-tomcat-5.5.9


亦可以在上圖裡按 "Add Server..." 增加以下 application server
BEA WebLogic Application Server 9.0
JBoss Application Server 4.0.3
Sun Java System Application Server
Tomcat 5.0
Tomcat 5.5



[4] 建立第一個 NetBeans web project:
NetBeans: File -> New Project
Choose Project 如下圖所示

Project Name 為 MyFirstWebApplication
Name and Location 如下圖所示

Frameworks 如下圖所示


項目目錄結構如下所示


右鍵點選 MyFirstWebApplication -> Run Project
NetBeans 會自動啟動 tomcat 及 deploy project, 並自動開啟預設的 browser
啟動後 tomcat console 如下圖所示

browser 如下圖所示


在 NetBeans 裡其中幾種功能本人覺得不錯.
當把 panel 縮成最小時, MouseOver 會自動跳出.


NetBeans: Views -> Toolbars -> Memory
這功能可以看到 Memory 使用的情況, 點擊它就會強迫 garbage collection


NetBeans: Window -> HTTP Monitor
這功能可以檢視 HTTP 相關資料


這些功能如下圖所示



[5] NetBeans 建立 Java 6 project (Scripting and JSR 223):
NetBeans: File -> New Project -> General -> Java Application
Project Name: ScriptingTest
然後按 "Finish"


建立 JavascriptTest.java
/*----------------- JavascriptTest.java -----------------------------*/
package scriptingtest;


import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;


public class JavascriptTest {
public JavascriptTest() {
}


public static void main(String args[]) {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("javascript");
// 取得 javascript engine
try {
engine.put("hello", "Hello"); // 定義 javascript hello variable
engine.eval(
"var world = 'World';" +
"function sayHello(){" +
" return hello + ' ' + world;" +
"}");
String world = (String)engine.get("world"); // 取得 javascript world variable
System.out.printf("print world: %s%n", world);

Invocable invokeEngine = (Invocable)engine;
Object o = invokeEngine.invoke("sayHello"); // 呼叫 javascript sayHello function
System.out.printf("print sayHello() output: %s%n", o);

} catch (NoSuchMethodException e){
System.err.println(e);
} catch (ScriptException e) {
System.err.println(e);
}
}

}
/*----------------- JavascriptTest.java -----------------------------*/


右鍵點選 JavascriptTest.java -> Run File 或按 Shift + F6
Console 輸出結果為
init:
deps-jar:
compile-single:
run-single:
print world: World
print sayHello() output: Hello World
BUILD SUCCESSFUL (total time: 0 seconds)


項目結構及 Console 輸出如下所示



[6] NetBeans 建立 Java 6 project (Java Compiler API):
NetBeans: File -> New Project -> General -> Java Application
Project Name: CompilerAPITest
然後按 "Finish"


建立 CompilerAPITest.java
/*----------------- CompilerAPITest.java -----------------------------*/
package compilerapitest;


import javax.tools.JavaCompilerTool;
import javax.tools.ToolProvider;


public class CompilerAPITest {
public static void main(String[] args) {
JavaCompilerTool compiler = ToolProvider.getSystemJavaCompilerTool();
// 取得編譯器
int results = compiler.run(null, null, null, "src/compilerapitest/Test.java");
// 編譯 src/compilerapitest/Test.java
System.out.println("Result: " + (results == 0));
// results 為 0 即表示編譯成功
}
}
/*----------------- CompilerAPITest.java -----------------------------*/


建立 Test.java
/*----------------- Test.java -----------------------------*/
package compilerapitest;
public class Test {
public static void main(String args[]){
System.out.println("Hello World, Joeyta");
}
}
/*----------------- Test.java -----------------------------*/


右鍵點選 JavascriptTest.java -> Run File 或按 Shift + F6
Console 輸出結果為
init:
deps-jar:
Compiling 1 source file to C:\Documents and Settings\joey.chan\CompilerAPITest\build\classes
compile-single:
run-single:
Result: true
BUILD SUCCESSFUL (total time: 1 second)


項目結構及 Console 輸出如下所示



[7] NetBeans 建立 Java 6 project (web service):
NetBeans: File -> New Project -> General -> Java Application
Project Name: WebServiceTest
然後按 "Finish"


建立 GoogleSearchTest.java
/*----------------- GoogleSearchTest.java -----------------------------*/
package webservicetest;


import java.io.*;
import java.net.*;
import javax.xml.ws.*;
import javax.xml.namespace.*;
import javax.xml.soap.*;
public class GoogleSearchTest {
public static void main(String args[]) throws Exception {
URL url = new URL("
http://api.google.com/GoogleSearch.wsdl");
QName serviceName = new QName("urn:GoogleSearch", "GoogleSearchService");
QName portName = new QName("urn:GoogleSearch", "GoogleSearchPort");
Service service = Service.create(url, serviceName);
Dispatch<SOAPMessage> dispatch = service.createDispatch(portName,
SOAPMessage.class, Service.Mode.MESSAGE);

InputStream soapRequestIS = new ByteArrayInputStream("blah blah".getBytes());
SOAPMessage request = MessageFactory.newInstance().createMessage(
null, soapRequestIS);
SOAPMessage response = dispatch.invoke(request);
response.writeTo(System.out);
}
}
/*----------------- GoogleSearchTest.java -----------------------------*/


右鍵點選 GoogleSearchTest.java -> Run File 或按 Shift + F6
由於只是測試, 能夠取回 Soap response message 即代表測試成功.
因此 soapRequestIS 的內容隨便輸入為 "blah blah"
如果真的要實作, 這裡的 soapRequestIS 應該是 Soap request message
因此取得的 Soap response message 會出現 org.xml.sax.SAXParseException


Console 輸出為
init:
deps-jar:
Compiling 1 source file to C:\Documents and Settings\joey.chan\WebServiceTest\build\classes
compile-single:
run-single:
<?xml version='1.0' encoding='UTF-8'?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="
http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance" xmlns:xsd="http://www.w3.org/1999/XMLSchema">
<SOAP-ENV:Body>
<SOAP-ENV:Fault>
<faultcode>SOAP-ENV:Client</faultcode>
<faultstring>parsing error: org.xml.sax.SAXParseException: Content is not allowed in prolog.</faultstring>
<faultactor>/search/beta2</faultactor>
</SOAP-ENV:Fault>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
BUILD SUCCESSFUL (total time: 1 second)


項目結構及 Console 輸出如下所示



[8] NetBeans 建立 Java 6 project (XML):
NetBeans: File -> New Project -> General -> Java Application
Project Name: XMLTest
然後按 "Finish"


建立 AccountXMLTest.java
/*----------------- AccountXMLTest.java -----------------------------*/
package xmltest;


import javax.xml.bind.*;
import javax.xml.bind.annotation.*;
public class AccountXMLTest {
public static void main(String[] args) {
try {
JAXBContext context = JAXBContext.newInstance(Account.class);
Marshaller m = context.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
Account p = new Account("joeyta", 18);
m.marshal(p, System.out);
} catch (JAXBException jex) {
System.out.println("JAXB Binding Exception");
jex.printStackTrace();
}
}
@XmlRootElement // 標記 Account class 為 root element
private static class Account {
String name;
int age;
public Account() {
}
public Account(String name, int age) {
this.name = name;
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
}
/*----------------- AccountXMLTest.java -----------------------------*/


右鍵點選 AccountXMLTest.java -> Run File 或按 Shift + F6
Console 輸出為
init:
deps-jar:
Compiling 1 source file to C:\Documents and Settings\joey.chan\XMLTest\build\classes
compile-single:
run-single:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<account>
<age>18</age>
<name>joeyta</name>
</account>

BUILD SUCCESSFUL (total time: 1 second)


項目結構及 Console 輸出如下所示



[9] NetBeans 建立 Java 6 project (Other):
NetBeans: File -> New Project -> General -> Java Application
Project Name: OtherTest
然後按 "Finish"


建立 VolumeTest.java
/*----------------- VolumeTest.java -----------------------------*/
package othertest;


import java.io.*;
import java.net.URL;
public class VolumeTest {
public static void main(String args[]) throws Exception {
File roots[] = File.listRoots();
for (File root: roots) {
System.out.printf("%s has %,d of %,d free%n", root.getPath(),
root.getUsableSpace(), root.getTotalSpace());
}

File file = new File("test");
URL url1 = file.toURL();
URL url2 = file.toURI().toURL();
System.out.printf("Old url %s%n", url1);
System.out.printf("New url %s%n", url2);
}
}
/*----------------- VolumeTest.java -----------------------------*/


右鍵點選 VolumeTest.java -> Run File 或按 Shift + F6
Console 輸出為
init:
deps-jar:
Compiling 1 source file to C:\Documents and Settings\joeyta\OtherTest\build\classes
Note: C:\Documents and Settings\joeyta\OtherTest\src\othertest\VolumeTest.java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
compile-single:
run-single:
A:\ has 0 of 0 free
C:\ has 739,835,904 of 10,487,197,696 free
D:\ has 244,649,984 of 29,504,077,824 free
E:\ has 0 of 37,828,608 free
F:\ has 167,845,888 of 8,391,688,192 free
G:\ has 167,845,888 of 8,391,688,192 free
H:\ has 167,845,888 of 8,391,688,192 free
K:\ has 792,875,008 of 838,860,800 free
S:\ has 792,875,008 of 838,860,800 free
U:\ has 245,153,792 of 838,860,800 free
X:\ has 60,586,983,424 of 146,804,764,672 free
Old url file:/C:/Documents and Settings/joeyta/OtherTest/test/
New url file:/C:/Documents%20and%20Settings/joeyta/OtherTest/test/
BUILD SUCCESSFUL (total time: 1 second)



建立 VolumeTest.java
/*----------------- DesktopTest.java -----------------------------*/
package othertest;


import java.awt.*;
import java.io.*;
public class DesktopTest {
public static void main(String args[]) {
if (Desktop.isDesktopSupported()) {
Desktop desktop = Desktop.getDesktop();
File dir = new File("."); // 開啟 project 下的所有檔案
File files[] = dir.listFiles();
for (File file: files) {
if (desktop.isSupported(Desktop.Action.OPEN)) {
System.out.println("Opening... " + file.getName());
try {
desktop.open(file);
} catch (IOException ioe) {
System.err.println("Unable to open: " + file.getName());
}
}
}
}
}
}
/*----------------- DesktopTest.java -----------------------------*/


右鍵點選 DesktopTest.java -> Run File 或按 Shift + F6
桌面上便會開啟所有在項目目錄下的檔案.


[10] NetBeans 常用 Hot-Key:
Ctrl + Shift + F
 : Reformat Code (與 eclipse 一樣)
Ctrl + G : Go to Line (與 eclipse 一樣)
Alt + Shift + R : rename (與 eclipse 一樣)
Alt + Shift + F : Fix Imports
Alt + Shift + O : Go to Class
Alt + Shift + W : Surround With try-catch
Alt + O : Go to Source
Ctrl + B : Go to Super Implementation
Alt + F7 : Find Usages
Ctrl + F : Find
Ctrl + H : Replace
Ctrl + Shift + P : Find in projects
Ctrl + Shift + T : Comment
Ctrl + Shift + D : Uncomment
Ctrl + Shift + 1 : Select in project
Ctrl + F3 : Find Selection
Shift + F3 : Find Previous
F3 : Find Next


Java 6 官方文檔:
http://java.sun.com/javase/6/docs/
http://java.sun.com/javase/6/docs/api/index.html


NetBeans j2ee 教學:
http://www.netbeans.org/download/docs/41/j2ee-tutorial/index.html


這裡測試了 NetBeans 5.0
http://www.netbeans.org/community/releases/50/
可選擇測試 NetBeans 5.5 Beta 2
http://www.netbeans.org/community/releases/55/
http://www.netbeans.org/community/releases/55/install.html

JDK 7 Project
https://jdk7.dev.java.net/


JBoss Web備忘記

JBoss Web Server 建基於 tomcat 上, 其目的是為了建立一個標準的 web server,
JBoss Web Server 可以同時運行 JSP, Servlet, Microsoft .NET , PHP 及 CGI,
以 Java 技術為接口, 提供 CGI , PHP 及 .NET 執行程序的進出,
混合技術模組提供良好的 threading 及 event 處理功能.
結合現代 OS 的良好性能, 建立一個名符其實的高性能混合處理系統.

開始備忘記:
[1] 安裝 jdk 5
[2] 安裝 JBoss Web Server
[3] 安裝 PHP 及 php servlet demo
[4] 測試程式

[1] 安裝 jdk 5:
下載 jdk-1_5_0_07-nb-5_0-win-ml.exe
http://java.sun.com/j2se/1.5.0/download-netbeans.html
安裝至 D:\jdk1.5.0_07
新增環境變數 JAVA_HOME=D:\jdk1.5.0_07
D:\jdk1.5.0_07\bin 加入至 PATH 中
D:\jdk1.5.0_07\lib\dt.jar 及 D:\jdk1.5.0_07\lib\tools.jar 加入至 CLASSPATH 中
執行 D:\>java -version
輸出 java version "1.5.0_07" 即安裝成功.

[2] 安裝 JBoss Web Server:
下載 jbossweb-windows-i586-1.0.0.GA.zip
http://labs.jboss.com/portal/jbossweb/downloads
http://labs.jboss.com/file-access/default/members/jbossweb/freezone/dist/1.0.0.GA/jbossweb-windows-i586-1.0.0.GA.zip
解壓至 D:\jbossweb-1.0.0.GA

下載 php5servlet-windows-i586.zip
http://labs.jboss.com/portal/jbossweb/downloads/native
http://labs.jboss.com/file-access/default/members/jbossweb/freezone/dist/1.0.0.GA/php5servlet-windows-i586.zip

[3] 安裝 PHP 及 php servlet demo:
解壓縮後為兩個目錄
PHP 及 webapps\php-examples.war

將 PHP 目錄複製至 D:\jbossweb-1.0.0.GA
將 D:\jbossweb-1.0.0.GA\PHP\bin 加入至系統環境變數 PATH 裡

將 php-examples.war 目錄複製至 D:\jbossweb-1.0.0.GA\server\default\deploy

[4] 測試程式:
執行 D:\jbossweb-1.0.0.GA\bin\run.bat 啟動 JBoss

進入 http://localhost:8080/php-examples/index.php
測試成功如下圖所示



官方提供的測試例子有些會出現 error, 那是由於沒有安裝 php 所需的模組..
而且沒有提供 .NET 及 CGI 的例子, 應該還在開發中吧.

官方文檔:
http://labs.jboss.com/file-access/default/members/jbossweb/freezone/index.html

http://labs.jboss.com/file-access/default/members/jbossweb/freezone/modules/php/index.html

tomcat 運行 php:
http://wiki.apache.org/tomcat/UsingPhp

JBoss Seam備忘記

JBoss Seam 是一個功能強大的開源應用程式框架,
集成眾多流行的 SOA (Service Oriented Architecture) 技術,
包括 AJAX (Asynchronous Javascript and XML), JSF (Java Server Faces),
EJB3 (Enterprise Java Bean), Java Portlets, BPM (Business Process Management) 及工作流程.

Seam 目的是簡化眾多技術的 API 及架構,
利用 POJOs (Plain Old Java Objects) 簡單標記, widgets 及小量的 XML,
使開發員能夠輕鬆開發 Web 應用程式.

使用 Seam 1.0 技術,
日後將會更容易集成於 ESB (Enterprise Service Bus) 及 JBI (Java Business Integration).


開始備忘記:
[1] 安裝 jdk 5:
[2] 安裝 ANT
[3] 安裝 JBoss JEMS with ejb3-clustering
[4] 部署及測試 Booking hotel [JSF]
[5] 部署及測試 DVD store [JSF & jBPM]
[6] 部署及測試 Issues [issues workflow system]
[7] 測試及分析簡單註冊實例[Registration]

[1] 安裝 jdk 5:
下載 jdk-1_5_0_07-nb-5_0-win-ml.exe
http://java.sun.com/j2se/1.5.0/download-netbeans.html
安裝至 D:\jdk1.5.0_07
新增環境變數 JAVA_HOME=D:\jdk1.5.0_07
D:\jdk1.5.0_07\bin 加入至 PATH 中
D:\jdk1.5.0_07\lib\dt.jar 及 D:\jdk1.5.0_07\lib\tools.jar 加入至 CLASSPATH 中
執行 D:\>java -version
輸出 java version "1.5.0_07" 即安裝成功.

[2] 安裝 ANT:
下載 apache-ant-1.6.5-bin.zip
http://ant.apache.org/bindownload.cgi
http://download.nextag.com/apache/ant/binaries/apache-ant-1.6.5-bin.zip
解壓縮至 D:\ant
新增環境變數 ANT_HOME=D:\ant
D:\ant\bin 加入至 PATH 中
執行 D:\>ant -version
輸出 Apache Ant version 1.6.5 compiled on July 16 2004 即安裝成功.

[3] 安裝 JBoss JEMS with ejb3-clustering:
下載 jems-installer-1.2.0.BETA.jar 至 D: 目錄下.
http://sourceforge.net/project/showfiles.php?group_id=22866&package_id=193295
http://umn.dl.sourceforge.net/sourceforge/jboss/jems-installer-1.2.0.BETA.jar

進入 D: 目錄
執行 D:\>java -jar jems-installer-1.2.0.BETA.jar

安裝至 D:\jboss-4.0.4.GA
安裝時選擇 ejb3-clustered , 如下圖所示


新增環境變數 JBOSS_HOME=D:\jboss-4.0.4.GA
D:\jboss-4.0.4.GA\bin 加入至 PATH 中

啟動 jboss
D:\jboss-4.0.4.GA\bin\run.bat

下載 jboss-seam-1.0.1.GA.zip
http://labs.jboss.com/portal/jbossseam/download
http://superb-east.dl.sourceforge.net/sourceforge/jboss/jboss-seam-1.0.1.GA.zip

解壓縮至 D:\jboss-seam-1.0.1.GA

修改檔案 D:\jboss-seam-1.0.1.GA\build.properties 成
(由於不使用 tomcat, 這裡主要修改 jboss.home 的位置 )
######## build.properties ##########
tomcat.home = C:\\Tomcat-5.5
jboss.home = D:\\jboss-seam-1.0.1.GA
jbossHasMyFacesLifecycleBug = true
######## build.properties ##########


[4] 部署及測試 Booking hotel [JSF]:
進入目錄 D:\jboss-seam-1.0.1.GA\examples\booking
執行 D:\jboss-seam-1.0.1.GA\examples\booking>ant deploy
如下圖所示


Browser 進入 http://localhost:8080/seam-booking
如下圖所示


"Register New User"
輸入
Username: joeyta
Real Name: joeyta chan
Password: 123123
Verify Password: 123123
按 Register 就會註冊新用戶

然後可以使用 joeyta / 123123 登入.
登入後可以進行 search 及 booking hotel 等動作.
如下圖所示



[5] 部署及測試 DVD store [JSF & jBPM]:
進入目錄 D:\jboss-seam-1.0.1.GA\examples\dvdstore
執行 D:\jboss-seam-1.0.1.GA\examples\dvdstore>ant deploy

Browser 進入 http://localhost:8080/seam-dvd
如下圖所示


"Create Account"
輸入
User Name: joeyta
Password: 123123
Verify Password: 123123
按 Continue 並填寫個人資料, 就會註冊新用戶

然後可以使用 joeyta / 123123 登入.
登入後可以進行 DVD online shopping.
如下圖所示


workflow 流程圖如下所示



[6] 部署及測試 Issues [issues workflow system]:
進入目錄 D:\jboss-seam-1.0.1.GA\examples\issues
執行 D:\jboss-seam-1.0.1.GA\examples\issues>ant deploy

Browser 進入 http://localhost:8080/seam-issues
如下圖所示

註冊帳戶後可以對項目 issue 進行委派及回覆等工作.


[7] 測試及分析簡單註冊實例[Registration]:
進入目錄 D:\jboss-seam-1.0.1.GA\examples\registration
執行 D:\jboss-seam-1.0.1.GA\examples\registration>ant deploy

Browser 進入 http://localhost:8080/seam-registration/register.seam
如下圖所示

輸入
Username: joeyta
Real Name: joeyta chan
Password: 123123
然後按 "Register"

成功後出現如下所示


代碼結構如下圖所示


-- registration
+-- build
`-- resources
`-- META-INF
-- application.xml
-- ejb-jar.xml
-- jboss-app.xml
-- persistence.xml
`-- WEB-INF
-- components.xml
-- faces-config.xml
-- web.xml
`-- src
`-- org
`-- jboss
`-- seam
`-- example
`-- registration
-- Register.java
-- RegisterAction.java
-- User.java
`-- test
-- RegisterTest.java
-- testng.xml
+-- test-output
`-- view
-- index.html
-- register.jsp
-- registered.jsp

測試流程如下所示


/*-------------------------- User.java --------------------------------*/
package org.jboss.seam.example.registration;

import static org.jboss.seam.ScopeType.SESSION;

import java.io.Serializable;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

import org.hibernate.validator.Length;
import org.hibernate.validator.NotNull;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;

@Entity // 定義此為 entity bean
@Name("user") // JSF 根據此名稱取得此實例
@Scope(SESSION) // 定義此實例範圍在 session 階段
@Table(name="users") // 定義對應資料庫的 table 名為 users
public class User implements Serializable
{
private static final long serialVersionUID = 1881413500711441951L;

private String username;
private String password;
private String name;

public User(String name, String password, String username)
{
this.name = name;
this.password = password;
this.username = username;
}

public User() {}

@NotNull // 定義對應資料庫裡此 column constraint 為 not null
public String getName()
{
return name;
}

public void setName(String name)
{
this.name = name;
}

@NotNull @Length(min=5, max=15) // constraint 定義除 not null 外, 長度為 5-15
public String getPassword()
{
return password;
}

public void setPassword(String password)
{
this.password = password;
}

@Id @NotNull @Length(min=5, max=15) // 定義此為 primary key
public String getUsername()
{
return username;
}

public void setUsername(String username)
{
this.username = username;
}

public String toString()
{
return "User(" + username + ")";
}
}
/*-------------------------- User.java --------------------------------*/


/*-------------------- RegisterAction.java --------------------------*/
package org.jboss.seam.example.registration;

import java.util.List;

import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.hibernate.validator.Valid;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Logger;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.core.FacesMessages;
import org.jboss.seam.log.Log;

@Stateless // 定義此為 stateless session bean
@Name("register") // JSF 根據此名稱取得此實例
public class RegisterAction implements Register
{

@In @Valid // @In 表示由 Seam 注入此實例
private User user;

@PersistenceContext // 表示注入 EJB3 entity manager
private EntityManager em;

@Logger // 注入 component's Log instance.
private Log log;

// JSF view 將觸發此 action listener method, 並依靠其返回值判斷下一步動作
public String register()
{
List existing = em.createQuery("select username from User where username=:username")
.setParameter("username", user.getUsername())
.getResultList();
if (existing.size()==0)
{
em.persist(user);
log.info("Registered new user #{user.username}");
return "/registered.jsp"; // 此返回值將確定下一步動作
}
else
{
FacesMessages.instance().add("User #{user.username} already exists");
// FaceMessages component 收集成功及失敗資訊
return null; // 此返回值將確定下一步動作
}
}

}
/*-------------------- RegisterAction.java --------------------------*/

/*-------------------- Register.java --------------------------*/
package org.jboss.seam.example.registration;

import javax.ejb.Local;

@Local // 定義此為 stateless session bean 的 local interface
public interface Register
{
public String register();
}
/*-------------------- Register.java --------------------------*/

################# components.properties ##############
myFacesLifecycleBug false
embeddedEjb true
jndiPattern #{ejbName}/local
################# components.properties ##############
此 properties 定義對應的 components 值

<!-------------------- components.xml ------------------------>
<components>

<component name="org.jboss.seam.core.init">
<property name="myFacesLifecycleBug">@myFacesLifecycleBug@</property>
<property name="jndiPattern">@jndiPattern@</property>
</component>

<component class="org.jboss.seam.core.Ejb"
installed="@embeddedEjb@"/>

</components>
<!-------------------- components.xml ------------------------>
此檔案告訴 Seam 如何尋找 components 對應的 JNDI
@xxxxx@ 值將由 components.properties 裡 key 對應的值取代

<!-------------------- web.xml ------------------------>
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">


<!-- Seam -->

<listener>
<listener-class>org.jboss.seam.servlet.SeamListener</listener-class>
</listener>

<!-- MyFaces -->

<listener>
<listener-class>org.apache.myfaces.webapp.StartupServletContextListener</listener-class>
</listener>

<context-param>
<param-name>javax.faces.STATE_SAVING_METHOD</param-name>
<param-value>client</param-value>
</context-param>

<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>

<!-- Faces Servlet Mapping -->
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>*.seam</url-pattern>
</servlet-mapping>

</web-app>
<!-------------------- web.xml ------------------------>
此 deployment descriptor 定義 Seam 及 MyFaces 的設定.


<!-------------------- faces-config.xml ------------------------>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE faces-config
PUBLIC "-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.0//EN"
"http://java.sun.com/dtd/web-facesconfig_1_0.dtd">
<faces-config>

<!-- Phase listener needed for all Seam applications -->

<lifecycle>
<phase-listener>org.jboss.seam.jsf.SeamPhaseListener</phase-listener>
</lifecycle>

</faces-config>
<!-------------------- faces-config.xml ------------------------>
此檔為 JSF 的設定檔, 這裡不需要對 managed bean 及 流程作描述.
交由 Seam 管理.


<!-------------------- ejb-jar.xml ------------------------>
<ejb-jar>
<assembly-descriptor>
<interceptor-binding>
<ejb-name>*</ejb-name>
<interceptor-class>org.jboss.seam.ejb.SeamInterceptor</interceptor-class>
</interceptor-binding>
</assembly-descriptor>
</ejb-jar>
<!-------------------- ejb-jar.xml ------------------------>
此檔為 EJB3 deployment descriptor, 交由 Seam 管理


<!-------------------- persistence.xml ------------------------>
<persistence>
<persistence-unit name="userDatabase">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<jta-data-source>java:/DefaultDS</jta-data-source>
<properties>
<property name="hibernate.hbm2ddl.auto" value="create-drop"/>
</properties>
</persistence-unit>
</persistence>
<!-------------------- persistence.xml ------------------------>
此檔告訴 EJB persistence provider 如何尋找 datasource,
java:/DefaultDS 表示會尋找 jboss deployment 目錄下 -ds.xml 結尾資料庫設定檔對應的 jndi name.
這實作裡使用 JBoss 預設的 HSQLDB, 即 jboss deployment 目錄下的 hsqldb-ds.xml
<property name="hibernate.hbm2ddl.auto" value="create-drop" /> 定義:
當project deploy 時就會自動產生 database schema. 當project undeploy 時就會自動清除 database schema.

<%---------------------- register.jsp -------------------%>
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<%@ taglib uri="http://jboss.com/products/seam/taglib" prefix="s" %>
<html>
<head>
<title>Register New User</title>
</head>
<body>
<f:view>
<h:form>
<table border="0">
<s:validateAll>
<tr>
<td>Username</td>
<td><h:inputText value="#{user.username}" required="true"/></td>
</tr>
<tr>
<td>Real Name</td>
<td><h:inputText value="#{user.name}" required="true"/></td>
</tr>
<tr>
<td>Password</td>
<td><h:inputSecret value="#{user.password}" required="true"/></td>
</tr>
</s:validateAll>
</table>
<h:messages/>
<h:commandButton type="submit" value="Register" action="#{register.register}"/>
</h:form>
</f:view>
</body>
</html>
<%---------------------- register.jsp -------------------%>
此為註冊 jsp page,
<s:validateAll> 表示將根據 entity bean 裡的 property constraints 判斷是否正確.
#{xxx.yyy} 為 JSF expression language 使用的標記,
當 page 開始 rendered 時, 會從 "xxx" managed backing bean 返回值對應的 "yyy" property,
當 submit rendered 時會設定此值到 "xxx" managed backing bean 的 "yyy" property 裡.
<h:commandButton type="submit" value="Register" action="#{register.register}"/> 表示:
當 submit 時, 會呼叫 register managed beans 裡 register method.


<%---------------------- registered.jsp -------------------%>
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<html>
<head>
<title>Successfully Registered New User</title>
</head>
<body>
<f:view>
Welcome, <h:outputText value="#{user.name}"/>,
you are successfully registered as <h:outputText value="#{user.username}"/>.
</f:view>
</body>
</html>
<%---------------------- registered.jsp -------------------%>
此為註冊成功的確認 jsp page,
這裡只簡單輸出 managed bean 的 property.


<!-------------------- application.xml ------------------------>
<application>
<display-name>Seam Registration</display-name>

<module>
<web>
<web-uri>jboss-seam-registration.war</web-uri>
<context-root>/seam-registration</context-root>
</web>
</module>
<module>
<ejb>jboss-seam-registration.jar</ejb>
</module>
<module>
<java>jboss-seam.jar</java>
</module>

</application>
<!-------------------- application.xml ------------------------>
這個 deployment descriptor 連結 enterprise archive 模組
及 /seam-registration 與 web application 關聯在一起.


/*------------------ RegisterTest.java -------------------*/
package org.jboss.seam.example.registration.test;

import org.jboss.seam.Component;
import org.jboss.seam.example.registration.Register;
import org.jboss.seam.example.registration.User;
import org.jboss.seam.mock.SeamTest;
import org.testng.annotations.Test;
// 這個 TestNG 測試程式繼承 SeamTest, 可呼叫 Seam 裡的 component
public class RegisterTest extends SeamTest
{

@Test // 定義此為測試 method
public void testLogin() throws Exception
{

new Script() {

@Override // 這 annotation 表示必須 override superclass 裡的 method
protected void updateModelValues() throws Exception
{
User user = (User) Component.getInstance("user", true);
// 這裡由 Seam 的 component 裡取出 user 實例
assert user!=null;
user.setUsername("1ovthafew");
user.setPassword("secret");
user.setName("Gavin King");
}

@Override
protected void invokeApplication()
{
Register register = (Register) Component.getInstance("register", true);
String outcome = register.register();
assert "/registered.jsp".equals( outcome );
}

@Override
protected void renderResponse()
{
User user = (User) Component.getInstance("user", false);
assert user!=null;
assert user.getName().equals("Gavin King");
assert user.getUsername().equals("1ovthafew");
assert user.getPassword().equals("secret");
}

}.run();

}

}
/*------------------ RegisterTest.java -------------------*/
此為 TestNG 測試程式

<!-------------------- testng.xml ------------------------>
<!DOCTYPE suite SYSTEM "http://beust.com/testng/testng-1.0.dtd" >
<suite name="Registration" verbose="2" parallel="false">

<test name="Register">
<classes>
<class name="org.jboss.seam.example.registration.test.RegisterTest"/>
</classes>
</test>

</suite>
<!-------------------- testng.xml ------------------------>

此為 TestNG 設定檔,
這裡表示將測試 org.jboss.seam.example.registration.test.RegisterTest



有興趣的同志可以依照以下教學,繼續進行個別項目測試及分析:
http://docs.jboss.com/seam/1.0.0.GA/reference/en/html/tutorial.html

JSF 教學:
http://www.coreservlets.com/JSF-Tutorial/

TestNG 官方網:
http://testng.org/doc/

http://myfaces.apache.org/gettingstarted.html

http://www.jboss.com/products/seam
http://labs.jboss.com/portal/jbossseam/gettingstarted

TestNG備忘記

TestNG(Testing, the Next Generation) 測試框架項目靈感來自 JUnit 及 NUnit,
主要目的是增強舊的測試框架不足的功能, 其新增的功能如下:
- JDK 5 Annotations
- 新增測試設定檔 (預設 testng.xml)
- 提供 data-driven testing (利用 @DataProvider 將 data 提供給測試程式)
- 支持 parameters (可使用設定檔或 @DataProvider 提供參數功能)
- 提供分佈式測試.
- 提供功能強勁的執行模組 (不再使用 TestSuite)
- 給予多種工具支援 (Eclipse, IDEA, Maven, etc...)
- 嵌入 BeanShell
- 預設的執行時期 JDK 功能及日誌
- 給予應用程式提供相依的 methods

詳細的介紹請參閱以下官方文檔.
(注意, 官方的文檔有些新的 method 及 depricated method 還沒有修改)
http://testng.org/doc/documentation-main.html

開始備忘記:
[1] 安裝 jdk 5
[2] 安裝 Eclipse WTP
[3] 安裝 TestNG eclipse plugin
[4] 建立 Eclipse project

[1] 安裝 jdk 5:
下載 jdk-1_5_0_07-nb-5_0-win-ml.exe
http://java.sun.com/j2se/1.5.0/download-netbeans.html
安裝至 D:\jdk1.5.0_07
新增環境變數 JAVA_HOME=D:\jdk1.5.0_07
D:\jdk1.5.0_07\bin 加入至 PATH 中
D:\jdk1.5.0_07\lib\dt.jar 及 D:\jdk1.5.0_07\lib\tools.jar 加入至 CLASSPATH 中
執行 D:\>java -version
輸出 java version "1.5.0_07" 即安裝成功.

[2] 安裝 Eclipse WTP:
下載 wtp-all-in-one-sdk-R-1.5.0-200606281455-win32.zip
http://www.eclipse.org/webtools/
http://www.eclipse.org/downloads/download.php?file=/webtools/downloads/drops/R-1.5.0-200606281455/wtp-all-in-one-sdk-R-1.5.0-200606281455-win32.zip
解壓至 D:\eclipse_wtp

[3] 安裝 TestNG eclipse plugin:
Eclipse:Help -> Software Updates -> Find and Install -> Search for new features to install
按 New Remote Site
Name: TestNG
URL: http://beust.com/eclipse
然後安裝.

[4] 建立 Eclipse project:
Eclipse: File -> New -> Other -> Java Project
Project Name: First_TestNG -> Finish

打開 TestNG View:
Eclipse: Window -> Show View -> Other ->> Java -> TestNG
如下圖所示


下載 testng-5.0.2.zip
http://testng.org/doc/download.html
http://testng.org/testng-5.0.2.zip

解壓縮至 D:\testng-5.0.2
將 D:\testng-5.0.2\testng-5.0.2-jdk15.jar 加入到 First_TestNG project classpath 裡.

建立測試檔案:
/*------------------ TestNGTest.java -----------------------*/
package testng.joeyta;

import org.testng.annotations.AfterMethod;
import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;

public class TestNGTest {

@BeforeTest // 標記指定所有測試進行前呼叫此 method
public void beforeAllTest(){
System.out.println("Before All Test");
}

@BeforeMethod
// 標記指定每個測試進行前呼叫此 method
public void setUp() {
System.out.println("Before Each Test Method");
}

@Test(groups = { "group1" }) // 標記為測試程式, 並為分組 group1
public void group1Test() {
System.out.println("Group 1");
}

@Test(groups = { "group2" })
// 標記為測試程式, 並為分組 group2
public void group2Test() {
System.out.println("Group 2");
}

@AfterMethod
// 標記指定每個測試進行後呼叫此 method
public void tearDown(){
System.out.println("After Each Test Method");
}

@AfterTest
// 標記指定所有測試進行後呼叫此 method
public void afterAllTest(){
System.out.println("After All Test");
}
}
/*------------------ TestNGTest.java -----------------------*/


右鍵點選 TestNGTest.java -> Run As -> TestNG Test

Cosole 輸出為:
Before All Test
Before Each Test Method
Group 2
After Each Test Method
Before Each Test Method
Group 1
After Each Test Method
Creating D:\eclipse_wtp\workspace\First_TestNG\test-output\First_TestNG\testng.joeyta.TestNGTest.html
PASSED: group2Test
PASSED: group1Test

===============================================
testng.joeyta.TestNGTest
Tests run: 2, Failures: 0, Skips: 0
===============================================

After All Test

===============================================
First_TestNG
Total tests run: 2, Failures: 0, Skips: 0
===============================================

Creating D:\eclipse_wtp\workspace\First_TestNG\test-output\First_TestNG\toc.html
Creating D:\eclipse_wtp\workspace\First_TestNG\test-output\First_TestNG\testng.joeyta.TestNGTest.properties
Creating D:\eclipse_wtp\workspace\First_TestNG\test-output\First_TestNG\index.html
Creating D:\eclipse_wtp\workspace\First_TestNG\test-output\First_TestNG\main.html
Creating D:\eclipse_wtp\workspace\First_TestNG\test-output\First_TestNG\groups.html
Creating D:\eclipse_wtp\workspace\First_TestNG\test-output\First_TestNG\methods.html
Creating D:\eclipse_wtp\workspace\First_TestNG\test-output\First_TestNG\methods-alphabetical.html
Creating D:\eclipse_wtp\workspace\First_TestNG\test-output\First_TestNG\classes.html
Creating D:\eclipse_wtp\workspace\First_TestNG\test-output\First_TestNG\reporter-output.html
Creating D:\eclipse_wtp\workspace\First_TestNG\test-output\First_TestNG\methods-not-run.html
Creating D:\eclipse_wtp\workspace\First_TestNG\test-output\First_TestNG\testng.xml.html
Creating D:\eclipse_wtp\workspace\First_TestNG\test-output\index.html
Creating D:\eclipse_wtp\workspace\First_TestNG\test-output\testng-failed.xml
Creating D:\eclipse_wtp\workspace\First_TestNG\test-output\First_TestNG\testng-failed.xml


執行結果如下圖所示


項目結構如下所示


由於使用 TestNG eclipse plugin , 故不需要建立 testng xml configuration file.
它會自動產生. 並會產生相關 test output html 文檔.

內容如下
<!----------------- temp-testng-customsuite.xml -------------------->
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="First_TestNG">
<test verbose="2" name="testng.joeyta.TestNGTest" annotations="JDK5">
<classes>
<class name="testng.joeyta.TestNGTest"/>
</classes>
</test>
</suite>
<!----------------- temp-testng-customsuite.xml -------------------->


右鍵選擇 /test-output/index.html -> Open With -> Web Browser
如下圖所示


官方網
http://testng.org/

http://jira.opensymphony.com/browse/TESTNG

http://beust.com/weblog/

JBoss jBPM(Workflow Management Engine)備忘記

JBoss jBPM (JAVA Business Process Management) 為開源工作流程引擎,
具有非常高的彈性及可擴展性, 可使用圖形介面事先定義工作流程序述,
提供非同步, 排程, 自動觸發動作等功能,


JBoss jBPM 可以與任何的資料庫集成, 並能嵌入於任何的JAVA企業應用系統.


開始備忘記:
[1] 安裝 jdk 5
[2] 安裝 JBoss jBPM server
[3] 安裝 Eclipse WTP
[4] 安裝 JBoss IDE
[5] 建立第一個 jBPM project


[1] 安裝 jdk 5:
下載 jdk-1_5_0_07-nb-5_0-win-ml.exe
http://java.sun.com/j2se/1.5.0/download-netbeans.html
安裝至 D:\jdk1.5.0_07
新增環境變數 JAVA_HOME=D:\jdk1.5.0_07
D:\jdk1.5.0_07\bin 加入至 PATH 中
D:\jdk1.5.0_07\lib\dt.jar 及 D:\jdk1.5.0_07\lib\tools.jar 加入至 CLASSPATH 中
執行 D:\>java -version
輸出 java version "1.5.0_07" 即安裝成功.


[2] 安裝 JBoss jBPM server:
下載 jbpm-starters-kit-3.1.2.zip
http://www.jboss.com/products/jbpm/downloads
http://superb-west.dl.sourceforge.net/sourceforge/jbpm/jbpm-starters-kit-3.1.2.zip
解壓縮至 D:\jboss_jbpm
D:\jboss_jbpm\readme.html 介紹每個目錄的功能.
這裡只需要關心 D:\jboss_jbpm\jbpm-server 目錄, 這是 JBoss jBPM server
執行 D:\jboss_jbpm\jbpm-server\start.bat 啟動 JBoss jBPM server
JBoss jBPM server 已提供了一個 JSF 的 web sale order 的例子.
進入 http://localhost:8080/jbpm/
隨便選一個 user 登入, 就可以測試預設的 web sale order workflow.


如下圖所示


官方的介面使用教學:
http://wiki.jboss.org/wiki/Wiki.jsp?page=JbpmGettingStarted


[3] 安裝 Eclipse WTP:
下載 wtp-all-in-one-sdk-R-1.5.0-200606281455-win32.zip
http://www.eclipse.org/webtools/
http://www.eclipse.org/downloads/download.php?file=/webtools/downloads/drops/R-1.5.0-200606281455/wtp-all-in-one-sdk-R-1.5.0-200606281455-win32.zip
解壓至 D:\eclipse_wtp


[4] 安裝 JBoss IDE:
Eclipse:Help -> Software Updates -> Find and Install -> Search for new features to install
New Remote Site
Name: JBOSS IDE
URL: http://download.jboss.org/jbosside/updates/stable
選擇最新的版本然後安裝.


[5] 建立第一個 jBPM project:
Eclipse:
File -> New -> Other -> JBoss jBPM -> Process Project
Project Name: First_jBPM -> Next -> Finish
預設會產生一整套測試檔案, 這備忘記就是對這些檔案作簡介.
如果想更深入了解 jBPM , 查看官方的文檔是最好的方法.


檔案結構如下所示


右鍵點選 gpd.xml -> Open With -> Text Editor , 就會出現如下所示代碼
<!---------------------- gpd.xml --------------------->
<?xml version="1.0" encoding="UTF-8"?>


<process-diagram name="simple" width="469" height="438">
<node name="start" x="150" y="25" width="140" height="40">
<transition name="to_state"/>
</node>
<node name="first" x="150" y="125" width="140" height="40">
<transition name="to_end"/>
</node>
<node name="end" x="150" y="225" width="140" height="40"/>
</process-diagram>
<!---------------------- gpd.xml --------------------->
這是 graphical process designer 的描述檔案.
這裡只是簡單描述三個 nodes 的位置: start , first, end


右鍵點選 processdefinition.xml -> Open With -> Text Editor , 就會出現如下所示代碼
<!---------------------- processdefinition.xml --------------------->
<?xml version="1.0" encoding="UTF-8"?>


<process-definition
xmlns="urn:jbpm.org:jpdl-3.1"
name="simple">
<start-state name="start">
<task>
<controller>
<variable name="color" />
<variable name="size" />
</controller>
</task>
<transition name="to_state" to="first">
<action name="action" class="com.sample.action.MessageActionHandler">
<message>Going to the first state!</message>
</action>
</transition>
</start-state>
<state name="first">
<transition name="to_end" to="end">
<action name="action" class="com.sample.action.MessageActionHandler">
<message>About to finish!</message>
</action>
</transition>
</state>
<end-state name="end"></end-state>
</process-definition>
<!---------------------- processdefinition.xml --------------------->
workflow engine 將根據此流程檔運作.
<start-state name="start"> 流程初始點.


<transition name="to_state" to="first">
<action name="action" class="com.sample.action.MessageActionHandler">
<message>Going to the first state!</message>
</action>
</transition>
這裡描述初始點將過渡至 first 點.
而過渡至 first 點時會觸發 com.sample.action.MessageActionHandler [後面會介紹]
並設定此 ActionHandler 的 message 為 "Going to the first state!"


<state name="first">
<transition name="to_end" to="end">
<action name="action" class="com.sample.action.MessageActionHandler">
<message>About to finish!</message>
</action>
</transition>
</state>

這裡描述 first 點. 通過此點將過渡至 end 點.
而過渡至 end 點時會觸發 com.sample.action.MessageActionHandler [後面會介紹]
並設定此 ActionHandler 的 message 為 "About to finish!"


<end-state name="end"> 流程結速點.



右鍵點選 processdefinition.xml -> Open With -> jBPM Graphical Process Designer
然後點 Diagram , 就會出現如下圖所示


可以使用 GPD (Graphical Process Designer) 來繪製 workflow
為了簡化備忘記, 參考以下官方教學文檔
http://docs.jboss.com/jbpm/v3/gpd



/*------------------ MessageActionHandler.java -------------------*/
package com.sample.action;
import org.jbpm.graph.def.ActionHandler;
import org.jbpm.graph.exe.ExecutionContext;
public class MessageActionHandler implements ActionHandler {
// 這是 Listener, 觸發這個 Action 將執行 execute method
private static final long serialVersionUID = 1L;

String message;

public void execute(ExecutionContext context) throws Exception {
context.getContextInstance().setVariable("message", message);
// 這裡將上面觸發的 message 加入到該點的 message property
}


}
/*------------------ MessageActionHandler.java -------------------*/



/*------------------ SimpleProcessTest.java -------------------*/
package com.sample;
import java.io.FileInputStream;
import junit.framework.TestCase;
import org.jbpm.graph.def.ProcessDefinition;
import org.jbpm.graph.exe.ProcessInstance;
public class SimpleProcessTest extends TestCase {


public void testSimpleProcess() throws Exception {


FileInputStream fis = new FileInputStream("processes/simple/processdefinition.xml");
ProcessDefinition processDefinition = ProcessDefinition.parseXmlInputStream(fis);
// 讀取 processdefinition.xml 的流程定義檔

assertNotNull("Definition should not be null", processDefinition);
// 測試這個檔案並不為 null


ProcessInstance instance = new ProcessInstance(processDefinition);
// 建立處理流程檔實例

assertEquals(
"Instance is in start state",
instance.getRootToken().getNode().getName(),
"start");
// 測試現在正處於 start 點

assertNull(
"Message variable should not exist yet",
instance.getContextInstance().getVariable("message"));
// 測試 start 點的 message 為 null


instance.signal();
// 呼叫 signal() 表示此點處理完成, 將跳至下一點
// 由於上面設定了 ActionHandler, 將會設定 message 為 "Going to the first state!"


assertEquals(
"Instance is in first state",
instance.getRootToken().getNode().getName(),
"first");
// 測試現在正處於 first 點

assertEquals(
"Message variable contains message",
instance.getContextInstance().getVariable("message"),
"Going to the first state!");
// 由於已經觸發 MessageActionHandler,
// 測試此點 message property 為 "Going to the first state!"


instance.signal();
// 呼叫 signal() 表示此點處理完成, 將跳至下一點
// 由於上面設定了 ActionHandler, 將會設定 message 為 "About to finish!"


assertEquals(
"Instance is in end state",
instance.getRootToken().getNode().getName(),
"end");
// 測試現在正處於 end 點

assertTrue("Instance has ended", instance.hasEnded());
// 測試現在是最後終點

assertEquals(
"Message variable is changed",
instance.getContextInstance().getVariable("message"),
"About to finish!");
// 由於已經觸發 MessageActionHandler,
// 測試此點 message property 為 "About to finish!"


}
}
/*------------------ SimpleProcessTest.java -------------------*/


點選 SimpleProcessTest.java -> Run As -> JUnit Test
如下所示即測試成功


Console 輸出為:
00:33:44,597 [main] INFO JbpmConfiguration : using jbpm configuration resource 'jbpm.cfg.xml'
00:33:44,644 [main] DEBUG JbpmConfiguration : loading defaults in jbpm configuration
00:33:45,269 [main] DEBUG ObjectFactoryImpl : adding object info 'default.jbpm.context'
00:33:45,269 [main] DEBUG ObjectFactoryImpl : adding object info 'resource.hibernate.cfg.xml'
00:33:45,269 [main] DEBUG ObjectFactoryImpl : adding object info 'resource.business.calendar'
00:33:45,285 [main] DEBUG ObjectFactoryImpl : adding object info 'resource.default.modules'
00:33:45,285 [main] DEBUG ObjectFactoryImpl : adding object info 'resource.converter'
00:33:45,285 [main] DEBUG ObjectFactoryImpl : adding object info 'resource.action.types'
00:33:45,285 [main] DEBUG ObjectFactoryImpl : adding object info 'resource.node.types'
00:33:45,285 [main] DEBUG ObjectFactoryImpl : adding object info 'resource.parsers'
00:33:45,285 [main] DEBUG ObjectFactoryImpl : adding object info 'resource.varmapping'
00:33:45,285 [main] DEBUG ObjectFactoryImpl : adding object info 'jbpm.msg.wait.timout'
00:33:45,285 [main] DEBUG ObjectFactoryImpl : adding object info 'jbpm.byte.block.size'
00:33:45,285 [main] DEBUG ObjectFactoryImpl : adding object info 'mail.smtp.host'
00:33:45,285 [main] DEBUG ObjectFactoryImpl : adding object info 'jbpm.task.instance.factory'
00:33:45,285 [main] DEBUG ObjectFactoryImpl : adding object info 'jbpm.variable.resolver'
00:33:45,285 [main] DEBUG ObjectFactoryImpl : adding object info 'jbpm.mail.address.resolver'
00:33:45,285 [main] DEBUG JbpmConfiguration : loading specific configuration...
00:33:45,753 [main] DEBUG JpdlParser$JpdlEntityResolver : resolving schema reference publicId(null) systemId(
http://jbpm.org/jpdl-3.1.xsd)
00:33:45,753 [main] DEBUG JpdlParser$JpdlEntityResolver : providing input source to local 'jpdl-3.1.xsd' resource
00:33:46,019 [main] WARN JpdlXmlReader : process xml warning: warning: no swimlane or assignment specified for task '<task xmlns="urn:jbpm.org:jpdl-3.1" blocking="false" signalling="true" priority="normal">
<controller config-type="field">
<variable name="color" access="read,write"/>
<variable name="size" access="read,write"/>
</controller>
</task>'
00:33:46,081 [main] DEBUG GraphElement : event 'process-start' on 'ProcessDefinition(simple)' for 'Token(/)'
00:33:46,081 [main] DEBUG GraphElement : event 'before-signal' on 'StartState(start)' for 'Token(/)'
00:33:46,081 [main] DEBUG GraphElement : event 'node-leave' on 'StartState(start)' for 'Token(/)'
00:33:46,081 [main] DEBUG GraphElement : event 'transition' on 'Transition(to_state)' for 'Token(/)'
00:33:46,081 [main] DEBUG GraphElement : executing action 'action[action]'
00:33:46,113 [main] DEBUG VariableContainer : create variable 'message' in 'TokenVariableMapc4aad3' with value 'Going to the first state!'
00:33:46,159 [main] DEBUG Converters : adding converter 'D', 'org.jbpm.context.exe.converter.DoubleToStringConverter'
00:33:46,159 [main] DEBUG Converters : adding converter 'C', 'org.jbpm.context.exe.converter.CharacterToStringConverter'
00:33:46,159 [main] DEBUG Converters : adding converter 'B', 'org.jbpm.context.exe.converter.BooleanToStringConverter'
00:33:46,159 [main] DEBUG Converters : adding converter 'Y', 'org.jbpm.context.exe.converter.BytesToByteArrayConverter'
00:33:46,159 [main] DEBUG Converters : adding converter 'A', 'org.jbpm.context.exe.converter.DateToLongConverter'
00:33:46,159 [main] DEBUG Converters : adding converter 'R', 'org.jbpm.context.exe.converter.SerializableToByteArrayConverter'
00:33:46,175 [main] DEBUG Converters : adding converter 'I', 'org.jbpm.context.exe.converter.IntegerToLongConverter'
00:33:46,175 [main] DEBUG Converters : adding converter 'H', 'org.jbpm.context.exe.converter.ShortToLongConverter'
00:33:46,175 [main] DEBUG Converters : adding converter 'G', 'org.jbpm.context.exe.converter.FloatToDoubleConverter'
00:33:46,191 [main] DEBUG Converters : adding converter 'F', 'org.jbpm.context.exe.converter.FloatToStringConverter'
00:33:46,191 [main] DEBUG Converters : adding converter 'E', 'org.jbpm.context.exe.converter.ByteToLongConverter'
00:33:46,206 [main] DEBUG GraphElement : event 'node-enter' on 'State(first)' for 'Token(/)'
00:33:46,206 [main] DEBUG GraphElement : event 'after-signal' on 'StartState(start)' for 'Token(/)'
00:33:46,206 [main] DEBUG GraphElement : event 'before-signal' on 'State(first)' for 'Token(/)'
00:33:46,206 [main] DEBUG GraphElement : event 'node-leave' on 'State(first)' for 'Token(/)'
00:33:46,222 [main] DEBUG GraphElement : event 'transition' on 'Transition(to_end)' for 'Token(/)'
00:33:46,222 [main] DEBUG GraphElement : executing action 'action[action]'
00:33:46,222 [main] DEBUG VariableContainer : update variable 'message' in 'TokenVariableMapc4aad3' to value 'About to finish!'
00:33:46,222 [main] DEBUG GraphElement : event 'node-enter' on 'EndState(end)' for 'Token(/)'
00:33:46,222 [main] DEBUG GraphElement : event 'process-end' on 'ProcessDefinition(simple)' for 'Token(/)'
00:33:46,222 [main] DEBUG GraphElement : event 'after-signal' on 'State(first)' for 'Token(/)'


如下圖所示:


這裡只是簡單的介紹如何開發 workflow system,
並沒有實作用戶介面, 可集成至 JSP, JSF 或 Tapestry 實作.


如果需要 embed 到其他的系統, 可參考官方文檔:
http://docs.jboss.com/jbpm/v3/userguide/deployment.html


由於本人亦使用過 OpenWFE [Sourceforge workflow engine 熱門項目]
OpenWFE 功能比較強, 文檔亦很多, 也比較複雜.
但 JBoss jBPM 有 JBossIDE 支持, 感覺上比較容易開發.
總結是現在沒有一套 workflow engine 能滿足用戶的所有需求.


OpwnWFE 官方網頁:
http://www.openwfe.org,


JBoss jBPM 用戶手冊:
http://docs.jboss.com/jbpm/v3/userguide/


JBoss jBPM 官方文檔:
http://www.jboss.com/products/jbpm/docs


JBoss jBPM wiki:
http://wiki.jboss.org/wiki/Wiki.jsp?page=JbpmWiki


workflow pattern:
http://is.tm.tue.nl/research/patterns/


星期五, 8月 04, 2006

JAMon(Java Application Monitor)備忘記

JAMon (JAva Application Monitor) 為一套開源, 以 J2EE 為主的性能監視工具.
其以 簡單易用 及 性能高 的優點, 取得眾多 J2EE 開發員的愛戴.
可以用來監視性能的瓶頸, 用戶及應用程式間互動, 並能測試應用程式的擴展性.
JAMon 主要用來監測 點擊數, 執行時間 及 同步應用程式訪問數.
並以報表形式顯示.


開始備忘記:
[1] 第一個 JAMon 例子
[2] 下載及安裝工具
[3] 簡單的 J2EE 測試


[1] 第一個 JAMon 例子:
將 jamon-2.1.jar 加入 CLASSPATH 中 [ 後面有說明如何下載jamon-2.1.jar ].
/*----------------- JAMonitorTest.java -----------------*/
package jamon.joeyta.test;
import com.jamonapi.Monitor;
import com.jamonapi.MonitorFactory;
public class JAMonitorTest {
    public static void main(String[] args) throws Exception {
        Monitor mon=null;
        for (int i=1; i<=10; i++) {
            mon = MonitorFactory.start("joeytaTryJAMon");
            Thread.sleep(100+i);
            mon.stop();
        }
        System.out.println(mon);
    }
}
/*----------------- JAMonitorTest.java -----------------*/


執行後輸出為:
JAMon Label=joeytaTryJAMon, Units=ms.: (Hits=10.0, Avg=104.7, Total=1047.0,
Min=94.0, Max=110.0, Active=0.0, Avg Active=1.0, Max Active=1.0,
First Access=Fri Aug 04 14:07:50 GMT+08:00 2006, Last Access=Fri Aug 04 14:07:51 GMT+08:00 2006)


Hits=10.0 : joeytaTryJAMon JAMon Label 的執行次數.
Avg=104.7 : 總執行時間除以執行次數 (i.e., Total/Hits).
Total=1047.0 : 總執行時間.
Min=94.0 : 執行次數裡最短的執行時間.
Max=110.0 : 執行次數裡最長的執行時間.
Active=0.0 : 這數量表示在多執行緒裡同時執行的數目.
Avg Active=1.0 : 在多執行緒裡平均執行的數目.
Max Active=1.0 : 在多執行緒裡最大的執行數目.
First Access : 第一次執行的時間.
Last Access : 最後一次執行的時間.


如下圖所示:



[2] 下載及安裝工具:
安裝 JBoss:
可參考 JBoss EJB3(HelloWorld)備忘記
http://blog.matrix.org.cn/page/joeyta?entry=jboss_ejb3_helloworld_%E5%82%99%E5%BF%98%E8%A8%98


下載 JAMonAll_071606.zip
http://sourceforge.net/project/showfiles.php?group_id=96550&package_id=103162&release_id=432512
http://superb-east.dl.sourceforge.net/sourceforge/jamonapi/JAMonAll_071606.zip


解壓後顯示 jamon-2.1.jar 及 jamon.war 兩個檔案
使用 winzip 或 winrar 打開 jamon.war
將 jamon-2.1.jar 拖放進 jamon.war -> WEB-INF -> lib 裡. 按確定.


[3] 簡單的 J2EE 測試:
將 jamon.war 放進 D:\jboss\server\default\deploy
點擊 D:\jboss\bin\run.bat 啟動 JBoss
進入 http://localhost:8080/jamon/menu.jsp
如下圖所示:


按 "Enable Monitoring!" 啟動 Monitor
點擊 JAMon Admin Page 連結,
就會顯示 "/jamon/jamonadmin.jsp, ms." 的 Hits 為 0
如下圖所示


再按重新整理兩下
"/jamon/jamonadmin.jsp, ms." 的 Hits 為 2
如下圖所示


進入 http://localhost:8080/jamon/query.jsp , 啟動 資料庫 query monitoring
[ 這裡使用了 HSQL DB ]
按 "Generate Data!"
如下圖所示


進入 http://localhost:8080/jamon/jamonadmin.jsp
就可以看到 "MonProxy-SQL-Match: SYSTEM_TABLES, ms." 的 Hits 為 1
如下圖所示



sourceforge JAMon:
http://sourceforge.net/projects/jamonapi/


JAMon 官方網:
http://jamonapi.sourceforge.net/


javaperformancetuning.com:
http://www.javaperformancetuning.com/tools/jamon/index.shtml


gerald loeffler 連結:
http://www.gerald-loeffler.net/links.html


 

JBoss EJB3(Message Driven Beans)備忘記

第一編介紹如何安裝 JBoss 及建立第一個 Stateless Session Beans HelloWorld:
http://blog.matrix.org.cn/page/joeyta?entry=jboss_ejb3_helloworld_%E5%82%99%E5%BF%98%E8%A8%98


第二編介紹 Stateful Session Beans:
http://blog.matrix.org.cn/page/joeyta?entry=jboss_ejb3_stateful_session_beans


第三編介紹 Entity Beans:
http://blog.matrix.org.cn/page/joeyta?entry=jboss_ejb3_entity_beans_%E5%82%99%E5%BF%98%E8%A8%98


EJB 提供 messaging 的功能, 這是一種輕量級的傳輸的實作,
解決以下這些 RMI-IIOP (Remote Method Invocation over the Internet Inter-ORB Protocol) 的缺點:
(1) 典型的 RMI-IIOP 客戶端每次請求必須等候系統回應.
(2) RMI-IIOP 客戶端與系統過於偶合, 這使得客戶端難於與系統分離.
(3) 當 RMI-IIOP 客戶端呼叫系統時, 這時系統或網路發生故障, 所有資料可能流失, 客戶沒有得到預期的執行結果.
(4) RMI-IIOP 限制了在一定的時間內每個客戶端只能訪問單一的系統, 並沒有提供多數的客戶廣播事件給多數的系統.


Messaging 則解決以上所有問題. 保證了接收者必須接收到發送者發送的信息.
           
在過去的幾年裡, 由於不同廠家的 MOM(Message-oriented middleware) 系統有自己一套的 API,
這阻礙了不同的系統間 messaging system 不能跨平台, JMS (Java Message Service)的出現就是解決這問題.
JMS 為 messaging 的標準, JMS 分為兩部份, 第一部份為傳送及接收訊息的 API,
第二部份則為 SPI (Service Provider Interface), 這嵌入於 JMS providers,
JMS providers 知道怎樣與 MOM 系統溝通, JMS 確保了只需要學習一次便能應用於各種不同的 MOM 系統.


Messaging 可分為兩類:
發佈 / 訂購 [Publish / Subscribe]: 多個發送者將不同的 messages 發送到 middleware,
middleware 將這些 messages 發送到不同的訂閱者, 當全部發送完成後刪除這些 messages.
這種形式為可 多發送 及 每個訊息可有多位接收者.


點對點 [Point-to-point]:發送者將 message 發送給 middleware,
middleware 將這 message 發送給接收者, 然後取消這 message.
這種形式為可 多發送, 但每個訊息只能有一個接收者.


EJB 的 message driven bean 可以接收 JMS messages 及其他種類的 messages.
這裡並沒有對 Message driven beans 作太多的說明, 有興趣的可參閱 Mastering EJB3.
下面的例子實作了 Publish / Subscribe 的 internal 及 external 的 message driven beans.



開始備忘記:

[1] Eclipse 啟動 JBoss Server
[2] Eclipse 建立 HelloWorldMdbEJB3 Project
[3] 建立 JBoss MBean 定義檔
[4] 建立 Message Driven Beans [即 Server 端 Consumer]
[5] 建立 Client 端 Consumer
[6] 建立 Client 端 Producer
[7] 使用 ANT 建立 EJB-JAR 並執行 Client 程式


[1] Eclipse 啟動 JBoss Server:
Eclipse: Windows -> Show View -> Other
  -->> JBoss-IDE -> Server Configuration 就會顯示 JBoss Server Configuration console
  然後 right client 它按 start , JBoss 就會啟動
  
[2] Eclipse 建立 HelloWorldMdbEJB3 Project:
Eclipse: File -> New -> Other -> EJB 3.0 -> EJB 3.0 Project
Project Name: HelloWorldMdbEJB3 -> Next
選擇上一編已建立的 JBoss 4.0.x: jboss_configuration [default](not running)
打開後右鍵點選 JBoss 4.0.x -> new
然後按 Finish. HelloWorldMdbEJB3 project 就建立了  


[3] 建立 JBoss MBean 定義檔:
<!----------- jbossmq-HelloWorldMdb-service.xml ----------->
<server>
 <mbean code="org.jboss.mq.server.jmx.Topic"
  name="jboss.mq.destination:service=Topic,name=jms/HelloWorldMdbTopic">
  <depends optional-attribute-name="DestinationManager">
   jboss.mq:service=DestinationManager
  </depends>
 </mbean>
</server>
<!----------- jbossmq-HelloWorldMdb-service.xml ----------->
檔案名成需為 xxxxxxx-service.xml, 必須有 -service.xml 結尾.
可以直接把 jbossmq-HelloWorldMdb-service.xml 放在 D:\jboss\server\default\deploy
在下面的 ANT 己定義一個 Task copy 到 D:\jboss\server\default\deploy
Jboss 就會自動建立 topic/jms/HelloWorldMdbTopic 的 MBean, 可以使用 JNDI 最得.
這裡 JBoss 如發現是 Topic 則會在前面加上 topic, Queue 則加上 queue
如果改成 Queue , 則只需將 Topic 改成 Queue
甚實這個檔案可以省略, 因為 Message Driven Beans 裡己設定 destination property
當 JBoss 找不到就會自動建立


[4] 建立 Message Driven Beans [即 Server 端 Consumer]:
/*------------------- HelloWorldMdbBean.java -----------------*/
package ejb3.joeyta.mdb;


import javax.annotation.PreDestroy;
import javax.ejb.ActivationConfigProperty;
import javax.ejb.MessageDriven;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;
@MessageDriven(activationConfig = {
 @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Topic"),
 @ActivationConfigProperty(propertyName = "destination", propertyValue = "topic/jms/HelloWorldMdbTopic")  
})
// MessageDriven 定義這 class 為 Mesasge Driven Beans, 必須繼承 MessageListener
// destinationType 定義使用 javax.jms.Topic, 如果是 queue 則使用 javax.jms.Queue
// destination 定義 目的地 是 topic/jms/HelloWorldMdbTopic

public class HelloWorldMdbBean implements MessageListener {


 public HelloWorldMdbBean(){
  System.out.println("Local Server initialized on HelloWorldMdbBean...");  
 }


 public void onMessage(Message msg) {  // 這是 MessageListener 裡必須實作的 method,
  if (msg instanceof TextMessage) {
   TextMessage tm = (TextMessage) msg;   // 這裡將 Message 轉換成 TextMessage
   try {
    String text = tm.getText();
    System.out.println("Local Server HelloWorldMdbBean received message : " + text);
   } catch (JMSException e) {
    e.printStackTrace();
   }
  }
 }


 @PreDestroy   // 為 callback method, 當 instance 消除前呼叫這函數
 public void remove() {
  System.out.println("Local Server HelloWorldMdbBean destroyed.");
 }
}
/*------------------- HelloWorldMdbBean.java -----------------*/



[5] 建立 Client 端 Consumer:
/*------------------- HelloWorldConsumerClient.java -----------------*/
package ejb3.joeyta.clients;


import java.util.Properties;


import javax.annotation.PreDestroy;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageListener;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.jms.Topic;
import javax.naming.InitialContext;


public class HelloWorldConsumerClient implements MessageListener {


 public static void main(String[] args) throws Exception {
  new HelloWorldConsumerClient();
 }


 public static InitialContext getInitialContext()
   throws javax.naming.NamingException {


  Properties p = new Properties();
  p.put(InitialContext.INITIAL_CONTEXT_FACTORY,
    "org.jnp.interfaces.NamingContextFactory");
  p.put(InitialContext.URL_PKG_PREFIXES,
    " org.jboss.naming:org.jnp.interfaces");
  p.put(InitialContext.PROVIDER_URL, "jnp://localhost:1099");
  return new javax.naming.InitialContext(p);
 }


 public HelloWorldConsumerClient() throws Exception {
  InitialContext jndiContext = getInitialContext();  // 初始化 JNDI


  ConnectionFactory factory = (ConnectionFactory) jndiContext.lookup("ConnectionFactory");
  // 1: 尋找 connection factory


  Connection connect = factory.createConnection();
  // 2: 以 connection factory 建立 JMD connection  
  
  Session session = connect.createSession(false, Session.AUTO_ACKNOWLEDGE);
  // 3: 以 connection 建立 session, false 表示不使用 transaction,
  //     Session.AUTO_ACKNOWLEDGE 為設定怎樣確定接收 message,

    
  Topic topic = (Topic) jndiContext.lookup("topic/jms/HelloWorldMdbTopic");    
  // 4: 尋找 destination, 如果是 queue, 這裡只需將 topic 改成 queue, Topic 改成 Queue.  
    
  MessageConsumer consumer = session.createConsumer(topic);
  // 5: 建立 message consumer  
  
  consumer.setMessageListener(this);
  // 將這個 Class 加入到 MessageListener 裡


  System.out.println("Remote Client listening for messages on HelloWorldConsumerClient...");
  connect.start();   // 開始連結
 }


 public void onMessage(Message msg) {
  if (msg instanceof TextMessage) {
   TextMessage tm = (TextMessage) msg;   // 這裡將 Message 轉換成 TextMessage
   try {
    String text = tm.getText();
    System.out.println("Remote Client HelloWorldConsumerClient received message : " + text);
   } catch (JMSException e) {
    e.printStackTrace();
   }
  }
 }


 @PreDestroy    // 為 callback method, 當 instance 消除前呼叫這函數
 public void remove() {
  System.out.println("Remote Client HelloWorldConsumerClient destroyed.");
 }
}
/*------------------- HelloWorldConsumerClient.java -----------------*/
由於這次實作使用 Topic,
故 Remote Client 及 Local server 端均可接收 message.
如果使用的是 Queue, 則最後註冊 Listener 才能收到 message.



[6] 建立 Client 端 Producer:
/*------------------- HelloWorldProducerClient.java -----------------*/
package ejb3.joeyta.clients;


import java.util.Properties;


import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.jms.Topic;
import javax.naming.InitialContext;


public class HelloWorldProducerClient {
 public static InitialContext getInitialContext()
   throws javax.naming.NamingException {


  Properties p = new Properties();
  p.put(InitialContext.INITIAL_CONTEXT_FACTORY,
    "org.jnp.interfaces.NamingContextFactory");
  p.put(InitialContext.URL_PKG_PREFIXES,
    " org.jboss.naming:org.jnp.interfaces");
  p.put(InitialContext.PROVIDER_URL, "jnp://localhost:1099");
  return new javax.naming.InitialContext(p);
 }


 public static void main(String[] args) throws Exception {
  InitialContext ctx = getInitialContext();  // 初始化 JNDI
  
  ConnectionFactory factory = (ConnectionFactory) ctx.lookup("ConnectionFactory");
  // 1: 尋找 connection factory


  Connection connection = factory.createConnection();
  // 2: 以 connection factory 建立 JMD connection


  Session session = connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
  // 3: 以 connection 建立 session, false 表示不使用 transaction,
  //     Session.AUTO_ACKNOWLEDGE 為設定怎樣確定接收 message, 這 client 為發送, 故不重要


  Topic topic = (Topic) ctx.lookup("topic/jms/HelloWorldMdbTopic");
  // 4: 尋找 destination, 如果是 queue, 這裡只需將 topic 改成 queue, Topic 改成 Queue.
  //    Message Driven Bean 當然也要修改.


  MessageProducer producer = session.createProducer(topic);
  // 5: 建立 message producer



  TextMessage msg = session.createTextMessage();
  msg.setText("Joeyta try HelloWorld Message Driven Beans.");
  producer.send(msg);
  // 6: 上面三句建立及發送 message


  producer.close();   // 關閉 producer
  System.out.println("Message produced.");
 }
}
/*------------------- HelloWorldProducerClient.java -----------------*/


項目結構如下圖所示:



[7] 使用 ANT 建立 EJB-JAR 並執行 Client 程式:
<!-------------------------- build.xml ------------------------>
<?xml version="1.0"?>
<project name="JBoss" default="ejbjar" basedir=".">
 <property environment="env" />
 <property name="src.dir" value="${basedir}/src" />
 <property name="resources" value="${basedir}/META-INF" />
 <property name="jboss.home" value="${env.JBOSS_HOME}" />
 <property name="classes.dir" value="bin" />


 <path id="classpath">
  <fileset dir="${jboss.home}/client">
   <include name="**/*.jar" />
  </fileset>
  <pathelement location="${classes.dir}" />
  <pathelement location="${basedir}/client-config" />
 </path>


 <target name="clean">
  <delete file="${basedir}/HelloWorldMdb.jar" />
  <delete file="${jboss.home}/server/default/deploy/HelloWorldMdb.jar" />
 </target> 


 <target name="ejbjar" depends="clean">
  <jar jarfile="HelloWorldMdb.jar">
   <fileset dir="${classes.dir}">
    <include name="ejb3/joeyta/mdb/*.class" />    
    <include name="META-INF/*.xml" />
   </fileset>
  </jar>
  <copy file="HelloWorldMdb.jar" todir="${jboss.home}/server/default/deploy" />
 </target>
 
 <target name="run.mdb.producer.client">
  <java classname="ejb3.joeyta.clients.HelloWorldProducerClient" fork="yes" dir=".">
   <classpath refid="classpath" />
  </java>
 </target> 
 
 <target name="run.mdb.consumer.client">
  <java classname="ejb3.joeyta.clients.HelloWorldConsumerClient" fork="yes" dir=".">
   <classpath refid="classpath" />
  </java>
 </target> 
 
 <target name="copy.mq.service.xml">
  <copy file="jbossmq-HelloWorldMdb-service.xml" todir="${jboss.home}/server/default/deploy" />
 </target>
 
 <target name="clean.mq.service.xml"> 
  <delete file="${jboss.home}/server/default/deploy/jbossmq-HelloWorldMdb-service.xml" />  
 </target> 
</project>
<!-------------------------- build.xml ------------------------>


執行 ANT Task:
點選 build -> Run As -> 3. Ant Build ->> copy.mq.service.xml
點選 build -> Run As -> 3. Ant Build ->> ejbjar
點選 build -> Run As -> 3. Ant Build ->> run.mdb.consumer.client
點選 build -> Run As -> 3. Ant Build ->> run.mdb.producer.client
這裡必須順序執行 ANT 裡的Task.


JBoss Console 的輸出結果為:
05:04:19,242 INFO  [jms/HelloWorldMdbTopic] Bound to JNDI name: topic/jms/HelloWorldMdbTopic
05:05:15,256 INFO  [Ejb3Deployment] EJB3 deployment time took: 297
05:05:15,443 INFO  [JmxKernelAbstraction] installing MBean: jboss.j2ee:jar=HelloWorldMdb.jar,name=HelloWorldMdbBean,service=EJB3 with dependencies:
05:05:15,912 INFO  [EJBContainer] STARTED EJB: ejb3.joeyta.mdb.HelloWorldMdbBean ejbName: HelloWorldMdbBean
05:05:16,224 INFO  [EJB3Deployer] Deployed: file:/D:/jboss/server/default/deploy/HelloWorldMdb.jar
05:06:16,987 INFO  [STDOUT] Local Server initialized on HelloWorldMdbBean...
05:06:17,190 INFO  [STDOUT] Local Server HelloWorldMdbBean received message : Joeyta try HelloWorld Message Driven Beans.


如下圖所示:


run.mdb.consumer.client Console 的輸出結果為:
Buildfile: D:\eclipse_wtp\workspace\HelloWorldMdbEJB3\build.xml
run.mdb.consumer.client:
     [java] log4j:WARN No appenders could be found for logger (org.jboss.mq.referenceable.SpyConnectionFactoryObjectFactory).
     [java] log4j:WARN Please initialize the log4j system properly.
     [java] Remote Client listening for messages on HelloWorldConsumerClient...
     [java] Remote Client HelloWorldConsumerClient received message : Joeyta try HelloWorld Message Driven Beans.
    
如下圖所示:


run.mdb.producer.client Console 的輸出結果為:
Buildfile: D:\eclipse_wtp\workspace\HelloWorldMdbEJB3\build.xml
run.mdb.consumer.client:
     [java] log4j:WARN No appenders could be found for logger (org.jboss.mq.referenceable.SpyConnectionFactoryObjectFactory).
     [java] log4j:WARN Please initialize the log4j system properly.
     [java] Remote Client listening for messages on HelloWorldConsumerClient...
     [java] Remote Client HelloWorldConsumerClient received message : Joeyta try HelloWorld Message Driven Beans.


如下圖所示:



這裡為了簡化 Message Driven Beans 備忘記,
故沒有延續前編的 Shopping Cart Entity Beans 備忘記,
才把它獨立建立 HelloWorld Message Driven Beans Project,
如需與 Shopping Cart 合備,
只需在 Entity Beans 備忘記 ShoppingCartBean.java 裡加入
@Resource(mappedName="ConnectionFactory")
private ConnectionFactory connectionFactory;


@Resource(mappedName="jms/HelloWorldMdbTopic")
private Topic topic;


@Remove
public void checkout() throws IncompleteConversationalState {
    try {
        Connection connect = topicFactory.createConnection( );
        Session session = connect.createSession(true,0);
        MessageProducer producer = session.createProducer(topic);


    TextMessage msg = session.createTextMessage();
    msg.setText("Joeyta try HelloWorld Message Driven Beans.");
        producer.send(msg);
        connect.close( );
    } catch(Exception e) {
        throw new EJBException(e);
    }
}


這次 JBoss EJB3 Message Driven Beans 教學己到了終點.

正籌備 Transactions, Security, Timers, Clustering, Web Services, Interceptors(AOP-like) 教學.


由於 Java Persistence 內容太多了,  所以放在最後.
不過我相信有了上面這個起點, 學下去也很容易.

如果想更了解 EJB3 , 這裡有免費的 Mastering EJB 3.0. 下載.
http://www.theserverside.com/tt/books/wiley/masteringEJB3/index.tss


J2EE tutorial
http://java.sun.com/j2ee/1.4/docs/tutorial/doc/


Sun EJB Documentation page:
http://java.sun.com/products/ejb/docs.html


JSR 220:
http://www.jcp.org/en/jsr/detail?id=220