Friday, April 01, 2005

XDoclet2 Terminate?

วันนี้อ่านพบ Hibernate 3.0 release แล้ว
ก็เลยตามดูว่า xdoclet มีความคืบหน้าในเรื่อง
การ support dtd ตัวใหม่ของ Hibernate 3.0
แค่ไหน
เท่าที่ตามเข้าไปดูใน xdoclet site
พบว่ามีสัญญาณที่ไม่ดีสำหรับ project นี้
โดยเฉพาะตัว xdoclet2 (ที่โปรยหัวไว้ 2 ปีมาแล้ว)
ใน cvs source ไม่พบว่ามี code ขยับมา 2 เดือนแล้ว
นอกจากนี้ก็ยังพบว่า ปีก่อนช่วงเดือน
june-november code ไม่ขยับเลย

เป็นอันปิดบัญชีสำหรับ xdoclet ไปได้แล้ว
ดังนั้นใน case Hibernate 3.0 ถ้าเกิดต้องใช้
ขึ้นมาคงต้องเข้าไปแก้ hibernate-module เองแล้ว
(ดีที่ architecture ของ xdoclet 1.x นั้น
ดีพอใช้อยู่แล้ว การเขียน module หรือ customize
module ก็เลยไม่หนักหนานัก)

Related link from Roti

รูปเชียนภู่กันจีน

ไปสัปดาห์หนังสือแห่งชาติมา
ได้หนังสือภาพจีนมาหลายเล่ม
เลย scan รูปให้ดูกัน


 Posted by Hello


 Posted by Hello


 Posted by Hello

Related link from Roti

ลง ingres r3 บน ubuntu

เมื่อวานเข้าไป load ingres database
(ซึ่งปัจจุบันเปลี่ยนเป็น opensource แล้ว)
เอามาลงบน notebook ของตัวเอง

ตัว package ของ ingres บน linux
อยู่ในรูป rpm
จึงต้องใช้ alien ในการแปลงให้เป็น debian-package ก่อน
โดยตัวที่จำเป็นในการลงก็มี
  • ca-ingres_3.0.1-110_i386.deb -> core package
  • ca-ingres-dbms_3.0.1-110_i386.deb -> database manager
  • ca-ingres-das_3.0.1-110_i386.deb -> เพื่อให้ต่อ jdbc ได้

เนื่องจาก ingres ไม่ยอมให้ root เป็นคน start
จึงต้อง สร้าง user ingres และ group ingres เสียก่อน
จากนั้นก็ install package โดยใช้ command dpkg
จะได้ file อยู่ใน directory /opt/CA/IngresII
เนื่องจากผมยังไม่คล่อง linux มากนักเลยยังไม่รู้วิธี
ที่จะ overwrite user, group ใน debian package
ก็เลยใช้วิธี manual โดยใช้ chgrp, chown เอง

เมื่อลงเสร็จแล้วก็ให้
export II_SYSTEM=/opt/CA/IngresII
export PATH=$PATH:$II_SYSTEM/ingres/bin:$II_SYSTEM/ingres/utility
export LD_LIBRARY_PATH=$II_SYSTEM/ingres/lib

สั่ง setup database ด้วยคำสั่ง
(ใครที่ไม่เคยใช้ ingres ควรจะอ่านคู่มือ
เสียก่อน เนื่องจากมีหัวข้อที่ต้อง setup
พอสมควร)
iisudbms

สั่ง setup DAS
iisudas

เท่านี้ก็เสร็จสิ้นขบวนการ
ในการใช้งานเราจะสั่ง start server ด้วยคำสั่ง ingstart
และ stop ด้วยคำสั่ง ingstop

Related link from Roti

Thursday, March 31, 2005

Article Hibernate vs. Rails

หลังจากเริ่มเป็น Issue มาได้พักหนึ่ง
ในที่สุดก็มีคนเขียนบทความเปรียบเทียบระหว่าง
Hibernate กับ Active Record (Rails)
TheServerSide.com - Hibernate vs. Rails: The Persistence Showdown

เริ่มต้นก็เน้น Pattern ที่แตกต่างของทั้ง 2
ActiveRecord is "an object that wraps a row in a database table or view, encapsulates database access and adds domain logic on that data"[Fowler, 2003]. This means the ActiveRecord has "class" methods for finding instances, and each instance is responsible for saving, updating and deleting itself in the database. It's pretty well suited for simpler domain models, those where the tables closely resemble the domain model. It is also generally simpler then the more powerful, but complex Data Mapper pattern.
Data Mapper Pattern

The Data Mapper is "a layer of mappers that moves data between objects and a database while keeping them independent of each other and the mapper itself"[Fowler, 2003]. It moves the responsibility of persistence out of the domain object, and generally uses an identity map to maintain the relationship between the domain objects and the database. In addition, it often (and Hibernate does) use a Unit of Work (Session) to keep track of objects which are changed and make sure they persist correctly.

Related link from Roti

Wednesday, March 30, 2005

Ruby on Rails #2

ลองมาดูที่ Object Relation Mapping ก่อน
ตัว ruby on rails ใช้ Active Record

ลองดูหลักการเบื้องต้นก่อน
  • การ map เข้ากับ table
    ใช้หลักง่ายๆก็คือ ชื่อ class ในรูปเอกพจน์ map กับ ชื่อ table ในรุปพหูพจน์
    class Person < ActiveRecord::Base
    end
    class person map เข้ากับ table persons
    และ column ทุก column ใน table persons
    automatic map เป็น property ของ class Person
    Note: จะเห็นได้ว่าตัว class person ต้อง extend base class ที่ชื่อ ActiveRecord
  • การ map Relationship
    class Project < ActiveRecord::Base
    belongs_to :portfolio
    has_one :project_manager
    has_many :milestones
    has_and_belongs_to_many :categories
    end
    • belongs_to เป็นได้ทั้ง many-to-one หรือ one-to-one
      โดย primary key ของ portfolios => foreign key ของ Projects
    • has_one มีลักษณะเป็น one-to-one
      โดย primary key ของ project => foreign key ของ project_managers
    • has_many เป็น one-to-many
    • has_and_belongs_to_many เป็น many-to-many โดยต้องมี table
      หนึ่งสำหรับเก็บ pk ของทั้ง categories และ projects
      โดย default table name จะใช้ชื่อว่า categories_projects (เรียงตามลำดับอักษร)
  • Aggregate Mapping (hibernate เรียก component mapping)
    เป็นการ map attribute ตัวเดียวหรือหลายๆตัวเป็น object
    class Customer < ActiveRecord::Base
    composed_of :balance, :class_name => "Money", :mapping => %w(balance amount)
    composed_of :address, :mapping => [ %w(address_street street), %w(address_city city) ]
    end
    • customer.balance จะมี datatype เป็น Money
      โดย attribute ที่ชื่อ balance จะถูก pass เป็น
      parameter ที่ชื่อ amount ใน Money Constructor Method
    • ส่วน customer.address จะมี datatype เป็น Address
      โดยใช้ attribute address_street, address_citry
      ในการ initialize Address object

  • Validation
    class Account < ActiveRecord::Base
    validates_presence_of :subdomain, :name, :email_address, :password
    validates_uniqueness_of :subdomain
    validates_acceptance_of :terms_of_service, :on => :create
    validates_confirmation_of :password, :email_address, :on => :create
    end
    • presence_of -> Require
    • uniqueness_of -> ค่าที่ set ต้อง unique ใน table
    • acceptance_of -> case พิสดารหน่อย ไม่มีการเก็บลง table จริง
      แต่ต้องการให้มีการ pass ค่ามา
      (เห็นเขายกตัวอย่าง use case กรณี หน้าจอที่ต้อง tick accept license)
    • confirmation_of ->case พิสดารเช่นกัน เขายกตัวอย่างพวก password
      confirmation ที่ค่า 2 ค่าต้องตรงกัน
  • Self Relate RelationShip (พวกที่ structure เป็น tree)
    class Item < ActiveRecord::Base
    belongs_to :list
    acts_as_list :scope => :list
    end

  • Etc. -> Callback, Observers, Inheritance, Transaction

จะเห็นได้ว่า Powerfull เหลือเฟือเหลือใช้
เป็นทางเลือกอีกทางที่น่าสนใจ

Related link from Roti

Ruby on Rails #1

พักนี้อ่านเจอ blog ที่พูดถึง ruby on rails เยอะ
ก็เลยอยากรู้ว่าดีแค่ไหน

เท่าที่อ่านดู
ruby เป็น script language
rails เป็น Web Application framework
ดูจาก diagram ข้างล่างแล้วมี concept เป็น MVC แบบเดียวกับ struts เลย
ส่วนที่ต่างกัน ก็คงจะเป็นตรงที่ไม่ต้องเขียน Configuration file (พวก Action mapping)
แล้วก็ออกแบบมาสำหรับ web application ที่มี backend
เป็น database โดยเฉพาะ
Request cycle in Rails
การ install
  • ใช้ synaptic install ruby1.8, libzlib-ruby, libyaml-ruby, libdrd-ruby, liberb-ruby, rdoc
  • download rubygem (ทำหน้าที่เหมือน apt-get)
  • extract rubygem.tar.gz, cd เข้าไปใน directory ที่ extract สั่ง "ruby setup.rb"
  • สั่ง "gem install rails --remote"
  • สั่ง "gem install postgres-pr" (กรณีใช้ postgresql เป็น backend)


ทดสอบสร้าง Hello world
  • สั่ง "rails test" เพื่อทำการสร้าง project
    ในขั้นนี้ rail จะ generate file ให้เราจำนวนหนึ่ง
  • สามารถทดลอง run ได้โดยสั่ง "ruby script/server" ใต้ project directory แล้วใช้ browser ชี้ไปยัง http://localhost:3000/
  • สร้าง controller โดยใช้คำสั่ง "ruby script/generate controller Hello"
    จะได้ file app/controllers/Hello_controller.rb
  • edit file Hello_controller.rb
    class HelloController < ApplicationController
    def index
    render_text "hello world"
    end
    end

  • ทดลองเรียก http://localhost:3000/Hello


Note: จะเห็นได้ว่าตัว Rail จะ map ชื่อcontroller เข้ากับ url ให้โดยอัตโนมัติ
แต่ยังไม่ได้ใช้ view เข้ามา render


ทดสอบต่อ Database
เข้าไป config file "config/database.yml"
development:
adapter: postgresql
database: pphetra
host: localhost
username: pphetra
password: xxxxxxx

test:
adapter: postgresql
database: pphetra
host: localhost
username: pphetra
password: xxxxxxx

production:
adapter: postgresql
database: pphetra
host: localhost
username: pphetra
password: xxxxxxx

Note: แค่เห็นว่าเตรียมแยก develop, test, production ให้ก็รู้สึกชอบใจแล้ว
สร้าง table "Categories"
create table Categories (
id integer primary key not null default nextval('id'),
title varchar(255) not null,
description text
)

สั่ง "ruby script/generate model Category"
สั่ง "ruby script/generate controller Category"
จะได้ file app/controllers/category_controller.rb
ให้แก้เนื้อหาใน file เป็น
class CategoryController < ApplicationController
scaffold:Category
end

จากนั้นเรียก url http://localhost:3000/category
จะได้ page ที่สามารถทำ create, update, delete, list
ได้
Note: rail จะ mapping ตัว model กับ table โดย model
ต้องอยู่ในรูป เอกพจน์ ส่วนชื่อ table ต้องอยู่ในรูป พหูพจน์


Note: การ insert เข้า Postgres ยังมีปัญหาในส่วนของ generate id อยู่ ยังไม่ได้เปิดคู่มือหาจริงๆจัง

ง่ายดีเหมือนกัน แต่ใช้ assumption ที่ต้องรู้กัน เยอะพอสมควร
ส่วนที่แสดงเป็นตัวอย่างอย่างง่าย แต่ถ้าทำใช้จริง
ก็จะต้องทำส่วน view plug เข้าไป เพื่อที่จะ
customize ให้มีหน้าตาที่ user รับได้

Related link from Roti

Monday, March 28, 2005

วันนี้เห็น stack trace ของ blogger

วันนี้ post ข้อมุลแล้วเกิด exception เลยได้เห็น stack trace ของ blogger
ก็เลยใช้วิชาเดาเข้ามาช่วย

ตัว blogger ใช้ tomcat 4.x เป็น app server
ตัว web server นั้นไม่รู้ รู้แต่ว่าใช้ jk เป็นตัวแจก request ให้ tomcat
และน่าจะใช้ sticky session ด้วยเพราะมี cookie ตัวหนึ่งที่เก็บค่า serverId
(ใครที่ยังไม่เคยใช้ tomcat cluster อ่านเพิ่มเติมได้ที่นี่ link)
จากนั้นก็เข้าสู่ framework ของ google
(มีบางส่วนเป็น pyra framework) ที่วางลักษณะ
เป็น pipe chain กับ Handler chain
เท่าที่เห็นก็มี
  • IdentityCookiePipe ไม่รู้ว่าเก็บอะไร เพราะ encode ไว้ (ข้อมูลอยู่ใน cookie ที่ชื่อ I)
  • StatsPipe -> สถิติ
  • ProfileLogPipe ->น่าจะ update lastupdate, กับ logging
  • PyraJspDispatchPipe ->framework ของ Pyra ซึ่งมี handler chain
  • AuthenticatedHandler
  • ActionHandler -> ตัวนี้น่าจะเก็บ post ลง database
  • PublishHandler -> ตัวนี้น่าจะ generate cache page เก็บไว้

java.lang.OutOfMemoryError: unable to create new native thread
at java.lang.Thread.start(Native Method)
at com.pyra.blogger.Blog.publishAsync(Blog.java:1938)
at com.pyra.blogger.frontend.PublishHandler.perform(PublishHandler.java:133)
at com.google.servlet.handlers.ActionHandler.execute(ActionHandler.java:27)
at com.google.servlet.handlers.AuthenticatedHandler.execute(AuthenticatedHandler.java:106)
at com.google.servlet.pipe.HandlerDispatchPipe.invoke(HandlerDispatchPipe.java:82)
at com.pyra.blogger.frontend.PyraJspDispatchPipe.invoke(PyraJspDispatchPipe.java:71)
at com.google.servlet.pipe.ServletPipe.invokeNextPipe(ServletPipe.java:118)
at com.pyra.blogger.frontend.ProfileLogPipe.invoke(ProfileLogPipe.java:48)
at com.google.servlet.pipe.ServletPipe.invokeNextPipe(ServletPipe.java:118)
at com.google.servlet.pipe.StatsPipe.invoke(StatsPipe.java:64)
at com.google.servlet.pipe.ServletPipe.invokeNextPipe(ServletPipe.java:118)
at com.google.servlet.pipe.PendingRequestPipe.invoke(PendingRequestPipe.java:31)
at com.google.servlet.pipe.ServletPipe.invokeNextPipe(ServletPipe.java:118)
at com.google.servlet.pipe.HttpRedirectPipe.invoke(HttpRedirectPipe.java:43)
at com.google.servlet.pipe.ServletPipe.invokeNextPipe(ServletPipe.java:118)
at com.pyra.blogger.frontend.IdentityCookiePipe.invoke(IdentityCookiePipe.java:137)
at com.google.servlet.pipe.ServletPipe.invokeNextPipe(ServletPipe.java:118)
at com.google.servlet.exceptionhandling.ExceptionHandlerPipe.invoke(ExceptionHandlerPipe.java:99)
at com.google.servlet.pipe.ServletPipe.invokeNextPipe(ServletPipe.java:118)
at com.google.servlet.pipe.LocaleContextPipe.invoke(LocaleContextPipe.java:116)
at com.google.servlet.BaseServlet.doGet(BaseServlet.java:89)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:740)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:853)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:247)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:256)
at org.apache.catalina.core.StandardPipeline$StandardPipelineValveContext.invokeNext(StandardPipeline.java:643)
at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:480)
at org.apache.catalina.core.ContainerBase.invoke(ContainerBase.java:995)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)
at org.apache.catalina.core.StandardPipeline$StandardPipelineValveContext.invokeNext(StandardPipeline.java:643)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:494)
at org.apache.catalina.core.StandardPipeline$StandardPipelineValveContext.invokeNext(StandardPipeline.java:641)
at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:480)
at org.apache.catalina.core.ContainerBase.invoke(ContainerBase.java:995)
at org.apache.catalina.core.StandardContext.invoke(StandardContext.java:2415)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:180)
at org.apache.catalina.core.StandardPipeline$StandardPipelineValveContext.invokeNext(StandardPipeline.java:643)
at org.apache.catalina.valves.ErrorDispatcherValve.invoke(ErrorDispatcherValve.java:171)
at org.apache.catalina.core.StandardPipeline$StandardPipelineValveContext.invokeNext(StandardPipeline.java:641)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:172)
at org.apache.catalina.core.StandardPipeline$StandardPipelineValveContext.invokeNext(StandardPipeline.java:641)
at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:480)
at org.apache.catalina.core.ContainerBase.invoke(ContainerBase.java:995)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:174)
at org.apache.catalina.core.StandardPipeline$StandardPipelineValveContext.invokeNext(StandardPipeline.java:643)
at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:480)
at org.apache.catalina.core.ContainerBase.invoke(ContainerBase.java:995)
at org.apache.coyote.tomcat4.CoyoteAdapter.service(CoyoteAdapter.java:223)
at org.apache.jk.server.JkCoyoteHandler.invoke(JkCoyoteHandler.java:261)
at org.apache.jk.common.HandlerRequest.invoke(HandlerRequest.java:360)
at org.apache.jk.common.ChannelSocket.invoke(ChannelSocket.java:604)
at org.apache.jk.common.ChannelSocket.processConnection(ChannelSocket.java:562)
at org.apache.jk.common.SocketConnection.runIt(ChannelSocket.java:679)
at org.apache.tomcat.util.threads.ThreadPool$ControlRunnable.run(ThreadPool.java:619)
at java.lang.Thread.run(Thread.java:534)

Related link from Roti

Transitive dependencies management

Ivy เป็น tool ตัวหนึ่งที่ช่วยจัดการ dependency library

สมมติว่า project เรา depend on project X
และ project X depend on project Y
เวลาเราเชียน build script ด้วย maven
เราต้องระบุว่า project เรา depend กับทั้ง
proejct Y และ Y

แต่สำหรับ Ivy แล้ว เราจะระบุ dependency
แค่กับ project X เท่านั้น ส่วน x จะไป
depend กับใคร ตัว Ivy จะ solve ให้เอง

feature Transitive dependencies ช่วย
ให้เราจัดการ dependency ได้ง่ายขึ้น
อย่าง project ล่าสุดของผม ใน maven ผม
ต้องระบุ dependency กับ jar ทั้งหมด
40 file แต่ถ้าเป็น Ivy, dependency ที่ต้องระบุ
จะเหลือแค่ 15 file

Related link from Roti

ตัวอย่าง Tapestry Ajax component

มีคนทดลองทำ Ajax style component อยู่ที่ XTile example
ดูตามที่เขาเขียนแล้ว ok เลย
xtile เป็น invisible component
ทำหน้าที่เป็นตัว call xmlhttprequest
เข้าไปยัง Page Listener

ชอบที่ call ตรงเข้า page listener เลย
ทำให้ source code ไม่กระจัดกระจาย

Related link from Roti

Saturday, March 26, 2005

มี java มานานแล้ว ในที่สุดก็มี bangkok

opensource project ตัวนี้มี code name ว่า bangkok
ตอนแรกนึกว่าคนไทยเขียน แต่ check ดูแล้วไม่ใช่แฮะ

Related link from Roti

Friday, March 25, 2005

config xterm function key

ช่วงนี้มีความจำเป็นต้องกลับไปช่วย maintain ระบบเก่า
ที่ต้องใช้ terminal emulator ในการ coding
แต่มีปัญหาว่า function key ที่ xterm map ไม่ตรง

ก็เลยเขียน shell script ง่ายๆสำหรับการ override function key
xterm  -title "SSO" -sb -sl 1200   -geo 80x24   \              
-cr red -xrm \
"XTerm*vt100.translations: #override
Shift F1: string(0x1b) string("Wa")
Shift F2: string(0x1b) string("Wb")
Shift F3: string(0x1b) string("Wc")
Shift F4: string(0x1b) string("Wd")
Shift F5: string(0x1b) string("We")
....
~Shift F1: string(0x1b) string("WA")
~Shift F2: string(0x1b) string("WB")
~Shift F3: string(0x1b) string("WC")
~Shift F4: string(0x1b) string("WD")
....
-e telnet 192.10.1.1 &

อ่านเพิ่มเติมได้ที่นี่ xterm Faq

Related link from Roti

Thursday, March 24, 2005

Joda Bean

อยู่ที่ Joda
หลายคนที่เคยเขียน UI คงเคยรู้ว่ามีแนวทางในการ
ส่ง business object ไปยัง UI layer อยู่ 2 พวกคือ
  • พวกที่นิยม Type Safe พวกนี้จะใช้ Java Bean
  • พวกที่นิยม Generic พวกนี้จะใช้ HashMap

จุดเด่นของพวก generic ก็คือง่ายต่อการ binding เข้ากับ UI component
ส่วนข้อเสียก็คือการเปิดโอกาสให้เกิด bug หรือ runtime error ได้ง่ายขึ้น

ตัว Joda Bean จะเป็นลูกผสมของ 2 พวกนี้
คือใช้ hashMap เป็น base class แต่ก็สามารถ
set property ในลักษณะ Type Safe ได้เช่นกัน
การเขียน Joda Bean จะเชียนดังนี้
public interface Person extends Bean {
StringProperty surname();
String getSurname();
void setSurname(String surname);
}

เราเพียงแต่เขียน interface
พอถึงเวลาใช้ ก็เพียงแต่
Person person = (Person) JodaFactory.create(Person.class);

Joda จะใช้ Java 1.3 Proxy ในการสร้าง class person ให้เรา

การนำไปใช้
กรณีที่เราต้องการ binding เข้ากับ swing JTextField
JTextField textField = new JTextField();
Person person = (Person) JodaFactory.create(Person.class);
SwingBinder binder = new SwingBinder();
binder.bind( textField, person.surname() );

Note: Joda binding ยังไม่ support พวก combobox, table component

นอกจากนี้ยังสามารถใช้ XPath ในการ access ได้ด้วย
XPath xpath = new XPath("address[2]/town/text()");
Profile profile = (Profile) JodaFactory.create(Profile.class);
String str = (String) xpath.selectSingleNode(profile);

Related link from Roti

color schema

สำหรับผู้ที่ขี้เกียจเปิด color schema book
หรือ programmer ผู้ที่มีปัญหาในการเลือกสี
color schema

Related link from Roti

IE send request for PDF twice

project ที่แล้วมี PDF report ที่เขียน jasper ไป 74 ตัว
ไม่เห็น issue นี้เลย (หรือว่า server จะเร็วจัดจนไม่เกิด
performance drop)
http://www.mail-archive.com/user@struts.apache.org/msg23445.html
ต้องกลับไปดู log ใหม่แล้ว

Related link from Roti

tomcat trimspace

ได้ความรู้ใหม่มา
ปกติเวลาเรา view html source
จาก page ที่สร้างจาก jsp
เราจะเห็นบรรทัดว่างๆอยู่ทั่วไปหมด
ใน tomcat 5.5.x เราสามารถ config ไม่ให้
มี whitespace พวกนี้โดย
 <init-param>
<param-name>trimSpaces</param-name>
<param-value>true</param-value>
</init-param>

Related link from Roti

toogle

ใครเคยใช้ search engine ตัวนี้หรือยังครับ
http://www.c6.org/toogle/

Related link from Roti

Maven Eclipse Plugin

ใครที่เคยใช้ java คงจะเคยใช้ ant มากันแล้ว
ข้อเสียของ ant (จริงๆก็ไม่ใช่ข้อเสียหรอก) ก็คือ
ไม่ว่าจะเขียนกี่ project
file build.xml ก็หน้าตาอีหรอบเดิมทุกที
(คนที่เคยเขียน ant คงรู้ว่า วิธีการเขียน
ant ที่เร็วที่สุดก็คือการ copy, paste จาก
build file เก่า)
จนมีคนคิดว่า เอ๊ะทำไมเราต้องมาเขียน
อะไรซ้ำไปซ้ำมา ก็เลยเกิดเป็น maven
ขึ้นมา

การเขียน maven ถ้าเป็น project ธรรมดา
ก็เพียงเขียนแต่ project.xml ซึ่งเป็น descriptor
อธิบายรายละเอียดของ project
ซึ่ง key สำคัญตัวหนึ่งที่ maven จัดการให้ก็คือ
library dependency ซึ่ง maven จะจัดการหา
jar file ตาม version ที่เราระบุให้
โดย mavenจะ maintain jar file นั้นไว้ใน
local repository ของตัวเอง กรณีทีในเครื่อง
local ไม่มี file นั้น ก็จะทำการ download
จาก remote repository (ผมชอบ feature นี้มาก
เพราะแทนที่เราจะมี jar file ที่ใช้บ่อยๆอย่าง log4j.jar
กระจายอยู่ในทุก project ถ้าใช้ maven ก็จะ
เกิดการ share jar ไว้ที่ repository ที่เดียว)

ที่นี้ถ้าเราต้องการใช้ eclipse ร่วมกับ
maven , maven ก็มี plugin อยู่ตัว
ที่ช่วยในการ generate .project กับ
.classpath ให้เรา (เป็น file ที่ eclipse
ใช้ในขบวนการ build) ทำให้เรา
เปิด eclipse มา edit source ได้เลย
ไม่ต้องไปเสียเวลา add jar file เข้ามา
ใน build path อีก

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

ตัว file .classpath ที่ maven สร้างให้จะสร้างตามนี้
1. the build source directory
2. the build unit test source directory
3. the JRE being used
4. the appropriate version of JUnit
5. each Maven project dependency
6. an output directory for compiled code: target\classes
7. any .zip source archives

ตัวอย่าง .classpath ที่ maven gen ให้
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry excluding="" kind="src" path="src/main/java">
</classpathentry>
<classpathentry excluding="" including="" kind="src" path="src/main/resources">
</classpathentry>
<classpathentry output="target/test-classes" kind="src" path="src/test/java">
</classpathentry>
<classpathentry kind="var" path="MAVEN_REPO/junit/jars/junit-3.8.1.jar">
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER">
</classpathentry>
<classpathentry kind="var" path="MAVEN_REPO/log4j/jars/log4j-1.2.8.jar">
</classpathentry>
<classpathentry kind="output" path="target/classes">
</classpathentry>
</classpath>

จะสังเกตุเห็นว่า มี variable อยู่ตัวที่ชื่อ MAVEN_REPO (maven repository location)
ซึ่งเราต้องเข้าไป manual add ใน eclipse เอง
หรือไม่ก็สั่ง
maven eclipse:add-maven-repo 

แต่การสั่งแบบนี้ต้องมีการ set ค่า ${maven.eclipse.workspace}
(eclipse workspace location)
ไว้ใน project.properties ด้วย

อ่านเพิ่มเติม

Related link from Roti

Wednesday, March 23, 2005

ปัญหา encoding ของ eclipse

ช่วงนี้อยู่ระหว่างการย้ายสภาพแวดล้อมการทำงาน
จาก window ไปเป็น linux (ubuntu)
ก็เลยมี issue ให้จัดการเยอะหน่อย
ล่าสุดก็เป็นกรณี eclipse editor preference ไม่มี encoding thai ให้เลือก
ทดลองสั่ง LANG=th_TH ก็พบ warning ตอน start eclipse
(process:5042): Gdk-WARNING **: locale not supported by C library

ก็เลยต้อง reconfig locales ใหม่ให้ถูกต้อง
sudo dpkg-reconfigure locales

Related link from Roti

ศึกษา ADHD case

ตอนนี้กำลังง่วนอยู่กับการศึกษา super-computer ที่บ้ายอยู่
กำลังหาหนังสืออ่านกรณี Attention Deficit Hyperactivity Disorder (ADHD)
กับกรณี Autistic Disorder
เพื่อไว้กรณี super-comp ของเราจะเข้าข่ายนี้

ปล. ใครยังไม่มีลูก ก็ลองหาลูกสักคนดูนะ
โปรแกรมยากมาก
debugger ก็ไม่มีให้ด้วย

Related link from Roti

Friday, March 18, 2005

จังหวัด-อำเภอ Component in Tapestry

เมื่อก่อนผมใช้ Struts เป็น Framework สำหรับ Web Application
ซึ่งก็ใช้ได้ดีทีเดียวแหล่ะ แต่มีอยู่จุดหนึ่งที่รู้สึกว่าเป็นจุดอ่อน
ของ struts ก็คือประเด็นเรื่อง reuse UI component
จริงๆอยู่เราสามารถทำ custom taglib ในส่วนนี้เองได้
แต่มันก็ยังขาดๆเกินๆอยู่

จนมาได้ลองใช้ Tapestry จึงได้พบคำตอบในส่วน UI Component
Tapestry เป็น Framework ที่มีรากฐานอยู่บน
Component เราจึงสามารถ reuse component
ต่างๆได้เต็มเม็ดเต็มหน่วย (แน่นอนมีสิ่งที่ต้องแลกเปลี่ยนเหมือนกัน)

framework ของ Tapestry เปิดโอกาสให้เรา
ได้ทำ binding ทั้ง 2 ทาง คือ ทั้งขา Request และ
ขา Response ซึ่งต่างจาก Taglib ที่ทำ binding
เฉพาะในส่วนการ Render อย่างเดียว (เมื่อ user post กลับมา
ตัว taglib จะไม่ได้ถูกเรียกใช้ แต่กรณีถ้าเป็น tapestry
ตัว component จะถูกเรียกใช้ ซึ่งในการเรียกใช้ครั้งนี้
ไม่ได้เพื่อทำการ render แต่เพื่อการ parse request parameter)

ลองมาดูกันว่า กรณีที่เป็น Tapestry Component แล้ว
เราต้องทำอะไรบ้าง

อำเภอ จังหวัด component
หลายคนคงเคยทำ ui ในส่วน "ที่อยู่" ที่ให้ user ป้อนมาแล้ว
หลักการง่ายๆก็คือ มี list box จังหวัด กับ list box อำเภอ
เมื่อ user เลือกจังหวัด list box ของอำเภอก็จะเปลี่ยน choice ตาม
การเปลี่ยนค่านี้กำหนดให้ใช้ XmlHttpRequest เพื่อไม่ให้เกิดการ
update page ทั้งหมด
โดยผมจะลองแสดงให้ดูว่าถ้าเป็น tapestry component
แล้วจะต้องทำอย่างไร

กำหนดให้ชื่อ component ว่า ZoneComponent

Component Specification
ตั้งชื่อว่า ZoneComponent.jwc มีหน้าตาดังนี้
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE component-specification
PUBLIC "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
"http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">
<!-- generated by Spindle, http://spindle.sourceforge.net -->

<component-specification
class="sdf.web.comps.ZoneProvince"
allow-body="no" allow-informal-parameters="yes">

<parameter name="province" direction="form"
type="sdf.domain.Province" required="yes"/>
<parameter name="zone" direction="form"
type="sdf.domain.Zone" required="yes"/>
<parameter name="serviceUrl" direction="in"
type="java.lang.String" required="yes"/>
<parameter name="serviceMethod" direction="in"
type="java.lang.String" required="yes"/>
<parameter name="disabled" direction="in"
type="boolean" required="no"/>

<property-specification name="name"
type="java.lang.String"/>
<property-specification name="form"
type="org.apache.tapestry.IForm"/>

<private-asset name="script"
resource-path="/sdf/web/comps/ZoneProvince.script"/>
</component-specification>

วัตถุประสงค์หลักๆของ component-spec ก็คือ
การกำหนดว่า component เรามี parameter อะไรให้ set ได้บ้าง
(คนที่ set ก็คือ คนที่เอา component เราไปร้อยเข้าเป็น Application)
ตัวที่น่าสนใจก็คือ parameter name="province"
type="sdf.domain.Province" -> อันนี้คือ business object ของจังหวัด
direction="form" -> ระบุว่าเป็น bi-direction ใช้ทั้งกรณี Request และ Response
ส่วนตัว serviceUrl, serviceMethod เป็นตัวกำหนดค่า url ของ service
ที่ให้บริการกรณีที่ user click เปลี่ยนจังหวัดและต้องการนำค่า อำเภอใหม่
ไป update "อำเภอ listbox" ค่าพวกนี้มี Direction เป็น "in"
เนื่องจากเป็นพวก readonly ไม่มีการ post back กลับมาจาก user

JavaScript Template
เนื่องจากเราใช้ XMLHttpRequest เป็น channel ในการ update listbox
ดังนั้นต้องมีการใช้ javascript เข้ามาช่วย
Tapestry วางแนวทางการ embed javascript ไว้แล้วโดย
ให้เราใช้ Javascript template ในการ define javascript
(เนื่องจาก component เราจะถูกประกอบทีหลัง ดังนั้นจะมี
ค่าบางค่าที่ยังไม่รู้ว่าจะเป็นอะไร อย่างเช่นชื่อของ component
จึงต้องออกแบบให้เป็น template
เมื่อรู้ค่าที่แน่นอนแล้วจึงจะสามารถสร้างออกมาเป็น javascript
ที่ใช้งานได้จริงๆ การทำเช่นนี้ทำให้ไม่เกิดปัญหา naming comflict
กรณีที่เรามี instance ของ component มากกว่า 1 instance ใน
page เดียวกัน เช่นกรณีที่อยู่ที่ใน 1 หน้าอาจมีทั้ง ที่ทำงาน ที่ติดต่อ สถานที่เกิด)

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE script PUBLIC
"-//Apache Software Foundation//Tapestry Script Specification 3.0//EN"
"http://jakarta.apache.org/tapestry/dtd/Script_3_0.dtd">

<script>
<include-script resource-path="jsrsClient.js"/>

<input-symbol key="provinceName"
class="java.lang.String" required="yes"/>
<input-symbol key="zoneName"
class="java.lang.String" required="yes"/>
<input-symbol key="formName"
class="java.lang.String" required="yes"/>
<input-symbol key="serviceUrl"
class="java.lang.String" required="yes"/>
<input-symbol key="serviceMethod"
class="java.lang.String" required="yes"/>
<input-symbol key="scriptChangedMethod"
class="java.lang.String" required="yes"/>

<let key="populateZone" unique="yes">
populateZone_${zoneName}
</let>


<body>
function clearDropDown (selField)
{
while (selField.options.length > 0)
selField.options[0] = null;
}

function ${populateZone} (valueTextStr)
{
var selField = document.${formName}.${zoneName};
clearDropDown(selField);

// create an array of each item for the dropdown
var aPairs = valueTextStr.split(";");
if (valueTextStr.substr(valueTextStr.length - 1) == ';') {
aPairs[aPairs.length - 1] = null;
aPairs.length--;
}
// Fill 'er up
for (var i=0; i <; aPairs.length; i++) {
// each item is a "value,name" pair
aValueText = aPairs[i].split(",");
oItem = new Option;
oItem.value = aValueText[0];
oItem.text = aValueText[1];
selField.options[selField.options.length] = oItem;
}
selField.options.selectedIndex = 0;
}

function ${scriptChangedMethod}()
{
jsrsExecute("${serviceUrl}", ${populateZone},
"${serviceMethod}",
document.${formName}.${provinceName}
.options[document.${formName}
.${provinceName}.selectedIndex].value);
}
</body>

จะเห็นว่ามี element หลักๆ อยู่ 3 กลุ่ม
  • <include-script> ตัวนี้จะเป็นตัวบอกว่ามี js library ตัวไหนที่ต้อง load มาบ้าง
    กรณีของเราเราใช้ jsrsClient.js

  • <input-symbol> ใช้กำหนดค่าตัวแปรที่ต้องถูก pass เข้ามา ณ ตอนที่
    generate javascript

  • <body> เป็น script หลักในการทำงาน
    จะเห็นได้ว่าตัวแปรที่ยังไม่รู้ค่าจะอยู่ในรูป ${xxxxx}


Component Class
ตรงนี้จะเขียน code หลักในส่วนของการ render และ
การ parse Request parameter เมื่อ user post form กลับเข้ามา

  • กรณีการ render component จะหน้าตาประมาณนี้

  Body body = Body.get(cycle);

if (body == null)
throw new ApplicationRuntimeException(
Tapestry.format("must-be-contained-by-body",
"ZoneProvince"),
this,
null,
null);

Map symbols = new HashMap();

symbols.put(SYM_PROVINCE_NAME, provinceName);
symbols.put(SYM_ZONE_NAME, zoneName);
symbols.put(SYM_FORMNAME, form.getName());
symbols.put(SYM_SERVICE_METHOD, getServiceMethod());
symbols.put(SYM_SERVICE_URL, getServiceUrl());
symbols.put(SYM_SCRIPT_CHANGED_METHOD, scriptChangedMethod);

// generate ส่วน JavaScript
_script.execute(cycle, body, symbols);


writer.begin("select");
writer.attribute("name", zoneName);
renderInformalParameters(writer, cycle);

String provCode = "10"; // default to bangkok
if (getProvince() != null) {
provCode = getProvince().getCode();
}
String zoneCode = null;
if (getZone() != null) {
zoneCode = getZone().getCode();
}

// render zone list
List list = svr.findZone(provCode,
IConstants.ORDER_BY_NAME);
for (Iterator iter = list.iterator(); iter.hasNext();) {
Zone zone = (Zone) iter.next();
writer.begin("option");
writer.attribute("value", zone.getCode());
if (zoneCode != null &&
zone.getCode().equals(zoneCode)) {
writer.attribute("selected", true);
}
writer.closeTag(); // close option tag
writer.printRaw(zone.getName());
writer.end("option");
}
writer.end("select");

// render province list
writer.begin("select");
writer.attribute("name", provinceName);
writer.attribute("onchange",
"javascript:" + scriptChangedMethod + "()");
renderInformalParameters(writer, cycle);

list = svr.findAllProvince(IConstants.ORDER_BY_NAME);
for (Iterator iter = list.iterator(); iter.hasNext();) {
Province province = (Province) iter.next();
writer.begin("option");
writer.attribute("value", province.getCode());
if (province.getCode().equals(provCode)) {
writer.attribute("selected", true);
}
writer.closeTag(); // close option tag
writer.printRaw(province.getName());
writer.end("option");
}

writer.end("select");


  • กรณีการ parse parameter เมื่อมีการ post กลับมา

  String provinceCode = cycle.getRequestContext()
.getParameter(provinceName);
String zoneCode = cycle.getRequestContext()
.getParameter(zoneName);

if (Tapestry.isBlank(provinceCode)) {
return;
} else {
Province value = svr.getProvince(provinceCode);
setProvince(value);
}

if (Tapestry.isBlank(zoneCode)) {
return;
} else {
Zone value = svr.getZone(zoneCode);
setZone(value);
}

มี code หลักๆ เท่านี้ก็ได้ component สำหรับ reuse ใช้ใน Application
เราแล้ว คราวนี้ไม่ว่าจะเป็น ที่อยู่, ที่ติดต่อ, บ้านเกิด, ที่ทำงาน ก็สามารถ
ใช้ component ชุดเดียวกันได้

ตัวอย่างเวลานำไปใช้
  • html file

    <td valign="top" class="label"><div 
align="right">ที่อยู่ :</div></td>
<td><table width="100%" border="0">
<tr>
<td><input jwcid="addr1@ValidField"
value="ognl:firm.address.addr1"
displayName="ที่อยู่"
validator="ognl:beans.addr1RequireValidator"
type="text" size="50" maxlength="50"/></td>
</tr>
<tr>
<td><input jwcid="@TextField"
value="ognl:firm.address.addr2"
type="text"
size="50"
maxlength="50"/></td>
</tr>
<tr>
<td><span jwcid="zoneProvince">
zone components
</span></td>
</tr>
</table></td>

จะเห็นว่าใช้แค่ jwcid="zoneProvince" เป็นการบอกว่า
ตรงนี้เป็น component ที่มีชื่อ instance ว่า zoneProvince
ซึ่งจะมีการ declare ไว้ใน Page Specification อีกที
  • Page Specification file

    <component id="zoneProvince" type="ZoneProvince">
<binding name="province"
expression="firm.address.province"/>
<binding name="zone"
expression="firm.address.zone"/>
<static-binding name="serviceUrl"
value="/sdf/zone"/>
<static-binding name="serviceMethod"
value="getZone"/>
</component>

จะเห็นว่าตรงนี้จะมีการ binding parameter เข้ากับ Page Object
โดยระบุตรง expression คำว่า "firm.address.province" หมายถึง
ให้ไปเอา object provice มาโดยใช้คำสั่ง
getFirm().getAdress().getProvince() กับ Page Object

Note: ต้วอย่างการนำไปใช้ที่แสดงให้ดู
เป็นการเขียนแบบ formal ถ้าเป็นแบบ informal code
จะกระทัดรัดกว่านี้ แต่จะอ่านยากกว่าถ้าไม่เคยใช้
tapestry มาก่อน


ข้อมูลเพิ่มเติม

Related link from Roti