Showing posts with label Java. Show all posts
Showing posts with label Java. Show all posts

26.8.11

Bootstrap twitter integrate with Richface4

หลังจากใช้ Richface มานานรู้สึกเบื่อกับ theme มานานละ และไปเจอกับ bootstrap twitter ทำให้นักพัฒนาหยิบไปใช้ เข้าท่าทีเดียวเลยหยิบมารวมกับ Richface 4 ปรับแต่งนิดหน่อยก็ได้อย่างที่เห็น เอิ่มมันก็ไม่ได้ยากนะ แต่เป็นงานที่ต้องใช้ความอดทน แต่คงได้ปรับแก้อีกเยอะกว่าจะสมบูรณ์


22.10.10

[News] Apple update Java Deprecation

Apple ประกาศเกี่ยวกับ Java Deprecation เห็นมีการปรับเนื้อหาเล็กน้อย


ไปเป็น


แต่ความหมายยังคงเดิม

14.5.10

Security-driven design #2 - SQL Injection

ผมเขียนตอนแรก ไว้ที่ เป็นเรื่องเกี่ยวกับการโจมตีจุดอ่อน XSS
บทความในตอนนี้เป็นตอนต่อเนื่องที่พูดเกี่ยวกับจุดอ่อนที่เรามักพบกันได้บ่อย นั่นคือ SQL Injection โดยเป็นการโจมตีโดยการฉีค SQL query ผ่านช่องทางต่างๆ ที่ให้เราส่ง input จาก client ไปหาตัวระบบได้ เมื่อทำสำเร็จแล้วผู้โจมตีจะสามารถเข้าถึงข้อมูลเพื่อทำ CRUD ที่อยู่ในฐานข้อมูลได้ โดยไม่ต้องผ่านการตรวจสอบสิทธิของ application นั้นๆ
ผลกระทบของ SQL Injection ขึ้นอยู่กับปัจจัยเหล่านี้
  • จุดที่ฉีคเข้ามา query
  • สิทธิของ user ที่ execute query นั้น
  • database platform และ version
  • database configuration
ยกตัวอย่าง SQL Injection ที่เกิดขึ้นเมื่อนักพัฒนาสร้าง dynamic query โดยนำมารวมกับ input ที่ผู้ใข้ส่งเข้ามาโดยไม่ผ่านวิธีการที่เหมาะสม
String query = “Select * from Users Where username = ‘“ + request.getParameter(”username”) + “‘ and password = ‘“ + request.getParameter(”password”) + “‘“;
  try {
    Statement statement = connection.createStatement( … );
    ResultSet results = statement.executeQuery( query );
  }
แล้วถ้าใช้ url ลักษณะนี้
http://www.yoursite.com/login?username=tofu’%20or%20‘1’%20=%20’1&password=1’%20or%20’1’%20=%20’1
เท่ากับเราจะได้ query ที่ค่าจะออกมาเป็น true และ query ผลลัพธ์ออกมาได้
Select * from Users Where username = ‘tofu’ or ‘1’=’1’ and password=’1’ or ‘1’=’1’ 
นอกจากนี้ยังมี query อีกสารพัดแบบที่ถูกฉีคเข้ามาทดสอบความอ่อนแอของ application เราหาได้ใน google

แนวทางป้องกันหลัก
  • Parameterized query
    • ใช้ Prepared Statement / HQL / ...
    • ใช้ Stored Procedures
  • Character escaping
  • User-input Handling ได้กล่าวไปในบท ก่อนหน้านี้ และดูเพิ่มเติมได้จาก นี้
  • ซ่อน error message ที่มาจาก database server เนื่องจากการทำ SQL injection จะเกิด error message ที่ return ออกมาจาก
  • database server ได้ซึ่ง message ดังกล่าวมันช่วยให้ผู้ไม่ประสงค์ดีนำไปวิเคราะห์ และนำกลับมาโจมตีเราใหม่
  • ใช้ user ที่มีสิทธิ์เท่าที่จำเป็นต่อการ execute query นั้นเท่านั้น ไม่ควรใช้ sa, dba หรือ admin
ตัวอย่างการใช้ Prepared Statement เพื่อส่งผ่าน parameter
String query = “Select * from Users Where username = ? and password = ?”;
  try {
    PrepareStatement pstmt = conn.prepareStatement(query);
    pstmt.setString(1, username);
    pstmt.setString(2, password);
    ResultSet results = pstmt.executeQuery(query);
  }
ใน query language ของ framework ต่างๆ ก็มี parameter statment ให้ใช้ เช่น Hibernate Query Language (HQL)
Query query = session.createQuery(”from Users where username =:username and password =:password”;
  query.setParameter(”username”, username);
  query.setParameter(”password”, password);
ตัวอย่างการใช้ Stored Procedure เพื่อส่งผ่าน parameterized query
String username = request.getParameter(”username”);
  String password = request.getParameter(”password”);
  try {
   CallableStatement cs = conn.prepareCall(”{call getUsers(?, ?)}”);
   cs.setString(1, username);
   cs.setString(2, password);
   ResultSet results = cs.executeQuery();
  } 
เทคนิค Escape อีกทางเลือกหนึ่งกรณีที่เราไม่ใช้ Parameterized query และส่งผลต่อ ประสิทธิภาพ ถ้าใช้งานอย่างไม่เหมาะสม เพื่อสร้าง dynamic query ลองเลือกเทคนิค Escape (เช่นเดียวกับ Java ที่มี character escaping) แต่อย่างไรก็ตามเทคนิคนี้ค่อนข้างเปราะบางกว่าเมื่อเทียบกับ parameterized query อ่านรายละเอียด character escaping ของ DBMS แต่ละเจ้าได้ดังนี้ศึกษาตัวอย่าง escape ได้จากโค้ด OracleCodec class ของ owasp-esapi
//encode ' to '' 
  public String encodeCharacter( char[] immune, Character c ) { 
    if ( c.charValue() == '\'' ) 
      return "\'\'";
    return ""+c; 
  } 
Note: การเลือกใช้ PreparedStatement หรือ Statement และการเพิ่มประสิทธิภาพการทำงาน เพิ่มเติม: Review Code for SQL Injection Test for SQL Injection Reference: SQL injection

10.5.10

Improve performance in JDBC

เลือก Driver ให้เหมาะกับงานที่กำลังทำอยู่
JDBC สร้าง driver ออกมาสี่ประเภทขึ้นอยู่กับความต้องการที่เราจะต้องเลือกไปใช้
  • JDBC-ODBC ใช้เมื่อไม่มี driver ที่สร้างสำหรับภาษา Java แต่มีรองรับ ODBC แต่การเชื่อมต่อด้วยแบบนี้จะช้ากว่าประเภทอื่นๆ เพราะต้องเรียกผ่าน ODBC อีกทีหนึ่ง
  • Native API ใช้เมื่อมี driver ที่สร้างสำหรับ Java เพื่อไม่ต้องผ่าน ODBC เหมือนกับแบบ JDBC-ODBC ดังนั้นประสิทธิภาพจึงดีกว่าประเภทที่ 1 แต่ประสิทธิภาพจะปานกลาง เมื่อเทียบกับแบบที่ JDBC-Net
  • JDBC - Net ใช้เมื่อติดต่อผ่าน proxy server เช่นพวก application server ทั้งหลาย (websphere, weblogic, ...) ประเภทนี้ง่ายต่อการ optimization เช่น connection pooling, caching, loga balancing และอื่นๆ

เลือกที่จะควบคุม transaction เอง
java.sql.Connection interface เตรียม method เพื่อจัดการกับ transaction ดังนี้
  • boolean getAutoCommit();
  • void setAutoCommit(boolean autocommit);
  • void commit();
  • void rollback();
ด้วย default JDBC transaction จะ commit ให้เองเมื่อแต่ละ statement ถูก execute นั่นคือ AutoCommit mode เป็น true โดยเราไม่จำเป็นต้องเรียก commit() method เอง มันง่ายต่อนักพัฒนา ถ้าเราต้องการ execute แค่หนึ่ง statement แต่จะให้ประสิทธิภาพที่ไม่ค่อยดีเมื่อมีหลาย statement ที่ต้อง execute เพราะว่ามันจะ commit หลังจากที่ statement แรกทำงานจบโดย default จึงควรตั้ง AutoCommit mode เป็น false และเรียก commit() method หลังจากเรียกกลุ่มของ statement execute เอง และใช้ rollback() method ใน catch block เมื่อไหร่ก็ตามที่เกิด exception ขึ้นในโปรแกรม

ตัวอย่าง 
try{
connection.setAutoCommit(false);
PreparedStatement ps = connection.preareStatement( "UPDATE employee SET Address=? WHERE name=?");
ps.setString(1,"Austin");
ps.setString(2,"RR");
ps.executeUpdate();
PreparedStatement ps1 = connection.prepareStatement( "UPDATE account SET salary=? WHERE name=?");
ps1.setDouble(1, 5000.00);
ps1.setString(2,"RR");
ps1.executeUpdate();
connection.commit();
connection.setAutoCommit(true);
}catch(SQLException e){ connection.rollback();}
finally{
if(ps != null){ ps.close();}
if(ps1 != null){ps1.close();}
if(connection != null){connection.close();}
}

Statement vs PreparedStatement
เรามักเข้าใจผิดว่า PreparedStatement จะให้ประสิทธิภาพที่ดีกว่าเมื่อหยิบมาใช้งาน ความจริงแล้วใช่ว่าจะเป็นเช่นนั้น เมื่อเราใช้ PreparedStatement object เพื่อ execute SQL Statement มันจะถูก compile และเก็บไว้ใน cache และเมื่อเราเรียกใช้อีกมันจะไม่ถูก compile เป็นครั้งที่สอง เพราะมันจะถูกพบใน cache และนำกลับมาใช้ใหม่ ซึ่งเป็นการเพิ่มประสิทธิภาพการทำงานเพราะลดเวลา compileแต่เมื่อเราใช้ Statement ทุกครั้งที่เราจะ execute SQL Statement มันจะถูก compile ใหม่ทุกครั้งถ้าดูตามเหตุผลด้านบนนี้ PreparedStatement เหมาะสมกับการถูกนำมาใช้อย่างยิ่ง แต่ ความจริง ไม่ได้เป็นอย่างนั้น
เมื่อเราเห็นผลการทดสอบดังกล่าว จะสรุปได้ว่าสำหรับ enterprise application ที่มีผู้ใช้จำนวนมากที่จะ execute SQL statement เดียวกันบ่อยๆ การลดเวลา compile ได้สามารถช่วยเพิ่ม performance ให้กับ database ได้ แต่สำหรับ client-side การใช้ PreparedStatement ใช้เวลานานกว่า Statement
แต่ถึงอย่างไรถ้าเรากังวลกับเรื่องของ SQL injection PreparedStatement ก็จะเป็นตัวเลือกที่สมควรหยิบไปใช้

นอกจากนี้ยังมีอีกหลายประเด็นทีน่าสนใจเช่นเรื่องของ batch, pre-fetch value ที่ผมยังไม่มีเวลาสรุป ลองศึกษาเพิ่มเติมได้ครับ

Referenece:
http://oreilly.com/catalog/jorajdbc/chapter/ch19.html

5.5.10

HTTPOnly cookie flag

HTTPOnly เป็นวิธีหนึ่งที่ใช้ป้องกัน client side script เข้าถึง cookie โดยที่เราไม่อนุญาตเพื่อป้องกันจุดอ่อนของ web application ที่ลดความเสี่ยงเรื่องของ XSS

เริ่มต้นจาก blog ของนาย Jordan Wiens, "No cookie for you!" (ตอนนี้ไปเป็น CTO ของ white hat security) แล้วถูกนำไป implement ครั้งแรกในปี 2002 ใน browser ของ microsoft internet explorer
ถ้าพูดตามหลักแล้ว HttpOnly เป็นเพียงการเพิ่ม flag ลงใน Set-Cookie HTTP response header แต่ browser จะต้องรองรับ flag นี้ด้วยถึงจะเข้าใจความหมาย

ถ้า HttpOnly flag เพิ่มลงใน Http response header, cookie จะไม่สามารถเข้าถึงได้ผ่าน client side script ผลก็คือถ้าเกิด cross-site scripting (XSS) คือผู้ใช้เผลอกด link ที่ไปเข้าหน้าที่เกิด XSS ตัว browser จะกัน client side script ไม่ให้เข้าถึง cookie ได้ แล้วจะ return เป็น empty string กลับคืนมาให้
ถ้า browser ไม่รองรับ HttpOnly และ website ได้มีการกำหนดค่า HttpOnly ตัว client side script ก็จะสามารถเข้าถึง cookie ได้

ตอนนี้มี browser ที่รองรับได้แก่
  • Internet Explorer 6 sp1+ http://www.petefreitag.com/item/644.cfm
  • Firefox 2.0.0.5+ http://www.petefreitag.com/item/644.cfm
  • Opera since 9.5+ (Prevents Read) http://manicode.blogspot.com/2008/06/opera-95-httponly-read-prevention.html
  • Netscape 9.0b3+

ตัวอย่างการใช้งาน
String sessionId= request.getSession().getId();response.setHeader("SET-COOKIE", "JSESSIONID=" + sessionId + "; HttpOnly");
ใน tomcat 6 เราสามารถกำหนด useHttpOnly=true ใน context.xml ได้

สำหรับ servlet 3.0 รองรับ HTTPOnly เช่นกันผ่าน method
void setHttpOnly(boolean isHttpOnly)boolean isHttpOnly()

Reference:
http://msdn.microsoft.com/en-us/library/ms533046.aspx

26.12.09

Servlet 3.0: Introduction

วันนี้อยู่ว่างๆ อยากลองความสามารถใหม่ของ servlet v.3 แต่ตอนนี้พบว่ายังไม่มี application server ตัวไหนรองรับ นอกจาก GlassFish v.3 เข้าใจว่าตอนนี้ application server ค่ายต่างๆ คงกำลังเร่งพัฒนาความสามารถของตัวเองอย่างเต็มที่เพื่อแย่งชิงส่วนแบ่งการใช้งานตรงจุดนี้
ไม่รอช้าครับ ผมโหลดมาติดตั้งเรียบร้อย

เปิด IDE คู่ใจขึ้นมาลองเลยครับ ถ้าใช้ Eclipse Galileo แล้วเกิดปัญหาไม่สามารถเพิ่ม application server adapter ใหม่ที่เป็น GlassFish v.3 เข้าไปได้ ให้ แก้ตามนี้ ส่วน Netbean ไม่ต้องลีลาเยอะสามารถใช้งานได้เลย

ในเวอร์ชั่นนี้ค่อนข้างให้ความสำคัญกับเรื่องของ annotation ที่จะนำมาเป็นทางเลือกหนึ่งในการ configuration web application ได้ค่อนข้างลงตัวทีเดียว ซึ่งในบทความนี้ผมจะละ deployment description (web.xml) ออกไปแล้วหันมาใช้ annotation แทน

เริ่มกันที่ตัวแรก @WebServlet ใช้เพื่อลงทะเบียน Servlet เพื่อบอกกับ servlet container ว่า class นี้เป็น Servlet class เพื่อให้ servlet container จัดการกับ class ตาม servlet lifecycle ข้างในมี attribute ดังนี้
  • name
  • description
  • value
  • urlPatterns
  • initParams
  • loadOnStartup
  • asyncSupported
  • smalIcon
  • largeIcon

ซึ่งความหมายทุกตัวค่อนข้างชัดเจนอยู่แล้ว ถ้าใครเคยเขียน Servlet v.2 มาก่อนมั่นใจว่าเข้าใจได้โดยไม่ต้องอธิบาย ยกเว้นจะมีแต่ asyncSupported ที่เพิ่งเพิ่มเข้ามาใน Servlet v.3 ในตัวอย่างใช้ urlPatterns เพื่อ mapping กับ url โดยมันเก็บค่าที่เป็น array จึงสามารถ mapping url ได้มากกว่า 1

@WebServlet(urlPatterns="/hello")
public class HelloServlet extends HttpServlet {
public void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException {
res.setContentType("text/html");
res.getOutputStream().print("

context path: " + req.getContextPath() + "

");
res.getOutputStream().print("

hello world!

");
}
}
ถ้าต้องการส่ง init parameter ให้กับ servlet หรือ filter ใช้ @WebInitParam ซึ่งมี attribute
  • name
  • value
  • description

ตัวอย่างการใช้งาน

@WebServlet (name="SimpleServlet",
urlPatterns={"/simple"},
initParams={
@WebInitParam(name="name", value="Phamonyut"),
@WebInitParam(name="penname", value="tofu")})
public class SimpleServlet extends HttpServlet {
public void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException {
PrintWriter out = res.getWriter();
out.println("name parameter: " + getInitParameter("name"));
out.println("penname parameter: " + getInitParameter("penname"));
}
}
@WebListener ใช้เพื่อลงทะเบียน listener ตามประเภทของ listener ดังนี้
  • Context Listener (javax.servlet.ServletContextListener)
  • Context Attribute Listener (javax.servlet.ServletContextAttributeListener)
  • Servlet Request Listener (javax.servlet.ServletRequestListener)
  • Servlet Request Attribute Listener (javax.servlet.ServletRequestAttributeListener)
  • Http Session Listener (javax.servlet.http.HttpSessionListener)
  • Http Session Attribute Listener (javax.servlet.http.HttpSessionAttributeListener)

ใน @WebListener มี attribute ดังนี้
  • filterName
  • description
  • displayName
  • initParams
  • servletNames
  • value
  • urlPatterns
  • dispatcherTypes
  • asyncSupported

การใช้งาน WebListener (ถ้าไม่ implement listener interface มาจะเกิดข้อผิดพลาดได้)

@WebListener
public class FooApplication implements ServletContextListener {
public void contextInitialized(ServletContextEvent event) {
System.out.println("inti context");
}
public void contextDestroyed(ServletContextEvent event) {
System.out.println("destory context");
}
}
@WebFilter ไว้ลงทะเบียน filter ซึ่งมี attribute ดังนี้
  • filterName
  • description
  • displayName
  • initParams
  • servletNames
  • value
  • urlPatterns
  • dispatcherTypes
  • asyncSupported


@WebFilter(value="/hello",
initParams={
@WebInitParam(name="message", value="Servlet says:")
})
public class TestFilter implements Filter {
private FilterConfig filterConfig;
public void init(FilterConfig filterConfig) throws ServletException {
this.filterConfig = filterConfig;
}
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws ServletException, IOException {
PrintWriter out = res.getWriter();
out.print(filterConfig.getInitParameter("message"));
System.out.println("call request -> filter");
chain.doFilter(req, res);
System.out.println("call filter <- response");
}
public void destroy() {}
}

@WebServlet(value="/hello")
public class HelloServlet extends HttpServlet {
public void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException {
System.out.println("call hello servlet");
PrintWriter out = res.getWriter();
out.println("Hello world!");
}
}

21.12.09

Java Captcha

เพื่อนติดปัญหาช่วยแก้เกี่ยวกับ captcha ลองหาดูพบว่า jcaptcha ใช้งานได้ค่อนข้างดี และ implement ไม่ยากนำไปผูกกับ framework ได้อย่างลงตัว

เริ่มแรกไปโหลด library jCaptcha และ library ที่เกี่ยวข้องมาให้เรียบร้อย
  • commons-collections-3.2
  • commons-logging-1.0.4
  • filters-2.0.235
  • jcaptcha-2.0-alpha-1-SNAPSHOT
  • jcaptcha-api-1.0
  • jcaptcha-integration-simple-servlet-2.0-alpha-1-SNAPSHOT

web.xml ระบุว่า jcaptcha ที่จะทำหน้าที่เป็นตัวสร้างภาพ captcha ตัว class ใด และจะเรียกใช้ผ่าน url อย่างไร


jcaptcha
com.octo.captcha.servlet.image.SimpleImageCaptchaServlet


jcaptcha
/jcaptcha.jpg


ตัวอย่างการเรียกใช้งาน






ฝั่ง server รับไปประมวลผล

PrintWriter pw = response.getWriter();
String userCaptchaResponse = request.getParameter("jcaptcha");
boolean captchaPassed = SimpleImageCaptchaServlet.validateResponse(request, userCaptchaResponse);
if(captchaPassed){
pw.println("good job");
}else{
pw.println("fail");
}

เสร็จเรียบร้อย run ได้เลย

11.8.09

Java send SMS via AT Command

เราใช้ AT Command ในการคุยกับอุปกรณ์เคลื่อนที่้ ในโทรศัพท์มือถือทุกวันนี้ก็ได้ implement at command ลงไปเพียงแต่ว่า แต่ละรุ่นก็ใส่ function ลงไปไม่เหมือนกัน ดังนั้นก่อนใช้งานต้องตรวจสอบก่อนว่าโทรศัพท์มือถือที่เราจะติดต่อนั้นรองรับ function ใดบ้าง

การส่ง AT Command ไปให้โทรศัพท์มือถือทำได้ไม่ยาก โดยใช้ Java I/O และจากนั้นส่งคำสั่งผ่าน stream

ตัวอย่างด้านล่างผมเขียนเป็น class util ง่ายๆ ไว้ใช้งานโดยมันสามารถสั่งให้อุปกรณ์มือถือเราส่ง SMS ดังนั้นมันจะเหมือนกับการส่ง SMS ผ่านมือถือปกติแต่จะแตกต่างเพียงแค่ว่าเราสั่งจาก PC ซึ่งมันยืดหยุ่นกว่าในกรณีที่เราจะเขียนโปรแกรมต่อยอดไปอีก


import java.io.*;
import java.util.*;
import javax.comm.*;
import com.sun.comm.*;


class Mobile {

//com.sun.comm.Win32SerialPort
//timeout second / 10
private static final int RESPONSE_TIMEOUT = 50;
//timeout second / 1000
private static final int DEVICE_TIMEOUT = 1000;
private static final String NEWLINE = "\n";
private static final String ENTER = "\r";
private static final String CTRL_Z = "\u001A";
private static final String CONNECTION = "connect";

private InputStream in;
private OutputStream out;

private SerialPort port;
private CommPortIdentifier portID;

public Mobile(String appName, CommPortIdentifier portID) throws IOException, PortInUseException, UnsupportedCommOperationException {
this.portID = portID;
//this.portID.addPortOwnershipListener(new MyCommPortOwnershipListener());
port = (SerialPort) portID.open(appName, DEVICE_TIMEOUT);
port.setSerialPortParams(9600, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
port.setFlowControlMode(port.FLOWCONTROL_NONE);
in = port.getInputStream();
out = port.getOutputStream();
}

public void close() throws IOException {
in.close();
out.close();
port.close();
}

public static Map<String, CommPortIdentifier> listPort() throws IOException {
Map<String, CommPortIdentifier> listPort = new Hashtable<String, CommPortIdentifier>();
Enumeration portList = CommPortIdentifier.getPortIdentifiers();
while (portList.hasMoreElements()) {
CommPortIdentifier portID = (CommPortIdentifier) portList.nextElement();
listPort.put(portID.getName(), portID);
}
return listPort;
}

public String sendSMS(String tel, String msg) throws IOException, InterruptedException {
String result = null;
sendCommand("AT+CMGF=1");
writeRequest("AT+CMGS=\"" + tel + "\"");
result = sendCommand(msg + CTRL_Z);
return result;
}

public String sendCommand(String cmd) throws IOException, InterruptedException {
writeRequest(cmd);
return readResponse();
}

private void writeRequest(String req) throws IOException {
out.write(new String(req + ENTER).getBytes());
out.flush();
}

private String readResponse() throws IOException, InterruptedException {
String strIn = "";
for (int i=0; i<RESPONSE_TIMEOUT; i++){
int numChars = in.available();
if (numChars > 0){
byte[] bb = new byte[numChars];
in.read(bb, 0, numChars);
strIn += new String(bb);
}
if (strIn.indexOf(">" + ENTER + NEWLINE) != -1) {
break;
}
if (strIn.indexOf("OK" + ENTER + NEWLINE) != -1) {
break;
}
if (strIn.indexOf("ERROR") != -1 &&
strIn.indexOf(ENTER + NEWLINE, strIn.indexOf("ERROR") + 1) != -1) {
break;
}
Thread.sleep(100);
}
return strIn;
}

}
ส่วนตอนเรียกใช้งานก็ง่ายๆ

import java.util.*;
import javax.comm.*;

public class Client {

public static void main(String[] args) throws Exception {
//choose port
Map<String, CommPortIdentifier> listPort = Mobile.listPort();
System.out.println(listPort);
Mobile mobile = new Mobile("SMS/GSM Nokia N73", listPort.get("COM12"));
System.out.println(mobile.sendSMS("+66123456789", "Hello World"));
System.out.println("---");
}

}
แน่นอนครับนอกจากส่ง SMS ได้แล้วยังสามารถทำอย่างอื่นได้อีกเช่น รับ SMS, สั่งโทรออก เป็นต้นแต่เราต้องแน่ใจก่อนว่าโทรศัพท์มือถือรองรับ function ดังกล่าว

แล้วเราจะรู้ได้อย่างไรว่าโทรศัพท์มือถือที่เราจะสื่อสารด้วยนี้รองรับ AT Command อะไรบ้าง?
วิธีตรวจสอบง่ายๆ คือการส่งคำสั่ง AT Command ที่เราจะใช้แล้วตามด้วย =? ถ้าตอบกลับมาเป็น ERROR นั่นหมายความว่าไม่อุปกรณ์ที่เราจะติดต่อนั้นไม่รองรับคำสั่งดังกล่าว

นอกจาก AT Command ยังมีวิธีอื่นๆ อีกครับที่สามารถใช้ติดต่อกับโทรศัพท์มือถือได้แต่นั่นก็ขึ้นอยู่กับผู้ผลิตมือถือแต่ละเจ้าด้วย อย่างที่ผมทดสอบผมใช้ Nokia N73 ซึ่งมันไม่รองรับ AT Command (AT+CGMR) ที่จะให้อ่าน SMS เลยต้องไปใช้ Nokia PC Connectivity API แทนครับ

30.4.09

POP3 check new mail

การดึงเมล์โดยใช้ protocol POP3 กับ IMAP4 มีความแตกต่างกันพอสมควรทั้งในแง่การ implement หรือคุณสมบัติต่างๆ ปัญหาอย่างนึงของ POP3 คือไม่สามารถคุยกับ mail server เพื่อดึงเฉพาะเมล์ใหม่ได้
เราอาจแก้ปัญหานี้ได้โดยการเข้าไปคุยกับ mail server เพื่อดึงเฉพาะ header มาตรวจสอบก่อน แต่ประเด็นของมันจึงอยู่ที่ขนาดของ header แทน ถ้าขนาดไม่กี่ kb ก็คงไม่เป็นไรแต่ถ้าขนาดใหญ่ถึง mb ดังนั้นการแก้ปัญหาด้วยการตรวจสอบจาก header จึงไม่ใช่แนวทางแก้ปัญหาที่ดี
ทางออกของปัญหานี้จะใช้การตรวจจาก uid (unique identify) แทนเพราะมันมีขนาดเล็กไม่ต้องกังวลเรื่องขนาดของข้อมูลตอนส่ง message
ตัวอย่าง (ตัด code มาบางส่วน)

Message[] message = folder.getMessages();
POP3Folder pop3Folder = (POP3Folder)folder;
FetchProfile fetchProfile = new FetchProfile();
fetchProfile.add(UIDFolder.FetchProfileItem.UID);
pop3Folder.fetch(message, fetchProfile);

for (int i = 0; i<message.length; i++) {
String uid = pop3Folder.getUID(message[i]);
if (isNewMessage(uid)) {
// blah blah....
}
}

ส่วนใน method isNewMessage(uid) ก็ส่งค่า uid เข้าไปตรวจจาก uid ที่เรามีอยู่แล้วโดยอาจเก็บไว้ใน database หรือ file system ก็ได้ตามสะดวก

29.4.09

Google Calendar API

ความสามารถของ Google Calendar มีประโยชน์มากมายอย่างที่ทราบกันดี ทาง Google ก็เปิดช่องทางให้เราสามารถเข้าถึงบริการของเค้าได้ง่าย
จึงขอลอง เพื่อเอามาประยุกต์ใช้กับโปรเจคใหม่ที่กำลังจะเกิดขึ้น
ตัวอย่างนี้จะทำการสร้าง event และส่งเข้า Google Calendar ของเรา

import java.net.URL;
import java.util.Calendar;
import util.Constant;
import com.google.gdata.client.calendar.CalendarService;
import com.google.gdata.data.PlainTextConstruct;
import com.google.gdata.data.calendar.CalendarEventEntry;
import com.google.gdata.data.extensions.Reminder;
import com.google.gdata.data.extensions.Reminder.Method;
import com.google.gdata.util.AuthenticationException;


public class GoogleCalendarUtil {

public CalendarService authenticate(String username, String password) {
CalendarService myService = new CalendarService("exampleCo-exampleApp-1");
try {
System.out.println("authenticate: start");
myService.setUserCredentials(username, password);
System.out.println("authenticate: success");
} catch (AuthenticationException e) {
e.printStackTrace();
}
return myService;
}

public void createEvent(CalendarService service, String content, String username) {
try {

URL postURL = new URL("http://www.google.com/calendar/feeds/" + username + "/private/full");

Calendar calc = Calendar.getInstance();
calc.add(Calendar.MINUTE, 1);
String timeStartEvent = calc.get(Calendar.HOUR_OF_DAY) + ":" + calc.get(Calendar.MINUTE);
// calc.add(Calendar.MINUTE, 1);
String timeEndEvent = calc.get(Calendar.HOUR_OF_DAY) + ":" + calc.get(Calendar.MINUTE);

System.out.println("addEvent: start");
CalendarEventEntry myEntry = new CalendarEventEntry();
content += " " + timeStartEvent + "-" + timeEndEvent;
System.out.println("content: " + content.toString());
myEntry.setContent(new PlainTextConstruct(content.toString()));
myEntry.setQuickAdd(true);

CalendarEventEntry insertedEntry = service.insert(postURL, myEntry);

Reminder reminder = insertedEntry.getReminder().get(0);
reminder.setMinutes(0);
reminder.setMethod(Method.SMS);
insertedEntry.getReminder().set(0, reminder);
insertedEntry.update();
System.out.println("addEvent: complete");
} catch (Exception e){
e.printStackTrace();
}
}

public static void main(String[] args) {
String username = "username";
String password = "password";
String content = "hello";
GoogleCalendarUtil helloGoogle = new GoogleCalendarUtil();
CalendarService service = helloGoogle.authenticate(username, password);
helloGoogle.createEvent(service, content, username);
}


}

เมื่อทำงานเสร็จคุณจะได้ sms แจ้ง event ที่ใส่เข้าไป
แน่นอนตรงจุดนี้ทำให้เราสามารถนำไปประยุกต์กับบริการต่่างๆ ได้อีกเยอะ

23.4.09

Receiver Mail via Gmail POP3

รับ mail ของ gmail ผ่าน pop3

import java.util.Properties;
import javax.mail.Folder;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Store;
import javax.mail.URLName;
import com.sun.mail.pop3.POP3SSLStore;

public class SimpleReceiver {

public static void main(String[] args) {
try {
String popServer = "pop.gmail.com";
String user = "yourmail@gmail.com";
String password = "yourpassword";
System.out.println("receive mail");
receive(popServer, user, password);
System.out.println("--- complete ---");
} catch (Exception ex) {
ex.printStackTrace();
}
}

public static void receive(String popServer, String user, String password) {
Store store = null;
Folder folder = null;
try {
String SSL_FACTORY = "javax.net.ssl.SSLSocketFactory";
Properties props = System.getProperties();
props.setProperty("mail.pop3.socketFactory.class", SSL_FACTORY);
props.setProperty("mail.pop3.socketFactory.fallback", "false");
props.setProperty("mail.pop3.port", "995");
props.setProperty("mail.pop3.socketFactory.port", "995");

URLName url = new URLName("pop3", popServer, 995, "", user, password);

Session session = Session.getDefaultInstance(props, null);

System.out.println("connect");
store = new POP3SSLStore(session, url);
store.connect();

System.out.println("folder");
folder = store.getDefaultFolder();
folder = folder.getFolder("inbox");
if (folder == null) {
throw new Exception("No default folder");
}

try {
folder.open(Folder.READ_WRITE);
} catch (MessagingException ex) {
folder.open(Folder.READ_ONLY);
}

System.out.println("message: " + folder.getMessageCount());
System.out.println("new: " + folder.getNewMessageCount());

Message[] message = folder.getMessages();
// FetchProfile fp = new FetchProfile();
// fp.add(FetchProfile.Item.ENVELOPE);
// folder.fetch(message, fp);
for (int i = 0; i<message.length; i++) {
String ct = message[i].getContentType();
if (message[i].isMimeType("text/plain")) {
System.out.println("message [" + i + "]:" + message[i].getContent());
} else {
System.out.println("message [" + i + "]:" + "not plain text.");
}

}

} catch (Exception ex1) {
ex1.printStackTrace();
} finally {
try {
if (folder != null) folder.close(false);
if (store != null) store.close();
} catch (Exception ex2) {
ex2.printStackTrace();
}
}
}

}

22.4.09

Hello JavaMail

พอดีมี project ที่ต้องใช้ JavaMail
ลองเล่นดูหน่อย..
เริ่มแรกไป download JavaMail API มาก่อน
จากนั้นก็ซัดไป


import java.util.Date;
import java.util.Properties;

import javax.mail.Message;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;


public class Hello {

public static void main(String[] args) {
try {
String smtpServer = "smtp.yourdomain.com";
String to = "youraccount@yourdomain.com";
String from = "me@mydomain.com";
String subject = "hello";
String body = "test message";
System.out.println("send mail " + smtpServer );
System.out.println("from: " + from);
System.out.println("to: " + to);
send(smtpServer, to, from, subject, body);
System.out.println("--- complete ---");
} catch (Exception ex) {
ex.printStackTrace();
}
}

public static void send(String smtpServer, String to, String from, String subject, String body) {
try {
Properties props = System.getProperties();
props.put("mail.smtp.host", smtpServer);
Session session = Session.getDefaultInstance(props, null);

Message msg = new MimeMessage(session);
msg.setFrom(new InternetAddress(from));
msg.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to, false));
msg.setSubject(subject);
msg.setText(body);
msg.setHeader("X-Mailer", "LOTONtechEmail");
msg.setSentDate(new Date());

Transport transport = session.getTransport("smtps");
transport.send(msg);
System.out.println("Message send OK.");
} catch (Exception ex) {
ex.printStackTrace();
}
}

}

14.11.08

JBoss Seam Setup

JBoss Seam Setup

ติดตั้ง JBoss Seam Project ให้พร้อมใช้งานโดยมีเครื่องมือที่จำเป็นพื้นฐาน 4 อย่างคือ Eclipse, JBossTool, JBossAS, Seam Framework

  1. ติดตั้ง Eclipse 3.4 ให้เรียบร้อย
  2. ดาวน์โหลด plug-in JBoss Tool ได้ที่ JBossDownload เราใช้ Eclipse 3.4 ต้องดาวน์โหลดเวอร์ชั่น 3.0 แต่ตอนนี้ยังเป็น Beta อยู่ (ถ้าใช้ Eclipse 3.3 ต้องใช้ JBossTool2.1.2+ แทน)
  3. ติดตั้ง plug-in JBoss Tool กับ Eclipse ก็แค่ก็อปไฟล์ plug-in แล้วไปวางตำแหน่งเดียวกับที่เราติดตั้ง Eclipse
  4. ติดตั้ง Seam Framework 2.1 ดาวน์โหลดได้ที่ SeamFramework
  5. ติดตั้ง JBoss Application Server 4.2 ดาวน์โหลดได้ที่ JBossAS
  6. สร้าง Project ใหม่เลือกประเภท Seam Web Project
    ตั้งชื่อ project อะไรก็ได้ (ตัวอย่างชื่อ hello)

  7. กำหนด Target Runtime
    ใน JBoss มี deploy 3 แบบเลือกแบบ default

  8. กำหนด Application Server ที่จะ deploy
    ถ้ามีการตั้ง user/pass ก็ใส่ให้เรียบร้อย

  9. เลือก technology ที่เราจะเลือกใช้ก็เลือก Seam 2.0 ตามรูปแรกสุด

  10. กด next เข้าสู่หน้าจอกำหนดชื่อ ContextRoot, Context Directory (view ทั้งหลาย), Java Source Directory (ชื่อ directory ตัว java source code) ก็ตาม default

  11. กด next จะเข้าหน้าจอจัดการเกี่ยวกับ JSF ตาม default

  12. กด next จะเข้าหน้าจอกำหนด Seam Runtime API, Database และชื่อ Package EJB ต่างๆ (เลือกประเภท deploy เป็น EAR ในกรณีต้องการใช้ EJB component ต่างๆ)
    รูป config เสร็จเรียบร้อยแล้ว (ในตัวอย่างใช้ MySQL สามารถเปลี่ยนได้)
    เลือก Seam Runtime ก็คือ folder ที่เราเอา Seam API มาวางไว้

  13.  กด finish เสร็จเรียบร้อย


13.11.08

Seam Hello World

Seam Hello World

เกริ่นนำ

Seam คือ application framework สำหรับ JavaEE5 เป็นโปรเจคหนึ่งของ JBoss พัฒนาขึ้นโดย Gavin King (ผู้เริ่มคิดค้น Hibernate ORM framework) โดย framework พัฒนาขึ้นโดยนำแนวคิดในกาัรพัฒนา web application framework หลายๆ ตัวมาใช้โดยเป้าหมายของ framework ตัวนี้คือ

"One of the (several) goals of Seam is to bring Ruby On Rails style productivity to the Java EE platform" (^^)

ความสามารถพื้นฐานของ seam คือมันเป็นกาวที่ทำให้ EJB3 กับ JSF มันเชื่อมต่อกันได้อย่างสวยงาม ถ้ามองกันการพัฒนา web application ตามมาตรฐานของ Sun ที่มีอยู่อาจต้องใช้ JNDI lookup, ประกาศ JSF backing bean, facade business method หรือความพยายามในการส่ง object ข้ามไปยัง tier ต่างๆ ที่เรารู้จักกันใน pattern ที่ชื่อ DTO (Data Transfer Object) แต่ seam มันบอกว่าสิ่งเหล่านี้เป็นความฟุ่มเฟื่อยแล้วมันจะ integerate JSF กับ EJB3 ให้เองโดยเราไม่ต้องไปเขียน xml config อะไรให้ฟุ้มเฟือย ในตัวอย่างนี้จะมาดูว่ามันเชื่อมต่อกันยังไง (คร่าวสุดๆ นะเพราะอยากให้เห็น code โดยรวมก่อน)

ลงมือ
1. สร้างโปรเจค
ก่อนอื่นติดตั้ง Eclipse + JBoss Tool ให้เรียบร้อย (หรืออาจใช้ seam gen สร้างโปรเจคขึ้นมาแล้วจับยัด (import) ลง Eclipse) เมื่อติดตั้งเรียบร้อยแล้วก็สร้าง seam web project ขึ้นมา config ให้เรียบร้อยเลือก deploy เป็น ear นะ (สมมุติ project นี้ชื่อ hello)

2. สร้าง Data Model
package org.domain.hello.entity;

import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import org.jboss.seam.annotations.Name;

@Entity
@Name("person")
public class Person implements Serializable{

    private static final long serialVersionUID = 1L;
    private long id;
    private String name;
    
    @Id @GeneratedValue
    public long getId() {
        return id;
    }
    public void setId(long id) {
        this.id = id;
    }
    
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

ด้วยความง่ายของ EJB3 เราสร้าง POJO + Annotation ก็จะกลายเป็น component หนึ่งของ EJB3 ในตัวอย่างนี้เป็น entity bean
โดยเราใช้ @Entity annotation เพื่อจะบอกว่าเราจะ map class นี้กับ relational database table ไหน และ attribute ที่เราอยู่ใน class ก็จะ map เข้ากับ column ใน table นั้น ดังนั้น 1 row ก็จะเท่ากับ 1 person instance (เพราะว่า seam บอกว่าการ configuration เป็นสิ่งทีมันพยายามหลีกเลี่ยงจึงใช้ annotation ในการช่วย config และเราไม่ต้องไปเขียน config xml ให้ฟุ่มเฟือย) และมี @Id เพื่อบอกถึง attribute ที่มันเป็น primary key และใช้ @GeneratedValue เพื่อบอกให้ application server สร้างค่าให้อัตโนมัติกับ Person instance เมื่อ save ลง database (ในตัวอย่างจะเห็นว่าเราไม่ต้องระบุชื่อ column เพราะใน table กับ class เราตั้งชื่อตรงกัน แต่ถ้าคนละชื่อต้องใช้ annotation เพื่อระบุชื่อลงด้วย)
ส่วนสำคัญที่สุดของ class นี้คือ @Name มันเป็นการ register กับ seam เพื่อให้เราสามารถเข้าจัดการ Person bean ตัวนี้ได้โดยการอ้างชื่อ person

3. Map ตัว Data Model กับ Web Form
ให้เราสร้างไฟล์ hello.xhtml ไว้ใน WebContent ก่อน

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">

    <head>
        <title>Hello Page</title>
    </head>

    <body>
        <h:form>
            Please enter your name: <br/>
            <h:inputText value="#{person.name}" size="15" /> <br/>   
            <h:commandButton action="#{manager.sayHello}" value="Say Hello" />
        </h:form>
        <h:dataTable value="#{fans}" var="fan">
            <h:column>
                <h:outputText value=#{fan.id}" />
            </h:column>
            <h:column>
                <h:outputText value="#{fan.name}" />
            </h:column>
        </h:dataTable>
    </body>

</html>

ใน JSF page นี้เราจะใช้ Person bean เป็นตัวเก็บข้อมูลเวลา input field เข้ามาโดยใช้ Expression Language (EL) #{person.name} (สังเกตุเราอ้างถึง person bean และกำหนดว่าให้เก็บลงใน attribute ที่ชื่อ name)
มีการเรียกตัว session bean ชื่อ sayHello ใน manager bean
และส่วนสุดท้ายเป็นการแสดงผลค่า fans ออกมาโดยใช้ dataTable มันจะเป็น iterater แล้ว list รายการ fans ออกมาจาก list ทั้งหมด


เมื่อผู้ใช้คลิกปุ่ม Say Hello ตัว Seam จะสร้าง person bean เพื่อรับ input data และเรียกไปที่ sayHello method ของ class ManagerAction ที่เป็น session bean
4. จัดการเหตุการณ์หน้าเว็บ

package org.domain.hello.session;

import java.util.List;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import org.domain.hello.entity.Person;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Logger;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Out;
import org.jboss.seam.log.Log;

@Stateless
@Name("manager")
public class ManagerAction implements Manager{

    @In @Out
    private Person person;
   
    @Out
    private List<Person> fans;
   
    @Logger
    private Log log;
   
    @PersistenceContext
    private EntityManager em;
   
    public String sayHello(){
        log.info("start sayHello");
        em.persist(person);
        fans = em.createQuery("select p from Person p").getResultList();
        log.info("end sayHello");
        return null;
    }
   
}

manager component ใน Seam คือ ManagerAction ที่เป็น session bean โดยเราใช้ @Name ในการประกาศชื่อ

@In (dependency injection) เป็นการบอกให้ Seam assign ตัว person component เข้ามาให้ โดยมันจะมาจากหน้า JSF form data ซึ่ง Seam จะส่งค่าให้ person ก่อนที่จะเข้ามาทำงานใน method ของ session bean

@Out (dependency outjection) เป็นการบอกให้ Seam assign ค่าของ fans ที่เก็บเป็น list of person หลังจากที่ execution method เสร็จแล้ว เช่นใน sayHello() method เรา update ค่า fans และ person field เมื่อทำงานใน method นี้เสร็จค่า fans จะถูก update ใหม่และส่งให้ที่หน้าเวบเพจ

บางครั้ง Seam อาจเรียกว่า bijection ซึ่งมันเป็นการอ้างถึง injection และ oiutjection ระหว่าง seam component กับ seam managed context

ใน sayHello() method จะทำการเก็บข้อมูลที่กรอบเข้ามาลงใน database ผ่าน EntityManager (JPA) ซึ่งตัว EntityManager ได้มาจากการ inject ผ่าน @PersistenceContext และเมื่อทำงานเสร็จแล้วก็ return null นั่นหมายถึงไม่มีการเปลี่ยนไปหน้า JSF page อื่นต่อ

แน่นอนสุดท้ายตามข้อกำหนด session bean อย่าลืม local ด้วยละ (เมื่อเราต้องการเรียกแบบ local)

package org.domain.hello.session;
import javax.ejb.Local;
@Local
public interface Manager {
    public abstract String sayHello();
}

5. deploy แล้วดูผลลัพธ์

1.11.08

Short Note EJB3

อ่าน EJB in Action แล้วกะจดสรุปไว้นิดหน่อย
เค้าเปรียบการพัฒนาการของ EJB ได้ว่า แมลงสวย --> ช้าง --> วัว (EJB3)
ลักษณะทั่วไป
  • EJB ให้มองว่ามันเป็น component ที่เราสามารถนำมันมา reuse ได้ เช่นทั้ง 2 app สามารถใช้ component เดียวกันได้ คำว่า component ถ้ามองในเทคโนโลยีอื่นๆ เช่น Microsoft COM+, CORBA
  • เราสามารถเขียน EJB โดยที่ไม่ต้องเขียนจัดการกับความซับซ้อนของ error-prone code, transaction, security access control เพราะทาง EJB Container เตรียมความสามารถเหล่านี้ไว้แล้ว เราเพียงแค่ใช้ metadata (Annotation Java5+) ในการประกาศว่าจะใช้ความสามารถเหล่านี้ ดังนั้น code ที่เราเขียนจึงมีไม่มากสามารถพัฒนาได้อย่างรวดเร็ว และเราสนใจเพียงแค่ business logic ไม่ต้องมองถึง infra structure code มากนัก
  • Pojo + Annotation = EJB ใน annotation เป็นการเขียน program แบบประกาศว่าเราต้องการ service อะไรให้ EJB เราบ้าง หรือใช้ระบุประเภทของ EJB ก็ได้
  • Entity bean ใน EJB3 รองรับ concept OO เช่น Inheritance, Polymophism มันเป็นสิ่งสำคัญมากที่ให้เราสามารถเพิ่ม business logic ใน Entity bean ของเรา ดังนั้นการ implement rich domain object ก็เป็นเรื่องที่ง่าย แต่มีบางคนที่ไม่ชอบ (Martin Fowler) ที่จะยัดความซับซ้อนลงใน domain object (EJB2 ทำได้ยากนัก) โดยเสนอให้สร้าง service/application layer ที่ทำหน้าที่ business logic ไว้ต่างหาก (service layer มันก็เหมือนกับ business layer แต่มันแค่บางกว่า) ดังนั้นจึงไม่น่าแปลกใจที่จะเห็นการเอา Session bean มาทำหน้าที่เป็น service layer
Layer Architecture
  • Presentation Layer --> [Service Layer] Business Layer --> Persistance Layer --> Database Layer
  • Domain-driven design
ประเภทของ EJB3
  • Session Bean เป็นที่เก็บ business logic ต่างๆ โดยแบ่งออกเป็น 2 ประเภทคือ
    • Stateless ที่จะไม่เก็บสถานะของ client ไว้ข้าม request ควรนำมาใช้งานเช่น ตรวจสอบวงเงิน หรือตรวจสอบว่าลูกค้าเคยซื้ออะไรแล้วบ้าง
    • Stateful จะเก็บสถานะของ client ข้าม request ได้เลยจนกว่า client จะ disconnection กับทาง web app เราเช่น transaction ของการซื้อสินค้า online ที่ต้องผ่านหลายหน้ากว่าจะเสร็จ 1 transaction
    ใน session bean สามารถเรียกใช้ได้ทั้งแบบ locally หรือ remotely โดยใช้ Java RMI แล้วใน stateless session bean ยังรองรับการเรียกผ่าน web service อีกด้วย
  • Message - Driven beans (MDB) คล้ายกับ session bean ที่เก็บ business เหมือนกันแตกต่างตรงที่ client เรียก MDB ไม่ได้แต่ MDB จะดักจับข้อมูลไปหา MDB Server (i.e. IBM Websphere MQ, Sonic MQ, Oracle Advanced Queueing, TIBCO) โดย MDB จะใช้กับระบบที่เชื่อมต่อกันหรือต้องการทำงานแบบ asynchronous process เช่นส่งรายการสินค้าที่ได้ใหม่ไปที่ระบบ automated retail หรือใช้ในระบบ supply chain management
  • Entities and Java Persistence API เป็น feature ที่น่าสนใจของ EJB3 ในส่วนของการจัดการ persistence
    • Persistence เป้นความสามารถในการเอาข้อมูลที่อยู่ใน relational database ออกมาเป็น Java Object ใน persistance ของ EJB3 จะจัดการโดยใช้ JPA โดยมันจะใช้เทคนิคที่เรียกว่า ORM การใช้เทคนิคดังกล่าวอาจต้องมีการ config เล็กน้อยเพื่อให้รู้ว่า field ใด map เข้ากับ attribute ใดโดยเราไม่ต้องสนใจว่าจะเขียน code ติดต่อกับ database อย่างไรเพราะ JPA จะจัดการส่วนนี้ให้ ช่วยทำให้ลดเวลา coding และช่วยลดความน่าเบื่อได้
    • Framework ที่มีความสามารถของ ORM แบบนี้ไม่ได้เป็น concept ใหม่แต่มันมีมานานแล้วเช่น Oracle Toplink, JBoss Hibernate
    • ใน EJB3 ในส่วน Persistence จะใช้ JPA ซึ่งมันมีการกำหนดมาตรฐานดังนี้
      • เขียน ORM config metadata เมื่อ mapping กับ relational database
      • EntityManager API เป็น API มาตรฐาน ที่เอาไว้ทำ CRUD ให้กับ Entity bean
      • Java Persistence Query Language (JPQL) ใช้ค้นหาและส่ง persistence app data
    • ตั้งแต่ Java มีการกำหนดมาตราฐาน JPA เพื่อให้เป็นมาตราฐานของ ORM Framework มันทำให้เราสามารถ plugin ORM product อื่นๆ เช่น JBoss Hibernate, Oracle Toplink, BEA Kodo ฯลฯ ภายใต้ JPA ได้
    • Entity ถ้าใช้ JPA ในการสร้าง persistence logic เราต้องใช้ entity bean
    • ถ้าเรามอง session bean เป็น verb ตัว entity เปรียบได้กับ noun
    • EntityManager มันเป็น interface โดยมันจัดเตรียมบริการต่างๆ ให้กับ persistence (CRUD) โดยมันจะอ่าน ORM metadata จาก entity และจัดเตรียมการทำงานต่างๆ ภายใต้ DB ให้ (CRUD) (เพิ่มเติม ข้างใน JPA มีการจัดเตรียมความสามารถในการจัดการ Lifecycle, Performance Turning, Caching และจัดการ Transaction)
    • Java Persistence Query Language เป็นสิ่งที JPA จัดเตรียม Query Language ไว้ทำหน้าที่คล้ายกับ SQL ปกติ
JavaEE Container ประกอบด้วย
เมื่อเราสร้าง Java class มันต้องรันผ่าน JVM นี่ก็เหมือนกันใน EJB session bean, mdb ทำงานอยู่บน ejb container ส่วน entity bean ทำงานบน persistence provider (จริงๆ EJB Container ก็คือ JVM แต่เพิ่มความสามารถเข้าไปเช่น transaction management, security management, remoting, webservice)
  • Web Container (deploy: jsp, jsf) สามารถเข้าถึง session bean, entity bean ได้
  • EJB Container (deploy: session bean, mdb) สามารถเข้าถึง entity bean ได้
  • Persistence provider (deploy: entity bean)

28.10.08

Short Note Spring MVC part 2

มาต่อ ส่วนนี้เจาะลงรายละเอียดเล็กๆ ของ ViewResolver , View

ViewResolver
DispatcherServlet ต้องการหา View Object เพื่อให้แสดงผลกลับไปให้ client โดยมันจะไปปรึกษากับ ViewResolver แล้ว ViewResolver จะ return ค่ากลับเป็น View Object ซึ่งมันมีการ implement ไว้ 4 แนวทางดังนี้
InternalResourceViewResolver ใช้ค้นหา View Object แบบง่ายๆ โดยจะค้นจากชื่อ logical view ใน application context ตัวอย่างเช่น

BeanNameViewResolver

XMLViewResolver

ResourceBundleViewResolver

ยังเขียนไม่เสร็จแปะไว้ก่อน ฮาๆ

25.10.08

Short Note Spring MVC part 1

บันทึกเก็บไว้หลังอ่าน Spring in Action จบแต่ละบทเริ่มจาก Spring MVC โดยลักษณะเหมือน web framework ทั่วไปคือ request-driven

ทำไมต้องใช้ Spring MVC
  • มีการออกแบบที่ดี ลองมองดูจะเห็นว่าเป็นการนำเอา Design Pattern ของ J2EE Design Pattern มา implement อยู่เบื่องหลังอีกทีถ้าเข้าใจตรงนั้นจะเห็นภาพการทำงานของ Spring MVC ง่ายขึ้น และใช้การออกแบบตาม MVC

  • มีการแบ่งหน้าที่ออกอย่างชัดเจน controller, validator, command object, form object, model object, DispatcherServlet, handler mapping, view resolver และอื่นๆ เหล่านี้ช่วยในการออกแบบ web app ของเราให้มีระเบียบ และมีหน้าที่ที่ชัดเจนมากยิ่งขึ้น
  • นำจุดเด่นของ Spring มาใช้ได้นั่นคือ Dependentcy Injection
  • มี Controller ต่างๆ ให้เลือกใช้มากมายตามความเหมาะสมของ web page นั้นๆ
  • รองรับการ Validator
  • รองรับ I18N ในส่วน Presentation ได้โดยใช้ bundle messages
  • รองรับการติดต่อกับ view technology อื่นๆ เช่น FreeMarker, Velocity
  • รองรับการจัดการ Handle Exception ด้วยตัวมันเอง
  • มี Spring tag library description ให้เลือกใช้ได้ตามสมควร
  • รองรับ Annotation ใน Controller เริ่มมีใน Spring MVC 2.5 ใช้ในส่วน Controller แต่ต้องใช้คู่กับ Java5+
การทำงานของ Spring MVC


DispatcherServlet เป็นหัวใจของ Spring MVC ที่ทำหน้าที่รับส่ง Request/Response ควบคุมการทำงานทั้งหมด
  • เขียน mapping url ไว้ใน web.xml ว่าถ้ามี url pattern นี้เข้ามาจะให้เข้าไปทำงานต่อที่ DispatcherServlet
  • [optional]เพิ่ม handleMapping ไว้ใน web.xml
HandleMapping เป็นที่ปรึกษาของ DispatcherServlet เพื่อจะได้รู้ว่า Request นี้จะต้องไปทำงานที่ Controller ตัวใด
  • default name คือ servletName-servlet.xml
  • มี 4 class ที่ implement HandleMapping ได้แก่ BeanNameUrlHandlerMapping (default), SimpleUrlHandlerMapping, ClassNameHandlerMapping, CommonsPathMapHandlerMapping
  • BeanNameUrlHandleMapping เป็นการ mapping controller กับ url ตามชื่อ bean






  • SimpleUrlHandlerMapping เป็นการ mapping controller โดยใช้ property เป็นตัวกำหนด




    homePageController



  • ClassNameHandlerMapping เป็นการ Mapping controller โดยใช้ชื่อ class เป็นตัวกำหนด เช่น com.appName.mvc.HomePageController ก็ map เข้ากับ url /homepage.htm


  • CommonsPathMapHandlerMapping เป็นการ mapping controller โดยใช้ metadata

  • เราสามารถใช้หลาย handle mapping หลากหลายแบบรวมกันได้แต่ต้องมีการกำหนด priopity ไว้

    • รองรับการจัดการ exception ที่เกิดขึ้นใน Controller โดยกำหนดได้ถึงขนาด exception แบบใดไปที่หน้าใด





      friendlyError



Controller ทำหน้าที่เป็นส่วนรับ request และร้อยเรียง business process เข้าด้วยกัน จากนั้นค่อยส่ง ModelAndView กลับไปให้ DispatcherServlet
มีหลากหลาย Controller ให้เลือกใช้ตามความเหมาะสมในแต่ละหน้า
AbstractController เป็น Controller ที่ใช้ง่ายสุดเหมาะกับหน้า ที่ไม่ต้องรับ request อะไรจาก client
AbstractCommandController เหมาะใช้กับหน้า ที่ต้องรับ request จาก client
  • ใช้ในกรณีที่ Controller เราต้องการรับ parameter จากหน้า client เพื่อทำงานบางอย่างต่อ
  • มันจะ binding request parameter เข้าไปกับ command object
  • Command object เทียบได้กับ ActionForm ใน Struts แต่จุดที่แตกต่างกันคือ Command object เป็น pojo แต่ ActionForm
  • ต้อง extends ActionForm
  • Spring จะ binding ชื่อ request parameter เข้ากับ Attribute name ใน Command object ให้
SimpleFormController เหมาะใช้กับหน้า ที่ต้องแสดงผลข้อมูลให้ client ให้เลือกก่อนเช่น แสดงผล listbox รายชื่อจังหวัดทั้งหมด
  • มีความสามารถเช่นเดียวกับ AbstractCommandController
  • onSumbit() จะเข้ามาได้ต่อเมื่อส่งข้อมูลแบบ http post
  • [optional] referenceData() เอาไว้เตรียมค่าบางอย่างลงใน form เช่น listbox
  • มันถูกออกแบบมาให้เราไม่ต้อง hardcode view ไว้แต่กำหนดไว้ที่ HandleMapping แทน
    • formView ระบุหน้า ที่แสดงผลเมื่อ controller หรือ http get หรือเมื่อเกิดข้อผิดพลาด
    • successView ระบุหน้า ที่แสดงผลเมื่อทำงานในเสร็จและไม่เกิดข้อผิดพลาด
  • ก่อนเข้า onSubmit() สามารถ validate ข้อมูลแต่ละ field ก่อนได้ โดยกำหนดไว้ที่ HandleMapping แล้ว inject Class ที่ทำหน้าที่
  • validate ลงไป การ validate แบบนี้เหมาะกับการ validate ที่ค่อนข้างซับซ้อน
  • Spring รองรับ Common Validator ให้เราได้เลือกใช้ในกรณีที่ validate ไม่ซับซ้อน ซึ่งใช้วิธีแบบ declarative validation โดยไม่
  • ต้องเข้าไปยุ่งกับ Java code โดยการไปใช้ declarative validation ของ Common validator
AbstractWizardFormController เหมาะใช้ในกรณีที่เป็นหน้า แบบ wizard ที่ต้องผ่านหน้าหน้ากว่าจะเสร็จ 1 transaction
MultiActionController เมื่อเราต้องการใช้หลาย Action ที่มีลักษณะคล้ายหรือ logic ที่สัมพันธ์กัน
ThrowawayController เมื่อเราต้องการจัดการ request แบบ command (ในลักษณะคล้าย WebWork Actions)

ModelAndView เป็น Object ที่ถูกส่งคืนกลับไปให้ DispatcherServlet หลัง Controller ทำงานเสร็จ โดย Spring MVC เตรียมพร้อมเพื่อรองรับการทำงานต่างๆ ดูได้จาก constructor ดังนี้
  • ModelAndView()
  • ModelAndView(Object view)
  • ModelAndView(Object view, Map model)
  • ModelAndView(Object view, String modelName, Object modelObject)
  • ModelAndView(String viewName)
  • ModelAndView(String viewName, Map model)
  • ModelAndView(String viewName, String modelName, Object modelObject)
สังเกตุจะเห็นว่าเราสามารถส่งชื่อ logical view เพื่อให้ ViewResoler ทำงานต่อหรือ View object (ควรใช้ในกรณีที่เราต้องการ custom การ render)กลับไปให้ DispatcherServlet ก็ได้และ model จะเป็น Object หรือ Map (ใช้ในกรณีที่มีหลายค่า) ก็ได้

ViewResolver เป็นตัวค้นหา View Object แล้วส่งกลับไปให้ DispatcherServlet







View View Object ที่สามารถ render เพื่อแสดงผลและส่งผลลัพธ์กลับไปหา client ในรูปแบบที่ browser อ่านแล้วตีความหมายได้

แปะ ViewResolver กับ View ไว้คร่าวๆ ก่อนเดี๋ยวอธิบายต่อ part 2

23.10.08

Getting Start BlazeDS

วันนี้อยากลองต่อ Flex (presentation layer) กับ JavaEE (infrastructure) เท่าที่ลองหาวิธีการรับส่งข้อมูลระหว่างกัน เห็นมีทั้งหมด 3 แนวทางคือ
  • BlazeDS
  • Adobe LiveCycle Data Services ES
  • Adobe LiveCycle Data Services Community Edition
ส่วนความแตกต่างเข้าไปดูกันได้ ที่นี่
เท่าที่ดูพบว่าทั้ง BlazeDS และ LiveCycle DS ต่างเป็น Open source น่าลองเล่นทั้งคู่และ feature ต่างกันไม่มากแต่ LiveCycle DS มีข้อจำกัดอยู่ถ้าเอามาเขียนเล่นๆ คงได้ถ้าใช้งานจริงคงไม่ดีกว่า แต่ถ้าอยากเล่นอะไรที่มันมีมากขึ้นลองเล่นตัว LiveCycle Data Service Community Edition แต่ว่ามันเป็น Commercial
ทดลองเล่น BlazeDS ก่อนเลย ในตัวอย่างนี้เป็นวิธี Remote Object นะ ขั้นตอนการ setup ไม่ยุ่งยากมาก
  • โหลด ที่นี่
  • แตกไฟล์ออกมาจะได้ blazds.war นำโครงสร้างที่ได้มาสร้าง java webapp แค่นี้ก็ได้โครงสร้างที่สามารถนำมาใช้พัฒนาต่อได้แล้ว
  • สร้าง java class ขึ้นมาเพื่อทดสอบเช่นสร้าง

    class Test{
    public void sayHello(String name){
    return "Hello " + name;
    }
    }
  • เข้าไปที่ <project>\<webcontent>\WEB-INF\flex\remoting-config.xml เพิ่มบรรทัด




    Hello

  • มาฝั่ง flex (client) สร้าง project ตามปกติแล้วเลือกเทคโนโลยีฝั่ง server เป็น JavaEE เลือกที่ server ที่จะ deploy งานไปไว้ที่เดียวกับ <project>\<webcontent> ฝั่ง java
  • แล้วลองสร้าง app ง่ายๆดู





    import mx.controls.Alert;
    import mx.rpc.events.ResultEvent;
    import mx.rpc.events.FaultEvent;

    public function resultHandler(event:ResultEvent):void{
    var s:String;
    s = event.result as String;
    Alert.show(s);
    }
    public function faultHandler(event:FaultEvent):void{
    Alert.show(event.fault.faultCode + ": " + event.fault.faultString + ": " +
    event.fault.faultDetail);
    }
    ]]>


    result="resultHandler(event)"
    fault="faultHandler(event)">





  • ทดลองรัน