Friday, August 12, 2005

Unsigned char in C <--> Signed byte in Java

ช่วงนี้ได้มีโอกาสใล่ดู algorithm ที่เกี่ยวกับการ encrypt, decrypt
ที่เขียนด้วย C แล้วต้องแปลงให้เป็น java เลยนั่งค้นหาดูว่ามีประเด็นอะไรบ้าง
ที่อาจเป็นปัญหา

ประเด็นแรกก็คือ เรื่อง byte ของ Java เป็น signed byte
แต่ใน c จะเป็น unsigned char
ปัญหาจะเห็นชัด กรณีที่เรา shift bit
เช่น
byte b1 = (byte) 0x80;          // in binary -> 10000000
byte b2 = b1 >> 4;
System.out.println(b2); // print -> -8

byte b1 = (byte) 0x80;
int b2 = b1 >>> 4;
System.out.println(b2); // print 268435448

ปัญหาของกรณีนี้ก็คือ 0x80 พอแปลงเป็น byte แล้ว มันก็คือ -128 (มี sign bit เป็น 1)
พอ shift bit แล้วก็ได้เรื่องเลย
ทางแก้ก็คือ ให้เรา & 0xff กับ byte ก่อน shift
byte b1 = (byte) 0x80;
byte b2 = (byte) ((b1 & 0xff) >> 4);
System.out.println(b2); // print -> 8

byte b1 = (byte) 0x80;
int b2 = (b1 & 0xff) >> 4;
System.out.println(b2); // print -> 8

การแปลง byte เป็น int ก็ใช้กฎเดียวกัน
byte b1 = (byte) 0x80 ;
int i1 = b1 & 0xff;
int i2 = b1;
System.out.println(i1); // print 128
System.out.println(i2); // (wrong for unsigned byte) print -128


ที่นี้บางครั้งที่เราต้องการแปลง byte array ให้เป็น int value หรือ long value
ก็ใช้การ & 0xff เช่นเดียวกัน
ตัวอย่างเช่น
public static int toInt(byte[] bs) {
return ((bs[3] & 0xFF) << 0) +
((bs[2] & 0xFF) << 8) +
((bs[1] & 0xFF) << 16) +
((bs[0] & 0xFF) << 24);
}

เราสามารถดูตัวอย่าง source code ของ Java
ได้ที่ class java.io.Bits ซึ่งถูกเรียกใช้จาก
ObjectInputStream, ObjectOutputStream
น่าเสียดายที่ class นี้ไม่ได้ประกาศเป็น public class
ทำให้ไม่สามารถเรียกใช้งานในโปรแกรมเราได้

Related link from Roti

Thursday, August 11, 2005

Struts Ti

Struts Ti เป็น derivative ของ struts 1.x
rewrite framework เพื่อให้ง่ายต่อการ develop
(idea หลายๆส่วนมาจาก ruby on rails)

Controller

  • No configuration - everything done through annotations
  • ใช้ XDoclet กรณีที่เป็น JDK1.4
  • จะใช้หรือไม่ใช้ Form object ก็ได้
  • controller เขียนแบบ POJO
  • มี annotation สำหรับทำ Validation
  • Result forwards view ไม่ต้อง define ก็ได้ มี default เป็นค่าเดียวกับ action name

Action Mapping URL จะอยู่ในรูป

http://HOST/ACTION_NAME/PARAM_VALUE1/PARAM_NAME2/PARAM_VALUE2


FormValidation
ออกแบบใหม่

Jsp Tag
ใช้แนวทางของ WebWork2
Note: ในส่วน tag นี้ jsp กับ rails จะไม่ต่างกันนัก
แต่ rails จะ integrate template กับ controller
เป็นเนื้อเดียวกันกว่า (template mix เข้ากับ controller
ทำให้มองเหมือนกันว่า ตัวเองเป็น controller)


เพิ่มเรื่อง Page Flow State Management เข้ามา
โดยใช้แนวทางจาก Beehive Page Flow

Related link from Roti

Wednesday, August 10, 2005

Dependency Injection is Ruby

วันนี้อ่านเจอ Presentation ใน OSCon 2005 ของ Jim Weirich
ซึ่งพูดถึงเรื่อง Dependency Injection กับ ruby

มีภาพตัวอย่างการเปรียบเทียบ Java Class กับ Ruby Class ที่โดนใจ
ก็เลย copy มาให้ชม



ชอบที่พูดถึงตรงนี้
DI really becomes useful in really large projects
There aren’t that many really large Ruby projects


Dependency Injection Frameworks for Ruby

Note: DIM กับ Matz มี link ไปหา source code ใน Presentation

หลังจากดูตัวอย่าง source code ของ DIM กับ Matz
แล้วรู้สึกมืน ชอบวิธีการที่พวก spring, hivemind
ใช้ xml เป็น descriptor มากกว่า

Related link from Roti

Newbie in Linux Server Installation

ปกติทำงานด้าน Development ไม่ได้ยุ่งกับการติดตั้ง Server มานานแล้ว
ช่วงนี้ได้กลับมาปัดฝุ่น นั่ง config Linux Server ได้ความรู้ขึ้นอีกเยอะเลย
เริ่มด้วยการ resize ขนาดของ ​LVM
ก็สั่ง lvresize เพื่อเพิ่มเนื้อทีี่ของ volumn
่ปรากฎว่าเนื้อที่ใน Logical Volume เพิ่มขึ้นแล้ว
แต่เนื้อที่ใน file system ที่ mount เข้ามายังขนาดเดิมอยู่
ก็เลยนั่งค้น net ปรากฎว่ามีจดหมายที่เขียนถามเรื่องนี้เหมือนกัน
แต่ไม่มีใครตอบเลย สุดท้ายหลังจากงมเข็มไปมาพักใหญ่ ก็ฟลุ๊คไปเจอคำสั่ง
resize_reiserfs ก็เลย bingo
นึกเห็นใจพวก newbie ขึ้นมาทันทีเลย
โถเขียนจดหมายไปถามแล้วก็ไม่มีใครตอบ

ระหว่างนั้นก็อ่านเจอพวกชุดคำสั่ง autofs, automount
ซึ่งช่วยจัดการ mount filesystem ให้เราอัติโนมัติ เมื่อเราต้องการใช้
(เช่นเมื่อเรา cd เข้าไปใน directory ที่เป็นจุด mount point นั้น
หรือเมื่อเราพยายาม access path ใน mount point นั้น)
น่าสนใจดีมาก โดยเฉพาะในส่วนที่ mountpoint นั้นจะ automatic unmount เอง
หลังจากที่เราไม่ได้ใช้งานในระยะเวลาหนึ่ง (ค่า default คือ 5 นาที)
ที่ชอบอีกจุดหนึ่งก็คือ AutoFS 's map สามารถกำหนด map ในลักษณะ
Wildcard Key ได้

Note: ใน suse มีปัญหาในกรณีที่เรากำหนด timeout ใน
auto.master เช่น
/auto /etc/auto.misc --timeout 30
ถ้าอยู่ใน suse ตัว "-" แรกจะหายไป ทำให้เราต้องใส่เป็น "---"
หรือไม่ก็เลี่ยงไปใช้ "-t 30" แทน

Related link from Roti

Monday, August 08, 2005

Config VPN with OpenVPN

ช่วงนี้กำลังหา solution ในการเชื่อมต่อ ระหว่าง
network ที่บ้าน กับ network ที่ทำงาน
เมื่อก่อนที่ใช้ windows ก็สามารถใช้ CIPE ในการทำ vpn ได้
เนื่องจากมีทั้ง version linux และ win32
(บน win32 ยังมีปัญหาในการติดตั้งบน XP อยู่บ้าง แต่ไม่ยากเกินไปนัก
สามารถหาวิธีแก้ใน mail-archive ได้)
มาตอนนี้เปลี่ยนไปใช้ OS X แล้ว ก็เลยต้องขวนขวายหา solution ใหม่

ตอนแรกก็ดู IPSec ไว้ เพราะว่ามีมาให้กับ Freebsd อยู่แล้ว
แถมใน Tiger ก็มี gui สำหรับ config และเรียกใช้งานด้วย
หลังจากปลุกปล้ำอยู่ 1 วันเต็มๆ ก็ถอดใจ
เนื่องจาก case ที่ config นี้เป็น case dynamic ip
ทั้งฝั่งที่บ้านและฝั่งที่ทำงาน ซึ่งน่าจะเป็น case ที่ config
ยากสุดสำหรับ IPSec

หลังจาก search net ไปมา ก็ไปเจอเข้ากับ OpenVPN
หลังจากอ่านคู่มืออยู่สักพัก ก็พบทางสว่างทันที
ก็เลย load เอา source มา build เอง
ตัว Server ที่ทำงานมี OS เป็น SuSE Enterprise 9
การ build ก็เลยไม่ยาก ขาดเหลือ lib อะไร
ก็เปิดแผ่นเอา
ส่วนการ build ที่ Mac ก็ใช้ fink
ในการ install library lzo เพิ่ม

การ Config ก็ไม่ยากเย็นอะไร เพราะใน distribution
มีตัวอย่าง config file มาให้พร้อมสรรพ
แถมยังมี document ที่อธิบายค่อนข้างละเอียดดีทีเดียว
เริ่มด้วยการ generate พวก Certificate ทั้งหลาย
เนื่องจากในการเชื่อมต่อระหว่าง client กับ server
จะมีการตรวจสอบ Certificate ของกันและกัน
โดยทั้งสองฝ่ายจะยอมรับ connection ต่อเมื่อ
certificate ของทั้งสองฝ่าย ถูก sign โดย
Root CA อันเดียวกัน
(ข้อดีของ OpenVPN ก็คือมันเตรียม shell script
ที่ใช้ในการสร้าง key หรือ sign key ไว้เสร็จสรรพ
ไม่ต้องทรมานไปนั่งอ่านวิธีใช้ openssl อีก)

ในส่วน Server ก็จะมี config เพิ่มในส่วนของ
Route table ที่จะ push ไปให้ Client
เพื่อที่จะกำหนดว่า client มีสิทธิ access subnet
อะไรได้บ้าง

ส่วนการ Config OpenVPN สำหรับ OS X
มีข้อแม้เพิ่มเติมจากพวก Unix ก็คือ
ต้องมีการติดตั้ง tun/tap driver for Mac OS X
ซึ่งเป็น kernel extension ด้วย
อันนี้แหล่ะที่ดูน่ากลัวเล็กน้อย
เพราะว่ากลัวว่าติดตั้งแล้ว จะทำให้ OS ไม่เสถียร
ก็เลยไปหาเอกสารมาอ่านทำความเข้าใจสักหน่อย
ว่าถ้ามีปัญหาแล้วจะ uninstall ยังไง

ในส่วนของ OS X ก็มีคนทำ gui สำหรับเรียกใช้ง่ายๆ
ชื่อ Tunnelblick

โดย download package ของ Tunnelblick มีให้เราครบหมดเลย
ทั้ง tun-tap, openvpn, gui

Note: ในกรณีที่เรา build openvpn เอง แล้วเลือก
install ใน path ที่ไม่ใช่ /usr/local/sbin
จะทำต้องทำ link มาไว้ที่นี่ด้วย เพราะเจ้า
TunnelBlick มันฝัง hardcode ไว้ใน source code
เลยว่า จะเปิด openvpn ได้จากตำแหน่งนี้เท่านั้น

Related link from Roti

Sunday, August 07, 2005

Java Puzzle

อ่านเจอในเอกสาร presentation ใน javaone 2005
ก็เลยลอกมาให้ดู
public class AnimalFarm{ 
public static void main(String[] args) {
final String pig = "length: 10";
final String dog = "length: " + pig.length();
System.out.println("Animalsare equal: " + pig == dog);
}
}


public class Assignment { 
public static void main(String[] a) throws Exception {
int tricky = 0;
for (int i = 0; i < 3; i++)
tricky += tricky++;
System.out.println(tricky);
}
}


public class Loop {
public static void main(String[] args) {
int[][] tests = { { 6, 5, 4, 3, 2, 1 }, { 1, 2 }, { 1, 2, 3 },
{ 1, 2, 3, 4 }, { 1 } };
int successCount = 0;
try {
int i = 0;
while (true) {
if (thirdElementIsThree(tests[i++]))
successCount++;
}
} catch (ArrayIndexOutOfBoundsException e) {
}
System.out.println(successCount);
}

private static boolean thirdElementIsThree(int[] a) {
return a.length >= 3 & a[2] == 3;
}
}


public class Mod {
public static void main(String[] args) {
final int MODULUS = 3;
int[] histogram = new int[MODULUS];
int i = Integer.MIN_VALUE;
// This loop iterates over all intvalues
do {
histogram[Math.abs(i) % MODULUS]++;
} while (i++ != Integer.MAX_VALUE);
for (int j = 0; j < MODULUS; j++)
System.out.print(histogram[j] + " ");
}
}


อันสุดท้ายนี่งงอยู่ตั้งนาน สุดท้ายเปิด javadoc ดูแล้วค่อยร้อง อ๋อ

Related link from Roti

Thursday, August 04, 2005

ตัวอย่าง AppServer Performance Tuning

เห็น theserverside.com ลงข่าวเรื่องที่ BEA ทำคะแนน
SPECjAppServer ได้สูงสุด ก็เลยตามเข้าไปดู
ที่ได้ติดมือมาก็คือได้เห็น tuning parameter ที่เขาใช้
เลยขอ Note ไว้เพื่อไปศึกษาต่อ โดยดูเฉพาะของ WebSphere ก่อน

WebSphere 's Parameters


MaxConnectBacklog = 1024
จำนวน connection ที่ยอมให้ค้างอยู่ใน buffer
ก่อนที่ server จะ reject ทิ้ง
ค่า default อยู่ที่ 511
Note: ค่านี้ไม่ได้สัมพันธ์กับจำนวน user แบบ 1:1
เพราะว่า browser สามารถเปิด connection ได้หลาย thread พร้อมกัน

MaxKeepAliveRequests = 10
ค่านี้น่าสนใจ (อาจจะตั้งน้อยเพราะเหมาะกับธรรมชาติของ benchmark นี้)
ในคุ่มือ ibm แนะนำไว้ว่าควรมีค่า = 90% ของ web container thread pool
่ใน benchmark นี้ ibm ตั้งไว้ที่ 10 (คิดเป็น 20 % ของ Maximum Thread pool)

2 ค่านี้ search หาใน internet ไม่เจอ
com.ibm.ws.pm.batch=true
com.ibm.ws.pm.deferredcreate=true

Linux 's Parameters


ของ Ibm SีuSE มีการ config sysctl.conf

net.core.netdev_max_backlog = 600
net.core.somaxconn = 1024
ตั้ง backlog ให้เยอะขึ้น (ใครเก่ง network ช่วยบอกด้วยว่า
ไอ้ 2 ค่าบนนี้มันต่างกันอย่างไร)
ส่วนใน BEA config เล่นตั้ง 2 ค่านี้ซะเวอร์เลย ตั้งไว้ตั้ง 20000

net.ipv4.tcp_fin_timeout = 30
ตั้งค่าน้อยๆเพื่อให้ reuse resource ได้เร็วขึ้น

kernel.shmmax=1073741824
ส่วนนี้เกี่ยวกับ Heapsize ของ Virtual Machine ด้วย
ในคุ่มือ Websphere แนะไว้ว่า
Set the maximum shared segment size to 2300MB plus a little more (about 95MB) (2511724800 = 2300 * 1048576 + 100000000)

(default ของ kernel คือ 32 Mb)
อ่านดูในเอกสาร ส่วนใหญ่มักจะเปลี่ยนให้ค่า shmall เป็นค่าเดียวกันด้วย
แต่ใน config นี้ไม่ได้พูดถึงค่านี้
(ฺBEA ตั้งค่านี้ไว้ 4294967295)

kernel.sched_yield_scale=1
ค่านี้เป็นเรื่องของ SuSE โดยเฉพาะ กรณี SuSE version < 8 SP2A

vm.bdflush=100 1200 128 512 15 5000 100 0 0
ค่านี้เกี่ยวกับ virtual memory
เห็นในเอกสาร Securing and Optimizing Linux บอกไว้ว่าใน redhat default ตั้งค่านี้ไว้ที่
40 500 64 256 500 3000 500 1884 2
ตัวเลขตัวแรกคือ % ของ dirty cache ก่อนที่จะ flush
Setting this to a high value means that Linux can delay disk writes for a long time

ตัวเลขที่ 2 คือ maximum number of dirty buffers that bdflush can write to the disk in one time
ตัวที่ 3 คือ This is the number of buffers that bdflush will add to the list of free buffers when refill_freelist() is called.
ตัวที่ 4 ไม่ค่อยต่างกัน เลยไม่สนใจ
ตัวที่ 5 นี่เกีี่ยวกับ age_buffer ลดลงเยอะมากเลย
อันนี้งงๆ เพราะหน่วงเวลาการ write ด้วย parameter ตัวที่ 1
แต่กลับให้ age_buffer มีอายุน้อยลง

net.ipv4.tcp_synack_retries = 20
fs.file-max=65535
net.ipv4.tcp_sack=0
net.ipv4.tcp_timestamps=0
2 อันหลังนี้รู้สึกจะปิด feature บางอย่างลงไป

ค่าที่เกี่ยวกับ network ลองอ่านเพิ่มเติมได้จาก
Squeeze Your Gigabit NIC for Top Performance
How to achieve Gigabit speeds with Linux
Enabling High Performance Data Transfers
http://www.bur.st/~paul/linux_tuning.txt

ถ้าใครอยากลอง run benchmark นี้บนเครื่องตัวเองบ้าง
ก็เพียงแต่แคะกระปุกออกมา $2000 เท่านั้นเอง

Related link from Roti

Wednesday, August 03, 2005

AJAX in Tapestry with Tacos

Post ก่อนหน้าที่ได้พูดถึง AJAX with Ruby on Rails ไปแล้ว
คราวนี้จะลอง implement feature แบบเดียวกันนี้ใน Tapestry บ้าง
โดยเราจะใช้ Component Library ของ Tacos

เริ่มที่ feature link_to_remote ของ Rails
ใน Tacos ก็มี component ที่ชื่อ PartialLink ที่ทำหน้าที่แบบเดียวกัน
ลองทำตัวอย่างแบบเดียวกับที่ทำใน Rails ก็จะต้องเขียน source code ดังนี้

เริ่มจาก Html Template File
จะประกอบด้วย Component PartialLink ที่ใช้ render html link
กับ Part Component ซึ่งใช้กำหนดบริเวณที่จะเกิด Partial Render
สังเกตุว่าเราจะ pass parameter refreshParts ให้กับ PartialLink ด้วยค่า "time_div"
ที่เป็น id ของบริเวณ Part ที่เราต้องการ render
<html>
<head jwcid="@Shell" title="Test Ajax Partial Link">
</head>

<body jwcid="@Body">
<a jwcid="@tacos:PartialLink" listener="ognl:listeners.refreshTime"
refreshParts="time_div"
>getTime</a>
<div id="time_div" jwcid="@tacos:Part">
<span jwcid="@Insert" value="ognl:currentTime">display time here</span>
</div>
</body>
</html>

ส่วน Page Descriptor File จะประกาศแค่ Property currentTime
มี datatype เป็น String และใช้เป็นแค่ temporary property
ในการเก็บค่าวันที่ที่ต้องการ render
...
<page-specification class="pok.test.TestPage">
<property-specification name="currentTime"
type="java.lang.String"/>
</page-specification>


ส่วนตัว Page Class นั้น จะ implement method ที่ชื่อ refreshTime
ที่จะถูกเรียกใช้เมื่อ user click บน PartialLink Component
package pok.test;

import java.util.Date;

import org.apache.tapestry.IRequestCycle;
import org.apache.tapestry.html.BasePage;

public abstract class TestPage extends BasePage {

public abstract void setCurrentTime(String timeStr);

public void refreshTime(IRequestCycle cycle) {
setCurrentTime(new Date().toString());
}
}


ข้อแตกต่างระหว่าง Tacos กับ Rails ที่เห็นชัดเจน
ก็คือ การ Render ผลลัพท์
ใน rails เราระบุ block ที่จะ render ได้แค่ 1 block
ส่วนใน Tacos เราสามารถระบุ block ได้มากกว่า 1 block
ตัวอย่างเช่น
<body jwcid="@Body">
<p>Test Partial Link</p>
<div id="time_div1" jwcid="@tacos:Part">
<span jwcid="@Insert" value="ognl:currentTime">display time here</span>
</div>
<a jwcid="@tacos:PartialLink" listener="ognl:listeners.refreshTime"
refreshParts="ognl:{'time_div1', 'time_div2'}"
>getTime</a>
<div id="time_div2" jwcid="@tacos:Part">
<span jwcid="@Insert" value="ognl:currentTime">display time here</span>
</div>
</body>

ในตัวอย่างข้างบน เราระบุว่าผลลัพท์ที่ได้จะ render บน
block time_div1 และ time_div2
สังเกตุว่าใน refreshParts parameter เราจะ pass parameter
เป็น Array

ข้อสงสัยอีกอย่างที่ผมสงสัยเจ้า Tacos ก็คือ วิธีการส่งผลลัพท์กลับไปที่ browser
ประเด็นก็คือ มันส่ง page กลับไปทั้งหมด แล้ว javascript ที่ฝั่ง browsser ค่อยเลือก render
เฉพาะที่ต้องการ หรือ ส่งเฉพาะส่วนที่เปลี่ยนแปลงกลับไปเท่านั้น
ก็เลยทดลอง sniff packet ที่รับส่ง
ผลปรากฎว่ามันส่งเฉพาะส่วนที่เปลี่ยนแปลง (ซึ่งดีต่อ traffic)

HTTP/1.1 200 OK
Date: Wed, 03 Aug 2005 10:19:44 GMT
Server: Jetty/5.1.4 (Mac OS X/10.4.2 ppc java/1.4.2_07
Content-Type: text/xml
Transfer-Encoding: chunked

a9
<?xml version="1.0" encoding="UTF-8"?><parts><part id="time_div2">
Wed Aug 03 17:19:45 ICT 2005
</part><part id="time_div1">
Wed Aug 03 17:19:45 ICT 2005
</part></parts>


ตัวอย่างที่ 2 ก็คือ กรณีที่ Rails มี feature form_remote_tag
เจ้า Tacos ก็มี component แบบเดียวกันที่ชื่อ PartialForm
ทดลอง Implement แบบเดียวกับที่ทำกับ Rails ได้ดังนี้



เริ่มจากส่วน Html Template
ในตัว Form จะมี TextField กับ Submit Button
ซึ่งกำหนดไว้ว่า เมื่อมีการ submit จะให้ method ที่ชื่อ partialSubmit ทำงาน
แล้วก็กำหนดให้มีการ render block div_item ใหม่
ส่วนใน block div_item ก็มีการใช้ Foreach component
ในการ render ​Items Array
สุดท้ายก็มี PartialLink ที่ใช้ในการลบข้อมูลใน list ทั้งหมด
<html>
<head jwcid="@Shell" title="Test Partial Form">
</head>

<body jwcid="@Body">
<form jwcid="@tacos:PartialForm" refreshParts="div_item"
listener="ognl:listeners.partialSubmit" direct="true">
<input type="text" jwcid="item@TextField" value="ognl:inputItem"/>
<input type="submit" jwcid="@Submit" label="Save"/>
</form>
<div id="div_item" jwcid="@tacos:Part">
<ul jwcid="@Foreach" source="ognl:items" value="ognl:item">
<li><span jwcid="@Insert" value="ognl:item"/>
</li>
</ul>
</div>
<a jwcid="@tacos:PartialLink" listener="ognl:listeners.clearAll"
refreshParts="div_item"
>Clear All</a>
</body>
</html>


ในส่วน Page Specification ประกอบด้วย
property items ซึ่งเป็น ArrayList
,item เป็นตัวแปรชั่วคราวสำหรับใช้ในการ render foreach loop
,inputItem เป็นตัวแปรที่ใช้รับข้อมูลจาก TextField ที่ user ป้อนเข้ามา
<page-specification class="pok.test.FormPage">

<property-specification name="items" type="java.util.ArrayList"
persistent="yes"/>

<property-specification name="item" type="java.lang.String"/>

<property-specification name="inputItem" type="java.lang.String"/>

</page-specification>


สุดท้ายส่วน Page Class
package pok.test;

import org.apache.tapestry.IRequestCycle;
import org.apache.tapestry.event.PageEvent;
import org.apache.tapestry.event.PageRenderListener;
import org.apache.tapestry.html.BasePage;

public abstract class FormPage extends BasePage implements PageRenderListener {

public abstract String getInputItem();

public abstract String getItem();

public abstract java.util.ArrayList getItems();
public abstract void setItems(java.util.ArrayList lists);

public void pageBeginRender(PageEvent event) {
if (getItems() == null) {
setItems(new java.util.ArrayList());
}
}

public void partialSubmit(IRequestCycle cycle) {
getItems().add(getInputItem());
}

public void clearAll(IRequestCycle cycle) {
getItems().clear();
}

}


ถ้าดูเปรียบเทียบกันระหว่าง Rails กับ Tapestry จะเห็นว่า
Rails ใช้จำนวนบรรทัดของคำสั่งและ config file น้อยกว่า
นอกจากนี้ก็ยังแตกต่างกันในแง่ concept ของการ render ที่ฝั่ง server ด้วย
โดยในส่วนของ Rails ที่ server code สามารถทำ Partial Render ได้
ไม่เหมือนกับ Tapestry ที่ฝั่ง Server จะทำการ Render ทั้ง Page
แต่ PartialService จะเลือกส่งกลับเฉพาะ Part ที่ระบุ

Related link from Roti

AJAX with Ruby on Rails

ใน package ของ Rails จะมี Helper ที่ชื่อ JavaScriptHelper
ที่ช่วยให้เราใช้ AJAX Feature ของ Prototype และ script.aculo.us ใน view ของเราได้ง่ายขึ้น

ตัวอย่างการใช้งาน
เริ่มต้นด้วยการ copy javascript ที่ต้องใช้ไปไว้ที่
directory project/public/javascripts
โดยมี javascript ทั้งหมด 4 ตัว
  • prototype
    • prototype.js (จาก prototype)
  • script.aculo.us
    • effects.js
    • dragdrop.js
    • controls.js

ใน view ที่ต้องการใช้ AJAX Feature ก็ให้ทำการ
include ด้วยคำสั่ง javascript_include_tag
<head>
<%= javascript_include_tag "prototype" %>
<%= javascript_include_tag "effects" %>
<%= javascript_include_tag "dragdrop" %>
<%= javascript_include_tag "controls" %>
</head>

Note: กรณีที่ใช้ script.aculo.us จำเป็นต้องมีการ
patch prototype.js เสียก่อน (ตัว patch หาได้ใน web site ของ script.aculo.us)


ใน prototype จะมี feature หนึ่งที่น่าสนใจ ก็คือ
กรณีที่เรา click link แล้ว prototype จะใช้ xmlhttprequest
เรียกไปที่ server จากนั้นก็จะเอาผลลัพท์ที่ได้ มา render
บนหน้าจอตรงบริเวณที่เรากำหนด (partial render)
โดยเราสามารถใช้งาน โดยใช้ method ของ JavaScriptHelper ที่ชื่อ link_to_remote

<body>  
<%= link_to_remote ("get time",
:update => "time_div",
:url => {:action => "getTime" },
:complete => visual_effect(:highlight, "time_div", :duration => 1.5 )) %>
<div id="time_div">
</div>
</body>

ซึ่ง render ได้หน้าจอดังนี้



เมื่อ user click ที่ "get time" เจ้า prototype ก็จะทำการ call
ไปยัง action ที่ชื่อ getTime ใน Controller ของเรา
ผลลัพท์ที่ได้จาก action ก็จะถูก replace ไปบน <div id="time_div">
class AjController < ApplicationController
...

def getTime
render_text "<p>The Time is " + DateTime.now.to_s + "</p>"
end
end

ผลลัพท์ที่ได้จากการ click ที่ user เห็นก็เป็นแบบนี้



Note: visual_effect จะเป็นการเรียกใช้ script.aculo.us effect
โดยกรณีของเราเรียกใช้ Highlight Effect ซึงจะทำการ เน้นสีเหลือง
บริเวณ <div id="time_div"> เป็นเวลา 1.5 วินาที
แล้วค่อยๆ fade หายไป
ส่วน complete: คือการระบุ callback ที่จะถูกเรียกใช้
หลังจากที่ prototype ทำการ replace DOM element สำเร็จแล้ว


มีบางกรณีที่เราต้องการให้ insert หรือ append ผลลัพท์
แทนที่จะเป็นการ replace
กรณีนี้เราสามารถกำหนดเป็น parameter ให้กับ link_to_remote ได้
<%= link_to_remote ("get time",
:update => "time_div",
:url => {:action => "getTime" },
:position => "after") %>


Method ที่น่าสนใจอีกอันก็คือ form_remote_tag
อันนี้ต่างกับ link_to_remote ตรงที่ว่าเป็นการ submit
form แทน
<body>  
<p>Test Add Item</p>
<%= form_remote_tag(:update => "list_div",
:url => { :action => "addItem" },
:position => "top" ) %>
new Item :
<%= text_field_tag :newItem %>
<%= submit_tag "Add" %>
<%= end_form_tag %>
<ul>
<div id="list_div">
</div>
</body>

ซึ่ง render ได้ผลลัพท์ดังนี้



เมื่อมีการ submit ก็จะเกิดการเรียกใช้ method ที่ชื่อ addItem
class AjController < ApplicationController
...

def addItem
render_text "<li>" + params[:newItem] + "</li>"
end
end

ผลลัพท์ที่ได้ก็จะทำการ insert ลงส่วนบนสุดของ <div id="list_div">



จะเห็นได้ว่า rails integrate AJAX feature ได้อย่างกลมกลืนน่าใช้มาก

เพิ่มเติม: อ่านภาคภาษาอังกฤษ (ต้นฉบับ)

Related link from Roti

Monday, August 01, 2005

MonetDB

วันก่อนอ่านเจอ Opensource Database ที่ชื่อ MonetDB
เจ้า database ตัวนี้เป็นงานวิจัยของ "The Institute for Mathematics and Computer Science Research of The Netherlands"
ออกแบบสำหรับ "high performance on complex queries against large databases" โดยเฉพาะ

ประเด็นที่น่าสนใจของ Monet ก็คือเรื่องของการ Design Storage
เจ้า Monet เลือกวิธีการเก็บ table structure ในลักษณะของ
BAT (Binary Association Tables)
ปกติ database ทั่วไป เวลาที่เก็บข้อมูลจะมองข้อมูล เป็น row
แต่ละ column ใน row หนึ่งจะเก็บอยู่ใน segment เดียวกัน
แต่เจ้า MonetDB เลือกที่จะ split Table หนึ่งออกเป็นหลายๆ BAT
โดยแต่ละ BAT จะเก็บข้อมูลแค่ 1 column เท่านั้น (จริงๆแล้วต้องเป็น
2 เพราะจะมีอีก column หนึ่งจะใช้เก็บ oid (object id))



การเก็บในลักษณะนี้เหมาะสำหรับงานพวก OLAP, Data Mining
หรืองานที่มี nature ของ sql ในลักษณะของการ scan column
เป็นอย่างมาก

Note: บางคนอาจจะสงสัยต่อไปว่า ถ้าเก็บข้อมูลในแบบ BAT แบบนี้
น่าจะเปลืองเนื้อที่ของ harddisk เพราะว่ามี OID ที่ซ้ำๆกันอยู่เต็มไปหมด
เจ้า MonetDB ก็เลยออกแบบให้ oid เป็น virtual column
ไม่มีการเก็บจริง โดยให้ค่าของ oid เริ่มต้นที่ 1000 เรียงไปเรื่อยๆ
ไม่มีการกระโดด กรณีทีต้องการ access BAT ที่ oid=1002
ก็หมายถึง record ในตำแหน่งที่ 2 (1002 ลบด้วย 1000)

หลังจากเห็น concept แล้วก็ลอง load source code มา build ดู
หลังจากลองมั่วอยู่สักพัก ก็ได้ข้อสรุปว่า ยังไม่น่าประทับใจนัก
ไม่แน่ใจว่าเป็นเรื่อง platform ด้วยหรือเปล่า เพราะผม build
บน mac os x มันก็เลยมีปํัญหาประปรายไปหมด
เช่น connect jdbc แล้วสั่ง create user แล้วเงียบหาย
cpu ทำงานไป 15 นาทีแล้วยังไม่ return result กลับมา
หรือ ทดลอง load sample data สำเร็จ แต่ select
แล้วดัน error
ไว้มีเวลา จะลอง load binary version มาทดสอบบน
linux หรือ windows อีกที

จริงๆมีเรื่องน่าสนใจอีกเยอะ สนใจติดตามอ่านดูใน paper นี้
Monet: A Next-Generation DBMS Kernel For Query-Intensive Applications. Ph.D. Thesis, Universiteit van Amsterdam, Amsterdam, The Netherlands, May 2002.
Link

Note: อ่านดูใน paper นี้แล้วในส่วนของ introduction มี
ประวัติของ DBMS ที่น่าสนใจ เลยตัดมาให้อ่านกัน

The first widly known relational implementations (DBMS) were INGRES, developed at UC Berkeley and System R, developed at the IBM San Jose research facility.


INGRES was the first system to use UNIX as its implementation platform.It use the relational query language QUEL, that is not identical, yet very similar, to today's SQL. Several ideas from INGRES can still be found in current relational products, like the concept of a query rewriter for implementing views and integrity constraints, the use of relational tables to store meta-information (the data dictionary) and the idea of extensible query access methods. INGRES was commercialized into product, and from it the Sybase DBMS evolved, later to be re-marketd and enhanced into Microsoft SQLserver.


System R introduced many important concepts like the SQL query language, and much exemplary work in logging and recovery was done in the implementation of the lower levels of the system.


Parts of System R made it into various IBM products like QBE, SQL/DS and indirectly DB2. Although not based on its source code, the Oracle system was designed to resemble System R very closely.


The successor project to INGRES, call Postgres..... Postgres was further developed into a product called Illustra, which was shortly after integrated into the commercial Informix DBMS.


ปัจจุบัน Ingres เป็น opensource ไปแล้วนะครับ
สนใจ download ได้ที่นี่ Link
ยังใช้งานได้ดีที่เดียว

Related link from Roti

Tuesday, July 26, 2005

ทดลองเขียน Hibernate XDoclet 's Eclipse Plugin #3

สืบเนื่องจากการพยายามทำ Eclipse Plugin ที่ช่วย generate
Hibernate mapping file โดยใช้ XDoclet เป็น engine

หลังจากที่ทดสอบการเรียก XDoclet แบบไม่ต้องผ่าน Ant
และ patch เพื่อให้เรียก gen per Class ได้แล้ว
ขั้นถัดไปก็คือการ Implement Builder

เริ่มด้วยการ extends IncrementalProjectBuilder
และทำการ implement method build
protected IProject[] build(int kind, Map args, IProgressMonitor monitor)
throws CoreException {
if (kind == IncrementalProjectBuilder.FULL_BUILD) {
fullBuild(monitor);
} else {
IResourceDelta delta = getDelta(getProject());
if (delta == null) {
fullBuild(monitor);
} else {
incrementalBuild(delta, monitor);
}
}
return null;
}


ตัว IResourceDelta ที่ได้มามีลักษณะเป็น hierarchy structure
ของ Resource ที่เกิดการเปลียนแปลง เช่นสมมติว่าใน project เรา
เจ้า file Server.java (package test) เกิดการเปลี่ยนแปลง
IResourceDelta ที่ได้มาก็จะมี structure ประมาณนี้

IResoucesDelta (src)
|
+--IResourceDelta (test)
|
+---IResourceDelta (Server.java)
|
+---IResourceDelta (Server.class)

Note: สังเกตุว่ามี class file มาด้วย
เพราะ eclipse บอกมันเป็น resource ที่เกิดการเปลี่ยนแปลงด้วย


โดยเราก็จะเลือกเฉพาะ leap node มาทำการ build
และกรองเอาเฉพาะพวกที่เป็น java file เท่านั้น
private void incrementalBuild(IResourceDelta delta, IProgressMonitor monitor) {
ArrayList list = new ArrayList();
getLeapNodes(list, delta);
for (Iterator iter = list.iterator(); iter.hasNext();) {
IResourceDelta rd = (IResourceDelta) iter.next();
if (rd.getProjectRelativePath().getFileExtension().equals("java")) {
docletBuild(rd);
}
}
}


ใน method docletBuild ก็จะ implement ดังนี้

เริ่มด้วยการพยายามหา path ที่จะเป็นตัวตั้งต้น
เพื่อให้ XJavaDoc ใช้ในการ solve หา Java Class ที่จะ parse
ประเด็นก็คือ เราจะหา source path ได้อย่างไร
เนื่องจากส่วนนี้ eclispe เปิดให้ user config ได้ตามใจชอบ

วิธีการหา source path ก็คือ ใช้ getRawClasspath()
ที่ return เป็น IClasspathEntry[] ซึ่งเราเลือกใช้เฉพาะ
พวกที่มีประเภทเป็น CPE_SOURCE


private IPath[] getSourcesPath(IJavaProject jproject)
throws JavaModelException {
ArrayList list = new ArrayList();
IClasspathEntry[] entries = jproject.getRawClasspath();
for (int i = 0; i < entries.length; i++) {

if (entries[i].getEntryKind() == IClasspathEntry.CPE_SOURCE) {
list.add(entries[i].getPath());
}

}
return (IPath[]) list.toArray(new IPath[list.size()]);
}


ส่วนเจ้าตัว IJavaProject หาได้จาก
IResource res = resourceDelta.getResource();
IJavaProject jpr = JavaCore.create(rd.getResource().getProject());


พอได้ sourcepath มา ก็จัดการ add ให้ XJavaDoc
XJavaDoc xdoc = new XJavaDoc();
for (int i = 0; i < sourcePaths.length; i++) {
File dir = new File(rootPath.toString(), sourcePaths[i].toString());
FileSourceSet fs = new FileSourceSet(dir);
xdoc.addSourceSet(fs);
}


จากนั้นก็ใช้ sourcepath มาจัดการตัด path segment ของ
resource ที่เรากำลังจะ compile ให้เหลือเฉพาะ ส่วนที่เป็น package
directory เท่านั้น

IPath resPath = rd.getResource().getProjectRelativePath();
for (int i = 0; i < sourcePaths.length; i++) {
if (sourcePaths[i].removeFirstSegments(1).isPrefixOf(resPath)) {
int cnt = sourcePaths[i].segmentCount();
resPath = resPath.removeFirstSegments(cnt - 1);
resPath = resPath.removeFileExtension();
break;
}
}


จากนั้นก็เข้าสู่ขั้นตอนการใช้ XDoclet

String fullName = resPath.toString().replace('/', '.');
XClass cls = xdoc.getXClass(fullName);

HibernateSubTask sbt = new HibernateSubTask();
DocletContext ctx = new DocletContext("/tmp/gen", null, null,
new SubTask[] { sbt }, new Hashtable(), new HashMap(),
false, false, null);
DocletContext.setSingleInstance(ctx);
ctx.setActiveSubTask(sbt);

ModuleFinder.setClasspath(getPluginClasspath(rootPath));

sbt.init(xdoc);
sbt.generateForClass(cls);


ประเด็นที่เป็นปัญหา ก็คือ เจ้า XDoclet นั่้น
มีวิธีการ solve หา Tag Handler ใน Template
ด้วยวิธี scan XDoclet Module 's jar file ด้วยตัวเอง
ดังนั้นต้องมีการ set ค่า classpath ให้มัน โดยใช้ ModuleFinder
ในกรณีนี้ เราต้องหาว่า library ของเราอยู่ที่ path ไหน
จะได้ทำการ generate classpath string
ออกมาได้
Note: ถ้าดูใน source code ของ XDoclet
จะเห็นว่าเขาใช้ ModuleFinder.initClasspath(this.getClass())
ซึ่งข้างใน method initClassPath(Class clazz)
จะ solve หา classpath ดังนี้
classpath = ((AntClassLoader) clazz.getClassLoader()).getClasspath();
ซึ่งใน case นี้จะมาใช้กับกรณีของเราไม่ได้ เพราะเราไม่ได้ run ใน ant



Class ที่ใช้จัดการกับ plugin resources ก็คือ Bundle

private String getPluginClasspath(IPath rootPath) throws Exception {
Bundle bundle = HbdocletPlugin.getDefault().getBundle();

String requires = (String) bundle.getHeaders().get(
Constants.BUNDLE_CLASSPATH);
ManifestElement[] elements = ManifestElement.parseHeader(
Constants.BUNDLE_CLASSPATH, requires);

String bundleLoc = HbdocletPlugin.getDefault().getBundle()
.getLocation();
if (bundleLoc.startsWith("update@")) {
bundleLoc = bundleLoc.substring(7);
}
StringBuffer buff = new StringBuffer();
for (int i = 0; i < elements.length; i++) {
if (i > 0) {;
buff.append(File.pathSeparatorChar);
}
System.out.println(elements[i].getValue());
buff.append(rootPath.toString()).append(File.separatorChar);
buff.append(bundleLoc);
buff.append(elements[i].getValue());

}
return buff.toString();
}

Note: ตัว path ที่ bundle return กลับมา
ในกรณีที่ launch runtime instance จาก eclipse ในระหว่าง develop
จะมี prefix ที่น่าสนใจติดมาด้วย ก็คือ update@
ซึ่งคิดว่าตอนที่ deploy เป็น plugin จริงๆ
เจ้า prefix นี้คงหายไป


แค่นี้เราก็ได้ incremental xdoclet builder แล้ว
ส่วนกรณี full build ก็ทำเหมือนว่า run จาก ant ได้เลย

ไว้ตอนหน้าจะมาลอง customize เพิ่มให้สามารถ config
File Pattern, Hibernate Dtd version ได้

Related link from Roti

Sunday, July 24, 2005

ทดลองเขียน Hibernate XDoclet 's Eclipse Plugin #2

สืบเนื่องจากการพยายามทำ Eclipse Plugin ที่ช่วย generate
Hibernate mapping file โดยใช้ XDoclet เป็น engine ตอนที่ 1

หลังจากที่ลอง Implement Nature ไปแล้ว
ปัญหาถัดไปที่ต้อง solve ก็คือ
ทำอย่างไรจึงจะ run Doclet โดยไม่ใช้ Ant ได้
โชคดีที่ concept ของ Ant เปิดช่อง
ให้เราสามารถ run Ant task ต่างๆโดยตรงได้

โดยปกติเวลาเราเขียน ant task
สำหรับการ generate mapping file
เราจะเขียนด้วย syntax ดังนี้
<hibernatedoclet destDir="src/dao">
<fileset dir="src/dao">
<include name="mx/rd/web/model/**/*.java"/>
</fileset>

<hibernate version="2.0">
</hibernate>
</hibernatedoclet>

พอมาเขียนให้เรียกใช้จาก java ตรงๆ
ก็เลยเขียนเลียนแบบดังนี้
  
HibernateDocletTask task = new HibernateDocletTask();
task.setDestDir(new File("/tmp/xdoclet"));

HibernateSubTask subtask = new HibernateSubTask();
HibernateVersion ver = new HibernateVersion();
ver.setValue(HibernateVersion.HIBERNATE_3_0);
subtask.setVersion(ver);

task.addSubTask(subtask);

FileSet fs = new FileSet();
fs.setDir(new File("/tmp/xdoclet"));
fs.setIncludes("Role.java");

task.addFileset(fs);
task.setProject(new Project());

task.execute();

เมื่อทดลองเรียกใช้โดยตรง ก็เกิดปัญหาถัดไปว่า
ตัว Eclipse Builder ใช้ Concept Incremental Build
ซึ่งจะเลือก compile เฉพาะบาง file เท่านั้น
แต่เจ้าตัว XDoclet ใช้ concept Batch Build (เวลาทำ ทำเป็น set)
ถ้าเรา implement ตรงๆ Performance คงออกมาไม่ดีเท่าไร
เช่นสมมติว่าตัว Eclipse Builder
ระบุว่าต้องการให้ build file X.java แต่เจ้าตัว XDoclet
ดันไป scan project directory ทั้งหมด เพื่อที่จะได้เจ้าตัว
X.java มาตัวเดียว

ดังนั้นเราก็เลยพยายาม hack ต่อไป เพื่อที่จะหาว่า
จะสามารถเรียกใช้โดยตรงได้อย่างไรบ้าง
ก็ไปพบว่า มันมี method อยู่ตัวหนึ่งใน TemplateSubTask
(ซึ่งเป็น superclass ตัวหนึ่งของ HibernateSubTask)
ที่ชื่อ public void generateForClass(XClass clazz)
โชคไม่ดีที่ method นี้มี modifier เป็น protected
ทำให้เราไม่สามารถเรียกใช้ตรงๆได้
ก็เลยต้องใช้วิธีไม้ตายขั้นสุดท้าย
ก็คือเข้าไปแก้ไข source code ของ XDoclet โดยตรงเลย
โดยทำการเปลี่ยนจาก protected ให้เป็น public

Note ได้มีการทดลอง extends HibernateSubTask แล้ว override เปลี่ยน modifier ของ method generateForClass ตรงๆ แล้ว ปรากฎว่าไปเกิดปัญหาในเรื่อง namespace ของ template

เจ้า method generateForClass นี้ parameter ที่รับก็คือ XClass (อยู่ใน library ของ XJavaDoc ที่เป็นโครงสร้างพื้นฐานของ XDoclet)
สามารถหามาได้โดยวิธีนี้
    XJavaDoc xdoc = new XJavaDoc();
FileSourceSet fs = new FileSourceSet(new File("/tmp/xdoclet"));
xdoc.addSourceSet(fs);
XClass cls = xdoc.getXClass("mx.Role");


หลังจากทดลอง run ก็พบว่า ยังมี property หลายตัว
ที่ยังไม่ถูก initialize (เนื่องจากเราไม่ได้ run จากช่องทางปกติ)
ก็เลยต้อง initialize เพิ่มเติมดังนี้
    HibernateSubTask sbt = new HibernateSubTask();      

DocletContext ctx = new DocletContext("/tmp/gen",
null, null, new SubTask[] {sbt},
new Hashtable(), new HashMap(), false, false, null);
DocletContext.setSingleInstance(ctx);
ctx.setActiveSubTask(sbt);

ModuleFinder.initClasspath(getClass());
sbt.init(xdoc);


หลังจากเรียก initialize property ต่างๆแล้ว
ก็สามารถเรียก generate โดยตรงได้ดังนี้
    sbt.generateForClass(cls);

Related link from Roti

Saturday, July 23, 2005

Shale Clay

หลายคนคงเคยได้ยิน stuts version ใหม่
ที่ชื่อว่า Shale ไปแล้ว
ตัว shale เองใช้ JSF (Java Server Face)
ในการ implement view
ดังนั้นในส่วนของ view ก็จะมีลักษณะเป็น
component model
ทำให้เราสามารถ reuse component ได้ดีกว่าเดิมมาก

เมื่อเทียบกับ Tapestry ซึ่งเป็น framework
ที่เป็น component model เช่นกันแล้ว
จุดด้อยของ JSF เมื่อเทียบกับ Tapestry ก็คือ
เราไม่สามารถเห็นหน้าตา output ได้ก่อนที่จะ run
(Tapestry View จะมีหน้าตาเหมือน Html
ทุกอย่าง สามารถ edit ด้วย html editor อะไรก็ได้)
ยกเว้นว่าไปหาซื้อ IDE ที่สามารถ design JSF
แบบ WYSWYG ได้

มาวันนี้ได้ข่าวจาก blog ของ Shale
เขาบอกข่าวดีว่า ตอนนี้ได้มีการ integrate
contribute code ชุดใหม่เข้าไป
ทำให้ Share สามารถใช้ view
ในลักษณะเดียวกับ Tapestry ได้แล้ว
โดยชื่อ code ชุดนี้ที่ integrate เข้าไปถูกตั้งชื่อว่า "Clay"

Wow ตอนนี้คุ่แข่งของ Tapestry เพิ่มมาอีกหนึ่งแล้ว

Note:
ตัวอย่างพวกที่ implement view แบบ Tapestry
ก็มี WebObject ของ Mac และก็ Wicket
(ซึ่งลอก idea ไปจาก Tapestry)

เปรียบเทีียบวิธีการ implement view
(เฉพาะส่วน html template)

Tapestry
<html>
...
<a href="#" jwcid="@DirectLink"
listener="ognl:listeners.clickMe">Create an account</a>
...
</html>


Shale Clay
<html>
...
<a href="#" jsfid="dialogLink">Create an account</a>
...
</html>


WebObject
<html>
...
<p><WEBOBJECT name="NAME_FIELD"><input
type="text"></WEBOBJECT>
<WEBOBJECT name="SUBMIT_BUTTON"><input type="submit"></WEBOBJECT></p>
...
</html>

Related link from Roti