Thursday, November 03, 2005

Implement Bound properties with AspectJ

วันนี้อ่านเจอ post ของ damnhandy
ที่เขียนถึง JavaBean Aspect
โดยเขาตั้งโจทย์ว่า
เขาต้องการใช้ JGoodies เป็นตัวกลางในการ
bind hibernate persistent class เข้ากับ Swing component
ซึ่ง JGoodies Require ว่า class ที่ต้องการ bind
เข้ากับ Swing component ต้องเป็น Java Bean ที่ support
Bound properties (สามารถ add propertyChangeListener ได้)

ซึ่ง solution ที่เขาเขียนนั้นเขาใช้ JBoss AOP
วันนี้ผมก็เลยทดลองใช้ AspectJ implement ดูว่า
AspectJ สามารถทำ feature ประเภทนี้ได้ไหม

solution ของ damnhandy นั้น มี feature
พอสมควร มีการใช้ annotation เข้ามาช่วยด้วย
ผมจะตัด feature บางอันออกก่อน เพราะเราจะทำแค่
ทดลองชิม AspectJ ว่ามีรสประมาณไหนก่อน

โจทย์ที่จะทดลองทำ จะมีแค่
ในกรณีที่เรามี POJO class อยู่แล้ว
และต้องการ add feature bound properties เข้าไป
โดยใช้ AspectJ เราจะต้องทำได้อย่างไร

ในตัวอย่างของ AspectJ มีตัวอย่างเรื่องนี้อยู่แล้ว
ในหัวข้อ BoundPoint aspect
แต่เขาทำในลักษณะของ per class, per method
นั่นก็คือ ต้อง declare ทุก class, ทุก method ที่ต้องการ add bound properties
ผมก็เลยทดลอง declare ให้เป็น Generic มากขึ้นดู

สมมติว่าเรามี Class ที่ชื่อ Person
package domain;

public class Person {
private Long id;
private String name;
private String addresss;

public String getAddresss() {
return addresss;
}
public void setAddresss(String addresss) {
this.addresss = addresss;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}


เริ่มด้วย สร้าง interface ที่ชื่อ IValueObject ขึ้นมาก่อน
package domain;

import java.beans.PropertyChangeListener;

public interface IValueObject {

public void addPropertyChangeListener(
PropertyChangeListener listener);

public void addPropertyChangeListener(
String propertyName,
PropertyChangeListener listener) ;

public void removePropertyChangeListener(
PropertyChangeListener listener) ;

public void removePropertyChangeListener(
String propertyName,
PropertyChangeListener listener) ;

}


เตรียม Abstract Aspect ที่ implement method ในส่วนของ IValueObject
package domain;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;

import org.apache.commons.beanutils.PropertyUtils;

public abstract aspect ValueObjectChangeAspect {

public PropertyChangeSupport IValueObject.support =
new PropertyChangeSupport(this);

public void IValueObject.addPropertyChangeListener(
PropertyChangeListener listener) {
support.addPropertyChangeListener(listener);
}

public void IValueObject.addPropertyChangeListener(
String propertyName,
PropertyChangeListener listener) {
support.addPropertyChangeListener(propertyName, listener);
}

public void IValueObject.removePropertyChangeListener(
PropertyChangeListener listener) {
support.removePropertyChangeListener(listener);
}

public void IValueObject.removePropertyChangeListener(
String propertyName,
PropertyChangeListener listener) {
support.removePropertyChangeListener(propertyName, listener);
}

abstract pointcut setter(IValueObject vo);

void around(IValueObject vo): setter(vo) {
String tmp = thisJoinPointStaticPart.getSignature()
.getName().substring("set".length());
String propName = tmp.substring(0, 1).toLowerCase()
+ tmp.substring(1);
try {
Object newValue = thisJoinPoint.getArgs()[0];
Object oldValue = PropertyUtils.getProperty(vo, propName);
proceed(vo);
vo.support.firePropertyChange(propName, oldValue, newValue);

} catch (Exception e) {
e.printStackTrace();
}
}

}

Aspect ValueObjectChangeAspect จะ delcare java.beans.PropertyChangeSupport instance
ที่จะไว้ใช้ช่วยทำ bound properties
โดยเราจะ declare pointcut ที่ชื่อ setter ไว้
ให้สังเกตุว่า pointcut นี้ยังเป็น abstract อยู่ นั่นก็คือว่ายังไม่มีการระบุให้ชัดว่า
จะเอาไปใช้กับ method อะไร,ที่ไหน
ในส่วนของ advice around จะ trig เมื่อ pointcut setter
ถูกเรียกใช้ ขั้นตอนการทำงาน ก็จะทำการหาค่า oldValue กับ newValue
(ใช้ common BeanUtils ของ apache เข้ามาช่วย)
เพื่อที่จะไว้ firePropertyChange
Note: โปรดระวัง code นี้ไม่สามารถใช้กับ production ได้
เพราะว่า ยังไม่ได้คิดว่า ถ้าเกิด exception แล้วจะทำอะไร


ถึงขั้นนี้ เราก็มี abstract aspect เตรียมไว้แล้ว ขั้นต่อไป ก็เป็นการ
ทำ Aspect PersonChangeAspect ที่เป็นการ implement IValueObject เข้ากับ Person Class
package domain;

public aspect PersonChangeAspect extends ValueObjectChangeAspect {

declare parents: Person implements IValueObject;

pointcut setter(IValueObject vo): call(void Person.set*(*)) && target(vo);

}

ข้างใน aspect ก็จะมีการ declare ว่าให้ Person ไป implement IValueObject เสีย
จากนั้นก็ declare pointcut setter ว่าให้ไปดักการทำงานของ
ทุก method ที่มีชื่อขึ้นต้นว่า set ใน class Person

สุดท้ายก็ทดสอบโปรแกรมดูดังนี้
package domain;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

public class TestRun implements PropertyChangeListener {

/**
* @param args
*/
public static void main(String[] args) {
// initialize object
Person p1 = new Person();
p1.setName("x");

IValueObject vo = (IValueObject) p1;
vo.addPropertyChangeListener(new TestRun());

p1.setName("y");
}

public void propertyChange(PropertyChangeEvent evt) {
System.out.println(evt.getPropertyName());
System.out.println(evt.getOldValue());
System.out.println(evt.getNewValue());
}

}


Output ที่ได้ ก็เป็นดังนี้

name
x
y

Related link from Roti

Wednesday, November 02, 2005

ทำไมไม่ใช้ RoR ในโปรเจคใหญ่

ผมเคย post ไว้ว่า จะเลือกใช้ RoR ในกรณีที่ project เล็กๆ
แล้วก็มี comment ถามเข้ามาว่า ทำไมถึงเลือก choice นั้น
ใช้เหตุผลด้านไหนเป็นตัวตัดสิน

วันนี้อ่านเจอ blog ของ Glenn Vanderburg
หัวข้อเรื่อง The Right Team for Rails
เขาเขียนได้ตรงใจทีเดียว

A good Rails team will consist of programmers who know what they should be doing, and usually have the discipline to do it.


The wrong Rails team is one that doesn’t understand those principles and practices. The fact that Rails makes things easy won’t be enough. In my experience, such teams expend amazing effort and ingenuity to do the wrong thing.


กรณีของผม ที่ผมวางแนวทางไว้ก็คือ
ถ้าเป็น developer มือใหม่ เข้ามาในบริษัทฯ
ผมจะยังไม่ให้ใช้ RoR เลย
จะให้หัดใช้ mvc แบบ Struts ให้เข้าใจ framework เสียก่อน
,ใช้ hibernate ได้อย่างไม่เก้อเขิน
,มอง springFramework เป็นเหมือนสวนหลังบ้าน (เปรียบเทียบเวอร์ไปนิด)
ให้ดี ก็ต้องใช้ tapestry เป็นด้วย (จะได้ซาบซื้งกับหลักการ reuse component)
แล้วก็ผ่านการพัฒนา project ด้วย java มาอย่างน้อยสัก 1 project ก่อน
คราวนี้แหล่ะที่พร้อมจะใช้ RoR ได้แล้ว
(ความหมายของคำว่า "พร้อม" นี้
หมายถึงพร้อมที่จะร่วมพัฒนาระบบงานใหญ่ๆ พร้อมๆกันเป็น team)

แก้ไขเพิ่มเติม ลืมบอกลักษณะของโปรเจคไป
โปรเจคที่ผมทำส่วนใหญ่เป็น web application ทีเป็นพวก Data Entry
มี form เยอะแยะเต็มไปหมด (ส่วนใหญ่จะ replace terminal app หรือ client-server
app ของเดิม

Related link from Roti

Tuesday, November 01, 2005

รูปจาก trip เที่ยวใต้

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


ส่วนรูปนี้เป็นรูปปูสูงอายุ ถ้าทางจะอายุมากเชียวหล่ะ

Related link from Roti

Thursday, October 27, 2005

พักการ post

พาลูกชายกับพี่เลี้ยงเด็ก(แม่เด็ก)
ไปตลุยภาคใต้มา
8 วัน 7 คืน 1660 km.

กลับมา ก็พบว่า adsl ที่บ้านต่อไม่ได้
อาการลงแดง(อยาก net) ก็กำเริบทันที
แต่หลังจากไม่ได้ใช้ net มา 2 อาทิตย์
ตอนนี้อาการอยาก net เริ่มดีขึ้นแล้ว ไม่ทุรนทุรายแล้ว

ส่วน adsl ที่มีปัญหา เกิดจากสายโทรศัพท์มี noice รบกวน
มากเกินไป connection เลยมีปัญหา

ช่วงนี้เลยขอพักการ post ชั่วคราว

Related link from Roti

Wednesday, October 12, 2005

Link น่าสนใจ (2005-10-12)

Related link from Roti

Tuesday, October 11, 2005

LINQ

LINQ ย่อจาก Language INtegrated Query
เป็น feature หนึ่งใน .net version ใหม่

The LINQ Project is a codename for a set of extensions to the .NET Framework that encompass language-integrated query, set, and transform operations. It extends C# and Visual Basic with native language syntax for queries and provides class libraries to take advantage of these capabilities.


ดูตัวอย่าง snippet ของการใช้ LINQ
using System;
using System.Query;
using Danielfe;

class Program
{
static void Main(string[] args)
{
string[] aBunchOfWords = {"One","Two", "Hello",
"World", "Four", "Five"};

var result =
from s in aBunchOfWords // query the string array
where s.Length == 5 // for all words with length = 5
select s; // and return the string

//PrintToConsole is an Extension method that prints the value
result.Print();
}
}


integrate ได้เนียนดีมาก
ลองมาดู java บ้าง
ถ้าต้องการ feature แบบนี้ เราต้องใช้ JoSQL เข้ามาช่วย
public static void main(String[] args) throws 
QueryParseException,
QueryExecutionException {
List list = new ArrayList();
list.add("one");
list.add("two");
list.add("Hello");
list.add("world");
list.add("four");
list.add("five");

Query q = new Query();
q.parse("select * from java.lang.String " +
"where length = 5 ");
QueryResults qr = q.execute(list);
List results = qr.getResults();
for (Iterator iter = results.iterator(); iter.hasNext();) {
String tmp = (String) iter.next();
System.out.println(tmp);
}
}

เนื่องจากเป็นแค่ api ก็เลยดูไม่เนียนตาเท่า LINQ ที่เป็น language extension

Related link from Roti

Monday, October 10, 2005

Develop Rails with vim

ช่วงหลังๆนี้เริ่มทดลองเขียน ruby(rails) เยอะขึ้น
ก็เริ่มมองหา editor ที่ใช้เขียน ว่าจะใช้อะไรเขียนดี
เริ่มตั้งแต่ RDT ที่ใช้ eclipse เป็น platform
,BBEdit, JEdit
สุดท้ายสูงสุดคืนสู่สามัญ
กลับมาใช้ vi เป็น editor หลัก
มูลเหตุที่ใช้ vi ก็เพราะว่า พึ่งจะฉลาดรู้ว่า
vi ที่เป็น vim version นั้น มันมี feature แพรวพราวไม่เบาเลย
(เมื่อก่อนใช้ vi ดัั้งเดิม ใน hp เป็นหลัก
เลยไม่นึกว่าเวลาเปลี่ยน แล้ว vi จะเปลี่ยนด้วย)

ลองมาดูว่าผมทำอะไรลงไปบ้าง เพื่อให้ใช้ vim กับ ruby
ได้อย่างสบายใจ

เริ่มดูการไป copy VimExtension จาก rubyforge ก่อน
ใน package นี้มี vim extension อยู่ 4 ส่วนคือ
  • Systax Highlighting อันนี้ไม่ต้องอธิบาย
  • Smart Indenting อันนี้ก็ไม่ต้องอธิบายเช่นเดียวกัน
  • Compiler อันนี้ช่วย map คำสั่ง make กับ error format
    ทำให้เรา run และแก้ไข error ที่เกิด ขณะอยู่ใน vim ได้


เพื่อให้ใช้งาน extension ตามข้างบน ก็ต้องมีการ
set .vimrc ดังนี้

set nocompatible " We're running Vim, not Vi!
syntax on " Enable syntax highlighting
filetype on " Enable filetype detection
filetype indent on " Enable filetype-specific indenting
filetype plugin on " Enable filetype-specific plugins


ด้วยเหตุผลอะไรก็ไม่ทราบ
(คิดว่าคงเนื่องจากไม่รู้จัก vim ดีพอมากกว่า)
ทำให้ function compiler บนเครื่องผมไม่ work
ผมก็เลยต้อง copy command ที่อยู่ใน compiler/ruby.vim
ไปไว้ใน ftplugin/ruby.vim แทน

และเพื่อให้สะดวกในการ Run
โดยให้มัน save ก่อน run
ผมก็เลยเพิ่ม map key เข้าไปดังนี้

function! RunRuby()
w
make %
endfunction

map <C-R> :call RunRuby()<CR>


ได้ข้างบนมาก็ช่วยได้ระดับหนึ่งแล้ว
ก็ต้องเพิ่มความสามารถด้วยการทำ folding
โดยกำหนด ให้ใช้ toggle การแสดง folder guild line
ส่วน tab key ก็ใช้ให้เป็นตัว toggle show/hidden fold

set fdm=syntax
set foldtext=getline(v:foldstart)
set foldcolumn=3
set fillchars=fold:\ "follow by white space
map <TAB> za

function! ToggleFoldColumn()
if &foldcolumn == 3
set foldcolumn=0
else
set foldcolumn=3
endif
endfunction
map <F3> :call ToggleFoldColumn()<CR>


จากนั้นก็ map ให้เป็น key ที่ใช้ toggle การแสดง linenumber

map :call ToggleNumber()
function! ToggleNumber()
if &number
set nonumber
else
set number
endif
endfunction


สุดท้ายก็ลง script winmanager เพื่อช่วยให้ access file
กับ switch buffer ได้ง่ายขึ้น

Related link from Roti

Sunday, October 09, 2005

บทความน่าสนใจ [2005-10-9]

  • Urban performance legends, revisited

    Pop quiz: Which language boasts faster raw allocation performance, the Java language, or C/C++? The answer may surprise you -- allocation in modern JVMs is far faster than the best performing malloc implementations.

Related link from Roti

Groovestry

Groovy เป็น script language ตัวหนึ่งที่เขียนด้วย java
เป็น script ที่น่าใช้ทีเดียว เพราะมี feature หลายอย่างที่ลอกมาจาก
ruby, python, smalltalk

วันนี้อ่านเจอใน Tapestry Mailing list ว่ามี project ที่ชื่อ Groovestry
ก็เลย load มาทดลองดู

เป็าหมายของโปรเจค ก็คือ นำ groovy เข้ามาช่วยเขียน code
ในส่วนของ Controller (MVC Pattern)
เพราะเดิมเวลาเราเขียน Web Application
เรามักจะมีขั้นตอนประมาณนี้
  1. start tomcat
  2. ใช้ browser เรียกทดสอบ programe
  3. โปรแกรมทำงานไม่ถูกต้อง
  4. แก้ไขโปรแกรม
  5. reload application
  6. วนกลับไปทำขั้นตอนที่ 2

ขั้นตอนที่น่าเบื่อ ก็คือตอนที่รอมัน reload application
ถ้า project ใหญ่ๆ มันจะช้าจนน่ารำคาญ
แน่นอน ถ้าทำ test case ไว้ดี ก็จะลดรอบ
ของการ iterate แบบที่ว่าไปได้เยอะ
แต่บางที เราก็อยากเขียนโปรแกรม
ในแบบของศิลปินบ้าง เขียนไป ดูผลลัพท์ไป ออกแบบไป
เหมือนช่างปั้น ที่ค่อยๆใส่ดินเข้าไป ตัดออก โปะเข้าไปใหม่ เกลี่ย ปาด..
(สำหรับคนที่ใช้ framework อื่น
tapestry ใช้วิธี dynamic enhance class
ในตอนที่ load controller ขึ้นมาทำงาน
ทำให้เราไม่สามารถใช้ feature reload class
ของ tomcat ได้)

เมื่อนำ Groovestry เข้ามาใช้
รอบ cycle การพัฒนาของเราก็จะเร็วขึ้น
เพราะสามารถตัดขั้นตอน การ reload application ออกไปได้

การใช้ Groovestry ก็ง่ายดาย
เพียงแค่ add jar เข้าไปใน WEB-INF/lib
แล้วก็กำหนดให้ page class เป็น ScriptPage
<page-specification class="org.apache.tapestry.contrib.script.ScriptPage">

เมื่อกำหนดแบบนี้แล้ว เวลา controller ถูกเรียกใช้งาน
มันก็จะมองหา groovy file ที่มีชื่อเดียวกับ page แต่มีนามสกุลเป็น
.groovy ให้เอง

ตัว controller ที่เขียนด้วย groovy จะมีหน้าตาประมาณนี้
package application.script

import org.apache.tapestry.contrib.script.ScriptObject

class Home extends ScriptObject {

void pageBeginRender(event) {
.....
}

public void submit(cycle) {
page2 = cycle.getPage("Page2");
page2.title = "< " + formTitle + " >";
page2.customerId = customerId;
cycle.activate(page2);
}
}

โดย script ของเราจะ extend จากScriptObject
(จริงๆไม่ extend ก็ได้ แต่จะขาด feature ของการ access
component, page property ไป)

เวลาใช้งานใน mode develope ก็ให้ set flag
-Dorg.apache.tapestry.disable-caching=true ด้วย
เพื่อไม่ให้ tapestry ทำ template-caching

หลังจากลองใช้ไปสักพักหนึ่ง
ก็พบว่า Groovestry มันขาด feature หนึ่งของ
tapestry ไป
feature ที่ว่า ก็คือการ binding ระหว่าง component กับ controller
ในลักษณะ ognl Expression
ยกตัวอย่าง
สมมติเรามี dynamic title อยู่อันหนึ่ง
<title>
<span jwcid="@Insert" value="ognl:title">xxx</span>
</title>

ถ้าเรา implement Controller ด้วย Java
เราก็ต้องมี method getTitle
ที่จะถูก bind เข้ากับ component Insert ใน html template
public class MyPage extends BasePage {
...
public String getTitle() {
return "my page title";
}
....
}

แต่พอมาใช้ Groovestry ก็พบว่าไม่สามารถ
เรียกข้ามไปใช้ method getTitle ที่เขียนไว้ใน groovy controller ได้
หลังจากแกะ code ดูสักพัก
ก็พบว่า จริงๆแล้ว มันพอที่จะทำได้เหมือนกัน
แต่ต้องแก้ code จำนวนหนึ่ง

สุดท้าย feature ที่เพิ่มเข้าไป ก็ทำให้สามารถเรียกใช้ ในลักษณะนี้ได้
<title>
<span jwcid="@Insert"
value="ognl:doAction('getTitle')">xxx</span>
</title>

ดูไม่ค่อยเนียนตานัก แต่ก็ทำงานได้
(java มันขาด feature แบบ ruby ที่สามารถ
implement missing method handler ได้
ไม่งั้น เราก็สามารถทำ dynamic method ได้เนียนกว่านี้)

Related link from Roti

JBI Interface Name & Service Name

ในการ implement JBI component
จะมีศัพท์พื้นฐานที่ต้องทำความเข้าใจอยู่จำนวนหนึ่ง
ไม่เช่นนั้นอาจจะต้องปวดหัวกับการ ส่ง message ไม่ได้
หรือ message วิ่งหายต๋อมไม่รู้ไปไหน

ศัพท์ตัวแรกก็คือคำว่า interface
เป็นศัพท์ที่ define ไว้ใน spec ของ WSDL 2.0
มีความหมายเดียวกับ คำว่า porttype ใน spec WSDL 1.1
10 ปากว่า ไม่เท่าตาเห็น
ลองดู WSDL 1.1 ของ google ดู
<portType name="GoogleSearchPort">

<operation name="doGoogleSearch">
<input message="typens:doGoogleSearch"/>
<output message="typens:doGoogleSearchResponse"/>
</operation>

</portType>

ถ้าเปรียบเทียบกับ OOP term
interface ใน WSDL 2.0 ก็มีความหมายเหมือนกัน interface ใน OOP

ศัพท์ตัวถัดไปก็คือ service
ความหมายก็คือ implementation ของ interface นั่นเอง

ศัพท์ตัวถัดไปก็คือ endpoint
endpoint คือจุดที่เกิดการเชื่อมต่อจริงๆ
คล้ายๆความหมายของ port ในเรื่อง socket (network)
ลองดูความหมายที่ WSDL 2.0 define ไว้ (JBI endpoint มีความหมายเดียวกับ WSDL endpoint)
Endpoints, in WSDL 2.0, refer to a particular address, accessible by a particular protocol, used to access a
particular service




ความสัมพันธ์ระหว่าง service กับ endpoint เป็น one-to-many ก็คือ
service หนึ่งๆมี endpoint ได้หลาย endpoint

เวลาเราส่ง Message ใน JBI
เราสามารถระบุ target ที่จะส่งได้ 2 วิธีคือ
  • ระบุแค่ service name
    ถ้าระบุแค่นี้ ตัว JBI container จะตัดสินให้ว่า ควรจะส่งให้ endpoint ไหนดี
  • ระบุทั้ง service name และ endpoint name (มีศัพท์เรียกว่า service endpoint)


ที่นี้ลองดูที่ jbi.xml จริงๆบ้าง
<services binding-component="false"
xmlns:logger="http://pok/dblogger/">
<provides interface-name="logger:log-interface"
service-name="logger:log"/>
</services>

source ข้างบนก็คือ การ define service ของ provider
จะเห็นว่ามีการระบุ interface-name กับ service-name

ถ้าเราไปดู source code ของ Java Class ที่ implement service นี้
public class DbLoggerComponent implements 
ServiceInterfaceImplementation,
ServiceEndPointImplementation {

public QName getInterfaceName() {
return new QName("http://pok/dblogger/",
"log-interface");
}

public void onMessage(MessageExchange msg) {
...
}
}

จะเห็นว่ามี method ที่ชื่อ getInterfaceName

เวลาเราเขียน component ใน servicemix
เราสามารถเลือกวิธีเขียนได้ 2 วิธีคือ
  • implment interface ของ JBI-api ด้วยตนเอง
  • ใช้ client-api ของ ServiceMix
    อันนี้จะสะดวกสบายขึ้นหน่อย จำนวนบรรทัดของ source code
    ที่เขียน จะลดลงหน่อย


ในที่นี้เราจะพูดถึงการเขียน component แบบที่ใช้ client-api เท่านั้น
เริ่มโดย เวลาเราเขียน component เราก็จะเขียน class ที่ implement
interface ServiceInterfaceImplementation
ซึ่งใน interface นี้จะบังคับให้เรา implement method ที่ชื่อ
getInterfaceName
ส่วน service name และ endpoint name
คงจะใช้การ lookup จาก jbi.xml

กรณีที่เรามี endpoint มากกว่า 1 endpoint ใน 1 service
การ implement แค่ ServiceInterfaceImplementation
จะไม่เพียงพอ ต้องมีการ implement ServiceEndPointImplementation
interface นี้จะบังคับเราให้เรา implement method getServiceName
กับ getEndpointName

Related link from Roti

Wednesday, October 05, 2005

ServiceMix Component

คราวก่อนทดลอง deploy component ด้วยการ wire เข้าไปใน container ตรงๆ
คราวนี้ได้ทดลอง deploy แบบ Hot deploy ดูบ้าง (ประเภทที่เอา zip file ไปวาง
ไว้ใต้ directory ที่กำหนด)

มี tutorial ที่พูดถึงเรื่องนี้อยู่ที่นี่
Develop JBI Component with Spring Client Toolkit

ทดลองทำตามแล้วไม่ได้ผลลัพท์ตามที่เขาว่าไว้
หลังจากนั่งมั่วอยู่ครึ่งวัน ก็ได้ข้อสรุปดังนี้

  • set Environment Variable SERVICEMIX_HOME
  • เวลาเรียกใช้งานให้เรียกใช้งาน program ที่ root path (/)
  • แก้ไข shell script $SERVICEMIX_HOME/bin/servicemix
    เปลี่ยนชื่อ path ต่างๆที่อยู่ข้างในให้เป็นชื่อเต็ม
  • เรียกใช้งานโดยระบุ configuration file เข้าไปด้วย
    $SERVICEMIX_HOME/bin/servicemix $SERVICEMIX_HOME/conf/servicemix.xml


การพัฒนา JBI Component นั้น
servicemix หลีกเลี่ยงขั้นตอนยุ่งยากทั้งหลาย
โดยการห่อ component ของเราไว้ใต้ SpringComponent
(สงสัยเหมือนกันว่าวิธีนี้จะนำไป deploy ใน JBI Container ตัวอื่นได้หรือเปล่า
แต่ขี้เกียจลอง)

Component ที่เรา deploy สามารถประพฤติตนได้ 2 แบบ
คือเป็น provider หรือเป็น consumer
provider คือ component ที่มีบริการให้เรียกใช้
ส่วน consumer คือ component ที่เรียกใช้บริการ
component หนึ่งๆ สามารถเป็นได้ทั้ง consumer และ provider

ตามตัวอย่าง tutorial ข้างบน จะเป็นการทำ component
TimerComponent กับ LoggerComponent
TimerComponent จะส่ง message ออกมา
ส่วน loggerComponent ถ้าได้รับ message ก็จะแสดงผลออกมาที่ console



เวลาเรา config component ฝั่ง provider เราจะประกาศ xml ดังนี้
<services binding-component="false"
xmlns:logger="http://pok/logger">
<provides interface-name="logger:log"
service-name="logger:myLogger"/>
</services>

จะเห็นว่า LoggerComponent ประกาศตัวเองว่า เป็น service ที่ชื่อ QName("http://pok/logger", "myLogger")
และมี interface ที่ชื่อ QName("http://pok/logger", "log")
Note: สงสัยเหมือนกันว่า 1 service มีได้มากกว่า 1 interface หรือเปล่า ?

ส่วน ฝั่ง consumer จะ ประกาศ xml ดังนี้
<services binding-component="false"
xmlns:logger="http://pok/logger">
<consumes interface-name="logger:log"
service-name="logger:myLogger"/>
</services>

สังเกตุว่า TimerComponent จะบอกว่าตัวเองต้องการใช้ service อะไร
และ interface ชื่ออะไร

จะเห็นว่าทั้งคนส่งและคนรับ จะรู้จักกันผ่านทางชื่อ interface และ ชื่อ service เท่านั้น
การแยก dependency ออกจากกันแบบนี้ เป็น key หลัก key หนึ่งของ SOA
(service oriented architecture)
การระบุชื่อแบบนี้ JBI เรียก End Point

ลักษณะการส่งของ Tutorial นี้ เป็นแบบ Explicitly นั่นคือ
ผู้ส่งกำหนดเองว่าต้องการส่งให้ใคร
ใน Spec JBI นั้น วิธีการระบุ End Point สามารถทำได้อีก 2 วิธีคือ
  • Implicitly ผู้ส่งจะระบุประเภทของ service อย่างเดียว ตัว NMR เลือกหาให้เอง
    (เบื้องหลังคือ NMR จะส่งคำถามไปยัง component ทีอยู่ในข่ายว่าเป็น candidate
    ว่าจะ accept ไหม)
  • dynamically อันนี้อ่านแล้วไม่่ค่อยเข้าใจ
    เดาๆว่า provider จะสร้าง vocabulary ชุดหนึ่งที่อธิบายถึง service ของตัวเอง
    ซึ่ง vocabulary พวกนี้ consumer สามารถ query ไปดูได้
    และ consumer ก็เลือกเอาว่าจะส่งให้ provider


ถ้าเราดู source code ของ TimerComponent ในส่วนของการส่ง
InOnly inOnly = serviceContext.createInOnly(new QName(
"http://pok/logger", "log"));
NormalizedMessage message = inOnly.createMessage();
message.setContent(new StreamSource(new StringReader(
"<hello>world</hello>")));
serviceContext.done(inOnly);

จะเห็นว่ามีการใช้ Object ที่ชื่อ InOnly
ลักษณะการส่ง Message ใน JBI จะแยกเป็น 4 แบบ คือ
  • One-Way หลังจากส่งไปแล้ว ผู้ส่งจะไม่รู้เลยว่า คนรับสามารถ process message ได้หรือไม่
    อันนี้ใช้ Class InOnly ในการส่ง)
  • Reliable One-Way ผู้รับสามารถแจ้ง fault กลับไปยังผู้ส่งได้ ในกรณีที่ไม่สามารถ process message ได้
    อันนี้ใช้ Class RobustInOnly ในการส่ง
  • Request-Response อันนี้ผู้ส่งคาดหวังที่จะได้ผลลัพท์ จากผู้ให้บริการ
    ใช้ Class InOut ในการส่ง
  • Request Optional-Response อันนี้พิสดารหน่อย
    อธิบายยาก ต้องลองเปิดดู diagram ใน JBI Specification
    แล้วจะร้องอ๋อ (แบบงงๆ ว่าทำไมมันต้องทำแบบนี้ด้วยวะ)

Related link from Roti

Corpse Bride

หนังเรื่องใหม่ของ Tim Burton.
เป็นหนังแบบ stop-motion (พวกที่ถ่ายทีละ frame)
หนังเรื่องนี้ใช้กล้อง canon EOS-1D MarkII ถ่าย
แล้วก็ตัดต่อด้วย program Final Cut Pro บน G5
ลองอ่านบทความ ว่าด้วยเบื้องหลังดูครับ
Link

ลองดู Trailer ได้ที่นี่ครับ
เนียนมาก ดูเหมือนสร้างด้วย computer graphic เลย
Link

Related link from Roti

Tuesday, October 04, 2005

Maven-Eclipse-Plugin

ช่วงนี้ทดลองพัฒนา JBI (Java Business Integration) Component อยู่
ก็เลยมีเหตุให้ต้องได้ใช้ Maven-Eclipse-Plugin อีกครั้ง
ก็เลยขอ Review หรือทบทวนตัว plugin นี้อีกครั้ง

ปกติเวลาเราเริ่มต้น project ที่จะใช้ Maven เป็น build tool
เราจะเริ่มด้วยการสร้าง file ที่ชื่อ project.xml กับ project.properties
ตัว project.xml จะเป็น descriptor ที่อธิบาย
  • ชื่อของโปรเจค
  • version ของ project
  • dependencies ของโปรเจค
  • directory ที่ใช้เก็บ source, test


<project>
<pomVersion>2</pomVersion>
<name>Echo</name>
<id>Echo</id>
<currentVersion>1.0</currentVersion>
<package>org.servicemix.demo</package>
<shortDescription>A new JBI component</shortDescription>
<description>A new JBI component</description>
<dependencies>
<dependency>
<id>servicemix</id>
<version>1.0</version>
<type>jar</type>
<url>http://www.servicemix.org</url>
</dependency>
.....
<dependency>
<groupId>servicemix</groupId>
<artifactId>servicemix-client</artifactId>
<version>1.0</version>
<properties>
<jbi.bundle>true</jbi.bundle>
</properties>
</dependency>
</dependencies>
<build>
<sourceDirectory>src/main/java</sourceDirectory>
<unitTestSourceDirectory>src/test/java</unitTestSourceDirectory>
</build>
</project>


ส่วน project.properties จะทำหน้าที่เป็นตัว customize ข้อมูล
ของ plugin ต่างๆ ให้ประพฤติหรือปฎิบัติตนตามที่เราต้องการ
เช่น ต้องการให้ javac compile โดยมี debug information ด้วย

เมื่อเราเขียน project.xml เสร็จ ก็ให้สั่ง
maven eclipse


Maven-Eclipse-Plugin ก็จะทำการ generate
file .classpath กับ file .project ให้เรา
ซึ่ง 2 file นี้เป็น file หลักที่ eclipse ใช้เก็บข้อมูล project

ที่นี้ลองมาดูว่าเขาเขียน Maven-Eclipse-plugin นี้กันอย่างไร
เริ่มที่ว่า เวลาเราสั่ง maven eclipse นั้น
มันจะเริ่มทำงานที่ไหน อย่างไร

plugin ของ Maven ปกติจะอยู่ใน Directory MAVEN_HOME/plugins
โดยอยู่ในรูป jar file
ซึ่ง maven จะแอบแตก file เหล่านี้เก็บไว้ใน directory $USER_HOME/.maven/cache
โดยแต่ละ plugin จะกลายเป็น 1 sub directory
ในแต่ละ plugin จะมี file หลักๆอยู่ 4 ตัวคือ
  • project.xml
    เป็น file ที่อธิบายถึง plugin นั้น
    โครงสร้างหน้าตาเหมือนกับ project.xml ของ project เรา
  • project.properties
    ตัวนี้ไม่ค่อยได้ใช้อะไรอ
  • plugin.jelly
    file นี้แหล่ะทีเป็นหัวใจของ plugin
    เพราะเป็น file ที่เก็บว่า plugin นี้ทำอะไรได้บ้าง
    รวมทั้งแต่ละ task ที่ทำ มี step หรือขั้นตอนการทำอย่างไร
  • plugin.properties
    file นี้เก็บข้อมูล properties ของ plugin
    ที่สามารถ override จาก project.properties ของเราได้


file plugin.jelly เขียนด้วย Jelly
ซึ่งเป็น template engine แบบหนึ่ง (เหมือนๆพวก jsp, velocity,...)
ถ้าเราลองเปิดดู file นี้จะเห็นว่ามีการ declare goal ที่ืชื่อ eclipse ไว้
<!--==================================================================-->
<!-- Generate Eclipse .project and .classpath files -->
<!--==================================================================-->
<goal name="eclipse"
description="Generate Eclipse project files"
prereqs="eclipse:generate-project, eclipse:generate-classpath">
<ant:echo>Now refresh your project in Eclipse (right click on the project and
select "Refresh")</ant:echo>
</goal>

ลักษณะของ goal จะเขียนเหมือนกับ ant target
ก็คือ ระบุว่ามี depend ถึง goal ไหนหรือไม่
task ภายในที่ต้องทำมีอะไรบ้าง
โดยส่วนใหญ่แล้ว maven plugin จะส่งต่อไปให้ ant task
เป็นตัวทำงานจริง
(จริงๆมันเป็นลูกผสม ระหว่าง ant กับ jelly
ปัญหาของ ant คือ มันไม่ได้เป็น language ทำให้เขียน build file
ไม่ได้เต็มที่. maven ก็เลยใส่ syntax ของ jelly เข้าไปผสม)

ถ้าลองตามไปดู goal eclipse:generate-project
ที่ถูกอ้างถึง จะเห็น source code ดังนี้
<goal name="eclipse:generate-project"
description="Generate Eclipse .project file">

<ant:echo>Creating ${basedir}/.project ...</ant:echo>
<j:file name="${basedir}/.project" prettyPrint="true" xmlns="dummy">
<j:import file="${plugin.resources}/templates/project.jelly" inherit="true"/
>
</j:file>

</goal>


อธิบายได้ง่ายๆคือ มันจะสร้าง file ที่ชื่อ .project
โดยเนื้อหาของ file ได้มาจากการ run template ที่ชื่อ template/project.jelly

ถ้าลองตามไปดู file template/project.jelly
ก็จะเห็น file หน้าตาประมาณนี้
<projectDescription>
<name>${pom.artifactId}</name>
<comment>${pom.description}</comment>
<projects>
<j:forEach var="lib" items="${pom.artifacts}">
<j:set var="eclipseDependency"
value="${lib.dependency.getProperty('eclipse.dependency')}"/>
<j:if test="${eclipseDependency == 'true'}">
<project>${lib.dependency.artifactId}</project>
</j:if>
</j:forEach>
</projects>

....

</projectDescription>


ผลลัพท์ของการ run template ข้างบน
จะได้ file หน้าตาอย่างนี้ออกมา
<projectDescription>
<name>Echo</name>
<comment>A new JBI component</comment>
<projects>
</projects>
...
</projectDescription>


จะเห็นได้ว่าวิธีการใช้ template จะไม่ค่อยยากนัก
เสียแต่ว่าดูแล้วลายตาไปหน่อย

Related link from Roti

Sunday, October 02, 2005

เริ่มทดลอง Tapestry 4

ช่วงนี้ Tapestry ออก version beta-8 แล้ว
ปลายปีนี้คงได้ฤกษ์ release แล้ว
ก็เลยได้ฤกษ์ load มาทดลองเหมือนกัน

เล่าย้อนหลังสำหรับคนที่ไม่รู้จัก Tapestry หน่อยหนึ่ง
ตัว Tapestry เป็น Framework ที่ใช้พัฒนา Web Application
มี model แบบ Component model
ทำให้ reuse component ได้ดี

ต้นเหตุที่ผมเปลี่ยนมาใช้เจ้า Tapestry ก็คือ
เจ้า struts มันไม่ค่อย productive ในด้าน ui เท่าไร
การ reuse source code ทำได้ยาก
จริงๆแล้วสามารถ reuse ในลักษณะ taglib ก็ได้แหล่ะ
แต่ปัญหาคือ มันมองไม่เห็นว่าหน้าตาเป็นอย่างไร
จนกว่าจะ run จริง
ส่วน tapestry ออกแบบมาให้เรา map component
เข้ากับ html page
ดังนั้นเราสามารถออกแบบ html page โดยใช้ wyswyg tool ได้
จากนั้นก็ค่อยๆ map component เข้ากับ html element ต่างๆ

ปัญหาของ tapestry อยู่ตรง
learning curve ค่อนข้างสูง
ตัว document ค่อนข้างน้อย
ปัจจุบันมีหนังสือแค่ 2 เล่มเองที่อธิบาย tapestry
(จริงๆมี 3 เล่ม แต่เล่มที่ 3 เป็นภาษาเยอรมัน ก็เลยไม่นับ)
แต่ถ้าผ่าน learning curve มาได้เมื่อไร
ก็เหมือนได้ติดปีกแล้ว, productivity ที่ได้ดีมาก

ปัจจุบัน ผมมีเกณฑ์ในการ implement project ดังนี้
กรณีที่เป็น Application ใหญ่ๆ ใช้ Tapestry + Spring + Hibernate
ถ้า App มีลักษณะที่ต้อง integrate legacy หรือระบบที่คนอื่นทำไว้ด้วย
จะเพิ่ม ServiceMix เข้าไปด้วย
แต่ถ้าเป็น App เล็กๆ ก็จะเปลี่ยนไปใช้ RubyOnRails แทน
นอกจากนี้ยังมอง solution แบบลูกผสมไว้ด้วย
นั่นคือ ในกรณีที่เป็น CRUD ธรรมดา
(พวก maintain base table)
ก็จะใช้ RubyOnRails เข้ามา
ส่วนที่ complex ค่อยใช้ Tapestry เข้ามาจัดการ
(ใช้ CAS เป็น single sign-on ระหว่าง tapestry กับ Rails)

กลับมาเข้าเรื่อง Tapestry ต่อ
ใครที่ยังไม่เคยใช้เลย ขอแนะนำว่าควรเริ่มต้นที่ version 3 ก่อน
เพราะ stable แล้ว
ส่วน version4 บอกได้คำเดียวว่า แค่ install ก็ยุ่งแล้ว

Tapestry version 4 มีการ restructure
โครงสร้างภายใน โดยเปลี่ยนไปใช้ hivemind เป็นแกน
ทำให้เราสามารถ ถอดเปลี่ยน module ภายในของ tapestry ได้ง่าย
(ในอีกทาง การเพิ่ม hivemind ก็ทำให้
ต้องเพิ่ม learning curve ที่ต้องรู้จัก hivemind เพิ่มอีกตัว)
Feature ที่สำคัญตัวหนึ่งของ hivemind
ก็คือ Configuration Points
ถ้าใครเคย implement Plugin บน Eclipse Platform
พอเห็น Concept Configuration Points
ก็คงจะได้กลิ่น Concept Extension point ของ Eclipse ขึ้นมาตะหงิดๆ

Related link from Roti

Friday, September 30, 2005

mnoGoSearch Alias

วันก่อนพูดถึง mnoGoSearch ไป
จุดประสงค์ที่ทดลองใช้ mnoGoSearch ก็เพื่อที่จะนำไปทำ
search engine สำหรับ
Code Snippet Application ที่เขียนขึ้นมา

ซึ่ง Feature ที่ดี mnoGoSearch อย่างหนึ่ง
ก็คือสามารถ access เข้า database
เพื่อทำ index จาก Database table ได้โดยตรง
Feature นี้ mnoGoSearch ตั้งชื่อ protocol ว่า htdb

ปัญหาที่เจอก็คือ เมื่อเราใช้ front-end tool
ของ mnogoSearch ค้นหาข้อมูล
ผลลัพท์ที่ได้ จะแสดงในรูป
htdb:/xx
โดย xx คือ primary key ของ table ที่ทำ index



ซึ่งในกรณีที่เราใช้ program ด้วยกันเป็นคน search
ก็คงไม่มีปัญหาอะไร เพราะสามารถตัดต่อ string
เพื่อนำไปสร้าง url ที่เหมาะสมได้

สำหรับกรณีที่เราต้องการให้ front-end tool ของ mnogosearch
สามารถแสดงผลเป็น url ที่สามารถ click ได้จริงๆ
ก็สามารถทำได้โดย
เข้าไปแก้ไข template file ที่ชื่อ search.htm
(เก็บอยู่ใน /usr/local/mnogosearch/etc)
เพิ่มคำสั่ง
Alias htdb:/ http://localhost/snn/snippets/show/

เท่านี้ mnoGoSearch ก็จะทำการ rewrite ผลลัพท์ให้เราใหม่

Related link from Roti

Config fastcgi ให้กับ rails

ช่วงนี้จะใช้ Snippets Application ที่เขียนขึ้นเองแล้ว
ก็เลยจะ deploy ให้เหมือนกันสถานการณ์การทำงานจริง



ปกติใน rails เราสามารถ run application ได้ 3 แบบหลักๆ คือ
  • ผ่านทาง WebBrick
    เจ้า WebBrick เป็น web server ที่เขียนด้วย Ruby
    เป็นสภาพแวดล้อมที่เหมาะสมอย่างยิ่งกับการ debug
  • ผ่านทาง Apache CGI
    อันนี้จะช้าหน่อย แต่เหมาะแก่การ test deploy ครั้งแรก
    เพราะจะได้ check พวก configuration, permission
    ให้ถูกต้องก่อน
  • ผ่านทาง Apache + mod_fastcgi


ที่เราจะทดลองก็คือ ใช้ FastCGI

เริ่มแรก ก็ต้อง install mod_fastcgi ก่อน
ขั้นนี้ไม่ยุ่งยากอะไร ก็แค่ download source code มา แล้วก็ build

จากนั้นก็ install fcgi developer kit
(ruby fcgi ต้องการใช้)

สุดท้ายก็ install ruby-fcgi

ส่วนการ deploy rails application
ถ้าอย่างง่าย ก็เพียงแต่ทำ link จาก htdocs
ไปยัง directory project/public

จากนั้นก็ config Directory ที่เราทำ link ไว้
โดย enable ให้สามารถ run cgi ได้
<Directory "/Library/WebServer/Documents/snn">
Options +ExecCGI FollowSymLinks
AllowOverride all
Allow from all
Order allow,deny
</Directory>


จากนั้นก็เข้าไปแก้ไข file .htaccess ที่อยู่ใน directory project/public
(file นี้ rails generate ให้เราอัตโนมัติ)
แก้ไข ส่วน rewrite rule จากของเดิมที่ใช้ dispatch.cgi
ไปเป็น dispatch.fcgi แทน

ปรับส่วน rewrite rule ให้เหมาะสมกับ structure ใหม่ของเราด้วย
(ขั้นนี้แหล่ะที่ค่อนข้างมั่ว เพราะเจ้า apache rewrite มันช่าง
เข้าใจยากจริงๆ)

เข้าไปแก้ไข projectdir/public/dispatch.fcgi
ระบุ directory ของ log file ที่ต้องการ
RailsFCGIHandler.process! '/tmp/snippet_fcgi_crash.log'


เมื่อเราเรียกใช้ครั้งแรกสุด apache จะ start process dispatch.fcgi ให้อัตโนมัติ
แต่บางทีเราต้องการให้ start process ขึ้นมารอเลย
(เพื่อไม่ให้เกิดอาการ lag เมื่อ user เริ่มเข้ามาใช้)
ในกรณีนี้ ก็ให้เพิ่ม directive นี้เข้าไปใน httpd.conf
<IfModule mod_fastcgi.c>
FastCgiIpcDir /tmp/fcgi_ipc
FastCgiServer /Library/WebServer/Documents/snn/dispatch.fcgi \
-initial-env RAILS_ENV=production -processes 15 -idle-timeout 60
</IfModule>

Related link from Roti

ทดลอง mnoGoSearch

วันนี้อ่านเจอใน Planet Eclipse ว่า
site eclipse.org จะปรับปรุง search engine ใหม่
โดยจะปรับปรุงกฎการให้น้ำหนัก
ที่น่าสนใจก็คือ เขาบอกด้วยว่า เขาใช้ mnoGoSearch เป็น search engine
ก็เลยตามไปดูสักหน่อย

ตัว mnoGoSearch เป็น search engine ที่ใช้ database table
ในการทำ index (lucene ใช้ file system)
ตัวมันประกอบด้วย 2 ส่วนคือ
  • Indexer ที่ใช้ scan web document เพื่อทำ index
  • CGI front-end ที่ใช้ในการ search


ที่น่าสนใจ ก็คือ mnoGoSearch support ภาษาไทย ด้วย
โดยมี dictionary thai มาให้ด้วย โดยมีจำนวนศัพท์มาให้ 32895 คำ

ในขั้นตอนการทำ index เราสามารถ config
ให้มัน scan web site ได้หลายลักษณะ เช่น

  • page scan เฉพาะ page ที่ระบุ
  • path scan เฉพาะ document ที่อยู่ใต้ path ที่ระบุ
  • site scan documents ที่อยู่ใน host นั้น
  • world scan any document



ตัวอย่างเช่น

# To index whole server "localhost":
Server http://localhost/
#
# You can also specify some path to index subdirectory only:
Server http://localhost/subdir/
#
# To specify the only one page:
Server page http://localhost/path/main.html


ผมไม่ค่อยสนใจ feature ที่ใช้ scan static web site เท่าไร
ที่สนใจก็คือ feature ที่สามารถใช้ scan database table ได้โดยตรงมากกว่า
ตัว mnoGoSearch มี protocol ที่เรียกว่า HTDB สำหรับทำแบบนี้

ลักษณะการทำงาน เป็นดังนี้
  • ระบุ connection ที่จะใช้ต่อ database (dbname, user, password)
  • select primary key ของ table ที่เราต้องการ scan ขึ้นมาทั้งหมด
  • กำหนด select ที่ทำให้ได้เนื้อหาของข้อมูลที่เราต้องการทำ index


ลองดูตัวอย่าง

HTDBAddr mysql://user:passwd@localhost/dbname/?dbmode=single

เริ่มด้วยการระบุ database


HTDBList "select id from snippets"

ในที่นี้เราต้องการทำ index บน table snippets


HTDBDoc "select concat('HTTP/1.0 200 OK\\r\\n', 'Content-type: text/plain;charset=utf-8\\r\\n', 'Content-Language: th\\r\\n','\\r\\n', description) from snippets where id = $1 "

จากนั้นก็ระบุ sql ที่ทำให้ได้เนื้อหาที่ต้องการทำ index
โดย mnoGoSearch จะ pass primary key เข้ามาให้
ในชื่อตัวแปร $1

จะเห็นได้ว่าเราต้องแปลงเนื้อหาของเรา
ให้อยู่ในรูปแบบของ http response เสียก่อน
key ที่สำคัญตัวที่หนึ่งก็คือ การระบุ language
ไม่งั้นมันจะพยายามเดา language ให้เอง
ส่วนตัวที่ 2 ก็คือการระบุ encoding ที่ใช้


ส่วนรูปข้างล่างนี้เป็น screen dump หน้าจอ search ของ mnoGoSearch



จะเห็นได้ว่า ผลลัพท์ที่เราได้จะมี url ในรูปของ htdb:/nn
ตัวเลข nn ก็คือ primary key ของ row ที่ search เจอนั่นเอง

ในส่วนของการ scan static web site นั้น
เท่าที่ลองใช้ดู ทดลอง scan 2 ที่นี้ดู

Server page http://www.pantip.com/cafe/index.html
Server page http://bact.blogspot.com/2005/09/web-20-office.html




ของ pantip ทำ index ออกมาได้ ทดลองค้นหาดู
พบว่าคำบางคำ ก็ค้นเจอ บางคำก็ไม่เจอ
เช่น ถ้าใช้คำว่า"ห้องสมุด" จะเจอ ส่วนคำว่า "ไกล", "ไกลบ้าน"
นั้นหาไม่เจอ แต่ถ้าเปลี่ยนเป็น "บ้าน" เฉยๆ ก็จะเจอ



ส่วนของ bact จะเห็นว่าอ่านภาษาไทยมาถูกต้อง
แต่ search ด้วยภาษาไทยไม่เจอเลย
ไปดูใน datadict ของ mnoGoSearch มันระบุว่า
site นี้ Content-Language เป็น ภาษาอังกฤษ (en)
ก็เป็นไปได้ว่ามันเดาภาษาผิด ก็เลยไม่ได้เอา dict ไทยไปจับ

Related link from Roti

Wednesday, September 28, 2005

The Wall of DEATH

เอามาจาก blog The Fishbowl
เขาพูดถึงวิธีการที่จะช่วยให้ developer focus งานที่ต้องทำ

ชอบตรง
The rules of the wall are:
1. Developers take an issue off the wall, and work on it until it is done
2. Developers must not work on anything that is not on the wall
3. Corollary to rule 2: unanticipated additional work must be filed in JIRA, then written on a card and placed on the wall
4. Developers must never take more than one issue off the wall at a time
5. Once an issue is done, the developer records the actual time taken in the other corner of the issue, dumps it in the ‘done’ pile, and updates the whiteboard.
6. Repeat until no issues remain on the wall


มันจับต้องได้ดี
ไว้โปรเจคหน้าจะเตรียมผนังไว้บ้าง

Related link from Roti

Monday, September 26, 2005

ทดลอง send message จาก ruby ไป java ผ่าน Stomp Protocol

วันนี้ทดลองใช้ ActiveMQ เป็นตัวกลางในการ communicate
ระหว่าง Java กับ Ruby

ในส่วนของ Java ผมใช้ jencks ซึ่งเป็น lightweight container
ที่สามารถ deploy ใน Spring ได้ ตัว jencks ทำให้เรา run
Message Bean นอก j2ee server ได้

ตัว Message Bean ทดลองเขียนดังนี้
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;

public class EchoBean implements MessageListener {

public void onMessage(Message msg) {
try {
System.out.println("i got message");
TextMessage txt = (TextMessage) msg;
System.out.println(txt.getText());
} catch (JMSException je) {
je.printStackTrace();
}
}
}


ส่วนของ Spring Application Config file นี้ยุ่งขึ้นมาหน่อย
เขียนดังนี้

เริ่มด้วยการ add jencks ก่อน
<bean id="jencks" class="org.jencks.JCAContainer">
<property name="bootstrapContext">
<bean class="org.jencks.factory.BootstrapContextFactoryBean">
<property name="threadPoolSize" value="25"/>
</bean>
</property>

<property name="resourceAdapter">
<bean id="activeMQResourceAdapter" class="org.activemq.ra.ActiveMQResour
ceAdapter">
<property name="serverUrl" value="tcp://localhost:61616"/>
</bean>
</property>
</bean>


จากนั้นก็ใส่ EchoBean ที่เราเขียนเข้าไป
<bean id="monitor" factory-method="addConnector" factory-bean="jencks" singleton
="true">
<property name="activationSpec">
<bean class="org.activemq.ra.ActiveMQActivationSpec">
<property name="destination" value="Monitor.Queue"/>
<property name="destinationType" value="javax.jms.Queue"/>
</bean>
</property>

<property name="ref" value="echoBean"/>
</bean>

<bean id="echoBean" class="mx.test.mq.EchoBean" singleton="true"/>


เท่านี้ก็เรียบร้อย เวลา run ก็เพียงแต่ load Application Context
ClassPathXmlApplicationContext appContext = 
new ClassPathXmlApplicationContext(
new String[] {"beans.xml"});


ก่อนที่จะเขียนส่วน Ruby Client
เราต้อง config ActiveMQ ให้เปิด port ที่รับ Stomp protocol เสียก่อน
ซึ่งทำได้โดย เข้าไปแก้ activemq.xml
เพิ่มบรรทัดนี้เข้าไป
<connector>
<serverTransport uri="stomp://localhost:61626"/>
</connector>


ทีนี้มาว่าด้วยส่วน ruby บ้าง
ตัว api ที่ใช้ interface กับ Stomp สามารถ download ได้ที่นี่ Link
วิธีการใช้งานทำได้ดังนี้

เริ่มต้นด้วยการ open connection กับ ActiveMQ ก่อน
conn = Stomp::Connection.open('user','pass','localh',port)


ในกรณีที่ต้องการส่ง Message ก็เพียงแต่
conn.send('/queue/test', 'hello');

Note: เราต้องระบุชนิดของ destination เสมอ
โดยการระบุ prefix ที่จะต้องขึ้นต้นด้วย /queue/ หรือ /topic/


กรณีที่ต้องการส่งหลายๆ message และให้มี transaction control ด้วย
ก็เพียงแต่ใส่ begin, commit ครอบลงไป
conn.begin
conn.send('/queue/test', 'hello');
conn.send('/topic/test', 'hello');
conn.commit


ส่วนกรณีที่ต้องการรับ Message
ก็ให้ subscribe เข้ากับ Topic หรือ Queue ที่ต้องการ
conn.subscribe('/queue/test') { |msg| 
puts msg.body
}

Related link from Roti