Friday, August 24, 2007

ทดลองเรียกใช้ Ofbiz service ผ่าน RMI

วันนี้ทดลองเรียกใช้ service บน Ofbiz ดู
หน้าตาของ client code เป็นดังนี้
String endPoint = "rmi://127.0.0.1:1099/RMIDispatcher";
RemoteDispatcher dispatcher = (RemoteDispatcher) Naming.lookup(endPoint);

Map ret = dispatcher.runSync("createPerson", UtilMisc.toMap("firstName", "polawat", "lastName", "phetra"));
String id = (String) ret.get("personId");


code ดูตรงไปตรงมาดี แต่ก็มีปัญหาจำนวนหนึ่ง

1. ผมสร้างโปรแกรม client ใน Eclipse
เนื่องจาก code มันอ้างถึง class ใน Ofbiz ด้วย
ดังนั้น ผมก็เลย add Ofbiz project เข้ามาเป็น dependency project ใน build path
ปัญหา ก็คือเวลา run ก็จะเกิด exception หน้าตาแบบนี้
Caused by: java.io.InvalidClassException: org.ofbiz.service.rmi.socket.ssl.SSLClientSocketFactory; 
local class incompatible: stream classdesc serialVersionUID = -6771263703492769955,
local class serialVersionUID = 266831034753376983
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:562)

สาเหตุเกิดจาก eclipse class ที่ server ของ ofbiz ใช้ (compile จาก ant)
กับ class ที่ eclipse compile มันเป็นคนละตัวกัน
ทางแก้ไขก็คือ ต้องใช้ class ตัวเดียวกัน โดยการ config build path ของ eclipse project ให้ถูกต้อง
ประเด็นถัดมา ก็คือ
แล้วเราจะ add jar ของ ofbiz ตัวไหน เข้าไปใน build path บ้าง
ตรงนี้ผมใช้วิธีค่อยๆทดลองใส่เข้าไปทีละตัว
พอมัน error ว่า classNotFound ตรงไหน ก็ใส่ตัวนั้นเพ่ิมเข้าไป
ได้ข้อสรุปว่า ต้องใช้ jar และ classpath ดังนี้
  • ofbiz-base.jar
  • ofbiz-service.jar
  • javolution-4.2.8.jar
  • jdbm-1.0.jar
  • log4j.jar
  • (classpath) ofbiz/framework/base/config


2. ปัญหาถัดไป ก็คือเวลา run แล้วมันเกิด exception นี้
Caused by: java.security.cert.CertificateException: No trusted certificate found
at org.ofbiz.base.util.MultiTrustManager.checkServerTrusted(MultiTrustManager.java:70)
at com.sun.net.ssl.internal.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:967)

หลังจากปล้ำกับ exception นี้ 3 ชั่วโมง ก็ได้ข้อสรุปดังนี้
เนื่องจาก RMI ของ ofbiz ใช้วิธีขี่บน SSL
เมื่อเปิด ssl connection, server จะส่ง certificate มาให้ client
ปัญหาที่เกิดก็คือ client จะต้องตรวจสอบก่อนว่า
มันควรจะเชื่อใจว่า server ตัวที่มันต่อ ใช้ตัวจริงหรือไม่
โดยมันจะทำการตรวจ certificate authority ที่ sign ให้ SSL server certificate
ว่าเชื่อใจได้หรือไม่ โดยการตรวจว่า CA certificate นั้นมีอยู่ภายใน keystore ของ current runtime หรือไม่
(ปกติ ก็คือ file jre/lib/security/cacerts)

วิธีแก้ไข ก็คือ เราต้องหา public key ของ CA ที่ sign ให้ SSL server cert. ให้เจอ
แล้วก็นำมา import เข้า jre/lib/security/cacerts ของเรา
ปัญหา ก็คือ จะหาจากไหน

วิธีแรกสุด ก็คือ ต้องรู้ก่อนว่า CA certificate มีหน้าตาเป็นอย่างไร
อันนี้ ทำได้โดย กำหนด property นี้ลงไป ตอน run client code ดังนี้
-Djavax.net.debug=ssl
ซึ่ง information ที่ได้มา ก็จะบอกว่า issuer เป็นใคร
  Key:  Sun RSA public key, 2048 bits
modulus: 17344648192569884299786164434338372557687257978285180720285392205708340271654619651329483132682296140227663296442343040152431948217141461304574537723511105703477881281981923049874873997527674061132974306266530070170417234226257753990168554412489871566243809200783575680539880294393348381403494415570636578448776926614739538191796509060477513894871026620210065065716045117073870737655887248726060382348138811502024743774034713421310777435989153209885185191144967681642453965102277302605991311051345165434979041324933404867053275745104875913849033117376130950416551314398706806603259933232713428792347281547375515443731
public exponent: 65537
Validity: [From: Fri May 04 03:36:54 ICT 2007,
To: Mon May 01 03:36:54 ICT 2017]
Issuer: CN=Unknown, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=Unknown
SerialNumber: [ 463a47e6]

เยี่ยมมาก Unknown ล้วนๆ

ขั้นถัดไป ก็คือมองหา certificate ที่หน้าตาแบบนี้ ใน directory ของ ofbiz
ลองใช้คำสั่ง find . -name '*.pem' -print
ก็ได้ผลลัพท์ดังนี้
@pann[~/dev/ofbiz]$ find . -name '*.pem' -print
./framework/base/cert/certreq.pem
./framework/base/cert/demoCA/cacert.pem
./framework/base/cert/demoCA/newcerts/01.pem
./framework/base/cert/demoCA/newcerts/02.pem
./framework/base/cert/demoCA/private/cakey.pem
./framework/base/cert/newcert.pem

ตัวที่ชื่อดูสื่อสุดก็คือ cacert.pem
ลองใช้คำสั่ง keytool -printcert -file cacert.pem เพื่อดู information
ก็พบว่า ไม่ใช่ file นี้
ลองดูที่เหลืออีก 5 ตัว ก็ไม่ใช่

สุดท้ายลองแกะ code ของ ofbiz ดู
ก็พบเงื่อนงำว่า ofbiz มี keystore ของตัวเอง
โดยตั้งชื่อ file ให้มีนามสกุล .jks
@pann[~/dev/ofbiz]$ find . -name '*.jks' -print
./framework/base/config/ofbizrmi.jks
./framework/base/config/ofbizssl.jks
./framework/service/config/rmitrust.jks

อยากรู้ว่าเป็นตัวไหน ก็ต้อง browse ดู
(จำได้ว่า ถ้าเป็น JDK ของ IBM มันจะมี GUI tool มาให้ด้วย
แต่วันนี้คิดว่าจะฝึกใช้ command-line ก็เลยไม่ได้ลองค้นดู)
@pann[~/dev/ofbiz/framework/service/config]$ keytool -list -v -keystore rmitrust.jks
Enter keystore password:

Keystore type: JKS
Keystore provider: SUN

Your keystore contains 1 entry

Alias name: mykey
Creation date: May 4, 2007
Entry type: trustedCertEntry

Owner: CN=Unknown, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=Unknown
Issuer: CN=Unknown, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=Unknown
Serial number: 463a47e6
Valid from: Fri May 04 03:36:54 ICT 2007 until: Mon May 01 03:36:54 ICT 2017
Certificate fingerprints:
MD5: 86:B1:D1:09:74:36:66:C7:30:18:7B:16:FA:C2:C2:9E
SHA1: 90:42:E8:91:BD:4E:88:59:E4:D7:F3:10:12:8F:57:3E:81:87:96:A2
Signature algorithm name: MD5withRSA
Version: 1


จากการเปรียบเทียบดู ก็พบว่า ofgizrmi.jks กับ rmitrust.jks
เก็บ cert ตัวเดียวกัน และตรงกับที่มันฟ้องใน debug console ของเรา

ขั้นถัดมาก็คือ export cert ออกจาก keystore
โดยใช้คำสั่ง keytool -exportcert -keystore ofbizrmi.jks -file pok.pem -alias rmissl
นำ file ที่ได้ไป import เข้า jre/lib/security/cacerts โดยใช้คำสั่ง
keytool -import -keystore ./cacerts -file pok.pem -alias ofbiz_rmissl

Note: จริงเราควรใช้ keystore ของเราเอง แทนที่จะใช้ global keystore
โดยกำหนดใน system property ดังนี้
-Djavax.net.ssl.trustStore=mySrvKeystore 
-Djavax.net.ssl.trustStorePassword=123456


Note: default password ของ keystore ของ ofbiz และ java คือ "changeit"

จบ ปิดคดี

Related link from Roti

Tuesday, August 21, 2007

ขั้นตอนการ start ของ Ofbiz

ลองแกะขั้นตอนการ start ของ Ofbiz ดู ก็พบขั้นตอนที่น่าสนใจดังนี้

1. เหมือนกับ framework ใหญ่ๆทั่วๆไป นั่นคือ
ถ้าต้อง start อะไรที่ซับซ้อนนัก ก็ควรมี bootstrapping code
โดย class ที่เป็นจุดเริ่มต้นทำงานของ Ofbiz ก็คือ org.ofbiz.base.start.Start.java
เมื่อ initialize ค่าเบื้องต้นต่างๆหมดแล้ว (เช่น log directory, base class path)
ก็จะทำการ load configuration ตาม command parameter ที่ user สั่งผ่าน command-line มา
เช่น ผมสั่ง
java -jar ofbiz.jar -pos
มันก็จะไปใช้ configuration file ที่ชื่อ /framework/base/src/start/org/ofbiz/base/start/pos.properties
ถ้าสั่ง
java -jar ofbiz.jar
มันก็จะใช้ default configuration file ซึ่งก็คือ start.properties

element ที่สำคัยภายใน properties file ข้างบน ก็คือ ofbiz.start.loader* (* คือเลขลำดับในการ load)
ตัว loader ที่สำคัญของ Ofbiz ก็คือ org.ofbiz.base.container.ContainerLoader
ซึ่งเมื่อมัน load ขึ้นมา มันจะใช้ค่า ofbiz.container.config ที่ระบุใน configuration file
ในการ load container ขึ้นมาทำงาน

2. ofbiz มองว่า การ start ครั้งหนึ่งๆ เราสามารถกำหนด container ที่เราต้องการได้หลายตัว
โดยต้องสร้าง xml file ที่ระบุ container ที่ต้องการ
และระบุชื่อ file ไว้ใน configuration ผ่าน property ที่ชื่อ ofbiz.container.config

อย่างในกรณีที่เราจะ start โปรแกรม point of sell
ค่า ofbiz.container.config ก็จะกำหนดแบบนี้
ofbiz.container.config=framework/base/config/pos-containers.xml

ofbiz ก็จะใช้ file pos-containers.xml ในการเลือก start container ที่ต้องการ

3. Container ที่สำคัญๆ และขาดไม่ได้ ของ Ofbiz ก็คือ
3.1 component-container ทำหน้าที่ load component ต่างๆ
ทั้งที่เป็น framework component และ application component
โดย default มันจะใช้ file framework/base/config/component-load.xml เป็นจุดตั้งต้นในการ load
ซึ่งภายในจะ recursive load module ของ framework, applications, specialpurpose, hot-deploy
ถัดจากตรงนี้ไป ยังมีเรื่องให้ตามอีกยาว ดังนั้นจะพักไว้แค่ตรงนี้ก่อน

3.2 classloader-container
ตรงนี้ยังแกะไม่เข้าใจ
โดย ofbiz จะใช้ classloader ที่ชื่อ CacheClassLoader.java

จากที่ตามดูขั้นตอนการ load Point of Sell
พบว่า ofbiz มัน load ทุก component, ทุก applications
ดูเหมือนจะ load เยอะกว่าที่เราต้องการ

ประเด็นที่น่าสนใจ กรณีจะทำเป็น Eclipse RCP
1. จะ pack มันเป็น bundle(plugin) อย่างไร
การ pack เป็น bundle จะส่งผลต่อ code แค่ไหน
ใช้ wrapping อย่างเดียว เพียงพอหรือไม่
ปัญหาที่ตามมา ก็คือเรื่อง class loader
เนื่องจาก Eclipse มี model class loader ที่ค่อนข้าง strict

2. ถ้าไม่ใช้ Eclipse RCP แต่เปลี่ยนไปใช้แค่ SWT+JFace
น่าจะช่วยให้หลีกเลี่ยงปัญหาก้อนใหญ่ไปได้

Related link from Roti

Monday, August 20, 2007

ลูกหมู 3 ตัว

เมื่อวานแวะไปซื้อหนังสือนิทานที่จตุจักรให้คุณลูกชาย
(มันมีหนังสือนิทานมือสอง ที่เขาเหมาตู้ container มาจากต่างประเทศ)
เลือกได้มาจำนวนหนึ่ง ในนั้นมีเรื่องลูกหมูสามตัวด้วย

กลับมาบ้าน ก็นั่งเล่าให้คุณลูกฟัง
เรื่องหมูสามตัว ที่ทำบ้านด้วย ฟาง,ไม้,อิฐ
เนื้อเรื่องตอนแรกๆ ก็เหมือนที่เคยอ่านตอนเด็กๆ
แต่ตอนจบนี่แปลกไปจากที่เคยจำได้
มันจบที่ว่า
"ลูกหมูได้กินเนื้อหมาป่าเป็นอาหารเย็น"
เล่าจบ ก็ร้อง "เฮ้ย" ด้วยความประหลาดใจ
หันไปถามแฟน กับหลานๆที่แวะมาเยี่ยมบ้าน
ก็ไม่เคยมีใครได้ยินตอนจบแบบนี้

นึกถึงเรื่องลูกหมูกับหมาป่าแล้ว
ก็เลยเถิดไปถึงสมัยที่เรียนอยู่มหาลัย
ผมเคยไปสอนหนังสือให้พวกเด็กชาวเขา
จำได้ว่า เคยเล่นละครเรื่องนี้ให้เด็กๆดูกัน
(แน่นอนด้วยบุคลิกของผม ผมต้องเล่นเป็นหมาป่าแหงๆ)
ตอนเล่นก็ไม่คิดว่าเด็กจะสนุกมากมายอะไร
แต่กลับกลายเป็นว่า
เด็กๆทั้งเต้นทั้งส่งเสียงกรี๊ด ลุ้นไปกับเนื้อเรื่อง
เป็นช่วงเวลาเห็นความสุขจริงๆ

Note: แก่แล้ว เริ่มมีย้อนอดีต

Related link from Roti