Showing posts with label java. Show all posts
Showing posts with label java. Show all posts

Tuesday, January 19, 2010

MultiMethod with Visitor Pattern

ต่อจากเมื่อวาน Link
อาจารย์โป้งได้อธิบายให้ฟังด้วยว่า วิธีหนึ่งที่เขานิยมใช้ implement multiple dispatch ใน Language ที่ support แต่ single dispatch ก็คือ Visitor Pattern (อาจารย์โป้งไม่ได้ใช้วิธีนี้)

ผมก็เลยเอาตัวอย่างเมื่อวานมาลองเขียนดู
เริ่มแรกก็คือ ให้เจ้า concrete animal ของเรามี accept method ที่รับ Visitor interface ก่อน
public interface Animal {
public void accept(IVisitor visitor);
}

public class Cat implements Animal {

@Override
public void accept(IVisitor visitor) {
visitor.visit(this);
}

}

public class Dog implements Animal {

@Override
public void accept(IVisitor visitor) {
visitor.visit(this);
}

}

จากนั้นก็ สร้าง Visitor Interface ที่มี method visit ครบทุก concrete animal ของเรา
public interface IVisitor {
public void visit(Cat cat);
public void visit(Dog dog);
}

ทดลอง feed อาหารโดยใช้ pattern ใหม่นี้
Cat cat = new Cat();
Dog dog = new Dog();

IVisitor bone = new IVisitor() {

@Override
public void visit(Cat cat) {
System.out.println("I don't like this. Meaw Meaw!!!");

}

@Override
public void visit(Dog dog) {
System.out.println("I 'm full. Hong Hong!!!");

}

};

IVisitor fish = new IVisitor() {

@Override
public void visit(Cat cat) {
System.out.println("I like this. Meaw Meaw!!!");

}

@Override
public void visit(Dog dog) {
System.out.println("Yes, I can eat this. Hong Hong!!!");

}

};

cat.accept(bone);
dog.accept(bone);

cat.accept(fish);
dog.accept(fish);

Related link from Roti

Monday, January 18, 2010

MultiMethod

เมื่อวันเสาร์ไป compkucamp มา เจออาจารย์โป้งเข้าก็เลยถามว่า ช่วงนี้ทำอะไรอยู่, อาจารย์โป้งก็ตอบว่ากำลัง implement multimethod บน c++ อยู่ ว่าแล้วก็ควักโน๊ตบุ๊คออกมาแสดง อาจารย์โป้งใช้ software ได้ผสมปนเปมาก เริ่มจากเปิด microsoft visual c++ ขึ้นมา จากนั้นก็ switch ไปใช้ terminal บน mac เพื่อใช้ django generate project files จากนั้นก็ show file content ให้ดูโดยใช้ textmate

หลังจากกลับมาบ้าน และส่งลูกเข้านอนหมดแล้ว เพื่อแก้ข้อสงสัยที่ฟังมา ก็เลยต้องเข้า wikipedia ไปหาอ่านเรื่อง Multimethod หรือ Multiple Dispatch บ้าง

ในการทำความเข้าใจกับเรื่องนี้ เราก็ควรจะเริ่มจาก basic สุดก่อนก็คือ Single Dispatch ซึ่งใช้ใน Java, C++, Smalltalk, Objective-C

ลองดู code นี้
public abstract class Animal {

public abstract void feed(Food food);

}

public abstract class Food {

}

public class Fish extends Food {

}

public class Bone extends Food {

}

public class Dog extends Animal {

@Override
public void feed(Food food) {
System.out.println("I 'm full. Hong Hong!!!");
}

}

public class Cat extends Animal {

@Override
public void feed(Food food) {
System.out.println("I 'm full. Meaw Meaw!!!");
}

public void feed(Bone food) {
System.out.println("I don't like this. Meaw Meaw!!!");
}

}



ถ้าลอง run แบบนี้ดู
public class Runme {

public static void main(String[] args) {
Animal a = new Dog();
Animal b = new Cat();

Bone bone = new Bone();
a.feed(bone);
b.feed(bone);

}

}


กรณีที่เป็น single dispatch ผลลัพท์ที่ได้ก็คือ
I 'm full. Hong Hong!!!
I 'm full. Meaw Meaw!!!

จะเห็นว่า single dispatch จะตัดสินใจเลือก method โดยดูแค่ว่าจะเลือกให้ class ไหนรับผิดชอบในการ handle การ call, โดยไม่ได้สนใจ type ของ arguments
ส่วน multiple dispatch มันจะเลือก method โดยดู type ของ arguments ด้วย
ถ้าทดลองนำ code ข้างบน ไป run ใน groovy ซึ่งเป็น multiple dispatch ผลลัพท์ทีได้ก็คือ
I 'm full. Hong Hong!!!
I don't like this. Meaw Meaw!!!

Related link from Roti

Thursday, March 19, 2009

excluding transitive dependency in maven

ใครที่เคยใช้ maven คงจะรู้ดีกว่า มันช่วยลดอาการปวดหัวในการจัดการกับ jar file ได้ระดับหนึ่ง เพราะมันช่วยเราจัดการพวก transitive dependency ให้เราได้ (แต่ไปปวดหัวเรื่อง setup มันแทน)

แต่การที่มัน solve transitive dependency ให้ก็นำมาซึ่งปัญหาอันใหม่ นั่นก็คือ version conflict, สมมติว่า project เรามี dependency ถึง library Foo กับ library Bar. ทั้ง Foo และ Bar บังเอิญมี dependency ซึ่ง Library Z เหมือนๆกัน แต่ดันเป็นคนละ version. เมื่อเรา build project, เราก็จะมี library Z เข้ามาใน class path เราทั้งสอง version, ทำให้เกิด error พวก missing method หรือ missing class ได้
(Note: ที่ผมเจอ ผมเจอปัญหานี้ตอน mvn eclipse:eclipse, แต่จากการทดสอบด้วยคำสั่ง mvn dependency:resolve ปรากฎว่า มันได้ผลลัพท์ไม่เหมือนกัน เจ้า dependency:resolve ดูเหมือนจะจัดการได้ถูกต้อง)

วิธีแก้ไขก็คือ การระบุ exclusion ลงใน pom.xml ของ maven
จากของเดิม
<dependency>
<groupId>my.pphetra</groupId>
<artifactId>foo</artifactId>
<version>1.0</version>
</dependency>

<dependency>
<groupId>my.pphetra</groupId>
<artifactId>bar</artifactId>
<version>1.0</version>
</dependency>

ไปเป็น
<dependency>
<groupId>my.pphetra</groupId>
<artifactId>foo</artifactId>
<version>1.0</version>
<exclusions>
<exclusion>
<groupId>test</groupId>
<artifactId>Z</artifactId>
</exclusion>
</exclusions>
</dependency>

<dependency>
<groupId>my.pphetra</groupId>
<artifactId>bar</artifactId>
<version>1.0</version>
</dependency>


ทางแก้ดูแล้วง่ายดาย แต่จริงๆแล้ว มันไปยุ่งตอนที่หาว่า library ตัวไหนที่เราควรจะไปใส่ excludes ให้มัน
วิธีการก็คือ ตอนที่สั่ง mvn command (กรณีของผมก็คือ eclipse:eclipse) ให้ใส่ -X ลงไปด้วย
จากนั้นก็นั่งไล่หาว่า เจ้าตัวไหนคือตัวปัญหา (คำเตือน วิธีนี้มีผลข้างเคียงก็คือ ทำให้เกิดอาการง่วงนอน)

Related link from Roti

Friday, September 12, 2008

เขียน Rome 's plugin

ถ้าจะต้องเขียนโปรแกรม parse feed, เราสามารถเลือกวิธีการได้หลายวิธี
แบบที่ง่ายที่สุดก็คือ ใช้พวก xml parser library ซึ่งมีอยู่มากมาย
เรื่องกวนใจที่ตามมาก็คือ feed มันมี format หลักๆอยู่ 2 แบบ คือ rss และ atom
โปรแกรม parser เราก็เลยฉลาดขึ้นมาอีกนิดหนึ่ง (จะเขียนโดยใช้ if หรือแยก function หรือแนว OOP ก็ว่าไป)

ในส่วนตัวผม ผมเลือกใช้ Rome (มันเป็น Java !!!)
ซึ่งเป็น api ในการ parse feed
โดยมันจะซ่อนความแตกต่างของโครงสร้างระหว่าง RSS และ ATOM ไว้จากเรา

ปัญหาที่เจอก็คือ กรณีที่เราไปดึง feed จาก FeedBurner
มันจะมี extension element จำนวนหนึ่งที่ Rome ไม่รู้จัก (ทำให้ไม่มี method ให้ get ค่าเหล่านั้น)
ยกตัวอย่าง Feed ของ Feedburner มันจะมีหน้าตาแบบนี้
<feed xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0">
...
<entry>
<title>xxx</title>
<content>yyyy<content>
<author>bact'</author>
<link href="http://feeds.feedburner.com/~r/bact/~3/326895937/hauntedness-management.html"/>
<feedburner:origLink href="http://bact.blogspot.com/2008/07/hauntedness-management.html"/>
</entry>
...
</feed>

สิ่งที่เราต้องการก็คือ origLink ซึ่ง ROME ไม่มี api ให้ใช้
แต่โชคดีที่คนออกแบบ Rome เขามองการณ์ไกล เขาก็เลยเตรียมช่อง plugin ให้เราไว้แล้ว

วิธีการก็คือ เราต้องสร้าง (High Level) Parser ของเราขึ้นมาตัวหนึ่ง
และทำการ register มันเข้ากับ Rome
เจ้า Parser ตัวนี้ จะถูกเรียกใช้หลังจากที่ Rome สร้าง DOM structure ขึ้นมาแล้ว
หน้าที่ของ parser ก็คือ return Object ที่ wrap ค่าที่เราต้องการไว้
ตัวอย่าง code
public class Parser implements ModuleParser {

public String getNamespaceUri() {
return FeedburnerModule.FEEDBURNER_URI;
}

public Module parse(Element element) {
Element origLink = element.getChild("origLink", FeedburnerModule.FEEDBURNER_NS);
if (origLink != null) {
FeedburnerModuleImpl impl = new FeedburnerModuleImpl(FeedburnerModule.class, FeedburnerModule.FEEDBURNER_URI);
impl.setOrigLink(origLink.getTextTrim());
return impl;
}
return null;
}

}

ในฝั่งคนใช้งาน ถ้าเขียนเป็น groovy ก็เขียนง่ายๆแบบนี้
def entry0 = feed.entries[0]
def module = entry0.getModule('http://rssnamespace.org/feedburner/ext/1.0')
assertEquals 'http://bact.blogspot.com/2008/07/hauntedness-management.html', module.origLink

Related link from Roti

Friday, June 20, 2008

Java, Parallel, Monad

เห็น code java นี้แล้ว ธาตุไฟแทรก

public static <A, B> F<Callable<A>, Callable<B>> fmap(final F<A, B> f) {
return new F<Callable<A>, Callable<B>>() {
public Callable<B> f(final Callable<A> a) {
return bind(a, new F<A, Callable<B>>() {
public Callable<B> f(final A ab) {
return new Callable<B>() {
public B call() {
return f.f(ab);
}
};
}
});
}
};
}


ใครธาตุแข็งแรงเชิญไปอ่านได้ที่นี่

Higher-Order Java Parallelism, Part 1: Parallel Strategies and the Callable Monad

Related link from Roti

Wednesday, May 14, 2008

delegate ใน groovy closure

งาน NJUG ที่ผ่านมาตอนที่ฟังคุณ cblue พูดเรื่อง groovy
มีตัวแปรใน closure ตัวหนึ่งที่ผมติดใจ ก็คือ delegate
กลับมาแล้วก็เลยต้องนั่งเปิด groovy document หาความรู้เพิ่มเติม
ก็เลยเจอ snippet code ที่น่าสนใจตัวนี้เข้า

เทคนิคที่น่าสนใจก็คือการเปลี่ยน reference ของ delegate ให้ชี้ไปยัง object ที่เราต้องการ

class XmlBuilder {
def out
XmlBuilder(out) { this.out = out }
def invokeMethod(String name, args) {
out << "<$name>"
if(args[0] instanceof Closure) {
args[0].delegate = this
args[0].call()
}
else {
out << args[0].toString()
}
out << "</$name>"
}
}

ลองดูตัวอย่างการ run code ข้างบน
จะเห็นว่ามีการใช้ closure ซ้อนๆกัน

def xml = new XmlBuilder()
xml.html {
head {
title "Hello World"
}
body {
p "Welcome!"
}
}

ทุกๆครั้งที่ มีการ execute inner closure ก็จะมีการ switch delegate ให้ชี้ไปที่
XmlBuilder Object แทนที่จะเป็น default parent object

Related link from Roti

Friday, April 11, 2008

Passing xml stream into BIRT

BIRT สามารถกำหนด datasource ได้หลายแบบ
ในบางสถานะการณ์ผมก็จะเลือกใช้ xml
ในคู่มือส่วนใหญ่ก็จะระบุว่า ถ้าเลือก xml ก็ต้องกำหนด location ของ xml
ซึ่งอาจเป็น url หรือ local file ก็ได้

ปัญหาก็คือ ผมอยากจะ pass xml stream ของผมเข้าไปที่ Engine ตรงๆ
ไม่อยากจะทำเป็น temporary file (ขี้เกียจจัดการ)
แล้วก็ไม่อยากทำเป็น url ด้วย (มีประเด็นเรื่อง security เพิ่มขึ้น)

พยายามมองหาวิธีในคู่มือ ก็หาไม่เจอ
สุดท้ายก็เลยต้องเปิด Krugle เพื่อค้นหาจาก source แทน
หลังจาก zoom ตาม hirarchy ของ class ไปเรื่อยๆ
(ไล่ข้าม project เลย, ปรากฎว่ามันอยู่ใน project Data Tools Platform)
ก็ไปเจอเจ้า Connection ซึ่งมี code แบบนี้
    public void open( Properties connProperties ) 
throws org.eclipse.datatools.connectivity.oda.OdaException
{
...

if ( appContext != null
&& appContext.get( Constants.APPCONTEXT_INPUTSTREAM ) != null
&& appContext.get( Constants.APPCONTEXT_INPUTSTREAM ) instanceof InputStream )
is = XMLDataInputStreamCreator.getCreator( (InputStream) appContext.get( Constants.APPCONTEXT_INPUTSTREAM ) );
else if ( file != null )
is = XMLDataInputStreamCreator.getCreator( file );
else
throw new OdaException( Messages.getString( "Connection.PropertiesMissing" ) );

...
}


ไล่เปิดดู Constants.APPCONTEXT_INPUTSTREAM สักหน่อยว่าค่าเป็นอะไร
แล้ว fix ลงโปรแกรมเราไปเลย
(ไม่ต้องการเพิ่ม dependency ด้วยการอ้างถึง class Constants,
แต่แน่นอนอนาคตถ้า upgrade birt ก็อาจ run ไม่ผ่านเอาดื้อๆได้)

Related link from Roti

Wednesday, February 27, 2008

แอบดู GWT code

เมื่อวานผมเขียน code GWT ในส่วนที่เกี่ยวกับ java.util.Date
แล้วเกิดสงสัยว่ามัน implement อย่างไร ก็เลยลอง download source code ของ GWT มาดู
เปิดดูแล้วก็ร้อง 'ฮ่อ'

ลองดูบางส่วนที่ผมตัดมาให้ดู

/**
* Represents a date and time.
*/

public class Date implements Cloneable, Comparable<Date>, Serializable {

public Date() {
init();
}

private native void init() /*-{
this.jsdate = new Date();
}-*/
;


public native int getDate() /*-{
return this.jsdate.getDate();
}-*/
;

public native int getDay() /*-{
return this.jsdate.getDay();
}-*/
;

public native int getHours() /*-{
return this.jsdate.getHours();
}-*/
;

public native String toGMTString() /*-{
var d = this.jsdate;
var padTwo = @java.util.Date::padTwo(I);
var month =
@java.util.Date::monthToString(I)(this.jsdate.getUTCMonth());

return d.getUTCDate() + " " +
month + " " +
d.getUTCFullYear() + " " +
padTwo(d.getUTCHours()) + ":" +
padTwo(d.getUTCMinutes()) + ":" +
padTwo(d.getUTCSeconds()) +
" GMT";
}-*/
;

private static String padTwo(int number) {
if (number < 10) {
return "0" + number;
} else {
return String.valueOf(number);
}
}
}

งามดีเหมือนกัน ส่วนที่ cross กันระหว่าง javascript กับ java (ก่อนที่จะแปลงเป็น javascript)

ที่น่าสนใจก็คือ ตอนที่ฝั่ง javascript call มาที่ฝั่ง java
จาก code ข้างบนลองดูวิธีการ call method padTwo

// เริ่มด้วยการอ้างถึง method ที่ต้องการ
// จะเห็นว่ามันใช้ signature ของ method ในการอ้างถึง method ที่ต้องการ
// สิ่งที่ return กลับมาก็น่าจะเป็น javascript function

var padTwo = @java.util.Date::padTwo(I);
...
padTwo(d.getUTCHours()) + ...
...

Related link from Roti

Wednesday, February 20, 2008

GWT กับ Spring

ผม search หาวิธี map GWT เข้ากับ SpringFramework มาพักใหญ่แล้ว
ส่วนใหญ่ที่เห็นก็จะเป็นการเขียน DispatcherServlet ของตัวเองขึ้นมา
เช่น GWT-SL
วิธีการของ GWT-SL จะเป็นดังนี้
  • config servlet ใน web.xml
      <servlet>
    <servlet-name>rpc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
    <servlet-name>rpc</servlet-name>
    <url-pattern>*.rpc</url-pattern>
    </servlet-mapping>

  • config spring context โดยใช้ GWTHandler เข้ามาเป็นตัว wrapper
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
    "http://www.springframework.org/dtd/spring-beans.dtd">

    <beans>
    <bean id="urlMapping" class="org.gwtwidgets.server.spring.GWTHandler">
    <property name="mappings">
    <map>
    <entry key="/wcfb110_jobService.rpc" value-ref="jobService"/>
    </map>
    </property>
    </bean>

    <bean id="jobService" class="wcf.web.fin.gwt.wcfb110.server.JobServiceImpl"/>
    </beans>
    </pre>


ดูก็ง่ายดีแล้ว แต่มาวันนี้เจอตัวอย่าง source code ของ Interface21(เจ้าพ่อ spring) ซึ่ง config spring กับ GWT
(โปรดระวัง เห็นนามสกุลเป็น .zip แต่จริงๆแล้วมันเป็นพวก .tgz)
เห็นแล้วต้องร้อง Wow, ทำไมมัน simple อย่างนี้

เขาใช้วิธีนี้
เริ่มด้วย web.xml ที่ทำเหมือนกันกับข้างบน
แต่ ตัว spring context เขาเปลี่ยนเป็นแบบนี้
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd"
>

<context:component-scan base-package="gwt.server" />

</beans>

แล้วตัว GWT Servlet ของเราก็ใส่ annotation เข้าไปแบบนี้
@Controller
public class NoteServiceImpl extends RemoteServiceServlet implements NoteService, ServletConfigAware {

public void setServletConfig(ServletConfig servletConfig) {
try {
init(servletConfig);
} catch (ServletException e) {
throw new IllegalStateException("Cannot initialise Controller.", e);
}
}

@RequestMapping("/gwt.Example/NoteService.rpc")
public void doRpc(HttpServletRequest request, HttpServletResponse response)
throws Exception {
doPost(request, response);
}

public Note loadNote(String name) {
return new Note(name, "This Note named " + name + " was constructed on the server");
}
}

ง่ายขึ้นเยอะ ลด dependency ของพวก wrapper ไปได้
แต่ข่าวร้ายสำหรับผมก็คือ annotation พวกนี้เป็น feature ของ spring 2.5.x
โชคร้ายที่โปรเจคผมยังใช้ spring 2.0.x อยู่เลย

Related link from Roti

Friday, January 11, 2008

Qi4j

Qi4j ออกเสียงว่า "chee for jay"
ความพยายามใหม่ของ Rickard Öberg (co-founder of JBoss)
ที่ตั้งชื่อว่า "Composite Oriented Programming"
โดยมีฐานต่อยอดจาก Java
Qi4j is a framework for domain centric application development, including evolved concepts from AOP, DI and DDD.


แนวคิดหลักๆคือ
1. fine-grained parts, แยกส่วน model ออกเป็นหลาย part เช่น behavior part กับ state part
แล้วเวลาใช้ ค่อยนำมาประกอบกันในลักษณะของ Composite
การแยกส่วนแบบนี้เชื่อว่า จะนำมาสู่การ reuse ที่ดียิ่งขึ้น
2. Context-base behavior, composite เดียวกัน แต่ถ้าอยู่ใน context ที่ต่างกัน
ก็จะมี behavior ที่ต่างกันได้

ลองดูเฉพาะข้อ 1 กันก่อน
ถ้าใครคุ้นกับ ruby, พออ่านข้อ 1 แล้วอาจจะนึกถึง Mixin
ลองเปรียบเทียบกับตัวอย่างใน tutorial ที่ว่าด้วย HelloWorld ของ Qi4j ดู

ปกติ HelloWorld model ถ้าเขียนแบบปกติ ก็เขียนได้ดังนี้
public class HelloWorld {
String name;

public String say() {
return "hello " + getName();
}

public void setName(String name) {
this.name = name;
}

public void getName() {
return name;
}
}

ถ้าเราแยก HelloWorld ออกเป็นส่วนๆ เราจะเห็นว่า มันประกอบด้วย
behavior part ซึ่งก็คือ method say
กับ state part ซึ่งก็คือ property name

เขียนแบบ Qi4j ก็จะต้องทำดังนี้
(ใน Qi4j เขามี concept ว่า "Classes ard dead, Long live interfaces.")

เริ่มด้วย declare model เราด้วย interface
public interface HelloWorld
extends HelloWorldBehaviour, HelloWorldState
{
}

@Mixins( HelloWorldStateMixin.class )
public interface HelloWorldState
{
Property<String> name();
}

@Mixins( HelloWorldBehaviourMixin.class )
public interface HelloWorldBehaviour
{
String say();
}

implement logic ของ behavior
public class HelloWorldBehaviourMixin
implements HelloWorldBehaviour
{
@ThisCompositeAs HelloWorldState state;

public String say()
{
return state.phrase() + " " + state.name();
}
}

implement logic ของ state
public class HelloWorldStateMixin
implements HelloWorldState
{
@PropertyField Property<String> name;

public Property<String> name()
{
return name;
}
}

สุดท้ายประกอบทั้งหมดเข้าหากัน
public interface HelloWorldComposite
extends HelloWorld, Composite
{
}


ลองเปรียบเทียบกับการเขียน Mixin ด้วย ruby ดูบ้าง
module HelloState
attr_accessor :name
end

module HelloBehavior
def say
puts "hello #{@name}"
end
end

class HelloWorld
include HelloState
include HelloBehavior
end


ตอนนี้ tutorial ในส่วน context base behavior ยังไม่ออกมา
ก็ต้องรอดูกันต่อไป ว่าจะออกมาหน้าตาเป็นอย่างไร

Related link from Roti

Friday, January 04, 2008

Singleton ?

ใครใช้ Java ก็คงรู้สึกว่า Singleton เป็นส่วนหนึ่งของชีวิต (โดยเฉพาะ Lazy-loaded)
เมื่อก่อน (นานมาแล้ว)
เขานิยมใช้ pattern แบบนี้
class MyClass {
private static MyClass instance;

private MyClass() {}

public static MyClass getInstance() {
if (instance == null) {
instance = new MyClass();
}
return instance;
}
}

ซึ่งก็ใช้ได้ ยกเว้นว่า ถ้าอยู่ในสภาพแวดล้อมแบบ concurrency แล้ว ก็มีโอกาสที่ MyClass จะถูกสร้างซ้ำซ้อนได้มากกว่า 1 ครั้ง
สมัยนั้นมีคนถกเถียงเรื่องนี้อยู่วุ่นวาย แต่ผมขี้เกียจอ่านและติดตาม
(ใครสนใจว่าถกกันอย่างไร ลองอ่านตัวอย่างอันนี้ดู The double-checked locking is broken)
ถึงจะรู้ว่ามีปัญหาเรื่อง concurrency แต่ผมก็ใช้ pattern นี้มาตลอด (โดยแอบเข้าข้างตัวเองว่า เอาน่า อย่างมากก็เปลือง resource นิดๆหน่อยๆ)
จนมาภายหลังที่มี springframework ออกมา ผมก็เลยยกหน้าที่นี้ให้ spring ไป

จนมาเมื่อวาน อ่านหนังสือเล่มหนึ่ง พบว่าเขามีวิธีการเขียนแบบใหม่ที่ดีกว่ากว่าแล้ว
เรียกว่า Initialization on demand holder idiom
public class Something
{
private Something() {}

private static class LazyHolder
{
private static final Something something = new Something();
}

public static Something getInstance()
{
return LazyHolder.something;
}
}


เป็นอันว่าเรื่องนี้ผมตกข่าวไป เพราะใน history ของ wiki เห็นเขาเขียนเรื่องนี้มาตั้งแต่ July 2006 แล้ว

Related link from Roti

Tuesday, December 11, 2007

native dependency with maven

วันนี้มีโจทย์ที่ต้อง config maven ให้เลือกใช้ dependency แบบที่ต้องขึ้นอยู่กับ platform
ต้นตอของเรื่องก็คือเจ้า swt library ที่ต้องเลือก jar ให้ถูกกับเครื่องที่จะไป run
เช่นถ้า build บน linux ก็ควรจะใช้ swt-linux-gtk-native.jar
หรือถ้าบน windows ก็ต้องเป็น swt-win32-native.jar

เริ่มต้นด้วยการ setup repository ก่อน
หน้าตาของ repository ก็ประมาณนี้

+ swt
+ swt-linux-gtk-native
+ 3.3.0
- swt-linux-gtk-native-3.3.0.jar
+ swt-win32-native
+ 3.3.0
- swt-win32-native-3.3.0.jar


จากนั้นก็ให้กำหนด profiles ใน pom.xml
สังเกตุว่าใน profile จะมี activate block ที่ใช้ข้อมูล os เป็น condition ในการ activate
<profiles>
<profile>
<id>unix</id>
<activation>
<os>
<family>unix</family>
</os>
</activation>
<properties>
<swt.os-dep>swt-linux-gtk-native</swt.os-dep>
<swt.version>3.3.0</swt.version>
</properties>
</profile>
<profile>
<id>win32</id>
<activation>
<os>
<family>windows</family>
</os>
</activation>
<properties>
<swt.os-dep>swt-win32-native</swt.os-dep>
<swt.version>3.3.0</swt.version>
</properties>
</profile>
</profiles>


จากนั้นใน dependency list ของเรา ก็ให้อ้างถึง jar โดยใช้ property name แทน

<dependency>
<groupId>swt</groupId>
<artifactId>${swt.os-dep}</artifactId>
<version>${swt.version}</version>
</dependency>

Related link from Roti

Thursday, September 20, 2007

Eclipse oAW

กลับจากงาน NJUG4 ด้วยความค้างคาใจในเรื่อง MDA
ด้วยความที่ Robert ไม่ยอมแสดงตัวอย่างเจ๋งๆใน MagicDraw ให้ดู

เมื่อวันก่อนอ่านพบใน Eclipse Planet ว่ามี project 's annoucement ที่ชื่อ OpenArchitecture Ware (oAW)
ซึ่งเป็น Framework สำหรับ MDA ที่ integrate เข้ากับพวก MDD projects ของ Eclipse (เช่น EMF)
ก็เลยได้ฤกษ์ทดลองเสียเลย

มาลองดู step ของการใช้ oAW
ตั้งแต่ define MetaModel ไปจนถึง การ generate Source code

  • step แรกเริ่มต้นกันที่ การสร้าง MetaModel ก่อน (M2)
    ที่มาของ MetaModel ใน oAW มีได้ 2 แบบหลักๆคือ
    1. จาก EMF
    เมื่อก่อนเคยสงสัยมานานแล้วว่า EMF ไว้ทำอะไร
    หลังจากไปฟัง Robert พูดมา และกลับมาอ่านเอกสารของ EMF
    ทำให้เริ่มมองภาพ EMF ออกมากขึ้น
    ตัว EMF จริงๆแล้วก็คือ Implementation ตัวหนึ่งของ MOF (Meta Object Facility)
    แต่ implement แค่ subset บางส่วน, และเพื่อไม่ให้สับสนกับ MOF
    ส่วน subset model ที่ EMF implement เขาก็เลยตั้งชื่อว่า Ecore
    (ปัจจุบัน MOF 2.0, subset ที่ EMF support เขาตั้งชื่อว่า EMOF)

    วิธีสร้าง MetaModel ก็มีหลายแบบ เช่น
    • define มันตรงๆเลย ด้วย Editor ของ EMF
    • export มาจากพวก UML Tool ที่ support XMI format
    • model ด้วย Java Code + Annotation
    • ใช้ XSD ในการ define


    2. สร้างจาก xText
    อันนี้น่าสนกว่าวิธีที่ 1 เยอะ
    ถ้าอ่าน overview ของ xText จะเห็นว่า
    เขาประกาศตัวเป็น Textual DSL development framework
    การสร้าง MetaModel ใน xText ก็จะใช้การเขียน Grammar rules แทน

    ตัวอย่างของ MetaModel ที่ oAW ใช้ใน tutorial ก็มีหน้าตาแบบนี้
    เป็นตัวอย่างของการ define MetaModel ของ Simple Class Diagram



  • ขั้นตอนถัดไป ก็คือการสร้าง Model (ที่เป็น instance ของ MetaModel ที่ได้จากขั้นตอนแรกของเรา)
    ถ้าเราใช้ MetaModel จาก EMF ในขั้นนี้ เราเลือกทางเลือกได้หลายทาง
    เช่น ให้ EMF generate Editor ให้เรา เพื่อที่เราจะได้ใช้ Editor ตัวนั้นสร้าง Model
    หรือจะไปพวกตระกูล GMF สร้าง Graphic Editor มาเพื่อที่จะมาสร้าง Model ก็ได้

    แต่ถ้าเลือก choice ของ xText
    ในขั้นนี้ เราก็จะให้ xText generate Text Editor ให้เรา
    แล้วเราก็ใช้ Text Editor นั้น เขียน DSL ของเราได้เลย

  • ขั้นถัดไป ก็คือการ กำหนด build script ที่ใช้ในการ generate
    oAW เรียก script พวกนี้ว่า Workflow
    ตัวนี้ดูแล้ว ได้อารมณ์เหมือนเขียน Ant เลย
    <workflow>
    <property file="workflow.properties"/>

    <!-- ให้ parser อ่าน Model ของเรา โดยอ้างอิง MetaModel ด้วย -->
    <component id="xmiParser"
    class="org.openarchitectureware.emf.XmiReader">
    <modelFile value="${modelFile}"/>
    <metaModelPackage value="data.DataPackage"/>
    <outputSlot value="model"/>
    <firstElementOnly value="true"/>
    </component>

    <!-- validate model โดยใช้พวก OCL script -->
    <component
    class="org.openarchitectureware.check.CheckComponent">
    <metaModel id="mm" class="org.openarchitectureware.type.emf.EmfMetaModel">
    <metaModelPackage value="data.DataPackage"/>
    </metaModel>
    <checkFile value="checks"/>
    <emfAllChildrenSlot value="model"/>
    </component>

    <!-- ลบ source file ที่เคยถูก generate ไว้ -->
    <component id="dirCleaner"
    class="org.openarchitectureware.workflow.common.DirectoryCleaner" >
    <directories value="${srcGenPath}"/>
    </component>

    <!-- generate file โดยใช้ template ที่ระบุ -->
    <component id="generator"
    class="org.openarchitectureware.xpand2.Generator">

    <metaModel id="mm"
    class="org.openarchitectureware.type.emf.EmfMetaModel">
    <metaModelPackage value="data.DataPackage"/>
    </metaModel>

    <expand value="templates::Root::Root FOR model"/>

    <outlet path="${srcGenPath}/">
    <postprocessor
    class="org.openarchitectureware.xpand2.output.JavaBeautifier"/>
    </outlet>

    </component>
    </workflow>


  • ในการ generate source code สิ่งที่ต้องมีก็คือ Template Engine
    หน้าตาของ oAW template มีหน้าตาแบบนี้
    ให้ความรู้สึกเหมือน XSL
    «DEFINE Root FOR data::DataModel»
    «EXPAND Entity FOREACH entity»
    «ENDDEFINE»

    «DEFINE Entity FOR data::Entity»
    «FILE name + ".java"»
    public class «name» {
    «EXPAND Attribute FOREACH attribute»
    }
    «ENDFILE»
    «ENDDEFINE»

    «DEFINE Attribute FOR data::Attribute»
    private «type» «name»;
    «ENDDEFINE»


แค่นี้ก็ได้แนวทางการ Generate code แบบง่ายแล้ว
(รายละเอียดมันเยอะกว่านี้
มีทั้งเรื่อง การ validate model โดยใช้ OCL,
การสร้าง helper code ที่ template สามารถใช้ช่วยในการ generate source code)

อืมม์ นี่แค่เริ่มต้น
เนื่องจากเห็นมองเห็นภาพรวมแล้ว
ทำให้รู้สึกสนุก และอยากหัดใช้ project ในตระกูล Eclipse MDD มากขึ้น
ตัว xText ที่เป็น Textual DSL ก็น่าใช้
พวกรุ่นหลังของ GMF ก็ออกมาง่ายมากขึ้นทุกที
ทำให้เราสร้าง Graphice Editor ได้ง่ายขึ้นเรื่อยๆ

์์Note: ต้องขอบคุณงาน NJUG มาก
เพราะถ้าไม่ได้ไปฟัง Robert พูด
ตอนที่เห็น oAW ก็คงไม่ได้ปิ๊งขนาดนี้หรอก

Related link from Roti

Thursday, September 13, 2007

CSP กับ คำถามของไอน์สไตน์

ช่วงนี้หันกลับมาเรียนรู้ Constraint programming ใหม่
กะจะเอาจริงมากขึ้น
คราวก่อนพลาดไปตรงที่
ดันไปใช้ constraint programming library บน Oz
ทำให้เปิดศึกสองด้าน เพราะต้องเรียนรู้ Oz ไปด้วย

มาคราวนี้เลยเลือกใช้ choco
ซึ่ง base อยู่บน java
(จริงๆอยากลองใช้ chr บน prolog นะ
แต่จะเข้าข่ายเดิมอีก ก็คือ ไปไม่ถึงไหน)

ลองโจทย์แรกก่อน Zebra Puzzle
(หรือที่นิยมเรียก "คำถามของไอน์สไตน์")

modeling ของเราก็คือ
มีบ้าน 5 หลัง
เรากำหนดให้แต่ละหลังแทนด้วยตัวเลข 1 ถึง 5

จากนั้นเราก็จะกำหนด variable มาเพื่อระบุว่า
คุณสมบัติที่ variable นั้น represent นั้น, มันตรงกับบ้านหลังใด เช่น
var colors[GREEN] = 3 (บ้านหลังที่ 3 เป็นสีเขียว)
แน่นอนว่า ในเบื้องต้น เราไม่รู้หรอกว่าบ้านสีเขียวจะตรงกับหลังไหน
ดังนั้น เราจะกำหนดแบบนี้แทน
var colors[GREEN] = 1..5
นั่นคือ บ้านที่เป็นสีเขียวเป็นไปได้ตั้งแต่หลังที่ 1 ถึงหลังที่ 5

ว่าแล้วก็ลองเขียนโปรแกรมดู
เริ่มจากสร้าง problem ขึ้นมาก่อน
Problem pb = new Problem();

จากนั้นก็สร้างตัวแปร Domain variable
IntDomainVar[] colors = pb.makeEnumIntVarArray("colors", 5, 1, 5);
IntDomainVar[] nationals = pb.makeEnumIntVarArray("nationals", 5, 1, 5);
IntDomainVar[] pets = pb.makeEnumIntVarArray("pets", 5, 1, 5);
IntDomainVar[] professions = pb.makeEnumIntVarArray("professions", 5, 1, 5);
IntDomainVar[] drinks = pb.makeEnumIntVarArray("drinks", 5, 1, 5);

parameter ตัวที่ 2 คือ ขนาด ของ array ที่ต้องการสร้าง
ส่วนความหมายของ parameter 1, 5 ที่อยู่ด้านหลัง ก็คือ range ของ domain value
ซึ่งหมายความว่ามี ค่าที่เป็นไปได้คือ 1,2,3,4 หรือ 5

เมื่อมี variable พร้อมแล้ว
ก็มาถึงขั้นที่ต้องกำหนด constraint
เริ่มด้วย constraint แรก
"The Englishman lives in the red house."
เขียนได้ดังนี้
pb.post(pb.eq(nationals[ENGLISH], colors[RED]));

pb.post คือคำสั่งที่สั่ง post constraint เข้าสู่ problem space.

พวกเงื่อนไขหลักๆยังง่ายอยู่ ก็ลอกๆตามไป
// The Spaniard has a dog:
pb.post(pb.eq(nationals[SPANISH], pets[DOG]));
// The Japanese is a painter
pb.post(pb.eq(nationals[JAPANESE], professions[PAINTER]));
// The Italian drinks tea
pb.post(pb.eq(nationals[ITALIAN], drinks[TEA]));
// The Norwegian lives in the first house on the left
pb.post(pb.eq(nationals[NORVEGIAN], 1));
// The owner of the green house drinks coffee:
pb.post(pb.eq(colors[GREEN], drinks[COFFEE]));
// The green house is on the right of the white house
pb.post(pb.eq(colors[GREEN], pb.plus(colors[WHITE], 1)));
// The sculptor breeds snails
pb.post(pb.eq(professions[SCULPTOR], pets[SNAILS]));
// The diplomat lives in the yellow house
pb.post(pb.eq(professions[DIPLOMAT], colors[YELLOW]));
// They drink milk in the middle house
pb.post(pb.eq(drinks[MILK], 3));
// The violinist drinks fruit juice
pb.post(pb.eq(professions[VIOLINIST], drinks[JUICE]));

ตัวที่เริ่มยาก (ยากตรงจะใช้ syntax ของ choco อันไหนดี)
ก็คือ "The Norwegian lives next door to the blue house"
ความหมายก็คือ |Norwegian - Blue| = 1
แต่ absolute ใน choco มันรับ parameter แปลกๆ ก็เลยหันไปใช้พวก Or แทน
ได้ออกมาอย่างนี้
// The Norwegian lives next door to the blue house
pb.post(
pb.makeDisjunction(new Constraint[] {
pb.eq(pb.minus(nationals[NORVEGIAN], colors[BLUE]), 1),
pb.eq(pb.minus(nationals[NORVEGIAN], colors[BLUE]), -1)
}));
// The fox is in the house next to the doctor’s
pb.post(
pb.makeDisjunction(new Constraint[] {
pb.eq(pb.minus(pets[FOX], professions[DOCTOR]), 1),
pb.eq(pb.minus(pets[FOX], professions[DOCTOR]), -1)
}));

// The horse is in the house next to the diplomat’s:
pb.post(
pb.makeDisjunction(new Constraint[] {
pb.eq(pb.minus(pets[HORSE], professions[DIPLOMAT]), 1),
pb.eq(pb.minus(pets[HORSE], professions[DIPLOMAT]), -1)
}));


สุดท้ายก็กฎพื้นฐานที่ว่า บ้านของแต่ละคุณสมบัติ ต้องไม่ซ้ำกัน
เช่นบ้านแต่ละหลังต้องสีไม่ซ้ำกับบ้านหลังอื่นๆ
pb.post(pb.allDifferent(colors));
pb.post(pb.allDifferent(nationals));
pb.post(pb.allDifferent(pets));
pb.post(pb.allDifferent(professions));
pb.post(pb.allDifferent(drinks));


เมื่อ define constraint เสร็จ ก็สั่งให้มันค้นหาคำตอบให้เราได้
โดยเราสามารถใช้ method getVal เพื่อดึงค่าคำตอบจาก Domain variable ได้เลย
pb.solve();
pb.getSolver().getSearchSolver().restoreBestSolution();

// who drink waters
for (int i = 0; i < 5; i++) {
if (nationals[i].getVal() == drinks[WATER].getVal()) { // found
System.out.println(map.get(i) + " drink waters");
}
}
// who has zebra
for (int i = 0; i < 5; i++) {
if (nationals[i].getVal() == pets[ZEBRA].getVal()) {
System.out.println(map.get(i) + " has zebra");
}
}


Note: ใช้ java เขียนพวก declarative นี่ช่างเยิ่นเย้อจริงๆ
ให้สั้นลงอีกหน่อย ก็คงต้องใช้ groovy (ตัวอย่าง Groovy ที่เรียกใช้ choco)

Related link from Roti

Thursday, August 30, 2007

ทดสอบต่อ RMI จาก Eclipse RCP ไป OFBiz

ประเด็นที่น่าสนใจ ก็คือ
  1. ปัญหาเรื่องการ pack Ofbiz library เฉพาะที่ client ต้องใช้ ให้เป็น Eclipse Plugin
  2. ปัญหาเรื่อง class loading


เริ่มที่อันแรกสุดก่อน
อันนี้ทำได้ง่ายๆ โดยการใช้ new wizard ของ Eclipse
ที่ชื่อ "Plugin from existing JAR Archives"
แต่ยังมีประเด็นที่ตาม ก็คือ library พวกนี้ ณ ขณะ runtime
มันมีการอ่าน properties file หรือ configuration file ด้วย
ดังนั้น เราเลยต้องสร้าง folder เพิ่มใน plugin
และกำหนดใน manifest file ให้มันเป็น classpath
จากนั้นก็ copy properties file ที่จำเป็นจาก ofbiz/framework/base/config
ซึ่งประกอบด้วย log4j.xml, jsse.properties, cache.properties

log4j.xml ถูกเรียกใช้จาก org.ofbiz.base.util.Debug lineno:86
ภายใน log4j.xml มี file appender อยู่จำนวนหนึ่งที่ระบุ file เป็น relative path อยู่
เนื่องจากตอนนี้ยังไม่อยากแตะประเด็นนี้ ก็เลยใช้วิธีแก้เปลี่ยนเป็น static path ไปไว้สักที่หนีงก่อน

jsse.properties ถูกเรียกใช้โดย org.ofbiz.base.util.SSLUtil lineno:249
เพื่อใช้หา configuration ของพวก proxyHost, proxyPort
ตัวต้นฉบับ ข้างในมีเนื้อหาที่ใช้จากฝั่ง server ด้วย
ซึ่ง client ไม่ต้องใช้, สามารถลบทิ้งทั้งหมดได้เลย

cache.properties ถูกเรียกใช้จาก org.ofbiz.base.util.cache.UtilCache lineno:217
ภายในมีการกำหนด relative path ของ disk cache file ด้วย
ก็ให้เปลี่ยนเป็น fix path ไปก่อน
(ตรงนี้ยังไม่ได้ดู แต่เข้าใจว่าสามารถลบทิ้งได้เยอะเหมือนกัน)

หน้าตา MANIFEST.MF file ของ plugin จะได้เป็นแบบนี้
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Rmiclient Plug-in
Bundle-SymbolicName: org.ofbiz.rmiclient
Bundle-Version: 1.0.0
Bundle-ClassPath: ofbiz-base.jar,
javolution-4.2.8.jar,
wsdl4j.jar,
ofbiz-service.jar,
jdbm-1.0.jar,
log4j-1.2.14.jar,
config/
Bundle-Vendor: com.orangegears
Bundle-Localization: plugin
Export-Package: .,
com.ibm.wsdl,
com.ibm.wsdl.extensions,
com.ibm.wsdl.extensions.http,
com.ibm.wsdl.extensions.mime,
...


จากตรงนี้ไป เราก็มี Ofbiz library ให้พร้อมเขียน RMI call แล้ว
ปัญหาถัดไปที่ตามมาก็คือ
เวลา run code นี้
RemoteDispatcher dispatcher = (RemoteDispatcher) Naming.lookup(connectionDetail.getServerUrl());
Map result = dispatcher.runSync("userLogin", UtilMisc.toMap("login.username",
connectionDetail.getUserId(),
"login.password",
connectionDetail.getPassword()));

มันจะเกิด error ดังนี้
java.rmi.UnmarshalException: error unmarshalling return; nested exception is: 
java.lang.ClassNotFoundException: org.ofbiz.entity.GenericValue
(no security manager: RMI class loader disabled)

เป็นประเด็นเรื่อง SecurityManager
แก้แบบ quick fix ด้วยการ implment Custom SecurityManager ลงไปก่อน
public class MySecurityManager extends SecurityManager {


public void checkPermission(Permission perm, Object context) {
}

public void checkPermission(Permission perm) {
}

}

จากนั้นก็แก้ code ข้างบนให้เป็นแบบนี้
System.setSecurityManager(new MySecurityManager());
RemoteDispatcher dispatcher = (RemoteDispatcher) Naming.lookup(connectionDetail.getServerUrl());
Map result = dispatcher.runSync(...);

Exception ตัวเก่า ก็จะหายไป ได้ exception ตัวใหม่มาแทน
java.lang.ExceptionInInitializerError
at org.ofbiz.base.util.UtilURL.fromOfbizHomePath(UtilURL.java:110)
at org.ofbiz.base.util.UtilURL.fromResource(UtilURL.java:76)
at org.ofbiz.base.util.UtilURL.fromResource(UtilURL.java:44)
at org.ofbiz.base.util.UtilProperties.getPropertyValue(UtilProperties.java:130)
at org.ofbiz.base.util.UtilProperties.getPropertyValue(UtilProperties.java:100)
at org.ofbiz.base.util.SSLUtil.loadJsseProperties(SSLUtil.java:249)
at org.ofbiz.base.util.SSLUtil.loadJsseProperties(SSLUtil.java:244)
at org.ofbiz.base.util.SSLUtil.(SSLUtil.java:52)
at org.ofbiz.service.rmi.socket.ssl.SSLClientSocketFactory.createSocket(SSLClientSocketFactory.java:42)

ถ้าลองเปิด code org.ofbiz.base.util.UtilURL ดูบริเวณ lineno 60-67 ซึ่งมีหน้าตาเป็นแบบนี้
        if (loader == null && url == null) {
try {
loader = Thread.currentThread().getContextClassLoader();
} catch (SecurityException e) {
UtilURL utilURL = new UtilURL();
loader = utilURL.getClass().getClassLoader();
}
}

ปัญหาจะอยู่ตรง Thread.currentThread().getContextClassLoader()
(ถือเป็นปํญหา classic ของคนเขียน Eclipse RCP ที่พยายามใช้ library ที่เคย work บน web app มาก่อน)
ตัว currentThread ก็คือ thread ที่เป็นตัว start eclipse
ซึ่งแน่นอนว่า classpath มัน ไม่รวมถึง classpath ของ plugin ด้วย

ทางแก้แบบ quick fix ที่ได้รับความนิยมสูง ก็คือ
ห่อ code ที่จะ call ofbiz ด้วยอันนี้เสีย
Thread thread = Thread.currentThread();
ClassLoader loader = thread.getContextClassLoader();
thread.setContextClassLoader(this.getClass().getClassLoader());
try {

System.setSecurityManager(new MySecurityManager());
RemoteDispatcher dispatcher = (RemoteDispatcher) Naming.lookup(connectionDetail.getServerUrl());
Map result = dispatcher.runSync("userLogin", UtilMisc.toMap("login.username",
connectionDetail.getUserId(),
"login.password",
connectionDetail.getPassword()));

} finally {
thread.setContextClassLoader(loader);
}


ปิดคดี

Related link from Roti

Tuesday, August 21, 2007

ขั้นตอนการ start ของ Ofbiz

ลองแกะขั้นตอนการ start ของ Ofbiz ดู ก็พบขั้นตอนที่น่าสนใจดังนี้

1. เหมือนกับ framework ใหญ่ๆทั่วๆไป นั่นคือ
ถ้าต้อง start อะไรที่ซับซ้อนนัก ก็ควรมี bootstrapping code
โดย class ที่เป็นจุดเริ่มต้นทำงานของ Ofbiz ก็คือ org.ofbiz.base.start.Start.java
เมื่อ initialize ค่าเบื้องต้นต่างๆหมดแล้ว (เช่น log directory, base class path)
ก็จะทำการ load configuration ตาม command parameter ที่ user สั่งผ่าน command-line มา
เช่น ผมสั่ง
java -jar ofbiz.jar -pos
มันก็จะไปใช้ configuration file ที่ชื่อ /framework/base/src/start/org/ofbiz/base/start/pos.properties
ถ้าสั่ง
java -jar ofbiz.jar
มันก็จะใช้ default configuration file ซึ่งก็คือ start.properties

element ที่สำคัยภายใน properties file ข้างบน ก็คือ ofbiz.start.loader* (* คือเลขลำดับในการ load)
ตัว loader ที่สำคัญของ Ofbiz ก็คือ org.ofbiz.base.container.ContainerLoader
ซึ่งเมื่อมัน load ขึ้นมา มันจะใช้ค่า ofbiz.container.config ที่ระบุใน configuration file
ในการ load container ขึ้นมาทำงาน

2. ofbiz มองว่า การ start ครั้งหนึ่งๆ เราสามารถกำหนด container ที่เราต้องการได้หลายตัว
โดยต้องสร้าง xml file ที่ระบุ container ที่ต้องการ
และระบุชื่อ file ไว้ใน configuration ผ่าน property ที่ชื่อ ofbiz.container.config

อย่างในกรณีที่เราจะ start โปรแกรม point of sell
ค่า ofbiz.container.config ก็จะกำหนดแบบนี้
ofbiz.container.config=framework/base/config/pos-containers.xml

ofbiz ก็จะใช้ file pos-containers.xml ในการเลือก start container ที่ต้องการ

3. Container ที่สำคัญๆ และขาดไม่ได้ ของ Ofbiz ก็คือ
3.1 component-container ทำหน้าที่ load component ต่างๆ
ทั้งที่เป็น framework component และ application component
โดย default มันจะใช้ file framework/base/config/component-load.xml เป็นจุดตั้งต้นในการ load
ซึ่งภายในจะ recursive load module ของ framework, applications, specialpurpose, hot-deploy
ถัดจากตรงนี้ไป ยังมีเรื่องให้ตามอีกยาว ดังนั้นจะพักไว้แค่ตรงนี้ก่อน

3.2 classloader-container
ตรงนี้ยังแกะไม่เข้าใจ
โดย ofbiz จะใช้ classloader ที่ชื่อ CacheClassLoader.java

จากที่ตามดูขั้นตอนการ load Point of Sell
พบว่า ofbiz มัน load ทุก component, ทุก applications
ดูเหมือนจะ load เยอะกว่าที่เราต้องการ

ประเด็นที่น่าสนใจ กรณีจะทำเป็น Eclipse RCP
1. จะ pack มันเป็น bundle(plugin) อย่างไร
การ pack เป็น bundle จะส่งผลต่อ code แค่ไหน
ใช้ wrapping อย่างเดียว เพียงพอหรือไม่
ปัญหาที่ตามมา ก็คือเรื่อง class loader
เนื่องจาก Eclipse มี model class loader ที่ค่อนข้าง strict

2. ถ้าไม่ใช้ Eclipse RCP แต่เปลี่ยนไปใช้แค่ SWT+JFace
น่าจะช่วยให้หลีกเลี่ยงปัญหาก้อนใหญ่ไปได้

Related link from Roti

Saturday, August 18, 2007

Unit Testing สำหรับ DAO

ปกติเวลาเขียน unit testing
ผมมักจะมีปัญหากับ test ในส่วนของ dao, service layer อยู่มาก
เนื่องจากมันต้องยุ่งเกี่ยวกับ Database
ซึ่งหมายถึงต้องมี Test Data เตรียมไว้ให้เรียบร้อย
ที่แย่ก็คือ พอ testing ที่ไปยุ่งกับ update, delete
มันก็จะทำให้ state ของ data ใน database เราเปลี่ยนไป

ดังนี้ปกติที่ผมทำ ก็คือ
เวลา test ใน service layer ก็ใช้พวก mock กับ stub มาช่วย
จะได้ไม่ต้องยุ่งกับ database

ส่วนใน dao layer ก็เอาพวก DBUnit มาใช้
ซึ่งก็ช่วยได้พอสมควร
แต่ก็รู้สึกว่าการใช้ DBUnit ก็ดูเป็นการทรมาน database ไปหน่อย
(delete, insert ทั้งหมด ทุกๆ test method)
หลังๆ เวลา test ก็เปลี่ยนมาใช้พวก in memory database แทน
ซึ่งผลพลอยได้ ก็คือประหยัดเวลา test ลงไปด้วย

แต่พอเป็นโปรเจคใหญ่ๆที่มี table สัก 200 table
มี data test เยอะพอสมควร
การใช้ DBUnit ก็ดูจะไม่ไหว

สุดท้าย ก็ไปลงเอยที่ AbstractTransactionalDataSourceSpringContextTests
ที่ spring เตรียมไว้ให้
โดยหลักการ ก็คือ spring มันจะเตรียม transaction ไว้ให้เราก่อนเรียก method test
และพอจบ method, spring ก็จะสั่ง rollback ให้
ซึ่งเป็นแนวคิดที่เรียบง่าย แต่ได้ผลดีมาก

แต่ก็มีปัญหาอยู่บ้าง ในกรณีที่เราใช้ Hibernate
hibernate มันจะ cache sql ไว้ (รอให้จบ transaction ก่อนจึงจะทำงาน)
ทำให้เกิดปัญหาว่า assert statement ของเรา
ทำงานไม่ถูกต้อง
ทางแก้ก็คือ เราต้องสั่งให้ hibernate flush sql statement
ก่อนที่จะทำการ assert

ปัญหาอีกอย่างที่เจอ ก็คือ ผมใช้ TestNG อยู่
แต่เจ้าตัว AbstractTransactionalDataSourceSpringContextTests
มัน base อยู่บน JUnit
ก็เลยต้องมีการดัดแปลงนิดๆหน่อยๆ
แต่ผลที่ได้ก็เจ๋งไปอีกแบบคือเลือก run ได้ทั้ง Junit และ TestNG
(พึ่งรู้ว่า เราใช้คำสั่ง assert* ของ Junit มา run ใน TestNG ได้ด้วย)

หน้าตาของ base class ของ testcase ผมก็จะเป็นแบบนี้
public class AbstractDaoTest extends
AbstractTransactionalDataSourceSpringContextTests {

protected SessionFactory sessionFactory;
protected HibernateTemplate hibernateTemplate;

public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
hibernateTemplate = new HibernateTemplate(this.sessionFactory);
}

@Override
protected String[] getConfigLocations() {
return new String[] { "classpath:test-dao.xml" };
}

@Configuration(beforeTestMethod = true, alwaysRun = true)
public void tNGsetUp() throws Exception {
super.setUp();
}

@Configuration(afterTestMethod = true, alwaysRun = true)
public void tNGtearDown() throws Exception {
super.tearDown();
}

protected void flush() {
hibernateTemplate.flush();
}
}


ตัวอย่าง test case
public class TestUserDao extends AbstractDaoTest {

UserDao dao;

@Test
public void testCreateRole() {
assertFalse(dao.getFetchRoles("u190240").isUserInRole("testing"));
dao.addRole("u190240", "testing");

flush();

assertTrue(dao.getFetchRoles("u190240").isUserInRole("testing"));
}

@Test
public void testFindAll() {
List<User> list = dao.findAll();
int cnt = jdbcTemplate.queryForInt("select count(*) from b_user_name");
assertEquals(cnt, list.size());
}

public void setDao(UserDao dao) {
this.dao = dao;
}

}

ข้อดีอีกอย่างของ AbstractTransactionalDataSourceSpringContextTests
ก็คือมัน inject dao เข้ามาให้เราโดยอัตโนมัติ
ไม่ต้องมา getBean เอง

Related link from Roti

Friday, August 17, 2007

bug bug bug

หลังจากลูกชายคนที่สองครบ 3 เดือน
แม่เด็กมีแรงดูลูกทีเดียว 2 คนแล้ว
ก็เลยได้ฤกษ์กลับไปทำงาน

วันแรกที่ทำ ก็เจอ bug ไปหลายตัวทีเดียว

เริ่มที่ตัวนี้ก่อน
เกิดจากการ update Hibernate-Annotation ไปเป็น version 3.3.0.ga
ปรากฎว่าพอสร้าง SessionFactory ก็จะเกิด exception นี้ขึ้น
Caused by: java.lang.NullPointerException
at org.hibernate.cfg.annotations.CollectionBinder.buildOrderByClauseFromHql(CollectionBinder.java:851)

ค้นดูแล้ว เป็น bug ANN-617
มีสาเหตุจากพวก @OrderBy annotation ในพวก one-to-many
เจอแล้ว ก็ถอย version กลับไปที่ 3.2.1.ga

ตัวที่สองเป็น bug ของ maven cobertura plugin
เป็นกับพวกที่ใช้ windows platform
โดยตอนที่มัน instrument class
file cobertura.ser มันสร้างไว้ผิดที่
ทำให้ตอน testing มันได้ผลลัพท์เป็น 100% coverage ตลอด
แก้โดยการกำหนด version เป็น 2.0 ลงใน pom.xml
        <plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>cobertura-maven-plugin</artifactId>
<!-- เพิ่มตรงนี้เข้าไป -->
<version>2.0</version>
...


ตัวที่สามเป็น bug ใน maven mojo plugin บน Linux
มันจะฟ้องว่า
java.io.IOException: java: not found 

เกิดจาก execute file "java" ไม่ได้อยู่ใน system path (พวก /usr/bin, /usr/local/bin)
ทำ link ให้แล้วก็หาย

นอกเรื่อง:
กลับมาถึงบ้าน ลูกชายวิ่งเข้ามากระโดดเข้ากอด
ชื่นใจจริงๆ

Related link from Roti

Monday, August 06, 2007

JGoodies binding.

วันนี้ลองเล่น JGoodies Binding จริงๆจังๆดูบ้าง
หน้าจอที่จะทดสอบ มีหน้าตาแบบนี้



ด้านซ้ายเป็น JList และด้านขวาเป็น edit panel

เริ่มด้วย Domain Model ก่อน
ก็คือ Person
public class Person extends Model {

private String name;
private double salary;

}

สังเกตว่า extends จาก com.jgoodies.binding.beans.Model
Note: ถ้า binding แบบง่ายๆ เราไม่ต้อง extend Model ก็ได้
แต่ถ้า feature ที่ใช้ซับซ้อนขึ้น มันบังคับให้เราต้อง extend Model.

แนวคิดหลัก ของ JGoodies (ซึ่งลอกมาจาก VisualSmalltalk อีกที)
ก็คือ ValueModel
GUI Widget จะไม่ access ข้อมูลผ่าน Domain Object ตรงๆ
แต่จะ set หรือ get ข้อมูล ผ่านทาง ValueModel

ตรงนี้อาจจะสับสนกับ Swing Model พวก TableModel, ListModel นิดหน่อย
ตรงที่ Swing model ก็มีวัตถุประสงค์ทำนองเดียวกันกับ Value Model
แต่ว่า Swing model ไม่ได้มีลักษณะเป็น universal model
ที่สามารถใช้ร่วมกันทั้งหมด ไม่ว่าจะเป็น widget ชนิดไหน แบบเดียวกับ ValueModel

ข้อดีของ Universal Model แบบ ValueModel
นอกจากจะทำให้การเขียน โปรแกรม เป็น pattern เดียวกันหมดแล้ว
ยังมีข้อดีในแง่ที่เราสามารถ นำ ValueModel มาต่อกันเป็น chain ได้
ทำให้เราสามารถทำ feature พวก Buffered, Convertor, Indirection ได้

กลับมาที่โปรแกรมเรา
เราจะออกแบบโปรแกรมของเรา โดยใช้ pattern Presentation Model (ใน VisualWork Smalltalk เรียก Aplication Model)
เริ่มด้วยการสร้าง PresentationModel ของเรา
public class ApplicationModel {
}


Person Object ของเรา จะเก็บอยู่ใน ArrayList แบบนี้
public class ApplicationModel {
private ArrayList<Person> persons = new ArrayList<Person>();

}


step ถัดไปคือ เตรียม ValueModel ที่จะใช้ binding กับ JList,
ตัว ValueModel ที่เราจะนำมาใช้ ห่อ persons ของเราก็คือ SelectionInList
ซึ่งออกแบบมาเพื่อใช้กับพวก container widget เช่น JList, JTable.
feature ของมันก็คือ มันจะช่วย provide ValueModel ของ Current selection,selection index
ทำให้เรานำไป binding กับ widget อื่นๆได้อีก
(ซึ่งในกรณีของเรา ก็จะนำ current selection ไป binding กับ edit widget ที่อยู่ด้านขวา)
public class ApplicationModel {
private ArrayList<Person> persons = new ArrayList<Person>();
private SelectionInList<Person> selectionInList;

public ApplicationModel() {
selectionInList = new SelectionInList<Person>(persons);
}

public SelectionInList getPersons() {
return selectionInList;
}
}


ในฝั่งของ GUI เวลาจะนำไปใช้ ก็จะทำประมาณนี้
public class MainFrame extends javax.swing.JFrame {

private ApplicationModel model = new ApplicationModel();

/** Creates new form MainFrame */
public MainFrame() {
initComponents();
Bindings.bind(jList1, model.getPersons());
}

...
}


step ถัดไป ก็คือการ bind current selection ใน JList เข้ากับ Editor Panel ที่อยู่ด้านขวา
ซึ่งในกรณีนี้ประกอบด้วย JTextField 2 ตัว สำหรับ name และ salary

เริ่มด้วยในฝั่ง ApplicationModel ก็เตรียม ValueModel ที่จะนำไป bind กับ JTextField
ตรงนี้เราสามารถทำได้หลายวิธี แต่วิธีที่เลือกใช้ก็คือ ใช้ com.jgoodies.binding.PresentationModel เข้ามาช่วย
(เพื่อที่จะได้ใช้ feature BufferedValueModel)
public class ApplicationModel {
private ArrayList<Person> persons = new ArrayList<Person>();
private SelectionInList<Person> selectionInList;
private PresentationModel<Person> person;

public ApplicationModel() {
selectionInList = new SelectionInList<Person>(persons);
person = new PresentationModel<Person>(selectionInList.getSelectionHolder());
}

public ValueModel getPersonName() {
return person.getBufferedModel("name");
}

public ValueModel getPersonSalary() {
return new DoubleToStringConvertor(person.getBufferedModel("salary"));
}

class DoubleToStringConvertor extends AbstractConverter {

DoubleToStringConvertor(ValueModel subject) {
super(subject);
}

public Object convertFromSubject(Object arg0) {
if (arg0 != null) {
return arg0.toString();
}
return null;
}

public void setValue(Object arg0) {
subject.setValue(new Double(arg0.toString()));
}

}
}

จาก code ข้างบน จะมีประเด็นที่น่าสนใจอยู่ 2 ประเด็นคือ
1. เราใช้ getBufferedModel ในการ return ค่า ValueModel ไปให้ GUI view
ซึ่งผลก็คือ การเปลี่ยนแปลงใดๆที่เกิดขึ้นที่ฝั่ง GUI View จะยังไม่มีผลต่อ Model
จนกว่าจะมีการสั่ง commit การเปลี่ยนแปลง
2. กรณีที่ Datatype ฝั่ง model ไม่ใช่ String ก็จะมีประเด็นเรื่องการ Convert Datatype
ซึ่งใช้เทคนิคการ chain ValueModel เข้าหากัน

ฝั่ง GUI View ก็สามารถนำไป binding ได้ดังนี้
Bindings.bind(jTextField1, model.getPersonName());
Bindings.bind(jTextField2, model.getPersonSalary());


ขั้นถัดไป ก็คือเตรียม Action ที่จะใช้เพื่อ commit การเปลี่ยนแปลงที่เกิดขึ้นใน BufferedValueModel

ฝั่ง ApplicationModel

public Action getApplyAction() {
return new AbstractAction("Apply") {

public void actionPerformed(ActionEvent arg0) {
person.triggerCommit();
selectionInList.fireSelectedContentsChanged();
}

};
}

ฝั่ง GUI View

jButton1.setAction(model.getApplyAction());


ปัญหาของ BufferdValueModel ก็คือ กรณีที่เกิดการแก้ขึ้นแล้ว
ยังไม่มีการ commit, ถ้า Selection เกิดเปลี่ยนไป ข้อมูลที่ bound ไว้
จะไม่ยอมเปลี่ยนตาม ดังนั้นเราต้องสั่ง cancel การแก้ไข ด้วยการเรียกใช้ method triggerFlush
selectionInList.addPropertyChangeListener(

SelectionInList.PROPERTYNAME_SELECTION,
new PropertyChangeListener() {

public void propertyChange(PropertyChangeEvent arg0) {
person.triggerFlush();
}

});


พอแค่นี้ก่อน ที่เหลือไม่ค่อยมีประเด็นแล้ว

Note.
1. แนวคิด + วิธีในการใช้งาน มีตัวเลือกให้ใช้มากเกินไป (many way to do one thing) ถ้า assign ให้เด็กใหม่เอาไปใช้ ไม่น่าจะรอด
2. กรณีที่มีการ refactor เปลี่ยนชื่อ property ของ Domain Model จะส่งผลกระทบเยอะ
เพราะ binding ใช้วิธีกำหนดชื่อของ property ในรุป String
3. รู้สึกว่า Groovy ก็มีใช้ pattern ValueModel ใน GUI ด้วยเหมือนกัน
(เจอใน gapi แต่ยังไม่เจอเอกสารที่พูดถึง)

Related link from Roti

Monday, July 09, 2007

LEL

โปรเจค Profligacy เป็นโปรเจค ที่ช่วยให้เรา paint swing component
โดยใช้ JRuby เข้ามาช่วย
สิ่งที่น่าสนใจในโปรเจคนี้ก็คือแนวคิดเรื่อง LEL (Layout Expression Language)
โดยนำแนวคิดของเรื่อง table format ในพวก wiki มาผสมกับ Constraint programming
ก็เลยได้ออกมาเป็นวิธีใหม่ในการวาง layout

ลองดูตัวอย่าง code
require 'profligacy/swing'
require 'profligacy/lel'

module Test
include_package 'javax.swing'
include Profligacy

layout = "
[ label_1 | label3 ]
[ (300,300)*text1 | (150)people ]
[ <label2 | _ ]
[ message | buttons ]
"


ui = Swing::LEL.new(JFrame,layout) do |c,i|
c.label_1 = JLabel.new "The chat:"
c.label2 = JLabel.new "What you're saying:"
c.label3 = JLabel.new "The people:"
c.text1 = JTextArea.new
c.people = JComboBox.new
c.message = JTextArea.new

c.buttons = Swing::LEL.new(JPanel, "[send|hate|quit]") do |c,i|
c.send = JButton.new "Send"
c.hate = JButton.new "Hate"
c.quit = JButton.new "Quit"
end.build :auto_create_container_gaps => false
end

ui.build(:args => "Simple LEL Example")
end


จะเห็นว่า main format เขาวางไว้แบบนี้
[ label_1         | label3      ]
[ (300,300)*text1 | (150)people ]
[ <label2 | _ ]
[ message | buttons ]


คำอธิบายก็คือ layout จะเป็น grid ที่มี 4 แถว 2 column,
< หมายถึง ชิดซ้าย
> หมายถึง ชิดขวา
* คือ expand ให้เต็มพื้นที่
_ หมายถึง empty cell
(150) หมายถึง กว้าง 150 pixel
(300,300) กว้าง 300 ยาว 300 pixel

ผลลัพท์ที่ได้ ลองตามไปดูที่นี่
http://ihate.rubyforge.org/profligacy/images/sample_lel_gui_nested.png

Related link from Roti