Thursday, July 07, 2005

Syntax Highligh Java source in Apache Forrest

เป้าหมายที่ต้องการทำก็คือ
ต้องการให้ Apache Forrest Application สามารถ
transform xdoc ในส่วนของ java source
ให้อยู่ในรูปสีสรรสดสวย
(เดิมใน xdoc มี tag ที่ชื่อ source
อยู่แล้ว แต่ไม่ได้ทำ highlight ให้)

วิธีการที่ทำได้มีอยู่ 2 ทางหลักๆ
  • Customize Project 's Pipeline
  • Customize Forrest 's Pipeline ที่ forrest installation directory โดยตรง


วิธีแรกดูดีสุด
โดยในคู่มือ forrest บอกไว้ว่า เราสามารถ
customize pipeline processing เฉพาะใน Document Project ของเรา
ผ่านทาง /src/documentation/sitemap.xmap ได้
โดยทำได้ 2 แบบคือ
  • render เองหมดเลย
  • render เฉพาะ body ที่เหลือปล่อยให้ forrest apply skin ให้

  • พยายามจะทำแบบที่ 2 แต่ไม่ได้สักที
    ก็เลยเปลี่ยนใจ ย้ายไป customize ที่ตัว
    forrest configuration โดยตรงเลย

    เริ่มจาก file /$FORREST_HOME/main/webapp/sitemap.xmap
    มันจะมีการ mapping url ไว้ดังนี้
    <map:match pattern="*.html">
    <map:aggregate element="site">
    <map:part src="cocoon:/skinconf.xml"/>
    <map:part src="cocoon:/build-info"/>
    <map:part src="cocoon:/tab-{0}"/>
    <map:part src="cocoon:/menu-{0}"/>
    <map:part src="cocoon:/body-{0}"/>
    </map:aggregate>

    <map:call resource="skinit">
    <map:parameter name="type" value="site2xhtml"/>
    <map:parameter name="path" value="{0}"/>
    </map:call>
    </map:match>


    นั่นคือหลังจากได้ url ที่ลงท้ายด้วย .html เข้ามา
    เจ้า forrest ก็จะแตกออกเป็น 5 dispatch ย่อย ที่จะ
    แจกไปให้แต่ละส่วนทำงาน เช่น ส่วน tab, ส่วน menu
    จากนั้นค่อย aggregate รวมกันแล้วส่งไป render อีกที

    กรณีของเรา ก็คือต้องการ customize ส่วน body
    จากของเดิมที่เป็นดังนี้
    <map:match pattern="**body-*.html">
    <map:generate src="cocoon:/{1}{2}.xml"/>
    <map:transform type="idgen"/>
    <map:transform type="xinclude"/>
    <map:transform type="linkrewriter" src="cocoon:/{1}linkmap-{2}.html"/>
    <map:transform src="resources/stylesheets/declare-broken-site-links.xsl" />
    <map:call resource="skinit">
    <map:parameter name="type" value="document2html"/>
    <map:parameter name="path" value="{1}{2}.html"/>
    <map:parameter name="notoc" value="false"/>
    </map:call>
    </map:match>


    โดยสิ่งที่เราจะเพิ่มเข้าไปก็คือ หลังจากที่ generator ได้ file xdoc
    มาแล้ว เราจะเข้าไป intercept sax event
    เพื่อที่ว่าจะได้ทำการ parse java source ให้อยู่ในรูป
    xml เสียก่อน จากนั้นก็จะส่งผ่านไปให้ xslt
    ทำการ render ให้อยู่ในรูปแบบที่ต้องการ

    <map:match pattern="**body-*.html">
    <map:generate src="cocoon:/{1}{2}.xml"/>
    <!-- parse java source to xml form -->
    <map:transform type="java2html" />
    <map:transform type="idgen"/>
    <map:transform type="xinclude"/>
    <map:transform type="linkrewriter" src="cocoon:/{1}linkmap-{2}.html"/>
    <!-- render xml syntax tree to html tag -->
    <map:transform src="resources/stylesheets/javahighlight.xsl"/>
    <map:transform src="resources/stylesheets/declare-broken-site-links.xsl" />
    <map:call resource="skinit">
    <map:parameter name="type" value="document2html"/>
    <map:parameter name="path" value="{1}{2}.html"/>
    <map:parameter name="notoc" value="false"/>
    </map:call>
    </map:match>


    ตัว transform type "java2html" จะทำหน้าที่แปลง
    javasource ให้อยู่ในรูป xml โดยมี tag แปะตาม
    ประเภทของ token
    เช่น
    package pok.test;
    import java.util.ArrayList;

    ก็จะถูกแปลงเป็น
    <javakeyword>package</javakeyword pok.test;
    <javakeyword>import</javakeyword> java.util.<java_type>ArrayList</java_type>;


    ส่วน transform อีกตัวจะทำหน้าที่ apply style sheet "javahighlight.xsl"
    เพื่อ render ให้อยู่ในรูปแบบ html ที่ต้องการ
    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:template match="/">
    <xsl:apply-templates/>
    </xsl:template>

    <!-- the obligatory copy-everything -->
    <xsl:template match="node() | @*">
    <xsl:copy>
    <xsl:apply-templates select="@*"/>
    <xsl:apply-templates/>
    </xsl:copy>
    </xsl:template>

    <xsl:template match="jshl">
    <pre class="hl">
    <xsl:apply-templates mode="javasource"/>
    </pre>
    </xsl:template>

    <xsl:template match="java_plain" mode="javasource">
    <xsl:apply-templates/>
    </xsl:template>

    <xsl:template match="java_keyword" mode="javasource">
    <span class="S_KEYWORD1"><xsl:apply-templates/></span>
    </xsl:template>
    ...


    วิธีการ implement transformer
    เขียน class Java2HtmlTransformer โดย extends AbstractSaxTransformer
    package pok.cocoon.transformer;



    import java.io.StringBufferInputStream;

    import org.apache.cocoon.transformation.AbstractSAXTransformer;
    import org.apache.cocoon.xml.AttributesImpl;
    import org.xml.sax.Attributes;
    import org.xml.sax.SAXException;



    public class Java2HtmlTransformer extends AbstractSAXTransformer {

    private static final String TAG_NAME = "javasource";

    public void endElement(String uri, String name, String raw)
    throws SAXException {
    if (name.equals(TAG_NAME)) {



    String srcStr = this.endTextRecording();
    JavaXmlRenderer render = new JavaXmlRenderer();
    sendStartElementEvent("jshl");
    try {

    render.highlight(new StringBufferInputStream(srcStr), this);

    } catch (Throwable e) {
    e.printStackTrace();
    }
    sendEndElementEvent("jshl");

    } else {
    super.endElement(uri, name, raw);
    }

    }

    public void startElement(String uri, String name, String raw,
    Attributes attr) throws SAXException {

    if (name.equals(TAG_NAME)) {
    this.startTextRecording();
    } else {

    super.startElement(uri, name, raw, attr);
    }
    }

    }

    หลังจากเขียน class แล้ว เราต้องบอกให้ Forrest
    รู้จัก transformer นี้โดยการเพิ่ม config เข้าไปใน sitemap.xmap
    <map:transformers>
    <map:transformer name="java2html"
    src="pok.cocoon.transformer.Java2HtmlTransformer"
    logger="pok"
    />
    </map:transformers>


    ลักษณะการทำงานของ Java2HtmlTransformer ก็คือเราจะสนใจ sax event
    เฉพาะส่วน startElement กับ endElement
    กรณีที่พบ tag <javasource> เราก็จะทำการ
    record text ที่เกิดขึ้นระหว่าง tag เปิด กับ tag ปิด
    และทำการส่งไปให้ JavaXmlRenderer ทำการ parse text ในส่วนนี้

    ส่วน Class JavaXmlRenderer ใช้ source code ตัวอย่างจาก
    project JHighlight
    package pok.cocoon.transformer;

    import java.io.BufferedReader;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.io.Reader;
    import java.io.StringReader;

    import org.apache.cocoon.transformation.AbstractSAXTransformer;

    import com.uwyn.jhighlight.highlighter.JavaHighlighter;
    import com.uwyn.jhighlight.tools.StringUtils;

    public class JavaXmlRenderer {



    public void highlight(InputStream in, AbstractSAXTransformer sax) throws Exc
    eption {

    JavaHighlighter highlighter = new JavaHighlighter();
    highlighter.ASSERT_IS_KEYWORD = true;

    Reader isr;
    isr = new InputStreamReader(in);


    BufferedReader r = new BufferedReader(isr);


    String line;
    String token;
    int length;
    int style;
    String css_class;
    String previous_class = null;
    int previous_style = 0;
    while ((line = r.readLine()) != null)
    {
    line += "\n";
    line = StringUtils.convertTabsToSpaces(line, 4);

    // should be optimized by reusing a custom LineReader class
    Reader lineReader = new StringReader(line);
    highlighter.setReader(lineReader);
    int index = 0;
    while (index < line.length())
    {
    style = highlighter.getNextToken();
    length = highlighter.getTokenLength();
    token = line.substring(index, index + length);

    if (style != previous_style)
    {
    css_class = getCssClass(style);

    if (css_class != null)
    {
    if (previous_class != null)
    {
    sax.sendEndElementEvent(previous_class);
    }
    sax.sendStartElementEvent(css_class);

    previous_class = css_class;
    previous_style = style;
    }
    }
    sax.sendTextEvent(token);

    index += length;
    }

    }
    }


    protected String getCssClass(int style)
    {
    switch (style)
    {
    case JavaHighlighter.PLAIN_STYLE:
    return "java_plain";
    case JavaHighlighter.KEYWORD_STYLE:
    return "java_keyword";
    case JavaHighlighter.TYPE_STYLE:
    return "java_type";
    case JavaHighlighter.OPERATOR_STYLE:
    return "java_operator";
    case JavaHighlighter.SEPARATOR_STYLE:
    return "java_separator";
    case JavaHighlighter.LITERAL_STYLE:
    return "java_literal";
    case JavaHighlighter.JAVA_COMMENT_STYLE:
    return "java_comment";
    case JavaHighlighter.JAVADOC_COMMENT_STYLE:
    return "java_javadoc_comment";
    case JavaHighlighter.JAVADOC_TAG_STYLE:
    return "java_javadoc_tag";
    }
    return null;
    }

    }


    โดยหลักการทำงาน ก็คือ JavaXmlRenderer จะเรียกใช้ scanner ที่ชื่อ
    JavaHighlighter (implement ด้วย Flex)
    ทำการ scan ทีละบรรทัด
    ผลลัพท์ที่ได้ จะได้ style กับ token ออกมา
    จากนั้น ก็จะทำการส่ง sax event ออกไป
    โดยมี tag name ตามรูปแบบ style ที่ได้มาจาก scanner

    ตัวอย่างการนำไปใช้
    <javasource>
    package pok.test;
    import java.util.ArrayList;

    /**
    * this is <code>test</code>
    * @param hi
    */
    public class Main implements java.io.Serializable {
    public static void main(String[] args) {
    System.out.println("helloWorld");
    if (1 != 2) {
    if (1 > 3) {
    // pok test
    }
    }
    }
    }
    </javasource>

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


    สรุป ปัญหาของการ customize forrest หรือ cocoon framework ก็คือ
    เกิดจากเจ้าตัว cocoon framework ที่ค่อนข้างจะซับซ้อนพอสมควร
    แถมมันยังใช้ IOC container ที่ชื่อ Avalon Excalibur
    ยิ่งทำให้การทำความเข้าใจ source code ต่างๆ ต้องใช้เวลามาก
    นอกจากนี้ ยังมีประเด็นความเข้าใจในส่วนของการ parse xml Document
    ที่มักจะมีปัญหาเรื่อง namespace เสมอ

    Related link from Roti

    Tuesday, July 05, 2005

    window1.01, 2.0 screenshots

    พึ่งเคยเห็น screen shot ของ window1.01 กับ 2.0
    ดู classic ดีจัง

    Link

    Related link from Roti

    Sunday, July 03, 2005

    ยืนด้วยครับ

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

    รู้สึกจี๊ดขึ้นมาที่หัวเลย
    แต่ก็ยังสงบอาการ
    บอกตำรวจไปว่า
    "เดี๋ยวมาแล้วค่อยยืน"

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

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

    ปล. ในเชิงวัฒนธรรมแล้ว
    ถือได้ว่าเป็นเรื่องเดียวกับ
    เรื่องรุ่นน้องต้องเคารพรุ่นพี่
    (หรือรุ่นพี่สั่งยืนแถว)
    ในตอนรับน้องไหมเนี่ย

    Related link from Roti

    Friday, July 01, 2005

    Flash Demo: Netbeans + J2EE1.5 + EJB3.0 + GlassFish

    Demo
    ดูแล้วน่าสนใจยิ่ง

    ยังไม่ได้อ่าน spec ของ EJB 3.0 เลย
    แต่คุ้นๆว่ามีการนำ annotation มาใช้เยอะมาก

    ใน flash demo มีตัวอย่าง
    ของ servlet เรียกใช้ session bean
    ชอบตรง annotation ที่ทำหน้าที่
    คล้ายๆ Injection
    (container จะรู้เองว่าควรเอา
    object หรืออะไร มา set ให้)

    Note:
    GlassFish คือ Opensource
    Application Server ของ Sun
    (เดิมคือ Sun Applicatoin Server 9
    ซึ่งให้ใช้ฟรีใน version PE )

    Related link from Roti

    Eclipse Bug Report in verse

    Eclipse Bug number 100918

    Shade Of Grey

    Lyrics: mvm
    Music: sung to the tune of the hit by "The Grateful Dead"

    I guess it's just a shade of grey
    When my editor dropdown opens I might realize
    Just might realize
    But those grey stripes
    That easy slice
    Gonna stay with me a while

    But out in Motif
    Well it's like a tractor factory
    Out in the streets
    They just don't use yellow

    Bout seven list items high
    All their dreams have been realized
    Are being verticalized
    And those grey strips
    Just light up and shine
    When I hit ctrl+e or touch that mouse of mine

    etc...

    Related link from Roti

    Wednesday, June 29, 2005

    Jdk1.5 & "Revenge of the Sith"

    จาก Kirill Grouchnikov's Blog
    The real story about how Anakin Skywalker became Darth Vader

    ใครลองทำ "บ้านทรายทอง" บ้างสิ

    Related link from Roti

    Art of Science Competition

    at princeton university
    Link

    "dynamic asset allocation problem in railroads"

    "Transgenic Neural Stem Cells"

    Related link from Roti

    Monday, June 27, 2005

    Forrest (Publishing framework)

    ช่วงนี้นั่งทำเอกสาร tutorial อยู่
    ก็เลยมองหา tool มาช่วยเขียน
    จำได้ว่าพวก Apache ใช้มาตรฐาน xdoc
    ในการทำเอกสาร ก็เลยตามเข้าไปดู

    ไปเจอโปรเจค Forrest เข้า
    Apache Forrest is a publishing framework that
    transforms input from various sources
    into a unified presentation
    in one or more output formats

    ก็เลย load มาทดลองใช้ดู

    การใช้งาน ก็ง่ายดี
    เริ่มต้นด้วยการสร้าง project ใหม่
    ด้วยคำสั่ง forrest seed
    โดยเจ้า forrest จะสร้าง directory, template file
    ต่างๆให้



    จากนั้นเราก็เข้าไป edit เอกสาร
    ซึ่งใช้ format xdoc
    และถ้าต้องการ generate web site ก็เพียงแต่สั่ง
    forrest run เจ้า forrest ก็จะจัดการ
    generate ให้เรียบร้อย

    นอกจากนี้ยังสามารถ start เป็น web application ได้ด้วย
    โดยสั่ง forrest run
    เจ้า forrest ก็จะทำการ start jetty ให้
    บน port 8888 ทำให้เราสามารถแก้ไขเอกสาร
    พร้อมกับดูผลลัพท์ได้ทันที

    หลังจากลองใช้สักพัก ก็เริ่มโลภมาก
    อยากได้ feature source highlight
    โดยกะว่าจะเพิ่ม tag พิเศษที่ชื่อ <javacode>
    จะได้เอาไว้ render source code ให้มีสีสรรสวยงาม
    ก็เลยค้นเอกสาร forrest
    จึงรู้ว่า เจ้า forrest ใช้ cocoon เป็นฐานราก
    Cocoon implements these concepts around
    the notion of 'component pipelines',
    each component on the pipeline
    specializing on a particular operation.
    This makes it possible to use a Lego(tm)-like approach
    in building web solutions,
    hooking together components into pipelines
    without any required programming.

    ใน pipeline ของ cocoon จะมี component
    อยู่ 3 แบบก็คือ
    • generator ทำหน้าที่สร้าง xml sax event
      ขึ้นจาก source ต่างๆ ไม่ว่าจะเป็น file system
      ,jdbc, xml database, etc..
    • transformer ทำหน้าที่ตามชื่อ
      โดยรับ input เป็น sax event
      และให้ output เป็น sax event
    • Serializer ทำหน้าที่แปลง
      sax event ออกเป็น final format
      ซึ่งอาจจะเป็น html, pdf, svg, office document, etc..

    forrests เปิดโอกาสให้เรา customize pipeline ได้
    ก็เลยกะว่าจะ implements เจ้า Java2HtmlTransformer
    ขึ้นมาเอง และก็ add เข้าไปใน pipeline ของ forrest เสีย
    โดยเอา library java2html มาใช้

    Related link from Roti

    del.icio.us direc.tor

    feature ใหม่ ของ del.icio.us
    ชื่อ direc.tor
    เป็นพวก AJAX อีกแล้ว
    ที่น่าสนใจก็คือ เขาเขียน "How does it work?"
    อธิบายการทำงานไว้ด้วย

    Related link from Roti

    The NetBeans Collaboration Project

    Sun ปล่อย opensource ออกมาอีกตัวแล้ว
    คราวนี้เป็น NetBeans Collaboration Project
    ซึ่งเดิมเป็น feature ใน Sun Java Studio Enterprise 7
    ซึ่งลักษณะการทำงาน ก็เป็นพวก IM
    ที่เปิดโอกาสให้ developer chat กันได้
    โดยผ่าน collaboration service ที่ java.net
    ให้บริการอยู่

    ได้ดู flash demo แล้ว
    ชอบ feature ในส่วน share code
    ที่เปิดโอกาสให้ร่วมกันแก้ไข source code กันได้พร้อมๆกัน
    โดยระหว่างแก้ source code ของอีกฝั่ง
    จะเปลี่ยนเป็น guard box ไว้ (lock เพื่อป้องกัน conflict)
    จนกว่าจะแก้เสร็จ (หยุดพิมพ์)

    Related link from Roti

    Saturday, June 25, 2005

    จดหมายผิดซอง

    วันนี้ได้รับจดหมายเชิญ

    Dear polawat phetra,

    You are confirmed to attend:

    Topic: Mobile & Entertainment Executive Summit Dinner
    Date: Tuesday, June 28, 2005
    Time: 7pm - 11pm (Reception 7pm, Dinner 8pm)
    Location: San Francisco Museum of Modern Art
    151 Third Street (between Mission and Howard Streets) (view map)
    Dress: Business/Cocktail

    Best Regards,
    Alan Brenner
    Vice President Engineering, Consumer & Mobile Systems Group


    หึ มาจากไหนวะ
    เลี้ยงใน Museum ด้วย งานน่าจะหรู

    จากนั้นอีกวันต่อมา
    Dear polawat phetra,

    Please accept our apologies as you were recently sent the below message in error. Sun Microsystems deeply regrets any inconvenience this may have caused. We are taking action to update our processes to ensure that this does not happen again.

    Thank you,
    Sun Microsystems

    Related link from Roti

    Tuesday, June 21, 2005

    Pan รูปขนาดใหญ่ด้วย javascript

    ดูได้ที่ Link นี้

    Related link from Roti

    คนเหล็ก

    ใน signal vs. noise วันนี้มี post
    ที่ชี้ไปยัง Lance Armstrong 's physical advantage

    Armstrong can ride up the mountains in France generating about 500 watts of power for 20 minutes, something a typical 25-year-old could do for only 30 seconds. A professional hockey player might last three minutes - and then throw up.


    มีหนังสือแปลชีวประวัติของเขา
    เป็นภาษาไทยด้วยนะ ออกมานานแล้ว

    Related link from Roti

    Monday, June 20, 2005

    Long Request Filter

    วันนี้เจอ Servlet Filter ที่น่าสนใจที่เดียว
    ชื่อว่า LongRequest ใช้เป็น solution
    สำหรับห่อ Web Application Request
    ที่ใช้เวลา processing นานๆ
    โดย filter ตัวนี้จะ intercept request
    จากนั้นก็ส่ง page เล็กๆ ที่ทำหน้าที่แสดง
    progress bar กลับไปก่อน
    และเจ้า progress page ก็จะทำการ poll
    ไปยัง filter เป็นระยะๆ
    เมื่องานจริงๆ เสร็จก็จะทำการแสดงผล

    ชอบ solution นี้ตรงที่มันไม่ต้องไปยุ่งกับ code
    ที่เราเขียนเลย แค่ดัก intercept ตรงข้างหน้าเท่านั้นเอง

    Related link from Roti

    ruby ติดดิน

    วันนี้เห็น blog Spring Live เขาอ้างถึง
    post The Rise of Open Source Java
    มี graph ยอดหนังสือของ O'REILLY ที่น่าสนใจ



    ดูเจ้า ruby สิ ติดดินเชียว

    Related link from Roti

    Sunday, June 19, 2005

    SWT Snippets

    SWT Snippets

    Related link from Roti

    ใช้ 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