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