Sunday, October 09, 2005

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

Friday, September 23, 2005

cherry tree

ใน Processing Blogs
blprnt เขา post source code ที่่ paint รูปต้นไม้

  • cherrytree (***** เอาไปห้าดาว)
  • birchtree (ตอนที่กิ่งน้อยๆ สวยกว่าตอนกิ่งเยอะๆ)

Related link from Roti

Thursday, September 22, 2005

My First JBI Component

วันนี้ทดลอง implement JBI Component
(Java Technology and Business Integration Services)
เพื่อที่จะทดลอง deploy เข้าไปใน ServiceMix

เหมือนเคย ถ้าทำตาม spec ของ Sun แล้ว
คงต้องทำอะไรต่างๆนานายุบยับไปหมด
(ตามประสา sun ที่ spec มักจะแบ่งเป็นขั้นเป็นตอน
ขั้นนี้ให้ role นี้ทำนะ ขั้นนู้นให้ role นู้นเขาเป็นคนทำ)
พวก developer มือเดี่ยวอย่างเรา (ทำมันทุก role)
ก็เลยขัดอกขัดใจ

โชคดีที่ ServiceMix เปิดช่องให้เรา
ใส่ component ของเรา ผ่านเข้าไปโดยใช้
Spring config file ทำให้ชีวิตง่ายขึ้น

เนื่องจากเป็น component แรกในชีวิต ก็เลยจัดการลอก
จากตัวอย่างที่มีนั่นแหล่ะดัดแปลงนิดหน่อย

เป้าหมายที่ทดสอบ


Quartz timer component ===msg===> Log Component



Quartz Timer Component ก็ใช้ build in ของ ServiceMix
ไม่ต้องลงแรง
ส่วน Log Component ก็ลอกเขามาเลย


public class LogComponent extends ComponentSupport {

private Log log = LogFactory.getLog(TraceComponent.class);


public Log getLog() {
return log;
}

public void setLog(Log log) {
this.log = log;
}

public void onMessageExchange(MessageExchange exchange) throws MessagingExce
ption {
// lets dump the incoming message
NormalizedMessage message = exchange.getMessage("in");
if (message == null) {
log.warn("Received null message from exchange: " + exchange);
}
else {
log.info("Exchange: " + exchange + " received IN message: " + messag
e);
}
}
}


ทดลอง run ปรากฎว่า เงียบหาย
ไม่เห็น message อะไรที่ console
ก็เลยสงสัยว่าเกิดอะไรขึ้น
นั่งพยายามค้นหาอยู่นาน
ในที่สุดก็พบว่า
  • ห้ามลืม implements MessageExchangeListener เด็ดขาด ถ้าลืมมันจะไม่สามารถรับ message ได้
    public class LogComponent extends ComponentSupport 
    implements MessageExchangeListener {...}

    อันนี้ผิดเอง ลอกเขามาแล้วยังลอกผิดอีก
  • ที่เราสั่ง log แสดงผลนั้น มันจะไม่แสดงผลอะไรออกมาให้เราเห็น
    ส่วนสาเหตุนั้นยังนึกไม่ออก ตอนแรกนึกว่าเป็นเพราะชื่อ package เราไม่ตรงกับเขา
    ตัว Logging Filter ก็เลยไม่แสดงผลของเรา
  • คำสั่ง System.out.println จะไม่แสดงผลออกที่ console
    ไม่รู้มัน redirect ไปไว้ไหน
  • สุดท้ายก็เลยต้องใช้วิธีเขียน log ลง File System เอง
    ก็เลยพบว่ามันทำงานได้เป็นปกติ


หลังจากอ่านเอกสารของ ServiceMix ไปสักพัก
ก็พบว่า เราสามารถ implement Component
ในลักษณะ POJO ได้เลย
public class PojoReceiver implements MessageExchangeListener {

public void onMessageExchange(MessageExchange exchange) throws MessagingExce
ption {
NormalizedMessage message = exchange.getMessage("in");
....

}

}


เป้าหมายที่จะลองถัดไปก็คือ
check ดูว่า ActiveMQ ที่ bundle มานั้น support Stomp Protocol แล้วหรือยัง
ถ้ายังก็จะได้ upgrade เสีย
หลังจากนั้นก็ทดลอง integrate ruby application
เข้ากับ ServiceMix ผ่านทาง Stomp protocol

Related link from Roti

convert wma to mp3

มี file wma แต่อยาก convert เป็น mp3
ก็เลย search ใน internet ดู
เจอ script นี้

#!/bin/bash
#
# Dump wma to mp3

for i in *.wma
do
if [ -f $i ]; then
rm -f “$i.wav”
mkfifo “$i.wav”
mplayer -vo null -vc dummy -af resample=44100 -ao pcm -waveheader “$i” -aofile “$i.wav” &
dest=`echo “$i”|sed -e ’s/wma$/mp3/’`
lame -h -b 192 “$i.wav” “$dest”
rm -f “$i.wav”
fi
done

ลอง run แล้ว ตอนแรกนึกว่า Error
เพราะเห็น message บอกว่า malloc ... ยาวเหยียด
แต่จริงๆแล้วไม่ใช่
คงเป็นเพราะเป็นการแปลงแบบไม่รู้ความยาวที่แน่นอน
(ใช้ fifo)

Related link from Roti

Wednesday, September 21, 2005

JBoss Seam

เมื่อวานอ่านเจอเรื่อง JBoss Seam
ซึ่งเป็น Application Framework สำหรับ Java EE5
จุดเด่นของ Seam ก็คือการ integrate context ระหว่าง
Presentation Context (JSF) กับ Business Logic Context (EJB)

Gavin King (founder ของ Hibernate) บอกว่า
One of the (several) goals of Seam is to bring Ruby On Rails style productivity to the Java EE platform


วันนี้ก็เลยนั่งทำ Diagram ที่ present ให้เห็นภาพว่า
การ integrate ระหว่าง jsf context กับ ejb context
ว่าเชื่อมต่อกันอย่างไร



  1. เริ่มจาก user submit ข้อมูลจาก jsp
    seam จะพยายาม binding data เข้าไปที่ Entity Bean
    เมื่อพบว่ามีการระบุ user.name
    seam ก็จะมองหา Entity Bean ที่มี annotaion ระบุ @Name("user")
    เมื่อรู้ว่าเป็น entity ตัวไหนแล้ว
    seam ก็จะ initialize Entity Bean นั้นขึ้นมา
    พร้อมกับทำการ set ค่าให้
  2. ใน submit component ที่ jsp มีการระบุ action ไว้ว่า
    ต้องการให้ trig action ที่ชื่อ register.register
    seam ก็จะทำการหา Session Bean ที่มีการ annotation ระบุเป็น @Name("register")
    แล้ว call method ที่ชื่อ register ให้
  3. ในจังหวะก่อนที่จะเริ่ม call นั้น
    seam จะตรวจว่า Session Bean นั้นต้องการให้ inject
    อะไรบ้าง ซึ่งจะพบว่า RegisterAction ต้องการให้ inject
    user (ที่เกิดจากข้อ 1) เข้ามาด้วย
    นอกจากนี้ยังสามารถ inject FacesContext (Context ของ Presentaion Layer)
    เข้ามาด้วย ซึ่งทำให้เราสามารถส่ง message กลับไปยัง Presentation Layer ได้ง่าย
    เช่นการ push Error Message กลับไป
  4. หลังจากทำงานสำเร็จ method register
    ก็จะ return name ของ Page ที่ต้องการแสดงผลกลับมา
    ซึ่งจะถูกนำไป search ใน face-config.xml file
    เพื่อหาว่า jsp ตัวไหนเป็นผู้รับผิดชอบในการ render


ดูแล้วเยี่ยมมากเลย
เมื่อบวกกับ ประเด็นที JBoss มี Embed EJB3 Container ออกมา
(สามารถนำไป embed ใน Tomcat ได้)
ทำให้ Seam ดูจะเป็นคู่แข่งที่น่ากลัวของ Tapestry 4



ส่วนใครที่ยังไม่เคยเห็น Spec ของ EJB3 นั้น
แนวทางของ EJB3 จะออกมาดังนี้

ใช้ Annotation เข้ามาช่วย
ทำให้ลด xml config file ไปได้จำนวนหนึ่ง
พร้อมทั้งเปลี่ยน model ในการเขียน Session Bean
ไปเป็น POJO (เหมือนที่ spring ทำอยู่)
ทำให้ลด Overhead ในการเขียน EJB แบบเดิม
ซึ่งต้องมี interface วุ่นวายไปหมด

ในส่วนของ Entity Bean ใน Spec EJB3 นั้น
สำหรับคนที่ใช้ Hibernate เป็นอยู่แล้ว
ก็แทบจะไม่ต้องเรียนรู้อะไรเพิ่มเติมเลย

อ่านเพ่ิมเติม
SEAM - Contextual Components

Related link from Roti

Monday, September 19, 2005

เขียน Code Snippet Web Application

ตอนนี้ศึกษาหลาย topic, language พร้อมๆกัน
ทำให้หน่วยความจำในสมองไม่พอใช้ เกิดอาการหลงลืม
คำสั่งต่างๆ ก็เลยคิดจะทำ code snippet ดีๆ ไว้ใช้เอง
วันนี้ก็เลยนั่งเขียนโปรแกรมนี้ทั้งวัน

feature ที่ต้องการก็คือ
  • syntax highlight code ได้
  • ระบุ Tag สำหรับแต่ละ snippet ได้
  • search by Tags ได้


opensource project ที่ load มาใช้ประกอบด้วย
  • Rails -> framework ที่ใช้เขียน
  • Taggable -> เป็น extension ของ ActiveRecord ใช้จัดการเรื่อง Tag
  • Syntax -> ใช้ทำ syntax highlighting
    (build-in support แค่ ruby, yaml, xml แต่สามารถเขียน plugin เองได้)


เวลาที่ใช้เขียนรวมๆ แล้วก็ประมาณ 6 ชั่วโมง
(ยังไม่ได้ทำเรื่องสวยงาม อันนี้ใช้เวลาเยอะกว่าเขียน code แน่ๆ)

ซึ่งไม่เลวเลยสำหรับ framework ที่ยังไม่เคยเขียน
จริงๆจังมาก่อน (เขียนไปนั่งเปิด manual อ่านไป)
ถือได้ว่า rails มี productivity ที่ใช้ได้เลย

ตัวที่ใช้เวลามากๆหน่อย ก็จะเป็นในส่วน
  • syntax highlight ที่ต้องเขียน plugin ให้ render java code ได้
  • taggable -> อันนี้พบ bug ในส่วน finder sql
    คนเขียนเข้าใช้ sql ที่ไม่เป็นมาตรฐาน ทำให้ run บน postgres ไม่ได้ ก็เลยต้องเขียน patch
    ที่ run บน postgres ได้


การเขียนด้วย opensource library แบบนี้ได้ความรู้ดี
เพราะถ้าเป็น opensource ใหม่ๆหรือที่ไม่ได้รับความนิยม
ก็จะไม่ค่อยมีเอกสารมากนัก ต้องเข้าไปใล่ source code ของเขาดูเอง
และเนื่องจากเป็น project ที่ขนาดไม่ใหญ่
ทำให้เราไล่ source code ได้ไม่ยากนัก

เป้าหมายถัดไป ก็คือไป load เอา source code ของ
http://www.bigbold.com/snippets/
มานั่งไล่ source code ดู
ตัวนี้เขียนด้วย rails เหมือนกัน

Related link from Roti

Implement Domain Validatation ด้วย "AspectJ Introduction"

เวลา Design Application เรามักต้องตัดสินใจว่า
จะ implement Validation Logic ไว้ที่ Layer ไหนดี

RubyOnRails implement Validation Logic ไว้ที่ Model
(domain layer)
struts (รวมทั้ง MVC อื่นๆของ java) provide Validation Logic
ไว้ที่ Presentation Layer

ข้อดีของการ provide Validation Logic ไว้ที่ domain
ก็คือ กรณีที่มี Presentation Layer หลายๆแบบ
validation logic ก็จะ share อยู่ที่เดียว ไม่กระจัดกระจาย

วันนี้เราจะลองเอา AspectJ ซึ่งเป็น AOP
มาลองทำ Validation แบบ Mixin บ้าง
สมมติเรามี Class Person
public class Person {

private String name;
private int age;

public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}

}

Note: Person Class ที่เราเขียน จะเป็น POJO เพียวๆเลย
ไม่มี business logic อะไรอยู่ในนั้นเลย


ออกแบบ Validataion Framework ให้มี Class ดังนี้



เป้าหมายที่เราต้องการ ก็คือ เราจะเขียน logic ในส่วนของการ
validate สำหรับ Person แยกไว้เป็นอีก file หนึ่ง (เรียก Aspect)
แล้วใช้ AspectJ ทำการ mix Person class กับ Aspect ที่เขียนไว้เข้าด้วยกัน

สร้าง Aspect ที่ชื่อ PersonValidate.aj
public aspect PersonValidate {
declare parents: Person implements Validatable;

public Errors Person.validate() {
Errors errs = new Errors();
if (this.getName() == null ||
this.getName().length() == 0) {
errs.addError("name", "name must not be empty");
}
if (this.getAge() <= 0) {
errs.addError("age", "age must not be negative or zero");
}
return errs;
}

}

ใน PersonValidate.aj จะมีการ declare parents: Person implements Validatable;
การสั่งแบบนี้ ก็คือเราจะกำหนดให้ Person Class implement Interface ที่ชื่อ Validatable
จากนั้นก็กำหนด method validate ให้กับ Person class
(method validate เป็น method ที่ declare ไว้ใน Validatable interface)
สังเกตุดูใน code จะเห็นว่าเราสามารถใช้ this ในการอ้างถึง person instance
ที่ถูก modify ได้

ทดลอง Run
    public static void main(String[] args) {
Person p = new Person();
p.setAge(-1);

Validatable vt = (Validatable) p;
Errors err = vt.validate();

for (int i = 0; i < err.size(); i++) {
System.out.println(err.getError(i));
}
}


ได้ผลลัพท์ดังนี้

name:name must not be empty
age:age must not be negative or zero


จะเห็นได้ว่า แนวคิดนี้ ดูน่าสนใจทีเดียว (ในแง่ Architecture)
แต่ความคิดนี้ยังไม่ได้ทดลองทำจริงนะ ก็เลยยังไม่รู้ว่าจะเกิดโทษ
หรือเกิดประโยชน์อย่างไรบ้าง

Related link from Roti

Sunday, September 18, 2005

Net อืดสุดๆ

ช่วง 3-4 วันมานี้ net ช้ามาก
ได้ความเร็วแค่ 2 k เอง

วันนี้ความเร็วเริ่มเพิ่มขึ้นแล้ว
ก็เลยฉลอง เข้าไปวาดรูปเล่นซะ 1 รูป
http://artpad.art.com/?imz0kkg8vsc

Related link from Roti

Saturday, September 17, 2005

คนตาบอดที่ป้ายรถเมล์

วันก่อน ระหว่างที่รอรถเมล์กลับบ้าน
ผมเหลือบไปเห็นคนตาบอด 2 คน กำลังรอรถเมล์อยู่
เป็นผู้ชายคน กับผู้หญิงคน
ผู้หญิงลงไปยืนในถนน 1 ก้าว
แวบแรกที่เห็น ก็เกิด บทสนทนาในใจ
"เอ๊ะใช่คนตาบอดหรือเปล่า"
"แต่เขามีไม้เท้าทั้งคู่นี่ ต้องเป็นคนตาบอดแน่เลย"
แวบที่สอง ก็ตามมา
"เราควรจะเข้าไปช่วยเขาดีหรือเปล่า?"
"เขามีใครคอยช่วยอยู่หรือยัง?"
หลังจากปล่อยให้เกิดบทสนทนาในใจอยู่พักหนึ่ง
ผมก็เลยเดินเข้าไปช่วยพาขึ้นรถ
(สำหรับคนที่ไม่เคยพาคนตาบอดเดิน
ขอแนะนำให้ใช้วิธี ให้เขาจับแขนบริเวณข้อศอกเรา
วิธีนี้เพื่อนตาบอดคนหนึ่งของผม เขาแนะนำมา)

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

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

ผมคิดว่า คนกลุ่มหนึ่งที่ป้ายรถเมล์นั้น
มองไม่เห็นคนตาบอด
คนที่มีบทสนทนาภายในกับตัวเองตลอดเวลา
จะมีข้อจำกัดในการเห็นโลกภายนอก
(ข้อนี้ยกตัวอย่างให้เห็นได้จาก คนที่เดินไป
คุยโทรศัพท์มือถือไป ถ้าเราสังเกตอิริยาบทเขา จะเห็นว่าเ
ขาจะเดินตัวลอยๆ ,สายตาจะไม่ focus จับที่ไหนเป็นหลัก
ถ้ามองให้ดี จะเห็นว่า สายตาของเขาบ่งบอกว่า
เขากำลังอยู่ในโลกของการสนทนา)
คนกลุ่มนี้ก็อาจจะกำลังครุ่นคิดถึงปัญหาของตนเองอยู่
(เรื่องงาน, เรื่องแฟน, เรื่องเงิน, ...)

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

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

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

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

นึกถึงเรื่องการทดลองนี้แล้วนึกถึง
การทดลองที่ Reader digest เคยทำ
เขาทดลองวางกระเป๋าสตางค์ (ที่มีที่อยู่เขียนไว้ข้างใน) ทิ้งไว้ตามที่ต่างๆ
แล้วแอบดูว่าคนที่เก็บมีลักษณะท่าทางอย่างไร
จากนั้นก็คอยติดตามว่า มีการติดต่อหรือส่งคืนกระเป๋าสตางค์หรือไม่
Reader digest เขาทดลองกับหลายๆประเทศใน Asia
ผลที่ได้ค่อนข้างตรงกันอย่างหนึ่งก็คือ
ถ้าคนที่เก็บได้เป็นคนที่ฐานะไม่ค่อยดี
มันจะได้กระเป๋าพร้อมเงินคืน
ส่วนกรณีที่คนเก็บได้ดูดีมีฐานะ
ก็จะไม่ได้กระเป๋าคืน

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

สำหรับตัวผมเอง
ผมคิดว่าทั้งเรื่องคนตาบอดหรือเรื่องกระเป๋าสตางค์
มีรากเหง้าตัวหนึ่งที่ตรงกันก็คือ "Ignorance"
..

จบดีกว่า ปัญหาพวกนี้คุยกับข้ามคืนก็ไม่จบ

Related link from Roti

Monday, September 12, 2005

Hierarchical Data in Database

ปกติผมจะเก็บ tree structure ใน db ด้วย schema แบบนี้

create table foo (
id long,
....
parent_id long
)


ซึ่งก็ ok แหล่ะ แต่การท่อง tree หรือหา path ของ tree ค่อนข้างกิน io ไปนิด
ซึ่งปกติที่ทำก็คือ load ทั้งหมดมา build เป็น graph structure แล้วเก็บ
มันไว้ใน cache

วันนี้ไปอ่านเจอที่ Gijs Van Tulder เขียนไว้ ในเรื่อง
Storing Hierarchical Data in a Database
ก็เลยได้เปิดหูเปิดตา ว่ามันมี technique อื่นที่ช่วยไม่ให้
การ select หา subtree หรือ path สามารถทำได้ใน select เดียว

ดยเขาจะสร้าง table ให้มี structure แบบนี้

create table foo (
id long,
..
parent_id long,
left_id long,
right_id long
)

ก็คือนอกจากจะมีการเก็บ parent id แล้ว
ก็ยังเก็บค่า left, right ไว้ด้วย
ซึ่งค่านี้ได้จากการ generate โดยการท่อง tree
แบบ Depth-First search

เวลาจะ select หา subtree ก็แค่

select * from foo where left_id between x and y

ค่า x และ y ก็คือ ค่า left_id, right_id ของ node ที่ต้องการหา subtree

...
มีที่น่าสนใจอีกเยอะเลย เช่นจะ select หา path ยังไร,
ผลลัพท์ที่ได้จะสร้างเป็น tree ได้อย่างไร,
การ add note เข้าไปใหม่ จะต้อง update ค่า left, right อย่างไร
ลองไปอ่านดูแล้วกันครับ

Note: อ่านแล้วก็อดนึกถึงคำพูดที่ว่า พวกเด็กที่เรียกไฟฟ้าเขียนโปรแกรมกันทื่อๆ
ก็ถูกของเขาแหล่ะ เพราะเราไม่ได้เรียนมาทาง computer โดยตรง
ก็เลยไม่ได้ผ่านหูผ่านตากับพวก algorithm แบบนี้

Related link from Roti

Commons CLI

วันนี้เขียนโปรแกรม utility เล็กๆ ที่สามารถ run จาก command line ได้
ก็เลยมองหา api ที่ช่วย parse argument
Commons CLI ของ jakarta เป็น api ที่ช่วยในการ parse argument
โดยมีวิธีการใช้ดังนี้

  • ระบุ Options ที่โปรแกรมเรารับได้
    Options opt = new Options();
    opt.addOption("d", true, "extract only specific date");
    opt.addOption("w", true, "extract only week that contain given date");
    opt.addOption("m", true, "extract for specific month,year");

    parameter ตัวที่ 2 ที่เรา pass ให้ method addOption ก็คือ การระบุว่า
    option ตัวนี้ต้องมี argument ตามมาด้วยหรือไม่
    อย่างในตัวอย่างที่เขียนนี้ ผู้ใช้สามารถเรียกใช้โปรแกรมได้ดังนี้
    program -d yymmdd -w yymmdd -y yymm infile outfile

    กรณีที่เราต้องการให้เลือกเฉพาะ option ใด option หนึ่งเท่านั้น
    ก็สามารถเขียนได้ดังนี้
    Options opt = new Options();
    OptionGroup grp = new OptionGroup();

    grp.addOption(new Option("d", true, "extract only specific date"));
    grp.addOption(new Option("w", true, "extract only week that contain given date")
    );
    grp.addOption(new Option("m", true, "extract for specific month,year"));

    opt.addOptionGroup(grp);

    ก็คือเราเปลี่ยนไปใช้ Optiongroup ในการ control ไม่ให้มีการเลือก
    มากกว่า 1 choice พร้อมๆกัน

  • ทำการ parse String[] args โดยระบุ parser ซึ่งเลือกได้ 3 แบบคือ BasicParser, GnuParser, PosixParser
    PosixParser parser = new PosixParser();
    CommandLine cmdline = parser.parse(opt, args);


  • เลือกใช้งาน argument หรือ option ผ่านทาง method getOptionValue
    หรือ getArgs()
    CommandLine cmdline = parser.parse(opt, args);

    File inFile = new File(cmdline.getArgs()[0]);

    if (cmdline.hasOption("d")) {
    Calendar[] ranges = parseDate(cmdline.getOptionValue("d"));
    ...
    }
    if (cmdline.hasOption("w")) {
    filter.setWeek(parseWeek(cmdline.getOptionValue("w")));
    }

Related link from Roti

NetBeans 5.0 Splashscreen

ดูดุดันขึ้นเยอะเลย



Related link from Roti

Hibernate Generic DAO + JDK5.0

อ่านเจอใน blog ของ Hibernate Link
เป็น idea ที่ดีมาก ใช้แต่ 1.4 จนลืมนึกถึง feature generic ของ 1.5 ไปเลย

ตัวอย่างที่เขาใช้
public interface GenericDAO<T, ID extends Serializable> {

T findById(ID id, boolean lock);

List<T> findAll();

List<T> findByExample(T exampleInstance);

T makePersistent(T entity);

void makeTransient(T entity);
}

public interface ItemDAO extends GenericDAO<Item, Long> {

public static final String QUERY_MAXBID = "ItemDAO.QUERY_MAXBID";
public static final String QUERY_MINBID = "ItemDAO.QUERY_MINBID";

Bid getMaxBid(Long itemId);
Bid getMinBid(Long itemId);

}

Related link from Roti

Sunday, September 11, 2005

Eclipse RCP & ClassLoader & Spring

เมื่อก่อนที่เคยเขียน RCP เคยพยายาม integrate
SpringFramework เข้ามาใช้ แต่ติดปัญหาว่า
เรื่องการ load class
โดย spring จะเรียกใช้ current ClassLoader โดยวิธี
Thread.currentThread().getContextClassLoader();

(Hibernate ก็ใช้วิธีนี้เหมือนกัน เข้าใจว่า framework ที่เกิดกับ
serverside นิยมใช้วิธีนี้กัน)
ซึ่งเมื่อนำมาใช้กับ Eclipse จะเกิดปัญหาทันที
เนื่องจาก classloader ที่ได้ มันจะเป็นคนละตัวกับ plugin ที่เราเขียน
ทำให้มองไม่เห็น class ที่เรา provide ให้

วิธีแก้ที่เห็นคนอื่นทำ ก็คือเขาจะ override ClassLoader ของ current thread
ชั่วคราว โดยทำ ณ ขณะที่ทำการ load ApplicationContext
public void start(BundleContext context) throws Exception {
super.start(context);

ClassLoader oldLoader = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(
this.getClass().getClassLoader());
applicationContext = new ClassPathXmlApplicationContext(SPRING_CONFIGS)
;
} finally {
Thread.currentThread().setContextClassLoader(oldLoader);
}
}


Note: คิดว่าวิธีนี้ยังไม่ support กรณีเราออกแบบให้มีการ merge
spring config file จากหลายๆ plugin เข้ามารวมที่ context เดียว

Related link from Roti

fvLogger, JavaScript Logging

อ่านเจอใน JavaScript Logging
เป็น api แนวเดียวกับ log4j แต่ใช้กับ javascript

วิธีการใช้
include logger.js กับ logger.css
<script type="text/javascript" src="logger.js"></script>
<link rel="stylesheet" type="text/css" href="logger.css" />

บริเวณที่ log information จะใช้ div แบบนี้
<div id="fvlogger">
<dl>
<dt>fvlogger</dt>
<dd class="all"><a href="#fvlogger" onclick="showAll();" title="show all" id="abcdef">all</a></dd>
<dd class="debug"><a href="#fvlogger" onclick="showDebug();" title="show debug" id="showDebug">debug</a></dd>
<dd class="info"><a href="#fvlogger" title="show info" id="showInfo">info</a></dd>
<dd class="warn"><a href="#fvlogger" onclick="showWarn();" title="show warnings" id="showWarn">warn</a></dd>
<dd class="error"><a href="#fvlogger" onclick="showError();" title="show errors" id="showError">error</a></dd>
<dd class="fatal"><a href="#fvlogger" onclick="showFatal();" title="show fatals" id="showFatal">fatal</a></dd>
<dd><a href="#fvlogger" onclick="eraseLog(true);" title="erase">erase</a></dd>
</dl>
</div>




ถ้าไม่ต้องการหรูมากนัก​ ก็ใส่เพียงเท่านี้ก็พอ
<div id="fvlogger"></div>




เวลา log ก็เรียก function debug, info, warn, error, fatal
function foo() {
debug("foo start");
....
}

Related link from Roti