Sunday, June 19, 2005

ใช้ jEdit ช่วยทำ Source Highlighting #2

วิธีการเขียน jEdit Plugin ก็คือ
เริ่มต้นที่ Plugin Core Class โดยเขียนง่ายๆดังนี้

package highlight;

public class HighlightPlugin extends EditPlugin {
public void start() {
}

public void stop() {
}

}

ในส่วน start กับ stop ใช้ในการเตรียม resources
ต่างๆที่ plugin จะใช้
ในกรณีของเราไม่ต้องการทำอะไรก็เลยเว้นไว้

จากนั้นก็ทำการเขียน plugin properties file
โดยมีชื่อเดียวกับ plugin class แต่ตัด word "Plugin"
ที่ต่อท้ายออก ในกรณีนี้ใช้ชื่อว่า Highlight.props

plugin.highlight.HighlightPlugin.activate=defer
plugin.highlight.HighlightPlugin.name=HighLight
plugin.highlight.HighlightPlugin.author=pphetra
plugin.highlight.HighlightPlugin.version=0.1
plugin.highlight.HighlightPlugin.depend.0=jedit 04.00.03.00

# Menu
plugin.highlight.HighlightPlugin.menu=highlight.convert

# Menu action labels
highlight.convert.label=convert to clipboard


ในตัว props จะประกอบด้วย 2 ส่วนคือ
ส่วน metadata กับส่วน menu
"highlight.convert" ก็คือ menuItem
ที่จะ add เข้าไปใน Plugin Menu

โดย ค่าของ plugin.highlight.HighLightPlugin.menu
มีความหมายว่า
  • ให้ add menu ที่มี label เป็นชื่อเดียวกับ Plugin Name เข้าไปใต้
    Plugins Menu
  • add menuItem ที่ชื่อ highlight.convert
    เข้าเป็น menuItem ของ menu ข้างบน


ส่วนกรณีที่มี submenu หลายๆอัน ก็แค่ใส่เรียงกันไป
เหมือนตามตัวอย่างข้างล่างนี้

plugin.highlight.HighLightPlugin.menu=highlight.current-buffer highlight.current
-selection highlight.dump

# Menu action labels
highlight.current-buffer.label=Current buffer
highlight.current-selection.label=Current selection
highlight.dump.label=dump


จากนั้นก็เขียน action mapping file
ที่ชื่อ actions.xml

<?xml version="1.0"?>
<!DOCTYPE ACTIONS SYSTEM "actions.dtd">
<ACTIONS>
<ACTION NAME="highlight.convert">
<CODE>
plugin = org.gjt.sp.jedit.jEdit.getPlugin("highlight.HighLightPlugin"
, true);
plugin.convert(textArea, view);
</CODE>
</ACTION>
</ACTIONS>

หลักการของ file นี้ก็คือ เราจะกำหนดว่า
เมื่อ action ถูก trig ให้ทำงาน
จะต้องทำงานอะไร
ซึ่งการสั่งว่าต้องทำงานอะไรนั้น จะใช้ BeanShell script
สำหรับในตัวอย่างนี้ กรณีที่ action highlight.convert
ถูกเรียกใช้ ก็จะทำการเรียกใช้
method convert ใน HighlightPlugin ของเรา

เพิ่ม method convert(..) เข้าไปใน HighlightPlugin ของเราดังนี้

public void dump(JEditTextArea textArea, View view) {
Selection[] selection = textArea.getSelection();
StringWriter sw = new StringWriter();
BufferedWriter out = new BufferedWriter(sw);

int last = 0;
for (int i = 0; i < selection.length; i++) {
if (selection[i].getEndLine() > last) {
last = selection[i].getEndLine();
}
}

// Sort selections by their start lines
MiscUtilities.quicksort(selection, new SelectionStartLineCompare());
int lastLine = -1;
for (int i = 0; i < selection.length; i++) {
int physicalFirst = selection[i].getStartLine();
int physicalLast = selection[i].getEndLine();

if (physicalLast <= lastLine) {
continue;
}

formatText(out, textArea, Math.max(physicalFirst, lastLine + 1),
physicalLast);

lastLine = physicalLast;
}
try {
out.flush();
out.close();
} catch (IOException io) {

}

Registers.setRegister('$', sw.toString());
}

จะเห็นว่าใน source code มีการทำ sorting ด้วย
เนื่องจาก jEdit สามารถทำ multiple selections ได้
จึงต้องจัดเรียงลำดับ block ให้ถูกต้องก่อน
จากนั้นก็ใล่ loop เพื่อทำการ format
แต่ละ block ให้เป็น html

ในการ convert เป็น html นั้น เราจะใช้
feature ของ jedit ในส่วนของ syntax highlighting เอง
โดยเราสามารถ access SyntaxStyle จาก JEditTextArea ได้
ซึ่ง jedit กำหนดไว้ว่า แต่ละภาษา
สามารถ config Token type ได้สูงสุด 18 แบบ
โดยวิธีกำหนด style ของแต่ละ syntax สามารถดูได้จาก
link นี้

source code ในส่วนที่แปลงเป็น html
ไม่ได้ลงไว้ให้ดูครับ เนื่องจากมันค่อนข้างยาว
ผู้ที่สนใจสามารถ download Code2HTML plugin
มาดูเป็นตัวอย่างได้

Related link from Roti

ใช้ jEdit ช่วยทำ Source Highlighting #1

อยากให้ blog ของตัวเอง
สามารถแสดง syntax highlighting ในส่วน source code ได้มานานแล้ว
แต่ยังไม่มีโอกาสสักที วันนี้ก็เลยมองหา solution ดู

กรณีที่เป็น unix command line
ก็จะมี tool ที่เรียกว่า source-highlight อยู่แล้ว
สามารถเรียกใช้ด้วยคำสั่ง
source-highlight -s java -f html -cstylename Main.java

แต่เนื่องจากวิธีนี้ไม่ค่อยสะดวกสบายนัก
เพราะขณะที่เราเขียน blog ด้วย editor
การ switch ไปใช้ command line ก็ไม่ใช่ทางเลือกทีี่ดีเท่าไร
(แถม output ที่ได้ ยังเป็น html ตัวเต็มๆ
ซึ่งเราต้องการแค่บางส่วนเท่านั้น )

ทางเลือกที่ 2 ที่เจอก็คือ
xmltools
แนวทางนี้ก็คือเขียน blog เป็น xml file เลย
จากนั้นก็ทำการ transform xml file นั้น
ทางเลือกนี้ก็น่าสนใจดี
เหมาะอย่างยิ่งสำหรับการเขียนเอกสาร ในลักษณะของ xml document
แต่ก็ยังไม่ถูกใจอยู่ดี

ทางเลือกที่ 3 ก็คือเขียนเป็น editor plugin หรือ script
ให้สามารถเลือกส่วนที่ต้องการ convert
จาก editor โดยตรง จากนั้นก็สั่ง
transform เพื่อให้เป็น html ที่ต้องการ

ตกลงใจเลือกทางเลือกที่ 3
จากการค้นดูใน net พบว่า
เจ้า jEdit มี plugin ที่ชื่อ Code2HTML อยู่
ก็เลยลอง load มาใช้
ผลลัพท์ที่ได้ก็ยังไม่น่าพอใจนัก เพราะ code ที่ gen
ออกมานั้นยังทำส่วน CSS ไม่ดีนัก

คำว่าไม่ดี ก็คือมันมี css element
ที่มีชื่อเป็น syntax1, syntax2, syntax3 ... ออกมาเต็มไปหมด
นอกจากนี้ การ gen ก็เป็นการ gen file html ทั้ง file
คือมี tag html, head, body ออกมาด้วย

สุดท้ายก็เลยทำการเขียน jEdit Plugin ขึ้นมาเอง
โดยใช้ตัวอย่าง code จาก code2HTML plugin
ซึ่งก็ได้ความมันส์พอสมควร เพราะมีบางอย่างที่คู่มือไม่ได้อธิบายไว้
ต้องไล่จากตัว source code ของ jedit เอง

Related link from Roti

Saturday, June 18, 2005

ปัญหา Common Logging + Log4j ใน Web Application

สงสัยมานานแล้ว ว่ามันทะแม่งๆ
วันดีคืนดีก็เจอปัญหาแปลกๆ LogConfigurationException บ้าง
log หายบ้าง
วันนี้เจอ article เรื่อง
Taxonomy of class loader problems encountered when using Jakarta Commons Logging

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

เอาเป็นว่าเขาสรุปทางแก้ไขไว้ดังนี้

เลิกใช้ jakarta common logging
ในกรณีที่ต้องการ abstract logging (พวกที่ switch ได้ว่า
จะใช้ log4j หรือ jdk1.4 log หรืออื่นๆ..ได้)
ให้เปลี่ยนไปใช้ UGLI แทน
แต่ถ้าไม่สน ก็ใช้ log4j ไปตรงๆเลย

สำหรับ tomcat 5.0.27 or later
  • เปลี่ยนไปใช้ jakarta commons logging ที่ version >= 1.0.4
  • อย่าไปยุ่งกับ file TOMCAT_HOME/bin/commons-logging-api.jar
  • ให้นำ file commons-logging.jar กับ log4j
    ไปวางไว้ที่ TOMCAT_HOME/commons/lib
  • อย่า include log4j, commons-logging.jar ใน WEB-INF/lib
  • ห้าม set system properties พวกนี้
    org.apaceh.commons.logging.LogFactory
    org.apache.commons.logging.Log


Note: พวกที่ใช้ Resin 2.0.x, Jetty ตามอ่านเอกสารเอาเองนะ
เพราะผมไม่ได้ใช้ เลยไม่ได้ลอกมาให้ดู

อ้างอิง
สำหรับคนที่ชอยอ่านประเด็นมากกว่านี้
ให้ดูที่ Link นี้

Related link from Roti

ปัญหา Memory Leak ใน Web Application

หลายคนคงจะเคยเจอปัญหา
OutOfMemoryError
หลังจากที่ทำ hot restart
Web Application ไปซักระยะหนึ่ง

วันนี้อ่านพบสาเหตุของปัญหาใน blog THE ART OF .WAR
เขาอธิบายและทำ reference ไว้ดีมาก

ส่วนคนที่ขี้เกียจอ่านโดยละเอียด
ผมจะสรุปให้ฟังคร่าวๆดังนี้

สาเหตุหลักของปัญหานี้เกิดที่ตัว Application ClassLoader
(war แต่ละตัวมี Application ClassLoader ของใครของมัน)
เมื่อใดก็ตามที่ AppServer ทำ hot restart
เจ้า AppServer ก็จะทำการโยน ClassLoader ตัวเก่าทิ้งไป
และทำการ new ClassLoader ตัวใหม่ขึ้นมา
ปัญหาจะเกิดขึ้นเมื่อยังมี Reference ที่ยังคงชี้ไปยัง ClassLoader ตัวเก่าอยู่
ทำให้ Garbage Collection ไม่สามารถเก็บกวาด ClassLoader ตัวเก่าได้

ตัว object ที่สามารถทำให้เกิดสาเหตุแบบนี้ได้
สามารถแบ่งออกเป็น 2 ประเภทคือ
  • JVM level Singletons
  • ThreadLocal object


ประเด็นของ JVM Singleton มีตัวที่เป็นสาเหตุหลักๆอยู่ 2 ตัวก็คือ
  • java.sql.DriverManager
  • java.beans.Introspector


ในส่วนของ DriverManager จะเกิดปัญหาเมื่อ
jdbc library ของเราวางอยู่ใน /WEB-INF/lib
เนื่องจาก DriverManager อยู่ใน System ClassLoader
ตัว jdbc อยู่ใน Application ClassLoader
ดังนั้นจะเกิด reference เชื่อมระหว่าง
System ClassLoader ไปถึง Application ClassLoader
ทำให้ GC ไม่สามารถเก็บกวาดได้

ทางแก้ไขก็คือ implements ServletContextListener
ใน Web App เพื่อที่จะทำการ deregister driver
ออกจาก DriverManager
    public void contextDestroyed(ServletContextEvent event) {
log.info("Shutdown");
Enumeration drivers = DriverManager.getDrivers();
while (drivers.hasMoreElements()) {
Driver o = (Driver) drivers.nextElement();
if(doesClassLoaderMatch(o)){
log.info("The current driver '" + o + "' is being deregistered.");
try {
DriverManager.deregisterDriver(o);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}else{
log.info("Driver '" + o + "' wasn't loaded by this webapp, so no touching it.");
}
}
}


สำหรับเจ้า Introspector (ตัวนี้จะใช้เยอะใน Spring Framework)
ก็เป็นปัญหาแบบเดียวกับ DriverManager
นั่นคือเกิด reference เชื่อมระหว่าง SystemClassLoader <--> Introspector
<--> cached Class <--> Application ClassLoader

วิธีแก้ไขก็ใช้วิธีเดียวกับ DriverManager
public void contextDestroyed(ServletContextEvent event) {
// Driver Clean up stuff omitted

// Flushes the cache of classes
java.beans.Introspector.flushCaches();
}


สำหรับประเด็นที่ 2 ที่เป็นปัญหาก็คือการใช้ ThreadLocal
ปัจจุบันนิยมใช้กันมาก
เช่นพวกกลุ่ม lightweight container, Web App. Framework
dom4j (ใช้เก็บ cache)
แม้แต่เจ้า Axis เองก็ใช้ (พวกนี้ยังไม่เห็นด้วยตานะ)

ผมชอบที่ THE ART OF .WAR เขาตั้งหัวข้อประเด็นนี้ว่า
ThreadLocals - With Great Power, comes Great Responsibility.


ประเด็นเรื่อง ThreadLocal นี้ก็เหมือนๆกับ เจ้า 2 ตัวบน
ก็คือมี reference ชี้ไปถึง Application ClassLoader
วิธีแก้ไขก็เช่นเดียวกัน ก็คือต้องมีการเรียกใช้ ThreadLocal.set(null)
หลังจากเลิกใช้แล้ว

ในส่วนของ Spring ก็มีพูดถึงใน mailing list ว่า
For Introspector-related problems in third-party libraries, Spring ships an
IntrospectorCleanupListener for web applications, to be registered as listener in web.xml. This
simply clears the entire JVM-level cache of the JavaBeans Introspector on web app shutdown.

For ThreadLocals, there's nothing we can do to clean up after third-party libraries, I'm afraid. So the
only way to move forward there is to make the developers aware of those issues in their products.

Related link from Roti

Acegi อีกที

ช่วงนี้ได้ใช้ acegi จริงๆแล้ว
ก็เลยขอบันทึกไว้กันลืมหน่อย

ขอเท้าความนิดหนึ่งก่อน
ตัว acegi เป็นส่วนเสริมของ spring framework
ที่ช่วยจัดการกับเรื่อง security โดยเฉพาะ
คำถามก็คือ ใน spec ของ j2ee ก็มีการกำหนด spec
เรื่อง Container Managed Security ไว้แล้วเช่นกัน
ทำไมเราไม่ใช้ตัวนั้น

ปัญหาของ container managed security ใน j2ee
ก็คือ portable กับ extension
ผมเคยพัฒนาโปรเจคโดยใช้ jboss โดยมีเป้าหมาย
ให้ deploy production บน websphere
ตอนแรกก็กะว่าจะใช้ Container Managed Security
แต่ปรากฎว่าพบปัญหา conflict เรื่อง portable กับ extension นี่แหล่ะ
คือในกรณีถ้าอยาก customize เพิ่ม ก็จะเกิดปัญหาในส่วน portable
สุดท้ายผมก็ต้องเปลี่ยนไปเป็น Application Managed Security แทน

ปัญหาอย่างหนึ่งของ acegi ที่เจอก็คือ
วิธีการ ร้อย process เข้าหากัน
ข้อดี ก็คือมันเปิดโอกาสให้เรา customize ได้เกือบทุกจุด
แต่ก็เกิดข้อเสีย ก็คือ learning curve ก็เลยสูงพอสมควร
เนื่องจากคน config เห็นทุกอย่างปรากฎแก่สายตา
โดยไม่มีการกรอง

คำเตือน
  • สำหรับผู้ที่ไม่คุ้นกับ xml file, spring configuration style
    การอ่านบรรทัดล่างๆต่อไปนี้ เป็นอันตรายต่อสายตาอย่างยิ่ง
  • การอ่านบทความต่อไปนี้ จะทำให้เกิดการง่วงนอนได้
    กรุณาอย่าอ่าน ขณะขับรถ หรือควบคุมเครื่องจักร


เริ่มแรกให้ทำการ add filter เข้าไปใน web.xml ก่อน
    <filter>
<filter-name>securityFilter</filter-name>
<filter-class>net.sf.acegisecurity.util.FilterToBeanProxy</filter-class>
<init-param>
<param-name>targetClass</param-name>
<param-value>net.sf.acegisecurity.util.FilterChainProxy</param-value>
</init-param>
</filter>
<filter>

เจ้า FilterToBeanProxy เป็น proxy ที่คอย
delegate request ไปให้ FilterChainProxy
ที่เป็นพระเอกตัวจริงของเรื่องนี้
โดย FilterToBeanProxy จะมองหา TargetClass
จาก Spring bean context (acegi ทำงานร่วมกับ spring เสมอ)

จากนั้นเราก็ทำการ define FilterChainProxy
ผ่านทาง spring configuration file
(ตัว Appfuse ใช้ชื่อ file ว่า
appContext-security.xml)
    <!-- ======================== FILTER CHAIN ======================= -->
<bean id="filterChainProxy" class="net.sf.acegisecurity.util.FilterChainProxy">
<property name="filterInvocationDefinitionSource">
<value>
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/j_security_check*=httpSessionContextIntegrationFilter,
authenticationProcessingFilter
/*.html*=httpSessionContextIntegrationFilter,
remoteUserFilter,
anonymousProcessingFilter,
securityEnforcementFilter
/*.jsp=httpSessionContextIntegrationFilter,
remoteUserFilter
</value>
</property>
</bean>

ในตัวอย่างนี้จะเห็นได้ว่า เรากำหนด chain ของ filter
ให้แต่ละกลุ่ม url pattern
โดยแต่ละกลุ่ม url ต้องการ processing ที่ไม่เหมือนกัน
อย่าง url /j_security_check เป็น request
ที่เกิดจากการ submit หน้าจอ login
ดังนั้นก็เลยต้องใส่ filtler ที่ชื่อ authenticationProcessingFilter
เข้าไป เพื่อที่จะรับหน้าที่ทำ authenticate

ที่นี้ก็มาว่าต่อว่า การ authenticate นั้นทำอย่างไร
<bean id="authenticationManager" class="net.sf.acegisecurity.providers.ProviderManager">
<property name="providers">
<list>
<ref local="daoAuthenticationProvider"/>
<ref local="anonymousAuthenticationProvider"/>
</list>
</property>
</bean>

หลักการก็คือเจ้า ProviderManager จะทำการ
iterate providers list ที่เราใส่เข้าไป
โดยถ้าตัวไหนมี response ว่า authenticate ได้แล้ว ก็จะหยุด iterate
และใช้ผลลัพท์ที่ได้นั้น
กรณีของเรา ก็คือ เราใช้ DaoAuthenticationProvider
ซึ่งกำหนดจะมีการใช้ pattern dao ในการหาข้อมูล user,password มาทำการ authenticate
โดยเราต้อง add bean เพิ่มขึ้นอีกดังนี้
<bean id="daoAuthenticationProvider" class="net.sf.acegisecurity.providers.dao.DaoAuthenticationProvider">
<property name="authenticationDao"><ref local="jdbcAuthenticationDao"/></property>
<property name="userCache"><ref local="userCache"/></property>
</bean>

<!-- Read users from database -->
<bean id="jdbcAuthenticationDao" class="net.sf.acegisecurity.providers.dao.jdbc.JdbcDaoImpl">
<property name="dataSource"><ref bean="dataSource"/></property>
<property name="usersByUsernameQuery">
<value>SELECT username,password,enabled FROM app_user WHERE username = ?</value>
</property>
<property name="authoritiesByUsernameQuery">
<value>SELECT username,role_name FROM user_role WHERE username = ?</value>
</property>
</bean>

จะเห็นว่า เจ้า DaoAuthenticationProvider จะใช้
JdbcDaoImpl เป็นตัว query ข้อมูลจาก database ตรงๆ
โดยมี query อยู่ 2 แบบก็คือ
authenticate query อันนี้ใช้เพื่อตรวจ login, password
กับ authorities query ซึ่งใช้เพื่อตรวจสิทธิการทำงาน

Note: จะเห็นว่ามีการ config userCache ด้วย
อันนี้ก็คือ cache ที่ช่วยลด query ที่เกิดกับ database ลง

ย้อนกลับไปที่ ProviderManager ข้างบนนิดหนึ่ง
จะเห็นได้ว่ามีการ config AnonymousAuthenticationProvider ด้วย
ตัวนี้ก็คือ feature ที่ช่วยให้คนที่ไม่ได้ login นั้นได้รับการ assign
บทบาทเป็น Anonymous

เอาหล่ะหลังจากที่ได้กำหนด filterchain กับ วิธีการ query ข้อมูลไปแล้ว
ก็มาถึงขั้นตอนการกำหนดว่า url ไหนต้องใช้ role อะไรในการเข้าถึง

<!-- Note the order that entries are placed against the objectDefinitionSource is critical.
The FilterSecurityInterceptor will work from the top of the list down to the FIRST pattern that matches the request URL.
Accordingly, you should place MOST SPECIFIC (ie a/b/c/d.*) expressions first, with LEAST SPECIFIC (ie a/.*) expressions last -->
<bean id="filterInvocationInterceptor" class="net.sf.acegisecurity.intercept.web.FilterSecurityInterceptor">
<property name="authenticationManager"><ref local="authenticationManager"/></property>
<property name="accessDecisionManager"><ref local="accessDecisionManager"/></property>
<property name="objectDefinitionSource">
<value>
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/signup.html=ROLE_ANONYMOUS,admin,tomcat
/passwordhint.html*=ROLE_ANONYMOUS,admin,tomcat
/*.html*=admin,tomcat
</value>
</property>
</bean>

จะเห็นได้ว่าตัว /*.html* นั้นเราอนุญาติให้ใช้ได้เฉพาะ
บทบาท admin และ tomcat เท่านั้น
ส่วนหน้าจอ signup (register เพื่อขอใช้บริการ) นั้น
อนุญาติให้ บทบาท anonymous (พวกที่ยังไม่ได้ login) เรียกใช้ได้

อธิบายเพิ่มเติมอีกหน่อย เนื่องจาก ObjectDifinitionSource นั้นเป็นเพียงแค่ชุด string
ธรรมดา จำเป็นต้องมี parser ที่เข้ามาอ่านและทำความเข้าใจอีก ก็เลยต้องมี
การ config AccessDecisionManager เข้ามาจัดการ
โดยในที่นี้ ก็คือใช้ role เป็นตัวตัดสินว่าจะ อนุญาติให้ใช้หรือไม่ให้ใช้
    <bean id="accessDecisionManager" class="net.sf.acegisecurity.vote.AffirmativeBased">
<property name="allowIfAllAbstainDecisions"><value>false</value></property>
<property name="decisionVoters">
<list>
<ref local="roleVoter"/>
</list>
</property>
</bean>

<bean id="roleVoter" class="net.sf.acegisecurity.vote.RoleVoter"/>


เฮ้อมาถึงตรงนี้ก็เริ่มเหนื่อยแล้ว
แต่ยังเหลือ bean อีก 3 ตัวที่ config ไว้ใน filterChain
ที่ยังไม่ได้พูดถึง

....
/*.html*=httpSessionContextIntegrationFilter,
remoteUserFilter,
anonymousProcessingFilter,
securityEnforcementFilter
....

ตัวแรกก็คือ SecurityEnforcementFilter
ทำหน้าที่ดัก check สิทธิ ว่าใครทำอะไรได้ไม่ได้ รวมทั้ง check ด้วยว่า
มีการ login มาแล้วหรือยัง ถ้ายัง ก็จะ redirect ไปยังหน้าจอ login ให้ด้วย
(โดยการอ้างอิงผ่าน bean authenticationEntryPoint)
    <bean id="securityEnforcementFilter" class="net.sf.acegisecurity.intercept.web.SecurityEnforcementFilter">
<property name="filterSecurityInterceptor"><ref local="filterInvocationInterceptor"/></property>
<property name="authenticationEntryPoint"><ref local="authenticationProcessingFilterEntryPoint"/></property>
</bean>

<bean id="authenticationProcessingFilterEntryPoint" class="net.sf.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint">
<property name="loginFormUrl"><value>/login.jsp</value></property>
<property name="forceHttps"><value>false</value></property>
</bean>


ตัวที่ 2 ก็คือ RemoteUserFilter
    <bean id="remoteUserFilter" class="net.sf.acegisecurity.wrapper.ContextHolderAwareRequestFilter"/>

ตัวนี้อ้างถึง ContextHolderAwareRequestFilter ซึ่งไม่ได้ทำหน้าที่อะไรมาก
หน้าที่หลักก็แค่ สร้าง HttpServletRequestWrapper เพื่อห่อ HttpServletRequest ที่ได้จาก servlet container เท่านั้น

ตัวที่3 ก็คือ HttpSessionContextIntegrationFilter
    <bean id="httpSessionContextIntegrationFilter" class="net.sf.acegisecurity.context.HttpSessionContextIntegrationFilter">
<property name="context"><value>net.sf.acegisecurity.context.security.SecureContextImpl</value></property>
</bean>

ทำหน้าที่เป็น context หลักของ acegi framework
(context ของ acegi จะ sync ลง HttpSession หลังจากจบ request)

พอก่อนดีกว่า
ใครที่อ่านมาได้ถึงตรงนี้ ขอชมว่ามีความอดทนดีมาก

Related link from Roti

Friday, June 17, 2005

TestNG

ได้ยินชื่อ TestNG มาระยะหนึ่งแล้ว
วันนี้ได้ฤกษ์ทดลอง load มาลองเล่นดู

TestNG ก็คือ Testing Framework
เช่นเดียวกับกับ JUnit นั่นเอง
ส่วนประเด็นที่ต่างกันก็คือ

  • TestNG นำ Annotation(jdk1.5)
    หรือ javadoc tag(กรณี jdk1.4) เข้ามาใช้
    เดิมใน JUnit ตัว testcase จะต้อง extends class TestCase
    แต่ใน TestNG เราไม่ต้อง extends หรือ implements อะไรเลย
    โดยเราจะเปลี่ยนมาใช้ annotation มาเป็นตัวอธิบาย nature
    ของ testcase แทน

    ยกตัวอย่างการเขียน test case ด้วย TestNG
    import com.beust.testng.annotations.*;
    public class SimpleTest {
    @Configuration(beforeTestClass = true)
    public void init() {
    ...
    }

    @Test(groups = {"basic"})
    public void testMe() {
    .... test go here.
    }
    }


    @Configuration เป็นการบอกให้รู้ว่า method ที่ระบุนั้น
    ต้องการให้ run เมื่อไร เช่น run ก่อน test method
    หรือ run หลัง test method
    กรณีนี้ก็คือ เราบอกให้ run method init ก่อนที่
    test method testMe ใน SimpleTest จะ run

    @Test เป็นการบอกให้รู้ว่า method ที่ระบุ
    เป็น test ที่ต้องการ run
    โดย parameter group ช่วยให้เราจัดกลุ่ม
    การ test ได้สะดวกขึ้น


  • การกำหนด test ที่จะ run
    TestNG กำหนดสิ่งที่จะ run ผ่าน xml file
    ตัวอย่าง
    <!DOCTYPE suite SYSTEM "http://beust.com/testng/testng-1.0.dtd">
    <suite name="run">
    <test name="runme">
    <classes>
    <class name="test.SimpleTest"/>
    </classes>
    </test>
    </suite>


    และ run โดยคำสั่ง
    java com.beust.testng.TestNG testng.xml



  • ความสามารถในการจัดกลุ่มการ test
    ใน junit เราสามารถจัดกลุ่ม test ได้โดยใช้
    suite เข้ามาช่วย แต่ก็มีลักษณะเป็นกลุ่มของ
    class
    แต่ใน TestNG เราสามารถจัดกลุ่มได้ละเอียด
    ถึงขั้น method เลย โดยแต่ละ method สามารถ
    กำหนดกลุ่มที่สังกัดได้มากกว่า 1 กลุ่ม

    นอกจากนี้ยังสามารถกำหนด กลุ่มของกลุ่ม (group of groups)
    ,partial groups, exclusion groups ในการ test ได้


  • TestNG สามารถกำหนด parameter ให้กับ Test Method ได้
    ลองดูตัวอย่าง

    @Parameters({"first-name"})
    @Test
    public void testMatchName(String firstName) {
    assert "pphetra".equals(firstName);
    }

    จากนั้นเราก็ระบุ parameter value ใน xml file
    ​<suite name="my suite">
    <parameter name="first-name" value="pphetra"/>

    <test name="simple>
    ....
    ...



  • การกำหนดลำดับของ test method
    ใน testNG สามารถกำหนด dependency ของ test method ได้
    เช่น
    @Test(groups = { "init"})
    public void serverStartedOK() {...}

    @Test(groups = { "init" })
    public void initEnvironment() {...}

    @Test(dependsOnGroups = { "init" })
    public void method1() {...}


    นอกจากกำหนด dependOnGroups ได้แล้ว
    ยังกำหนด dependOnMethods ได้อีกด้วย


  • TestNG สามารถกำหนด Factories เพื่อใช้ในการ
    สร้าง TestCase แบบ Dynamic ได้
    ความหมายของ Dynamic ก็คือเราสามารถ instantiate
    TestCase ขึ้นมาได้หลายๆแบบ ผ่านทาง Factory
    class ที่เราเขียนเอง
    เช่น

    public class TestFactory {
    @Factory
    public Object[] createInstance() {
    Object[] result = new Object[10];
    for (int i = 0; i < 10; i++) {
    result[i] = new TestCase(i);
    }
    return result;
    }
    }

    public class TestCase {
    private int initValue;

    public TestCase(int initValue) {
    this.initValue = initValue;
    }

    @Test
    public void testMethod() {
    ... do test with given initValue
    }
    }

    จากนั้นเมื่อต้องการ run ก็สั่ง run ผ่านทาง TestFactory


เห็นได้ว่าตัว TestNG มี feature ที่น่าสนใจทีเดียว
โดยเฉพาะการกำหนด groups และการกำหนด dependencies
กรณีของผม ถ้าเมื่อไรก็ตามที่เปลี่ยนไปใช้ jdk1.5
ก็คงจะเปลี่ยนจาก JUnit ไปใช้ TestNG แทน

Related link from Roti

Next Lotus Notes base on Eclipse RCP ?

เห็นข่าวจาก Planet Eclipse


หน้าตาสวยงามจนไม่รู้เลยว่าอยู่บน RCP

Related link from Roti

ตามไป gotoknow.org

ตาม bact ไปเที่ยว gotoknow มา
มีส่วนของคุณธวัชชัย ที่น่าสนใจทีเดียว
(ส่วนของคนอื่นยังอ่านไม่หมดครับ เลยไม่ได้พูดถึง)

Related link from Roti

Tuesday, June 14, 2005

คิดเล่นๆ กับเรื่อง "รับน้อง"

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

เรื่องการรับน้องนี่ก็แปลกนะ ทำไมคนถึงชอบกันนักหนา
คงเป็นการเสพติดทางอารมณ์แบบหนึ่งนะ
เหมือนกับละครที่เราเห็นใน tv. ทุกวันนี้
เด็กๆที่เข้าไปตอนปี 1 ถูกกดดันทางอารมณ์ด้วยการว็าก
ค่อยเพิ่มๆ pressure จากนั้นก็ปิดด้วย climax
(บางทีก็เพิ่มความมันส์ด้วยการใส่ประสบการณ์ที่ลืมไม่ลง
พวกประสบการณ์ห่ามๆ โรคจิตนิดๆ ลามกเยอะๆ)
จากศัตรูกลายมาเป็นพี่เพื่อนที่หวังดี
(เหมือนนางเอกที่เกลียดพระเอกเข้าใส้
เพราะพระเอกเก็ก ดุ สุดท้ายก็รักกันจี๋จ๋า)
จากบรรยากาศกดดัน กลายมาเป็นบรรยากาศเบาสบาย
(อารมณ์แบบนี้มัน peek เลยนะ)
จากนั้นพอเด็กๆเหล่านี้ขึ้นปีสอง
แน่นอนเราเคยชอบหรือประทับใจอะไร ก็ย่อมอยากให้
คนในกลุ่มเดียวกับเรา (ในที่นี้คือน้องผู้มาใหม่)
ได้รับความประทับใจอย่างนั้นบ้าง
(หรืออาจจะเกิดอารมณ์อยากดูละครเรื่องนี้ซ้ำอีก
ก็เลยใช้น้องเป็นตัวแสดงแทน (การดูก็ได้อรรถรส
นะไม่งั้นละครคงไม่มีใครติดงอมแงมหรอก))

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

เอาแค่มุมสองมุมแล้วกัน
จริงๆแล้วเรื่องนี้มันมีแง่มุมมากมายนับไม่ถ้วน
(เพราะเป็นเรื่องของคน)

ส่วนกรณีลูกชายผม ไว้ถึงตอน 17 ปีข้างหน้า
ผมก็หวังว่ามันจะมีวิญญาณกบฎแบบพ่อมันบ้างนะ
(อย่าไปยอมมันลูก เกิดก่อนแค่ปีเดียว มันจะอะไรกันนักกันหนา)

Related link from Roti

Virtual Model

ลองเข้าไปที่ site นี้แล้วลอง
กด link "My Virtual Model" ดูครับ
idea ก็คือ เปิดโอกาส ให้เราได้ลองเสื้อแบบ online ดูครับ
(เป็นผู้ชายก็ควรจะเลือก model ชายนะครับ)

ใช้ java เขียนเสียด้วย

Related link from Roti

automatic start postgres on mac os x

ช่วงนี้ใช้ mac os x
ในการพัฒนา web application อยู่
แต่มีปัญหาความไม่สะดวกเล็กๆน้อยในส่วนของ
การ start/stop postgres service
ซึ่งไม่รู้จะทำอย่างไร (os x ไม่เหมือน linux ที่ใช้ init.d)
วันนี้ได้ฤกษ์ค้นหาเสียที

ได้ความว่า os x มี directory
/System/Library/StartupItems
สำหรับทำเรื่องนี้โดยเฉพาะ

วิธีการก็คือ

sudo -c sh
cd /System/Library/StartupItems
mkdir Postgres
cd Postgres
vi Postgres


โดยเนื้อหาของ file Postgres
เป็นดังนี้
#!/bin/sh
DATABASE="/usr/local/pgsql/data"
. /etc/rc.common

StartService ()
{
if [ "${POSTGRES:=-NO-}" = "-YES-" ]; then
ConsoleMessage "Starting Postgres database"
sudo -u postgres /usr/local/pgsql/bin/pg_ctl start -D $DATABASE
elif [ "${POSTGRES:=-NO-}" = "-AUTOMATIC-" ]; then
sudo -u postgres /usr/local/pgsql/bin/pg_ctl start -D $DATABASE
fi
}

StopService ()
{
ConsoleMessage "Stopping Postgres database"
sudo -u postgres /usr/local/pgsql/bin/pg_ctl stop -D $DATABASE
}

RestartService ()
{
if [ "${POSTGRES:=-NO-}" = "-YES-" ]; then
ConsoleMessage "Restarting Postgres database"
sudo -u postgres /usr/local/pgsql/bin/pg_ctl stop -D $DATABASE
sudo -u postgres /usr/local/pgsql/bin/pg_ctl start -D $DATABASE
else
StopService
fi
}

RunService "$1"


จากนั้นก็
vi StartupParameters.plist


โดยมีเนื้อหาดังนี้
{
Description = "Postgres database server";
Provides = ("");
Requires = ("Resolver");
Uses = ("Network Time", "NFS");
Preference = "None";
Messages =
{
start = "Starting Postgres";
stop = "Stopping Postgres";
restart = "Reloading Postgres database";
};
}


เสร็จแล้วก็อย่าลืม chmod +x Postgres ด้วย
สุดท้ายก็ืคือ vi /etc/hostconfig
เพิ่มบรรทัดนี้
POSTGRES=-YES-

เป้าหมายเพื่อใช้เป็น flag ควบคุมการ start, stop service

Related link from Roti

Sunday, June 12, 2005

Tapestry Engine Service

เจ้าตัว tapestry engine service
เป็นตัวที่ผมหลบเลี่ยงไม่ยอมอ่านมานานมากแล้ว
ตอนนี้คงถึงเวลาที่จะทำความเข้าใจเสียที

เนื่องจาก Tapestry ใช้ servlet ตัวเดียว
ในการ handle incoming request ทั้งหมด
ดังนั้นต้องมีการ encode/decode URL ที่เกิดขึ้น
โดยจะต้องมีการ encode ในตอน render response
และมีการ decode ในตอนรับ request
เพื่อที่จะรู้ว่าจะต้องทำ operation อะไร
กับ component/page ไหน โดยมี
parameter อะไรบ้าง

เนื่องจาก request ที่เข้ามานั้นแบ่งออกเป็นหลายประเภท
เช่นเป็นพวก form submit, เป็นพวก link
หรือเป็นพวก event ที่เกิดจาก form element ต่างๆ
(เช่น onchange ของ listbox)
request แต่ละประเภทก็มีการทำ operation ที่ไม่เหมือนกัน
ดังนั้น tapestry จึงออกแบบให้มี object
ที่รับผิดชอบ request แต่ละประเภท
แยกจากกันไปเลย ซึ่งเราเรียกว่า service

โดย tapestry มี build in service
อยู่ 9 แบบ และยังเปิดโอกาสให้เรา
implement service เพิ่มเติมเข้าไปได้เองด้วย

ปัญหาที่ผู้ใช้ tapestry บ่นกันบ่อยๆก็คือ
ตัว url ที่ encode นั้นดูไม่เป็นธรรมชาติเลย
จึงเกิด patch ที่เรียกว่า friendly URL patch
เพื่อใช้สำหรับแก้ไขให้ tapestry gen url
ออกมาสวยงามขึ้น
ยกตัวอย่างเช่น กรณี page service
จากของเดิม
http://localhost/app?service=page/mylisting

ก็จะเปลี่ยนเป็น
http://localhost/path/mylisting.htm

ซึ่ง patch ที่ได้ก็พอกล้อมแกล้มได้ระดับหนึ่ง
แต่เนื่องจาก framework ที่มีลักษณะเป็น component model
ทำให้บาง request ยังคงมีความซับซ้อนเหมือนเดิม
ยกตัวอย่างเช่น direct service
ของเดิม
http://localhost/app?service=direct/1/mydetail/$DirectLink&sp=21

เปลี่ยนเป็น
http://localhost/path/mydetail.htm?service=direct&service=1&service=$DirectLink&sp=21

ซึ่งดูแล้ว ก็ไม่ได้แตกต่างกันเท่าไรเลย

Related link from Roti

Rhythm Process

เป็นขบวนการพัฒนา software
ในกลุ่มของพวก agile development
แปลมาจาก blog ของ Raible
ซึ่งเขาฟังมาจาก Brian Boelsterli ซึ่งเป็นวิทยากร
ใน meeting ของ Denver 's JUG

Heartbeat แทนความหมายของ iteration
โดยกำหนดให้เริ่มต้นที่เช้าวันพฤหัส และจบที่คืนวันพุธ
โดยมีเหตุผลทางจิตวิทยาในการเลือกเริ่มต้นทีวันพฤหัส
ก็คือ คนทำงานจะกลัวการทำงาน weekend ก็เลยขยันกว่าปกติ
ตัว iteration จะกำหนดไว้ที่ 1 อาทิตย์
ให้สังเกตุว่า 1 อาทิตย์สั้นไปสำหรับ cycle ในการพัฒนา
ที่ต้องมี requirement, development, deploy
แต่เขาไม่ได้ทำเป็น iteration ธรรมดา
แต่ทำเป็น stacked iteration นั่นก็คือ
แต่ละ step จะเกิดก่อน step ถัดไป 1 อาทิตย์

Iteration Advocate เป็นบุคคลที่มีหน้าที่เพียงอย่างเดียวก็คือ
ทำอย่างไรก็ได้ให้แต่ละ iteration สำเร็จให้ได้ (มี deliveries ออกมา)
ให้สังเกตุว่าบทบาทนี้เดิมเป็นของ project manager
แต่แยกออกมาเพื่อที่จะให้ PM focus
อยู่กับการจัดการ project เพียงอย่างเดียว

Software Iteration Plan (SIP)
เป็น listing ของ feature, task, defect to fix
ของแต่ละ iteration
โดยจะมี check list meeting ทุกวันพุธเย็น

Iteration Transition Meeting (ITM)
เป็นหัวใจหลักของขบวนการนี้ จัดขึ้นทุกวันพฤหัสเช้า
โดยมีสิ่งที่ต้องทำคือ
  • คนออกแบบ ทำการเสนอ requirement, analysis, design spec
  • คนพัฒนา ทำการเสนอ functionality ที่ implement
  • คนทดสอบ ทำการเสนอ test plans และ specs

Featureless iterations
เป็น iteration ที่ไม่ได้เน้นการพัฒนา feature
แต่เป็นช่วงที่สนใจในส่วนของ performance test
กับ regression test
โดยจะทำ 2 ครั้ง ณ จุดที่ project สำเร็จไป 40% และ 60%

Note: ควรจะดู ppt ของ Brian

Related link from Roti

Friday, June 10, 2005

Jester-the JUnit test tester.

ในสภาพแวดล้อมแบบ extreme programming
testing คือ key สำคัญตัวหนึ่ง
แต่ปัญหาก็คือ เราจะมั่นใจในตัว test ได้อย่างไร

วิธีการหนึ่งก็คือใช้ coverage tool ในการ monitor
testing process เพื่อที่สรุปได้ว่า source code
ที่เรา test คิดเป็นกี่เปอร์เซนต์ของ source code
ทั้งหมด
ตัวอย่างของ coverage report ของ Tapestry project

ตัว Jester เสนออีก idea ในการ test tester
ก็คือ ตัวมันจะทำการแก้ไข source code
บางส่วน (source ของโปรแกรมที่จะ test)
ก่อนที่จะ run test program
จากนั้นก็ทำการ run test
ถ้า test ผ่าน แสดงว่า test case นั้นยังไม่ครอบคลุม
source code ในจุดที่ jester แก้ไข

จุดใน source code ที่ jester จะทำการแก้ไข
ประกอบด้วย
  • แก้ไขพวกตัวเลข เช่น จาก 0 เป็น 1
  • แก้ไข boolean จาก true->false, false -> true
  • เปลี่ยนจาก if ( เป็น if (true ||
  • เปลี่ยนจาก if ( เป้น if (false &&

จะเห็นว่าเป็นการแก้ไขแบบ simple มากๆ
แต่ก็มีโอกาสทำให้ logic ของโปรแกรมเปลี่ยนแปลงได้

Related link from Roti

"Matisse", NetBeans Form Designer

ไม่รู้ว่าเห็นกันหรือยังครับ
Project Matisse

ผมได้ดู demo แล้ว
น่าสนใจมาก ดูมันเป็นธรรมชาติดี
โดยเฉพาะเส้น guild line ที่ช่วยในการ align component
ทำให้ชีวิตง่ายขึ้นเยอะ

Note: demo ที่ผมดู ช่วงต้นๆภาพจะหาย
ต้องรอสักพักถึงจะมีภาพ

Related link from Roti

Tapestry DatePicker Component Patch

DatePicker Component มี bug เมื่อใช้กับ IE 6

Related link from Roti

avoiding too many classes in CSS

วันนี้อ่าน blog ของ Joshuaink
เรื่อง avoiding-classitis
เป็นเรื่องการหลีกเลี่ยงการใช้ class ใน css มากเกินไป (overuse)
ซึ่งจะช่วยให้เรา maintain site ได้ง่ายขึ้น

Related link from Roti

Thursday, June 09, 2005

message ท่วมหัว

หลังจากพาลูกและแม่เด็กไปพักผ่อนริมทะเล
กลับมาถึงบ้าน
พ่อเด็กเจอ tapestry mailing list 120 กว่าฉบับ
feed ใน blogline + NetNewsWire อีกจำนวนหนึ่ง
นั่งอ่านจนหัวโงกเงกไปมาแล้ว
(ง่วงโว้ย)

Related link from Roti

Thursday, June 02, 2005

Summer of Code

google "summer of Code"
Note: project ของ apache มี memtor แถมให้ด้วย

อิจฉาเด็กๆยุค internet เสียจริงๆ
ถ้ารักดีเสียอย่าง มีอะไรให้ทำเยอะแยะเลย

จำได้สมัยเรา แค่หาหนังสือดีๆมาอ่าน ก็ยากแล้ว

Related link from Roti

SimpleDateFormat is not Synchronized

วันนี้อ่านเจอประเด็นนี้ใน Tapestry mailing list
SimpleDateFormat is not thread safe. You will get weird behavior
under high concurrency. It cannot be static in the Global


ใน javadoc ก็ย้ำไว้ว่า
Date formats are not synchronized. It is recommended to create separate format instances for each thread. If multiple threads access a format concurrently, it must be synchronized externally.


คุ้นๆว่าเคยประกาศ SimpleDateFormat ให้เป็น public static เหมือนกัน
(หวังดี อยากลด resource)

ทางเลือก
เปลี่ยนไปใช้ thread-safe FastDateFormat ของ jakarta commons lang แทน

Related link from Roti

Ruby Message (method)

เอามาจาก "10 Things Every Java Programmer Should Know About Ruby"

ในการเรียกใช้ method
in ruby
obj.method

in java
obj.method()

ซึ่งดูแล้วน่าจะเหมือนกัน
แต่ลองดูตัวอย่างนี้

class VCR
def initialize
@messages = []
end

def method_missing(method, *args, &block)
@messages << [method, args, block]
end

def play_back_to(obj)
@messages.each do |method, args, block|
obj.send(method, *args, &block)
end
end
end


class VCR มี method หลักๆอยู่อันเดียวคือ
play_back_to ส่วน method_missing
จะใช้กรณีที่มีการเรียกใช้ method ที่ไม่ได้มีการ definition ไว้

อธิบายการทำงานได้ดังนี้
initalize เรียกใช้เมื่อมีการ new object
โดยจะทำการ initialize Array ที่ชื่อ message
(เครื่องหมาย @ หมายถึง instance variable)
จากนั้นถ้ามีการ call method ที่ไม่ได้ประกาศไว้
ให้ทำการเก็บ list ของ method, param, block ไว้ใน message Array
กรณีที่ call playback ก็ให้นำ method ทั้งหมดที่เก็บไว้ใน
message Array ออกมาเรียกใช้กับ object ที่ pass มา

ลองดูการนำไปใช้

require 'src/vcr'

vcr = VCR.new
vcr.sub!(/Java/) { "Ruby" }
vcr.upcase!
vcr[11,5] = "Universe"
vcr << "!"

string = "Hello Java World"
puts string

vcr.play_back_to(string)
puts string


ผลลัพท์ที่ได้

Hello Java World
HELLO RUBY Universe!


อธิบายเพิ่มเติมสำหรับคนที่ยังไม่คุ้น syntax ruby
โดยหลังจากที่เรา new VCR object แล้ว
ก็เรียกใช้ method sub!, upcase, array assignment([]=), concat(<<)
ซึ่งทั้งหมดนี้ไม่ได้มีการประกาศไว้ใน VCR Class เลย
ดังนั้น method ทั้งหมดก็จะถูกเก็บไว้ใน messages array
จากนั้นเมื่อมีการเรียกใช้ play_back_to
ก็จะนำเอา method เหล่านี้ออกมาเรียกใช้กับ
String ที่ pass เข้าไป

sub! -> inplace substitution
upcase! -> inplace upper case
[11,5]="Universe" -> เป็นการแทน char ที่ตำแหน่ง 11 โดยแทนที่ 5 ตัวอักษร
<<"!" -> concat ต่อท้าย

ดูแล้ว ก็รู้สึกว่า language มัน dynamic ดีจัง
เขียนแล้วคงจะสนุกดี (เพราะมี choice ให้ creative ได้หลายแบบ)
แต่น่าจะปวดหัวพอสมควร สำหรับการใล่ code
ที่เขียนด้วย ruby

Related link from Roti

Wednesday, June 01, 2005

Tapestry and OutOfMemoryError

อ่านพบใน mailing list ของ Tapestry
ว่ามีคนสั่ง start server ด้วย parameter
-Dorg.apache.tapestry.disable-caching=ture
แล้วเกิด OutOfMemory ขึ้น

ตัว parameter disable-caching โดยปกติจะมีค่าเป็น false
ซึ่งหมายความว่าทุกๆ page นั้นจะมี การ cache page
เก็บไว้ใน page pool
เมื่อมี request มาก็จะดึง page object จาก pool
ออกมาแจกงานให้ทำ
โดยวัตถุประสงค์ของ cache ก็คือ เพื่อลดเวลา
ที่ต้องเสียไปในการ initialize page object
ทุกครั้งที่ request วิ่งเข้ามา

ในกรณีที่ developer มีการแก้ไขค่าใน html template
หรือ page specification บ่อยๆ ก็สามารถ
disable-caching เพื่อที่จะได้ไม่ต้อง restart web application กันบ่อยๆ

ประเด็นว่าทำไมจึงเกิด OOME (out of memory error)
มีคนคาดเดาว่า น่าจะเป็นที่ Permanent heap
เกิดเต็มขึ้นมา (ตัว Permanent Heap เป็นที่เก็บ
classes, types) ส่วนสาเหตุที่เต็มก็อาจเป็นเพราะ
ทุกครั้งที่เกิดการ setup page จะเกิดการสร้าง
Enhance Class จาก Page Class เดิม (dynamic extends)
ซึ่งน่าจะเป็นสาเหตุให้ Permanent Heap เต็มได้

ทางแก้ไม่มี (ถือเป็นกรรมของ developer
ว่าให้คิดก่อนแก้ อย่าแก้เล็กแก้น้อยบ่อยๆ)
แต่มีการแนะนำให้ยืดเวลาก่อนเต็มออกไปได้โดย
ระบุ parameter ของ jvm ในส่วนของ
-XX:MaxPermSize (default 64MB)

Related link from Roti

Monday, May 30, 2005

เรื่องเบาๆบ้าง

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

ส่วนข่าวถัดไปเป็นการแข่งขัน
ทารกร้องให้
โดยจับตู่เด็กให้หันหน้าเข้าหากัน
จากนั้นก็เขย่าๆไปเรื่อยๆ ใครร้องให้ก่อนชนะ
ได้ยินว่า คนญี่ปุ่นเชื่อว่า
เด็กที่ร้องให้ง่าย แสดงว่าเป็นเด็กที่สุขภาพแข็งแรง โตไว

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

Related link from Roti

AppFuse กับ Struts-menu

วันนี้นั่งดู code ที่ generate โดย AppFuse
มีการใช้ struts-menu ในการ render menu
(เดิม struts-menu มี dependency กับ struts
แต่ version 2.3 มีการ remove dependency
ออกไปแล้ว)

วิธีการใช้งาน
add listener ใน web.xml เพื่อใช้ load configuration ของ menu
<listener>
<listener-class>net.sf.navigator.menu.MenuContextListener</listener-class>
</listener>


สร้าง file menu-config.xml ใต้ WEB-INF
ภายในมีเนื้อหาอยู่ 2 ส่วน คือส่วน displayers ซึ่งใช้
config ว่าจะ render menu ด้วยวิธีไหน
กับส่วนเนื้อหาของ menu (ในส่วนเนื้อหานี้จะ
เป็นการ list menu ทั้งหมด โดยไม่เกี่ยวข้องกับลำดับ
ในการแสดงผล)
<MenuConfig>
<Displayers>
<Displayer name="DropDown"
type="com.fgm.web.menu.displayer.DropDownMenuDisplayer"/>
<Displayer name="Simple"
type="com.fgm.web.menu.displayer.SimpleMenuDisplayer"/>
</Displayers>

<Menus>

<Menu name="FileUpload" title="menu.selectFile"
description="File Upload" page="/selectFile.html"/>

<Menu name="indexMenu" title="Examples">
<Item name="indexMenu1" title="Basic Example"
toolTip="Shows usage of the menu displayers using defaults."
page="/menutest1.jsp"/>
<Item name="indexMenu2" title="Advanced Example"
toolTip="Shows customized menu displays."
page="/menutest2.jsp"/>
</Menu>

<Menu name="ToDoListMenuFile" title="FILE" description="test" width="50">
<Item name="TDLnew" title="NEW">
<Item name="TDLnewcase" title="CASE" image="images/case-new.png"
location="NewCase.jsp"/>
</Item>
</Menu>

</Menus>


ในส่วนการแสดงผลใน jsp เราจะใช้ taglib มาช่วย
<%@ taglib uri="http://struts-menu.sf.net/tag" prefix="menu" %>

เมื่อต้องการ render ก็เพียงแต่จับคู่เลือก displayer
กับ Menu Item ที่ต้องการแสดง
<menu:useMenuDisplayer name="ListMenu" permissions="rolesAdapter">
<menu:displayMenu name="Home"/>
<menu:displayMenu name="About"/>
</menu:useMenuDisplayer>


จะเห็นว่า menu ที่แสดงให้ดูโดยวิธีนี้ ยังมีลักษณะ
เป็น static อยู่ กรณีที่ต้องการ dynamic menu
(content menu เก็บอยู่ใน database)
ให้ดูที่นี่ ​Dynamic Menu

Related link from Roti

Sunday, May 29, 2005

เรียนรู้ yield ใน ruby

วันนี้ได้ลองเรียนรู้ ruby ในส่วนของ yield
ซึ่งเป็นส่วนที่สงสัยมานานแล้ว (ไม่รู้ว่ามันคืออะไรแน่)

ลองดูที่ code นี้
def three_times
yield
yield
yield
end

three_times {puts "hello"}

ผลลัพท์ทีได้
Hello
Hello
Hello


ส่วน {puts "hello"} เราเรียกว่า block
การที่เราเรียกใช้ method three_times
จะเกิดการส่ง block ไปด้วย แต่ยังไม่มีการ execute
จนกว่าจะมีการสั่ง yield จึงจะทำงาน

ที่นี้ลองดูโจทย์ประเภทหาค่า Fibonacci กันบ้าง
def fib_up_to(max)
i1, i2 = 1, 1
while i1 <= max
yield i1
i1, i2 = i2, i1 + i2
end
end
fib_up_to(1000) {|f| print f, " "}

ได้ผลลัพท์

1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987

จะเห็นว่า block สามารถ declare parameter ได้
เพื่อให้ yield สามารถส่งค่า parameter ไปให้

scope ของ block ก็น่าสนใจทีเดียว
ลองดู testcase นี้
def test_yield1
i = 0
def x
yield
yield
yield
end

x {i = i + 1}
assert_equal(3, i)
end

จะเห็นว่าภายใน block สามารถอ้างถึง variable
ที่อยู่ภายนอกได้ รวมทั้งสามารถ modify ค่าได้ด้วย

ใน ruby เราจะเห็นว่ามีการใช้ block และ yield เต็มไปหมด
เช่นในเรื่องของ Array เราสามารถใช้ Enumeration#find
ในการค้นหาค่าที่ต้องการได้
b = [1, 3, 5, 7, 9].find {|v| v*v > 30}
assert_equal(7, b)


ซึ่งในการ implement feature find ของ ruby ทำได้ดังนี้
class Array 
def find
for i in 0...size
value = self[i]
return value if yield(value)
end
return nil
end
end


ลองดูตัวอย่าง method ของ array ที่ชื่อ inject บ้าง
b = [1, 3, 5, 7].inject {|sum, element| sum+element}
assert_equal(16, b)

inject จะ pass parameter ให้ block 2 ตัว
โดยเอาค่ามาจาก array ที่ index = 0 และ 1
จากนั้นผลลัพท์ที่ได้จาก yield ก็จะนำมาเป็น
parameter ตัวที่ 1 และนำค่าจาก array ตัวถัดไป
มาเป็น parameter ตัวที่ 2 ทำซ้ำไปเรื่อยๆจนหมด array
ผลลัพท์ที่ได้ในตัวอย่างก็คือ ค่า sum ของทุกค่าสมาชิกใน array นั่นเอง

ตัว block สามารถเก็บเป็นตัวแปรไว้ได้เช่นกัน
โดยใช้ method ที่ชื่อ lambda
จากนั้นก็เรียกใช้โดยผ่าน method call
def test_lambda 
def n_times(init)
return lambda {|n| init * n}
end

p1 = n_times(2)
assert_equal(6, p1.call(3))
assert_equal(8, p1.call(4))

end

Related link from Roti

Learning by Testing

ตอนนี้ว่างๆ ก็พยายามจะหัดใช้ ruby
ปกติเวลาผมหัดเขียนโปรแกรม ผมก็จะใช้วิธีเขียนโปรแกรม แล้วก็
dump ตัวแปร ออกมาดูว่าค่าต่างๆมีการเปลี่ยนไปอย่างไร
เช่น สมมติจะทดลองใช้ ruby array
ผมอาจจะเขียน
a = [1, 2, 3, 4, 5, 6]
a[1..2] = "pok"
puts a

พอเห็นผลลัพท์ทีได้ เราก็จะรู้ว่ามันตรงกับที่ใจเราคาดหวังไว้หรือไม่

วันนี้อ่านเจอ วิธีการเรียน ruby ของ Mike Clark
เขาใช้ unit testing เข้ามาช่วย
แทนที่จะเห็นผลลัพท์ด้วยตา
ก็เปลี่ยนมาใช้การ assert แทน
จากตัวอย่างข้างบน ก็เปลี่ยนมาเขียนแบบนี้แทน
require 'test/unit'
class MyTest < Test::Unit::TestCase
def test_array
a= [1, 2, 3, 4, 5, 6]
a[1..2] = "pok"
assert_equal(5, a.length)
assert_equal("pok", a[1])
assert_equal(4, a[2])
end
end


ซึ่งผมเห็นด้วยกับวิธีเรียนรู้แบบนี้เลยนะ
ลองเรียนรู้ด้วยวิธีนี้แล้ว รู้สึก productive ดี
นอกจากนั้นสิ่งที่เขียนเก็บไว้
ก็สามารถนำมาใช้อ้างอิงหรือทบทวนได้ง่ายกว่า
เนื่องจากมีค่า "ผลลัพท์ที่พึงได้" อยู่ใน source code ด้วยเลย

Related link from Roti

Saturday, May 28, 2005

TiddlyWiki

Wiki that written in HTML, CSS and JavaScript.
No any Serverside Logic.
Link

Related link from Roti

Friday, May 27, 2005

Sitemesh unsupported encoding

วันนี้แวะไปบริษัทฯ ได้รับแจ้งว่าตัว tapestry มีปัญหา
กับ sitemesh โดยเกิด exception ที่ตัว
sitemesh ที่ Class TextEncoder
มีข้อความว่า "Unsupported encoding Windows-874"

ก็เลยต้องใล่ดู source code ของ Sitemesh
พบว่าตัว Class TextEncoder ใช้
class java.nio.charset.Charset ในการ check encoding
ซึ่งการเรียก Charset.isSupport("Windows-874")
จะได้ผลลัพท์เป็น false

ก็เลยพยายามนั่งหาว่า
ใครเป็นคนทำให้เกิด charset Windows-874 ขึ้นมา
ทดลอง -Dfile.encoding=TIS-620 ก็แล้ว
ทดลองใส่ <meta-equiv="content-type" content="text/html;charset=TIS-620" />
ก็ยังเป็นอยู่

จากนั้นก็ลองไปทดสอบบน linux server
พบว่าเจ้า Windows-874 ยังตามมาหลอกหลอน
ก็เลยรู้ว่าเป็นปัญหาที่ตัว Tapestry ยังแน่นอน
จากการไล่ดู พบว่าที่ Application config file มีการใส่ว่า
<property name="org.apache.tapestry.output-encoding">Windows-874</property>

ก็เลยจัดการเปลี่ยนเป็น
<property name="org.apache.tapestry.output-encoding">tis-620</property>


ส่วนประเด็นว่าใครเป็นคนใส่เข้ามา
ก็พบว่าตัวเองนี่แหล่ะเคยเขียน App ไว้ App หนึ่ง
ที่ config ไว้แบบนี้
สมัยนั้นไม่ได้ใช้ sitemesh ก็เลยไม่เกิดปัญหาอะไร
พอมาเริ่มโปรเจคใหม่ น้องๆเขาก็เล่น copy
มาทั้งดุ้น ก็เลยเกิดปัญหาขึ้น

จากการใล่ทดสอบดูพบประเด็นเพิ่มเติมดังนี้
  • ถ้าเราใช้ jdk1.5 คำสั่ง Charset.isSupport("Windows-874")
    จะได้ผลลัพท์เป็น True
    ส่วน jdk1.4 ได้ผลลัพท์เป็น false
  • กรณีที่เราไม่ได้ระบุค่า org.apache.tapestry.output-encoding
    ค่า default ของ Tapestry ก็คือ utf-8
  • ใน tapestry ถ้าเราใช้ component shell
    ตัว tapestry จะ generate meta-equiv="content-type"
    ให้เราโดยอัติโนมัติ

Related link from Roti

Monday, May 23, 2005

On-the-fly Sparklines with JavaScript and XBM

คงจำ SparkLine ที่เคยพูดถึงกันได้นะครับ
วันนี้เจอ javascript library ที่ใช้ generate
spark line ที่ฝั่ง browser ได้เลย
Link

Note: เท่าที่ลองดู ดูได้แต่ใน firefox
แต่ยังไม่ work ใน safari, opera 6, ie

Related link from Roti

Processing

สิบปากว่าไม่เท่าตาเห็น ลองดูที่นี่ครับ
Substrate
อย่าลืมลอง lunch applet หล่ะครับ

จากนั้นก็ลองเล่นหมากรุกที่นี่ดูครับ
Thinking Machine4

ตัวที่เป็นหัวใจก็คือ Processing
Processing is a programming language and environment built for the media arts and design communities. It is created to teach fundamentals of computer programming within a visual context and to serve as a software sketchbook. It is used by students, artists, designers, architects, and researchers for learning, prototyping, and production.

Related link from Roti

Sunday, May 22, 2005

jMock

ใน Application ที่ AppFuse generate มาให้นั้น
มีตัวอย่างการ test service layer โดยใช้ Mock Object ด้วย
โดยตัว Tool ที่ทาง AppFuse เลือกใช้ ก็คือ jMock

หลักการก็คือ สมมติว่าเรามี Service ที่ชื่อ AuthenticateService อยู่
class AuthenticateService {
private UserDao userDao;

public boolean isAuthenticate(String userId, String passwd) {
User user = userDao.getUser(userId);
if (user != null && user.getPassword().equals(passwd)) {
return true;
}
return false;
}

public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}


ตัว AuthenticateService เรียกใช้ UserDao
โดย UserDao จะค้นหา user จาก persistent layer ให้
interface UserDao {
public User getUser(String userId);
}


ในการ test กรณีที่เราต้องการ test unit เฉพาะ
AuthenticateService ว่า logic ทำงานถูกต้องหรือไม่
โดยไม่ต้องการลากเอา Persistent Layer หรือ Dao มา test ด้วย
ก็ทำได้โดยใช้ Mock Object เข้ามาช่วย

เริ่มโดยการเขียน TestCase ที่ extends MockObjectTestCase
public class AuthenticateServiceTest extends MockObjectTestCase {
public void testAuthenticate() {

}
}


จากนั้นใน test method เราก็จะทำการ setup Mock Object ก่อน

Mock userDaoMock = new Mock(UserDao.class);
AuthenticateService svr = new AuthenticateService();
svr.setuserDao(userDaoMock.proxy());


ก่อน test เราก็ setup ค่าที่เราคาดหวังไว้ให้
mock ตอบกลับออกมาเมื่อมีการเรียกใช้
method getUser

User userData = new User();
userData.setUserId("tomcat");
userData.setPassword("testIt");

userDaoMock.expects(once()).method("getuser")
.with(eq("tomcat")).will(returnValue(userData));


จากนั้นก็เป็นเริ่ม test
โดย assert ผลลัพท์ที่ได้จากการ call isAuthenticate

boolean result = svr.isAuthenticate("tomcat", "testIt");
assertTrue(result);
result = svr.isAuthenticate("tomcat", "blabla");
assertFalse(result);
userDaoMock.verify();


หลายคนที่เคย test ระบบที่มี dependency
อีรุงตุนังไปหมดนั้นคงรู้ว่ามันยากต่อการ run test ขนาดไหน
แต่จะเห็นได้ว่าการใช้ mock เข้ามาช่วยนั้น
จะทำให้เราสามารถ Isolate การ test ออกเป็นส่วนย่อยๆได้
ซึ่งทำให้ง่ายต่อการ test และ test ซ้ำได้ง่ายขึ้น

Related link from Roti

Saturday, May 21, 2005

Song Search By Tapping

เป็น project ที่ทดลองค้นหาเพลงโดย
ใช้การเคาะจังหวะเป็นข้อมูล input
ผมทดลองแล้ว 4 เพลง ตรงเพี๊ยะเสีย 2 เพลง
ที่ค้นได้ก็มี 10 little indian กับ twinkle little start
ส่วน happy birth day เคาะยังไงก็หาไม่เจอ
ลองได้ที่นี่ครับ link

Related link from Roti

SiteMesh

หลังจากลองไล่ดู code ที่ AppFuse generate ให้
ก็พบว่ามีการนำ SiteMesh มาใช้
เจ้า sitemesh ก็คือ decoration framework (ทำหน้าที่เหมือน Tiles ใน Struts)
ทำหน้าที่ intercept response จาก web application
แล้วทำการ extract เฉพาะส่วน body, head
เพื่อนำไปแสดงบน template ที่ได้เตรียมไว้
อธิบายยากแท้ ดูรูปเอาแล้วกัน



เท่าที่อ่านดู feature หรือ การใช้งาน
ก็ดูดี คงต้องทดลองใช้งานก่อน
จึงจะรู้ว่าดีกว่า Tiles หรือเปล่า
แต่เท่าที่จำได้เวลาใช้ tiles จะน่าเบื่อ
ตอนเขียน xml definition ของ tiles
ซึ่งซ้ำไปซ้ำมา และมี Naming ของ page
เกิดขึ้นอีก 1 กลุ่ม (ต้องตั้งชื่อให้กับ
ผลลัพท์ของการจับคู่ template กับ content)

feature ของ sitemesh
  • Determine decorator based on path of requested page.
  • Use different decorators based on time, locale or browser.
  • Use simplified decorators for search-engine robots.
  • Switch decorators based on a URL parameter, request attribute or meta-tag.
  • Use custom decorators based on user's saved settings...


ในการ config ใช้ sitemesh ก็เพียงแต่ใส่ filter, filter-mapping เข้าไปใน web.xml
<filter>
<filter-name>sitemesh</filter-name>
<filter-class>com.opensymphony.module.sitemesh.filter.PageFilter</filter-class>
</filter>

<filter-mapping>
<filter-name>sitemesh</filter-name>
<url-pattern>/*</url-pattern>
<!-- These are needed by Tomcat 5 for forwards -->
<!--dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher-->
</filter-mapping>


เริ่มแรกก็สร้าง file sitemesh.xml ไว้ใต้ WEB-INF
parser tag เป็นตัวกำหนดว่าจะใช้ parser ตัวไหนสำหรับ content แต่ละแบบ
แต่เจ้า sitemesh ให้มาเฉพาะ html parser เท่านั้น
ส่วน decorator-mapper เป็นตัวกำหนด
ว่าจะใช้ decorator อะไรสำหรับเงื่อนไขแบบไหน
ในกรณีนี้เป็นการใช้ ConfigDecoratorMapper
ซึ่งจะอ้างถึง config file ที่ชื่อ decorators.xml อีกที
<sitemesh>
<property name="decorators-file" value="/WEB-INF/decorators.xml"/>
<excludes file="${decorators-file}"/>
<page-parsers>
<parser default="true" class="com.opensymphony.module.sitemesh.parser.HTMLPageParser"/>
<parser content-type="text/html" class="com.opensymphony.module.sitemesh.parser.HTMLPageParser"/>
<parser content-type="text/html;charset=ISO-8859-1" class="com.opensymphony.module.sitemesh.parser.HTMLPageParser"/>
</page-parsers>

<decorator-mappers>
<mapper class="com.opensymphony.module.sitemesh.mapper.ConfigDecoratorMapper">
<param name="config" value="${decorators-file}"/>
</mapper>
</decorator-mappers>
</sitemesh>

ในเนื้อหาของ file decorators.xml
เราสามารถกำหนดได้ว่า จะให้ exclude อะไร
แล้วก็กำหนด pattern ว่า pattern อะไร
ใช้ jsp ตัวไหนเป็น template

<decorators defaultdir="/decorators">
<excludes>
<pattern>/demos/*</pattern>
<pattern>/resources/*</pattern>
</excludes>
<decorator name="default" page="default.jsp">
<pattern>/*</pattern>
</decorator>
</decorators>


สุดท้ายก็เป็นตัวอย่างของ template
ซึ่งใช้ taglib decorator:head กับ decorator:body
เป็นตัวกำหนดจุดที่จะนำเนื้อหามา insert

<%@ taglib
uri="http://www.opensymphony.com/sitemesh/decorator"
prefix="decorator" %>
<html>
<head>
<title>
My Site -
<decorator:title default="Welcome!" />
</title>
<decorator:head />
</head>
<body>
<table>
<tr>
<td>
<H1>
SiteMesh Corporation
<H1>
</td>
</tr>
<tr>
<td><decorator:body /></td>
</tr>
<tr>
<td> SiteMesh copyright</td>
</tr>
</table>
</body>
</html>


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

Related link from Roti

Thursday, May 19, 2005

Eclipse 3.0 stat

มีใตรเคยเห็น stat นี้หรือยังครับ
ตัวที่ผมชอบก็มี

Automated JUnit tests run every build: 21,332

Wow เยี่ยม เคยทำ test แค่หลัก 20-100


Donuts consumed (North American committers): 1,120
Cigarettes inhaled (European committers): 14,656

ของเราอาจจะใช้จำนวนซองมาม่า แทน


Most CVS revisions for one file (JavaCore.java): 413

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


Lines of Java source code: 1,903,219

โปรเจคที่แล้ว 4 เดือนครึ่ง 200,000 กว่าบรรทัด
เทียบกันไม่ติด

Related link from Roti

Dumbster - Email Testing

ในการเขียน Application ในยุค Internet นี้
ส่วนใหญ่เรามักจะออกแบบให้มี feature
Mail Alert ซึ่งเป็นการแจ้งเตือนผู้ใช้
ถ้าเกิดเหตุการณ์อะไรบางอย่าง
สมัยก่อนเวลาผม implement feature นี้
แล้วเกิดต้องการจะ test
ก็เพียงแต่ set config ให้ mail ไปหา
account ตัวเอง แล้วก็ทดลองจำลอง
เหตุการณ์ จากนั้นก็เข้าไป check mail
ตัวเองว่ามีการส่ง mail มาจริงหรือเปล่า

มี solution อีกอันที่ผมพบใน AppFuse
ก็คือใช้ Dumbster ซึ่งเป็น Fake SMTP Server
ร่วมกับ junit ในการ test

ตัวอย่างการเขียน testcase
public void testExecute() throws Exception {
request.addParameter("username", "tomcat");
page.setRequestCycle(getCycle(request, response));
SimpleSmtpServer server = SimpleSmtpServer.start(2525);

page.execute(page.getRequestCycle());

assertFalse(page.hasErrors());

// verify an account information e-mail was sent
server.stop();
assertTrue(server.getReceivedEmailSize() == 1);

// verify that success messages are in the request
assertNotNull(page.getSession().getAttribute("message"));
}


กรณีที่ test แบบนี้ เราจะเรียตัว dumbster ว่าเป็น
Mock Object (จริงๆอาจจะเรียกได้ไม่เต็มปากนัก
แต่ลักษณะ pattern เป็นไปตามนั้น)
สำหรับใครที่ยังไม่คุ้นกับการ
test แบบนี้ ลองอ่านเรื่อง Mock object ที่ link นี้ครับ
What Are Mock Objects?
MockObjects

ไว้คราวหน้าจะแสดงให้ดูว่า Tapestry
มีวิธีการใช้ MockObjects ในการ test อย่างไร
(ได้ตัวอย่างมาจาก AppFuse อีกเช่นกัน)

Related link from Roti

Wednesday, May 18, 2005

Rounded, shaded corner box with minimum div tag

ปกติเวลาเราทำ rounded box ด้วย css จะพบว่า
ตัว content ที่เราใช้ถึงแม้จะเอาส่วน presentation ออกไปแล้ว
ก็ยังอุดมไปด้วย div block อยู่ดี (แถมยังไม่ค่อยสื่อความหมายเท่าไรด้วย)
ตัวอย่าง
<div class="cb">
<div class="bt"><div></div></div>
<div class="i1">
<div class="i2">
<h1>title go here</h1>
<p>xxxxx</p>
</div>
<div class="bb"><div></div></div>
</div>


มีอีก idea หนึ่งก็คือเปลี่ยนไปใช้ javascript
ให้เป็นคน insert tag พวกนี้แทน
จากตัวอย่างข้างบน ก็จะลดรูปเหลือแค่
<div class="cbb">
<h1>title go here</h1>
<p>xxxx</p>
</div>

จากนั้นเมื่อเวลาที่ browser load ไป
ตัว javascript ก็จะจัดการแทรก div tag
เพิ่มเติมให้เรา

อย่างนี้สิ ค่อยดู simple หน่อย (เฉพาะใน
content)

ดูบทความที่พูดถึงเรื่องนี้ที่
Transparent custom corners and borders

Related link from Roti

Tuesday, May 17, 2005

DBUnit

ต่อจากเมื่อวานที่ได้ทดลองใช้ AppFuse ไป
จากการอ่าน tutorial ทำให้รู้ว่า AppFuse ใช้
DBUnit เป็นตัว setup database สำหรับ
การ run testcase

สมัยก่อนผมใช้ ant task ที่ชื่อ <sql>
ในการ setup database ก่อนที่จะทำ
test case โดยใช้ผ่าน ant ดังนี้
<sql
driver="org.postgresql.Driver"
url="jdbc:postgresql://${test.host.address}:${test.port}/${test.database}?charSet=MS874"
userid="postgres"
password="xxxxx"
autocommit="true"
classpathref="maven.dependency.classpath"
onerror="continue"
encoding="MS874"
>
<fileset dir="src/test/sql">
<include name="serverSetup.sql"/>
</fileset>
</sql>


ในส่วนของ DBUnit ก็สามารถใช้ผ่าน ant เหมือนกัน
(หรืออาจะใช้ api ตรงๆใน testcase ก็ได้)
โดยมีวิธีใช้ดังนี้
<dbunit driver="${database.driver_class}"
supportBatchStatement="false"
url="${database.url}"
userid="${database.username}"
password="${database.password}">
<operation type="${operation}" src="${file}" format="xml"/>
</dbunit>


สิ่งที่แตกต่างกันก็คือถ้าเราใช้ sql task
เราจะต้องเตรียม data ด้วยคำสั่ง sql
ส่วนใน dbunit สามารถเตรียม dataset ใน
format xml ได้
กรณี fomat XmlDataSet
<?xml version="1.0" encoding="utf-8"?>
<dataset>
<table name='user_role'>
<column>username</column>
<column>role_name</column>
<row>
<value>tomcat</value>
<value>tomcat</value>
</row>
<row>
<value>mraible</value>
<value>admin</value>
</row>
</table>
</dataset>

กรณี format FlatXmlDataSet
<!DOCTYPE dataset SYSTEM "my-dataset.dtd">
<dataset>
<user_role username="tomcat"
role_name="tomcat"/>
</dataset>


สังเกตุว่าใน DBUnit Task จะมี Attribute operation
โดยเราสามารถระบุ operation ได้ดังนี้
  • UPDATE DBUnit จะตั้ง assumption ว่ามี data อยู่ใน Database แล้ว
    โดยพยายามจะนำ dataset ที่เตรียมไว้ไป update
  • INSERT ใช้ assumption ว่า ยังไม่มี table อยู่ใน Database
  • DELETE DBUnit จะ delete ข้อมูลใน table เฉพาะ
    ที่อยู่ใน dataset
  • DELETE_ALL delete ข้อมูลทุก row
    สำหรับ table ที่มีชื่ออยู่ใน DataSet
  • REFRESH ถ้า data มีอยู่แล้วใช้ update
    แต่ถ้ายังไม่มีใช้ insert
  • CLEAN_INSERT DBUnit จะ delete all
    ก่อนที่จะ insert ข้อมูลเข้าไป

Related link from Roti

Monday, May 16, 2005

ลองใช้ AppFuse

ติดตาม blog ของ Raible Design มาได้พักใหญ่แล้ว
ได้ยินเจ้า AppFuse มาหลายเดือนแล้ว ยังไม่ได้ลองสักที

พอดีมีโปรเจคใหม่อันหนึ่งเป็นโปรเจคเล็กๆ แค่แสดงความ
เป็นไปได้เฉยๆ ก็เลยวางแผนว่าจะใช้
Hibernate + Spring + Tapestry
แต่ปัญหาของโปรเจคใหม่ ก็คือ การ setup environment
ในการพัฒนา โดยกะว่าจะให้คนอื่นๆที่ร่วมทำสามารถ
เลือกได้ว่าจะใช้ netbeans หรือ eclipse ก็ได้
ทำให้ต้องเลือกวิธีการ build + test + deploy โดยใช้ ant
หรือ maven แทนที่จะอิงกับ feature ของ Ide

จากการนั่ง setup project โดยการใช้ maven
ก็จัดแจงไป download maven version ใหม่มา
จากนั้นก็นั่ง setup project structure, files
จากการทดสอบ build พบปัญหาว่าเจ้า xdoclet plugin ที่มากับ
maven 1.0.2 มันเปลี่ยน concept ใหม่ไปเยอะเลย
ทำให้ script เก่าๆที่เคยใช้ มัน run ไม่ผ่าน
ก็เลยต้องตัดใจ เพราะถ้าเอาไปพวกน้องๆที่มาช่วย code
ซึ่งไม่คุ้นเคยกับ maven เลย ก็คงจะ
สร้างความสับสน ตลอดจนเสียเวลาเรียนรู้และ setup เยอะ
ก็เลยไป load เจ้า AppFuse มาลองดู
(เป็นนิสัยเสียอย่างหนึ่งที่ได้ version ใหม่มาแล้ว
ก็ไม่อยากย้อนกลับไปใช้ version เก่า)

เจ้า concept ของ AppFuse ก็คือ
เป็นตัวช่วย kickstart ของโปรเจค
กล่าวคือ ตัวมันจะทำหน้าที่ generate
dummy project ให้เรา โดยมี function
ในระดับที่สามารถทดสอบ run ทดสอบเบื้องต้นได้ทันที
โดยตัว function ที่มีให้ก็คือ หน้าจอ login
, หน้าจอ main, edit profile ที่ถือว่าเป็น
หน้าจอบังคับของ web application
นอกจากนี้ยัง generate file, directory structure
ต่างๆที่พร้อมจะให้เรา coding, building, testing, deploy
ผ่านทาง ant ได้ทันที

การใช้งาน (คร่าวๆ)
  • Install AppFuse โดยเลือก Framework ที่ต้องการ
  • Install Ant (version 1.6.2+)
  • Install Jarkatar Tomcat
  • Install MySql (เป็น default database แต่ของผม ผมเลือกใช้ Postgres แทน)
  • config Environment Variable
  • สร้าง app ใหม่ด้วยคำสั่ง ant new
    ตัว AppFuse จะ generate project structure ให้เรา
  • cd ไปยัง project directory แล้วก็สั่ง ant setup
    แค่นี้เราก้ได้ dummy project ที่สามารถ run ได้แล้ว


จากการทดลองใช้ สิ่งที่ชอบก็คือ
project structure ที่ AppFuse สร้างให้เรา
ดูมีระเบียบดีมาก
แล้วก็ setup project ได้เร็ว ไม่ต้องนั่ง
หลังขดหลังแข็ง copy file จากโปรเจคเก่า

ส่วนข้อเสีย ก็คือขนาดของ project
มันใหญ่จัง ขนาดตั้ง 50 ​MB ไม่รู้ว่า
มันเอา library อะไรยัดมาให้เราบ้าง
คงต้องนั่งตัดเอา library ที่ไม่ใช้ออกเสียบ้าง

Related link from Roti

Thursday, May 12, 2005

JFugue Java API for Music Programming

project นี้มีมานานแล้ว พึ่งเห็นนี่แหล่ะ
เป็น api สำหรับการเล่น note ดนตรี

ตัวอย่าง


import org.jfugue.Player;
import org.jfugue.Pattern;
/**
*
* @author pphetra
*/
public class Main {

/** Creates a new instance of Main */
public Main() {
}

/**
* @param args the command line arguments
*/
public static void main(String[] args) {
Player player = new Player();

// "Frere Jacques"
Pattern pattern1 = new Pattern("C5q D5q E5q C5q");

// "Dormez-vous?"
Pattern pattern2 = new Pattern("E5q F5q G5h");

// "Sonnez les matines"
Pattern pattern3 = new Pattern("G5i A5i G5i F5i E5q C5q");

// "Ding ding dong"
Pattern pattern4 = new Pattern("C5q G4q C5h");

// Put it all together
Pattern song = new Pattern();
song.add(pattern1);
song.add(pattern1);
song.add(pattern2);
song.add(pattern2);
song.add(pattern3);
song.add(pattern3);
song.add(pattern4);
song.add(pattern4);

// Play the song!
player.play(song);
}

}


กรณีที่ต้องการเล่นหลายโน็ตพร้อมกัน
new Pattern("C5+E5+G5")

กรณีที่เป็น chord
new Pattern("Cmaj5q")

เปลี่ยนเครื่องดนตรี ใช้ I (instrument) นำหน้าตามด้วย
ชื่อเครื่องดนตรี
I[Piano]


ที่มาของชื่อ JFugue
Fugue is a musical style in which a common theme, often called a statement, is played, then other voices join in and play subtle variations on the statement.


ใครที่ต้องการรู้ definition ของ JFugue ชัดๆ
ให้ตามไปที่นี่ JFugue Anatomy
เห็นแล้วปวดหัวเลย

ส่วนอันนี้สิเด็ด เอา DNA sequence มาแปลงเป็นเพลง
Red Blood DNA Sequences

Related link from Roti

Wednesday, May 11, 2005

prefuse "interactive information visualization"

วันนี้เห็น demo ของ prefuse
ชอบใจเลย link มาให้ดู

Related link from Roti

Tuesday, May 10, 2005

Linux Mainframe 's benchmark

ต่อเนื่องมาจาก

ด้วยความอยากรู้ว่าประสิทธิภาพของ linux บน mainframe
จะมีขนาดไหน ก็เลยทดลองหา benchmark ต่างๆมา
ทดลอง run ดู โดยมีเครื่องเปรียบเทียบคือ
  • Ibm Unix Server cpu powerpc 4 cpu ram 4GB, AIX, ibmjava1.3
  • Linux Mainframe 2 cpu, ram 3GB, SUSE Linux, ibmjava1.3
  • Labtop p4 2.4GHz 1 cpu ram 1 GB, ubuntu Linux, sunjava1.5



ตัวแรกก็คือ scimark2 ตัวนี้วัดกันที่ cpu ตัวเดียว
benchmark ตัวนี้มีจุดบกพร่องหน่อยตรงไม่ได้
ควบคุม jdk ให้เป็น version เดียวกัน

IBM Unix Server
Composite Score: 266.65277256015247
FFT (1024): 271.51941127067306
SOR (100x100): 216.80410690128483
Monte Carlo : 6.929870290609104
Sparse matmult (N=1000, nz=5000): 316.2934686121103
LU (100x100): 521.7170057260851


java.vendor: IBM Corporation
java.version: 1.3.1
os.arch: ppc
os.name: AIX


Linux Mainframe
Composite Score: 47.96155598565569
FFT (1024): 36.411169514987805
SOR (100x100): 90.41643126719151
Monte Carlo : 14.357907621051798
Sparse matmult (N=1000, nz=5000): 45.22218974459271
LU (100x100): 53.40008178045459

java.vendor: IBM Corporation
java.version: 1.3.1
os.arch: s390x
os.name: Linux
os.version: 2.4.19-3suse-SMP


Linux on laptop
Composite Score: 207.36273014140824
FFT (1024): 92.19030829632334
SOR (100x100): 362.3458191150928
Monte Carlo : 45.1455518873811
Sparse matmult (N=1000, nz=5000): 138.26159614289057
LU (100x100): 398.8703752653535

java.vendor: Sun Microsystems Inc.
java.version: 1.5.0_01
os.arch: i386
os.name: Linux
os.version: 2.6.10-5-386




ทดสอบ IO ด้วย Bonnie
benchmark อันนี้ java ไม่เกี่ยว ​
IBM Unix Server
              -------Sequential Output-------- ---Sequential Input-- --Random--
-Per Char- --Block--- -Rewrite-- -Per Char- --Block--- --Seeks---
Machine MB K/sec %CPU K/sec %CPU K/sec %CPU K/sec %CPU K/sec %CPU /sec %CPU
100 59947 96.6 248629 99.5 106319 33.2 77576 100.0 862010 101.0 92464.2 231.2


Linux Mainframe
              -------Sequential Output-------- ---Sequential Input-- --Random--
-Per Char- --Block--- -Rewrite-- -Per Char- --Block--- --Seeks---
Machine MB K/sec %CPU K/sec %CPU K/sec %CPU K/sec %CPU K/sec %CPU /sec %CPU
100 8307 100.0 86405 99.6 589488 103.6 8672 100.0 934170 100.4 53273.0 186.5


Linux on Labtop
              -------Sequential Output-------- ---Sequential Input-- --Random--
-Per Char- --Block--- -Rewrite-- -Per Char- --Block--- --Seeks---
Machine MB K/sec %CPU K/sec %CPU K/sec %CPU K/sec %CPU K/sec %CPU /sec %CPU
100 32729 91.3 146186 76.7 294090 91.9 41498 99.2 815416 93.9 844.4 2.0



สุดท้ายทดสอบด้วย volano โดยทดสอบแค่ loopback อย่างเดียว
IBM Unix Server
VolanoMark version = 2.5.0.9
Messages sent = 20000
Messages received = 380000
Total messages = 400000
Elapsed time = 16.817 seconds
Average throughput = 23785 messages per second


Linux Mainframe
VolanoMark version = 2.5.0.9
Messageon = 2.5.0.9
Messages sent = 20000
Messages received = 380000
Total messages = 400000
Elapsed time = 35.91 seconds
Average throughput = 11139 messages per second


Linux on Labtop
VolanoMark version = 2.5.0.9
Messages sent = 20000
Messages received = 380000
Total messages = 400000
Elapsed time = 34.923 seconds
Average throughput = 11454 messages per second


ผลลัพท์ของ benchmark ก็เป็นตัวชี้นำให้เห็น
performance ได้ระดับหนึ่ง
เนื่องจากตัว Application ที่ย้ายขึ้นไป มีลักษณะเป็น
business application ไม่ได้ตั้งหน้าตั้งตา
คำนวณแต่อย่างไร
นอกจากนั้น IO ก็ไม่ได้ระดมยิงเข้ามา
มากมายนัก (ส่วนใหญ่เป็น read เสียมากกว่า)

ไว้คอยดูตอน run จริงทั่วประเทศ
ว่าจะเป็นอย่างไร เมื่อเทียบกับของเดิม
ที่อยู่บน IฺBM Unix Server

Related link from Roti

CLI0125E Exception ใน WebSphere

สืบเนื่องมาจากการย้าย Application server
จาก Aix ขึ้นไปบน Linux Mainframe

ผลการทดสอบ ปรากฎว่าพบปัญหา
[9/5/2005, 16:25:13:275 ICT] 3d24ec3d SystemOut     O 16:25:13,273 ERROR [UnEmpl
   oyInfoFindCmd] COM.ibm.db2.jdbc.DB2Exception: [IBM][CLI Driver] CLI0125E Functi
   on sequence error. SQLSTATE=HY010
   COM.ibm.db2.jdbc.DB2Exception: [IBM][CLI Driver] CLI0125E Function sequence err
   or. SQLSTATE=HY010
    at COM.ibm.db2.jdbc.app.SQLExceptionGenerator.throw_SQLException(SQLExce
   ptionGenerator.java:273


ก็เลยต้องไปใล่ค้นใน net ดู
ปรากฎว่าเป็นปัญหาประเภทว่า
เรา select ค่ามาใส่ result set (cursor เปิดอยู่)
จากนั้นเราก็ iterate ไปตาม result
โดยใช้ rs.next()
โดยแต่ละค่าที่ iterate จะมีการเปิด statement
ใหม่เพื่อที่จะ select ค่า (จากอีก table หนึ่ง)
ตรงนี้แหล่ะที่เป็นปัญหา
เข้าใจว่า พอเราทำอีก statement หนึ่ง
ตัว cursor ก็จะปิดไป ทำให้พอ iterate
ไปยัง result ตัวถัดไป ก็จะเกิด Exception นี้ขึ้น

ทางแก้ไข
ต้องเข้าไป set datasource property

    Name: connectionAttribute
    Type: java.lang.String
    Value: cursorhold=1


ประเด็นที่น่าสนใจ
ทำไมใน Aix ไม่ต้อง set ก็ run ได้

อ้างอิง
To resolve a CLI0125E error, can I set the CURSORHOLD property to 1 (TRUE) for a DB2 V8.1 datasource (using WebSphere Application Server V5.0.2), as is recommended for a DB2 V7.1 datasource in earlier versions of WebSphere?
A: Yes, this solution is still valid for WebSphere Application Server V5.0.2 and a DB2 V8.1 datasource.

The performance ramifications of this setting are minimal. Currently, if the transaction is completed or closed, the cursors are not destroyed in the memory. However, they are destroyed when a specific CLOSE CURSOR is issued. Therefore, if your code follows good programming practices (that is, OPEN a cursor, FETCH rows, CLOSE cursor), and if the code does the CLOSE CURSOR for every OPEN CURSOR, then setting the CURSORHOLD property should not be a performance concern.

Related link from Roti

Sunday, May 08, 2005

เปิด blog เพิ่ม

วันนี้ผมเปิด blog ใหม่
สำหรับเนื้อหาในส่วนของ ERP โดยเฉพาะครับ
ตัว blog อยู่ที่นี่ครับ Link

ตัวเนื้อหา ก็จะเป็น short note ในการที่จะ
พยายามทำเข้าใจระบบ ERP ต่างๆที่มีอยู่
(เท่าที่จะหาคู่มือมาอ่านได้)
โดยจะเริ่มต้นที่ SAP ก่อน
(ยิ่งเห็นว่าค่า Train มันแพงมาก
ก็ยิ่งอยากรู้ว่ามันทำอะไรได้แค่ไหน)

ซึ่งผลลัพท์สุดท้ายที่ต้องการ
ก็คือการพัฒนาระบบ ERP ดีๆสักระบบ
ที่พัฒนาโดยคนไทยเอง
(คงไม่ใช่การลอกหรอกนะ แต่เป็นการ
ศึกษาจุดดี หรือข้อจำกัดของคนอื่นเสียมากกว่า)

Related link from Roti

Saturday, May 07, 2005

NerdTests

เดี๋ยวคุณ bact กับ คุณ Keng จะไม่เชื่อ
เอารูปมาให้ดูเลย
จะได้ไม่โดนข้อหาแต่ง javascript :)

mynerd

ภรรยาผมเดินผ่านมา เลยบังคับให้ทำด้วย
ผลลัพท์คุณเธอได้ 12 แต้ม

Related link from Roti

Thursday, May 05, 2005

swt + opengl plugin

OpenGL Plug-in

Article ของ Eclipse OpenGl Plugin
Using OpenGL with SWT

สำหรับผู้ที่ใช้ swing ก็มีตัวที่คล้ายๆกันคือ Jogl

สุดท้ายก็ ariticle ที่พูดถึง java3D กับ java-opengl

Related link from Roti

Star Wars Asciimation

http://www.asciimation.co.nz/

Related link from Roti

j4K

J4KJava Game Contest
game must be 4096 bytes or under.

ตัวอย่างเกมส์
http://www.cokeandcode.com/4k/tilt

Related link from Roti

MIR

Music Information Retrieval
M2K (java opensource)
Tools

เห็นแล้วน่าสนใจดี (แต่ชาตินี้คงไม่ได้ใช้)
คุณ bact น่าจะชอบ

Related link from Roti

Swing Pointers

Link

Related link from Roti

Memory Leak TestCase

ตัวเนื้อหาได้มาจาก blog ของ Tim Boudreau

ปัญหาที่พบบ่อยในการเขียน Swing ก็คือ Memory leak
ซึ่งเกิดการการ add listener ระหว่าง component ต่างๆ
แล้วไม่มีการ remove ออก

ใน project netbean ซึ่งเป็น swing ขนาดใหญ่
ก็มีการจัดการกับปัญหานี้ผ่านทาง testcase + INSANE
โดย testcase ของเขามี pattern ดังนี้

SomeObject o = new SomeObject();
something.doSomethingWith (o);
WeakReference ref = new WeakReference (o);
o = null;
assertGC ("Object still referenced", ref);

object o เป็น object ที่เราสนใจว่ามีการ leak
เกิดขึ้นหรือไม่ โดยสังเกตุว่ามีการสั่ง o = null ซึ่ง
เป็นการ clear referenece ที่อ้างถึงภายใน method นี้แล้ว
ดังนั้นถ้ามี gc เกิดขึ้นเมื่อไร object o ควรจะหายไปจาก Heap
ซึ่งวิธีการตรวจสอบก็คือ wrap object o ด้วย
WeakReference ก่อนแล้วค่อยส่งไปตรวจด้วย method assertGC

ใน method assertGC มี code เขียนไว้ดังนี้
    public static void assertGC(String text, java.lang.ref.Reference ref) {
ArrayList alloc = new ArrayList ();
int size = 100000;
for (int i = 0; i < 50; i++) {
if (ref.get() == null) {
return;
}
System.gc();
System.runFinalization();
try {
alloc.add (new byte[size]);
size = (int)(((double)size) * 1.3);
} catch (OutOfMemoryError error) {
size = size / 2;
}
try {
if (i % 3 == 0) Thread.sleep(321);
} catch (InterruptedException t) {
// ignore
}
}
alloc = null;
fail(text + ":\n" + findRefsFromRoot(ref.get()));
}

หลักๆ ก็คือวน loop 50 ครั้งเพื่อ check ว่า
เจ้า object ที่อยู่ใน weak reference หายไปหรือยัง
โดยแต่ละครั้งก็พยายาม force gc ให้เกิด
โดยมีการ simulate การใช้ heap ผ่านทาง
ArrayList alloc

กรณีที่ loop ครบ 50 ครั้งแล้ว
ref.get() != null
ก็แสดงว่า memory leak แล้ว
ก็ call findRefsFromRoot ซึ่งมีการทำงานดังนี้
    private static String findRefsFromRoot(final Object target) {
final Map objects = new IdentityHashMap();
boolean found = false;

Visitor vis = new Visitor() {
public void visitClass(Class cls) {}

public void visitObject(ObjectMap map, Object object) {
objects.put(object, new Entry(object));
}

public void visitArrayReference(ObjectMap map, Object from, Object to, int index) {
visitRef(from, to);
}

public void visitObjectReference(ObjectMap map, Object from, Object to, java.lang.reflect.Field ref) {
visitRef(from, to);
}

private void visitRef(Object from, Object to) {
((Entry)objects.get(from)).addOut(to);
((Entry)objects.get(to)).addIn(from);
if (to == target) throw new RuntimeException("Done");
}


public void visitStaticReference(ObjectMap map, Object to, java.lang.reflect.Field ref) {
((Entry)objects.get(to)).addStatic(ref);
if (to == target) throw new RuntimeException("Done");
}
};

try {
ScannerUtils.scanExclusivelyInAWT(ScannerUtils.skipNonStrongReferencesFilter(), vis, ScannerUtils.interestingRoots());
} catch (Exception ex) {
// found object
found = true;
}

if (found) {
return findRoots(objects, target);
} else {
return "Not found!!!";
}
}

ซึ่งเป็นการ dump เอา Reference ทั้งหมดออกมา
ผมยังไม่ได้หา javadoc ของ ScannerUtils ดู
แต่เดาว่า skipNonStrongReferencesFilter คงจะ
เป็นการ scan หาเฉพาะพวกที่อยู่ใน WeakReference เท่านั้น

ที่นี้ลองดูตัวอย่างที่ Tim เขาเขียนไว้
public class MyFrame extends javax.swing.JFrame {
MenuAction menuAction = new MenuAction();
public MyFrame() {
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
setBounds (20, 20, 300, 300);
getContentPane().addMouseListener (new MouseAdapter() {
public void mouseReleased (MouseEvent me) {
getPopupMenu().show((Component) me.getSource(), me.getX(), me.getY());
}
});
}

JPopupMenu getPopupMenu() {
JPopupMenu menu = new JPopupMenu();
menu.add (new JMenuItem (menuAction));
return menu;
}

static final class MenuAction extends AbstractAction {
public MenuAction() {
putValue (Action.NAME, "Do Something");
}
public void actionPerformed (ActionEvent ae) {
System.out.println("Action performed");
}
}
}

ผลลัพท์ที่ได้จากการ Test ออกมาหน้าตาประมาณนี้
Testcase: testLeak(javaapplication7.MyFrameTest):       FAILED
Popup menu should have been collected:
private static java.awt.Component java.awt.KeyboardFocusManager.focusOwner->
javaapplication7.MyFrame@d1e89e->
javaapplication7.MyFrame$MenuAction@f17a73->
javax.swing.event.SwingPropertyChangeSupport@3526b0->
javax.swing.event.EventListenerList@3ddcf1->
[Ljava.lang.Object;@105c1->
javax.swing.JMenuItem$1@f74864->
javax.swing.JMenuItem@110003->
javax.swing.JPopupMenu@627086

จะเห็นว่า leak ครั้งนี้ขึ้นใน JMenuItem
ของ JMenuItem ยัง hold reference ถึง MyFrame$MenuAction อยู่
โดยตัวที่ทำให้ leak ก็คือ menu.add(new JMenuItem(menuAction))
ซึ่งกรณีที่เรา new JMenuItem จะเกิดการ add listener
ระหว่าง MenuAction กับ MenuItem
ผลก็คือ PopupMenu ยังมี link จาก menuItem วิ่งไป menuAction

สรุป

โดยปกติเวลาเราสงสัยว่า memory leak หรือไม่ก็ต้องใช้
พวก Profiler ทำการใล่ trace ดู
แต่ที่ Tim เขาแสดงให้ดูก็คือ เราสามารถเขียน Testcase
เพื่อดักจับไว้ได้ก่อนเลย ทำให้เมื่อ project evolve
ไป ถ้าเกิดมีใครมาแก้อะไรแล้วทำให้ memory leak
ตัว testcase ก็จะฟ้องออกมาทันที

Related link from Roti

qemu

วันนี้อ่านเจอ Qemu
ซึ่งเป็น opensource emulator
ก็เลยทดลอง run linux บนเครื่อง mac

โดยจะทดลอง run knoppix ดู
1. เริ่มแรกก็สร้าง knoppix cdrom image file
สั่ง umount cdrom ก่อน
sudo umount /Volumns/KNOPPIX

ใช้ dd สร้าง image file
sudo dd if=/dev/disk1s0 of=/tmp/testimage.img bs=2048


2. ใช้ qemu run จาก image file
qemu -cdrom /tmp/testimage.img -pci -m 512 -k en-us  -boot d 


knoppixonmac
ความเร็วที่ได้นั้นไม่เป็นที่น่าประทับใจแต่อย่างไร

Related link from Roti

Tuesday, May 03, 2005

Linux บน Mainframe

วันนี้ได้มีโอกาสสัมผัส linux บน ​Mainframe
โดยมีหน้าที่ standby เพื่อเกิดปัญหาในการ
ย้าย Web Application จาก AIX Unix Server
ไปยัง Mainframe

ตัว Architecture มีลักษณะดังนี้
ที่ชั้นแรกสุดก็คือชั้นของ LPAR (Logical Partition)
ซึ่งก็คือการแบ่ง computer resources (processors, memory, storage)
ออกเป็นส่วนๆ โดยแต่ละส่วนก็จะมี Operating System
ของตัวเอง
ชั้นที่ 2 ก็คือ VM (virtual machine) ไม่ใช่ Java Virtual Machine นะ
แต่เป็น Virtual machine ที่เปิดโอกาศให้เรา
ติดตั้ง Virtual Server หลายๆตัวใน VM ได้
(แตละตัวก็จะมี virtual cpu, virtual memory, I/O, network
ของตัวเอง)
ขั้นที่ 3 ถึงจะเป็น Linux
โดยที่นี่ใช้ SUSE Linux Enterprise Server 8

หลังจาก Telnet ได้ เริ่มแรกสุดก็ทดสอบ Java Benchmark ก่อนเลย
โดยใช้ Scimark2
ได้คะแนน 66 คะแนน
ยิ้มเลย notebook เรายังได้แต้มตั้ง 200 นิดๆเลย
ด้วยความไม่แน่ใจก็เลยลองไป run ทดสอบ
บน Aix Unix Server ด้วย ปรากฎว่าเจ้า Power PC
ทำคะแนนได้ 266 คะแนน
หลังจากสับสนงงงวยกับคะแนนที่ได้ ก็เลย
ไปหา Red Book ของ IBM มานั่นอ่านดู
ก็พบว่า ตัว linux บน Z os นั้นเหมาะกับงานที่มีลักษณะ
I/O bound สลับกับ CPU bound แต่ไม่เหมาะอย่างยิ่ง
กับงานประเภท Deep Computing หรือ cpu intensive

เริ่มแรกสุดก็เจอปัญหาไม่สามารถต่อ jdbc ไป db2 ได้
มีข้อความว่า unsatisfied linkage error ใล่หา
สักพักจึงเจอว่า
ตัว SUSE เป็น 64 bit
ตัว WebSphere เป็น 31 bit (ไม่ได้พิมพ์ผิดนะ 31 bit)
ส่วน db2 client เป็น 64 bit
ปัญหาเกิดเมื่อ java ที่มากับ Websphere พยายาม
ที่จะต่อ jdbc ซึ่งจะต้องมีการเรียก native code
ของ db2 client ซึ่งเป็น 64 bit
ก็เลยขอให้คนของ ibm ลง db2client
version 31 bit ให้แทน

ปัญหาต่อไปก็คือเวลาที่ใช้ในการทำ I/O
เริ่มตั้งแต่การ FTP ตัวฐานข้อมูล ซึ่งช้ามากๆ
จากนั้นก็เป็นการ create table space
ของ db2 (เนื้อที่ประมาณ 30 GB)
ซึ่งก็ใช้เวลาไปอีก 1 ชั่วโมงครึ่ง
น้องชิ้มแจ้งว่า Data ชุดเดียวกันนี้
เคยเอาไปลงที่เครื่อง PC เวลาที่ใช้ในการ
create table space กับ copy data
ใช้เวลารวมกันแค่ 22 นาที
ด้วยความสงสัยก็เลยเอา Bonnie
มาทดสอบเปรียบเทียบกับ unix server
ปรากฎว่า write I/O ตัว unix กินขาด
(เผอิญว่าลืมเอา thumbdrive ที่บันทึกผลลัพท์กลับมา
ไว้ได้กลับมาเมื่อไรจะ post ให้ดูอีกที)
ก็เลยแจ้งคนของ IBM ว่าช่วยดูให้ด้วย
config อะไรผิดหรือเปล่า

ปัญหาถัดไปก็เป็นเรื่องของ web server
ซึ่งเราจำเป็นต้องใช้ mod_gzip ด้วย
ตัว IBM http Server ที่ bundle มากับ Websphere นั้นไม่
สามารถใช้ mod_gzip ได้
ก็เลยต้องใช้ Apache แทน
แต่ปรากฎว่าเกิดปัญหา conflict ของ 64 bit กับ 31 bit
อีกแล้ว
เนื่องจาก so ของ websphere เป็นแบบ 31 bit
ส่วน apache ที่มากับ SUSE เป็น 64 bit
ก็เลยลอง load source code ของ apache
มา build เองโดยกำหนด CFLAGS=-m31 เพื่อบังคับ
ให้ gcc compile ออกมาเป็น 31 bit
แต่ก็ไปตายตอน ld อยู่ดี
ในส่วนนี้ก็เลยให้คนของ ibm (อีกแล้ว)
ไปจัดหา Ibm HttpServer version
ที่ใหม่กว่า 1.3.31 และเป็น 31 bit มาให้

นอกจากนี้ยังพบปัญหาเกี่ยวกับการ FTP
ที่ส่ง file ได้สำเร็จแต่เกิดมีเนื้อหาบางส่วน
ผิดเพี้ยนไป
แต่ปัญหาในส่วนนี้คาดว่าไม่เกี่ยวข้องกับ linux บน Mainframe

ส่วนปัญหาหนักๆที่ยังไม่ทดสอบ
ก็จะเป็นเรื่อง Jasper Report ที่น่าจะมีปัญหาในส่วน
AWT Thread กับ X Window

สรุปเบื้องต้น
ก็คงต้องอวยพรให้บริษัทที่ปรึกษา ผู้ซึ่งแนะนำให้
move infra structure ขึ้นไปอยู่บน Mainframe
จงโชคดีในการย้ายด้วยเทอญ
ส่วนตัวผมนั้นรู้สึกหวั่นๆอยุ่บ้าง เพราะดูท่าแล้ว
resources ที่มีให้มัน fit เปรี๊ยะเสียเหลือเกิน
(แถมยังแพงด้วย มี paper อยู่อันบอกไว้ว่า
ด้วยราคาของ mainframe แล้ว เราต้อง
cosolidate linux อย่างน้อย 32 server
จึงจะเริ่มมี cost ที่สูสีกัน)

ปล. ขอนินทาที่ปรึกษา (ไม่บอกนะว่าอาจารย์
มหาลัยไหน) และคนของ ibm
ด้วยเถอะ สมัยที่เริ่มต้นโครงการใหม่ๆ (เมื่อต้นปีก่อน)
ทั้งที่ปรึกษาและคนของ ibm บอกว่าเครื่อง
database server ควรใช้ 4 cpu
ตัว application server ควรใช้ 2 cpu
ส่วนผมยืนยันและนั่งยันว่าผิด มันต้องกลับกัน
ผลลัพท์ก็ prove ไปแล้ว
ปรากฎว่า load ปัจจุบัน ถ้าใช้คำสั่ง sar ดู
เครื่อง App Server มี cpu load อยู่ที่ 40-60 %
ส่วน DB Server มี cpu load -> 5 % (tune ไว้ดี)

Related link from Roti

Monday, May 02, 2005

Absenter

http://www.absenter.org/
เกี่ยวกับ Photographs อีกแล้ว

ลองดู Design เขาสิครับ
ใช้รูปเป็นหลัก
ที่เหลือ (link, banner, note) เปลี่ยนสีไปตามรูป
ชอบตรงนกที่เกาะเหนือตัว R

Related link from Roti