วันเสาร์ที่ 18 กรกฎาคม พ.ศ. 2558

Read ExcelFile to Entity By @Annotation

 เรามาเขียน @Annotation กันเถอะ

 วันนี้ผมมีงานที่ต้องนำข้อมูล Excel file ลง Database 
 ลักษณะงานแบบนี้หลายคนคงเจอกันบ่อยมากมาย และหลายคนก็คงเขียนกันแบบถึก ๆ 

 ... เสียใจด้วย ที่ java code ที่ท่านเห็นต่อไปนี้ จะเป็น version 7 
 ... #ร้องให้หนักมาก 
 
 วันนี้เรามาทำให้มันยากกว่าเดิมกันดีกว่า ... จะทำไปทำไม 

 เปลี่ยนเป็นวันนี้เรามาทำอะไรกันเล่น ๆ  สนุก ๆ กันดีกว่า 
 
 ลักษณะ การทำงาน ก็คงคล้าย ๆ กับ การทำ Mapping ... ใครที่เคยเขียน 
 Hibernate Annotation Mapping คงคุ้นเคยกันดีว่า เราสามารถใช้ความ
 สามารถของเจ้า @Annotation นั้น ทำการ Map Field ใน Entity Class
 ให้ตรงกับ Field ใน Database ... แต่อันนั้นมันฉลาดกว่าที่ผมทำอะ -_-"

 เริ่มเลยดีกว่า เรามาทำหนด Class ที่เป็น Entity ที่ต้องการนำข้อมูลจากเจ้า
 Excel File มายัดลงกันเลย 
package x.y.z.util.excelupload;

import x.y.z.util.excelupload.annotation.ExcelField;
import x.t.z.util.excelupload.type.ExcelFieldType;

public class TestBean {
    
    @ExcelField(type = ExcelFieldType.STRING, position = 0) 
    private String pid;
    
    @ExcelField(type = ExcelFieldType.STRING, position = 1) 
    private String name;

    @ExcelField(type = ExcelFieldType.STRING, position = 2) 
    private String surname;
    
    public TestBean(){
        
    }

// get - set ...
    public String getPid() {
        return pid;
    }
//...
}
 เราทำการกำหนด class มาตัวนึง ใช้เก็บข้อมูล pid, name, surname
 ก็ประมาณว่า เลขที่บัตรประจำตัวประชาชน, ชื่อ, นามสกุล อะไรประมาณนี้

 อย่าลืมเขียน get - set ให้ครบนะครับ แต่หลัก ๆ เราจะใช้ set เพื่อจะ
 ใช้ในการบันทึกข้อมูล 

 จากด้นบน เราจะเห็นว่ามี @ExcelField ดูหน้าตาแปลก ๆ ไปนิดถ้าชอบเดี๋ยวก็ชิน
 เรามาดูข้างใน @Annotation ตัวนี้กันเลยว่ามันมีหน้าตาเป็นแบบใหน
package x.y.z.util.excelupload.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import x.y.z.util.excelupload.type.ExcelFieldType;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelField {

    public int position() default 0;
    
    public ExcelFieldType type() default ExcelFieldType.STRING;

    public String format() default "";

}
 จาก code หลัก ๆ ผมก็จำลองกำหนดแค่ 
 - position คือตำแหน่ง cell ใน excelfile
 - type คือรูปแบบข้อมูล เป็นตัวอักษร, ตัวเลข, วัน(แต่ตรงนี้เราคงไม่ได้กำหนดหมด)
 - format อันนี้ผมทำเผื่อไว้ กรณีต้องกำหนดข้อมูลวันที่เข้ามามีรูปแบบ วันที่จาก excel 
 file หรือจะเป็นรูปแบบตัวเลข 

 มีการประกาศ enum ไว้ในส่วน type เพื่อกันการเลือกประเภทข้อมูลผิด 
package x.y.z.util.excelupload.type;

public enum ExcelFieldType {

    STRING, DATE, INTEGER, BIGDECIMAL
}
ก็มีแค่นี้แหละ enum จัดไปซะตั้ง 1 file เปลืองจริง เอาหละ แล้วเรามาดูในส่วน util ที่เราสร้างมาเอาฮา เอ้ย มาทำงานกัน
package x.y.z.util.excelupload;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import x.y.z.util.excelupload.annotation.ExcelField;
import x.y.z.util.excelupload.type.ExcelFieldType;

public class ExcelToBeanUtil {

    private Class clazz;
    private List dataList;
    private int rowAt = 0;

    public ExcelToBeanUtil(Class clazz) {
        this.clazz = clazz;
        dataList = new ArrayList<>();
    }

    private void addDataTolist(T data) {
        if (null == dataList) {
            dataList = new ArrayList<>();
        }
        dataList.add(data);
    }

    public ExcelToBeanUtil startRowAt(int rowAt) {
        this.rowAt = rowAt;
        return this;
    }

    public List execute(String pathFile) throws
            InvalidFormatException, IOException,
            InstantiationException, IllegalAccessException {
        
        FileInputStream file = new FileInputStream(
                new File(pathFile));

        XSSFWorkbook workbook = new XSSFWorkbook(file);
        XSSFSheet sheet = workbook.getSheetAt(0);

        Iterator rowIterator = sheet.iterator();
        for (int i = 0; rowIterator.hasNext() && i < rowAt; i++) {
            rowIterator.next();
        }

        while (rowIterator.hasNext()) {
            Row row = rowIterator.next();
            if (null == row.getCell(0).toString()
                    || row.getCell(0).getStringCellValue().equalsIgnoreCase("")) {
                break;
            }

            T data = clazz.newInstance();
            Field[] fields = data.getClass().getDeclaredFields();
            for (Field f : fields) {
                if (null == f.getAnnotations() || 0 == f.getAnnotations().length) {
                    continue;
                }
                ExcelField exField = f.getAnnotation(ExcelField.class);
                String fName = f.getName();
                fName = "set"
                        + fName.substring(0, 1).toUpperCase()
                        + fName.substring(1, fName.length());

                try {
                    Method method;

                    if (exField.type().equals(ExcelFieldType.INTEGER)) {
                        method = data.getClass()
                                .getDeclaredMethod(fName, Integer.class);
                        method.invoke(data, Integer.parseInt(
                                row.getCell(exField.position()).toString()));
                        
                    } else if (exField.type().equals(ExcelFieldType.BIGDECIMAL)) {
                        method = data.getClass()
                                .getDeclaredMethod(fName, BigDecimal.class);
                        BigDecimal b = new BigDecimal(
                                row.getCell(exField.position()).toString());
                        method.invoke(data, b);
                        
                    } else {
                        method = data.getClass()
                                .getDeclaredMethod(fName, String.class);
                        method.invoke(data,
                                row.getCell(exField.position()).toString());
                    }
                } catch (Exception ex) {
                    Logger.getLogger(ExcelToBeanUtil.class.getName())
                            .log(Level.SEVERE, null, ex);
                }
            }
            addDataTolist(data);
        }
        return this.dataList;
    }
}
 เอาหละ ทำไมมันได้ยาวแบบนี้ ... อย่าคิดมาก copy มาล้วน ๆ เดียวว่าง ๆ 
 ค่อยมาปรับแต่งให้ดูดี
 
  การทำงานของตัว util  นี้ไม่มีอะไรมาก มันแค่ไปหาว่า @Annotation ที่เราสร้าง
 ขึ้นนั้น ไปประกาศบน field ใดบ้าง หลังจากนั้น มันก็จะเอา field ไป เพิ่ม .set 
 เพื่อทำการบันทึกข้อมูลให้ field นั้น ... เอ่อ อย่าคิดมาก 
 วิธีการดึงข้อมูลก็ใช้แบบเรียกตรงไปเลย
 
  อีกส่วนเป็นการกำหนด ให้เราเริ่มอ่าน excel ได้ตั้งแต่บนนทัดใหน startRowAt อันนี้
 ผมลักไก่ไปก่อน ha ใช้วิธีวนทิ้งตามจำนวน

 เอาเป็นว่า เรามาเขียน test กันดีกว่า ว่ามันใช้งานได้จริงๆนะ
 เรามาเริ่มดูข้อมูลที่สมมติขึ้นมาก่อน 
PidNameSurname
3777777777777kanswatchupong
4888888888888ดรีมทีม
4888888888889ซูเปอร์ทีม
 Code Test ก็จะประมาณนี้ (อันนี้ gen test file จาก netbean 
 นะครับเขียนเองก็ได้)
package x.y.z.util.excelupload;

import java.util.List;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

public class ExcelToBeanUtilTest {

    public ExcelToBeanUtilTest() {
    }

    @BeforeClass
    public static void setUpClass() {
    }

    @AfterClass
    public static void tearDownClass() {
    }

    @Before
    public void setUp() {
    }

    @After
    public void tearDown() {
    }

    @Test
    public void testExecute() throws Exception {
        System.out.println("execute");
        
        ExcelToBeanUtil instance = new ExcelToBeanUtil(TestBean.class)
                .startRowAt(1);
        
        List beanList = instance
                .execute("/Users/kanswa/Desktop/profiletest.xlsx");

        for (TestBean bean : beanList) {
            System.out.println(" RESULT : "
                    + ToStringBuilder.reflectionToString(
                            bean, ToStringStyle.MULTI_LINE_STYLE
                    )
            );
        }
    }
} 
 ผลการ run ถ้าไม่มีอะไรนอกเหนือจากนี้นะจะเป็นดังนี้

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running z.y.z.util.excelupload.ExcelToBeanUtilTest
execute
 RESULT : x.y.z.util.excelupload.TestBean@66b697de[
  pid=3777777777777
  name=kan
  surname=swatchupong
]
 RESULT : x.y.z.util.excelupload.TestBean@7163ff8b[
  pid=4888888888888
  name=ดรีม
  surname=ทีม
]
 RESULT : x.y.z.util.excelupload.TestBean@5c1e38d5[
  pid=4888888888889
  name=ซูเปอร์
  surname=ทีม
]
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 5.525 sec
  จาก code test นี้จะเห็นว่า เวลาเขียนใช้งาน เราก็แค่โยน Entity ที่เรากำหนด
 ประเภทจาก @ExcelField แล้วเข้าไป ก่อนทำการเลือก file ที่ต้องการอ่าน 
 
  ข้อดีของการเขียนแบบนี้คือ เมื่อเรามีการเพิ่ม field เพื่อรับข้อมูลจาก Excel file  
 ที่มีการเปลี่ยนแปลง ก็จะสามารถทำได้โดยงาน 

  ข้อเสียคือ ... ขี้เกียจอธิบายให้คนอื่นเข้าใจ -_-" 

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

  เอาจริง ๆ คือ entity ที่ผมกำหนดนี้ก็เดี๋ยวใส่ @Annotation ของ Hibernate 
 แล้วจับ save เลยเนี่ยแหละครับ 

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

 ขอบคุณครับ ^^

วันพฤหัสบดีที่ 5 มีนาคม พ.ศ. 2558

Java 8 : Streams & Functional Programming

    ในปัจจุบัน Java ก็ได้ Update มาเป็น Version 8 แล้ว แต่ในเมืองไทย องค์กรต่าง ๆ ก็ยังไม่สามารถ update ได้อย่างเต็มที่ เนื่องจาก Tool และ Library ต่าง ๆ ยังจำเป็นต้องคงไว้ เนื่องจาก หากมีการเปลี่ยน Version ไม่ว่าจะเป็น Java หรือ Library ต่าง ๆ จะมีผลกระทบต่อ Application ที่กำลังดำเนินการไปมากกว่าครึ่งทางแล้ว หรือ Application ที่ได้ทำการติดตั้งไว้แล้วกำลังดำเนินการช่วง Maintenance แต่หากเป็นไปได้ ใน Application ใหม่ ๆ ก็ เปลี่ยนไปสู่สิ่งที่ดีกว่ากันเถอะครับ ^^
    จริง ๆ แล้ว ใน Java Version 7 เองก็มีอะไรที่น่าสนใจเหมือนกัน แต่คงข้างมาเขียน 8 ก่อนเพราะมัน Wow กว่า ซึ่งที่ว่ามีอะไรใหม่ ๆ ที่น่าสนใจใน version นี้ก็ได้แก่ Collection และ Lambda
    มาลองดูตัวอย่าง Code ง่าย ๆ กันก่อน ว่าเราเห็นอะไรบ้าง ถ้าเรามีข้อมูลใน List ประมาณนี้
        List<String> s = new ArrayList<>();
        s.add("1");
        s.add("2");
        s.add("3");
    หากเราต้องการ ดูข้อมูลแต่ละรายการก็ต้องทำการวนข้อมูลเพื่อทำการดูข้อมูลทีละข้อมูล
        for(String a : s){
            System.out.println(a);
        }
    ต่อไป เป็นของ Java 8
        s.forEach(System.out::println);
    ตรงนี้จะเห็นว่ามี 2 ส่วนที่เสนอไปยังหัวเรื่องคือ
  1. Streams > forEach ที่เป็นความสามารถใหม่ของ Collection (จริง ๆ แล้วน่าจะบอกว่ามาจาก Iterable) ส่วนนี้จะทำหน้าที่ในการวนข้อมูลทีละรายการมาทำงาน
  2. Functional Programming ในนี้คือ a -> System.out.println(a) โดยส่วนแรกจะเป็น a คือ ตัวรับ parameter ส่วนที่อยู่หลังเครื่องหมาย -> คือ คำสั่ง
    จากตัวอย่างเล็ก ๆ ข้างต้น ความน่าสนใจมันอยู่ตรงนี้ครับ คือมันมีการลด code ในการทำ logic ที่ไม่จำเป็นต้องมีในทาง business ลงไป เพื่อตัดปัญหาในการเกิด error ที่ไม่จำเป็นขึ้นมา
    เรามาเริ่มทีละเรื่องกันดีกว่า เรื่องแรกขอเป็นไกล้ตัวก่อนเลยคือ Streams Stream นั้นเป็นตัวช่วยแสดงลำดับข้อมูล ของ Object ประเภท Iterator ซึ่งมีความสามารถในการทำรายการแบบขนานกันได้ ในการทำงาน Stream นั้นจะถูกสร้างจาก Collection
เราจะมาดูว่าเราใช้อะไรจาก Stream ได้บ้าง
  • For Each ส่วนแรกนี้เราได้เห็นตามตัวอย่างข้างบนแล้ว
  • Map/Filter/Reduce


  • ... ไว้แค่นี้ก่อนนะ เดี๋ยวไว้มาต่อ ...

    วันพุธที่ 13 มีนาคม พ.ศ. 2556

    Test clear field by configure on Java Annotation

    บทความนี้เป็นบทความตัวอย่าง ที่ทำการเขียน Method ที่ใช้ในการ Clear ค่าตัวแปรที่อยู่ใน Class ที่เรากำลังใช้ทำงาน โดยใช้ Annotation เป็นตัวกำหนดว่าค่าตัวแปรใน Class ชื่ออะไรบ้างที่ต้องการทำการ Clear ค่าโดยค่าก็จะสามารถกำหนดได้ว่า จะทำการ Clear ค่าให้เป็นค่าว่างเปล่า => Null หรือค่าเริ่มต้น => New Instance ซึ่งจากตัวอย่างอาจจะนำไป Apply ไปใช้กับการทำงานอื่น ๆ ได้ เราจะกำหนด Code ไว้ดังนี้ TestBean.java < ใช้เป็นตัวแปรที่ทำการเก็บค่า และทดสอบการ Clear ข้อมูล TestAnnotation.java < ใช้เป็น Class ที่ทำการทดสอบการดำเนินการ และเก็บตัวแปร TestBean.java ไว้ เราจะกำหนด Annotation ไว้ดังนี้ FormBeanReset.java < เป็น Annotation ที่ใช้ดำเนินการ FormBeanResetType.java < เป็น Class ที่ไว้กำหนดประเภทการดำเนินการ ตัวอย่าง Code FormBeanReset.java
    package test.annotation;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface FormBeanReset {
        FormBeanResetType resetType() default FormBeanResetType.NEW_INSTANCE;
    }
    
    ตัวอย่าง Code FormBeanResetType.java
    package test.annotation;
    
    public enum FormBeanResetType {
     NULL, NEW_INSTANCE;
    }
    
    ตัวอย่าง Code TestBean.java
    package test.bean;
    
    public class TestBean {
    
        private String code;
     
        public String getCode() {
            return code;
        }
    
        public void setCode(String code) {
            this.code = code;
        }
    }
    
    ตัวอย่าง Code TestAnnotation.java
    package test.annotation;
    
    import java.lang.annotation.Annotation;
    import java.lang.reflect.Field;
    import java.lang.reflect.Method;
    
    import test.bean.TestBean;
    
    public class TestAnnotation {
    
        @FormBeanReset(resetType=FormBeanResetType.NULL)
        public TestBean testBean;
     
        public TestBean getTestBean() {
            return testBean;
        }
    
        public void setTestBean(TestBean testBean) {
            this.testBean = testBean;
        }
    
        public static void main(String[] args) throws Exception {
            testResetBean();
        }
    
        private static void testResetBean() throws Exception {
            TestAnnotation test = new TestAnnotation();
            test.setTestBean(new TestBean());
            test.getTestBean().setCode("TEST_CODE_001");
    
            System.out.println("[testResetBean TestBean] : " + test.getTestBean().getCode());
      
            Field[] fs = test.getClass().getFields();
            for(Field f : fs){
                System.out.println("[testResetBean FieldName]:" + f.getName());
    
                if(f.isAnnotationPresent(FormBeanReset.class)){
                    System.out.println("[testResetBean Annotation FieldName]:" + f.getName());
                    FormBeanReset fb = f.getAnnotation(FormBeanReset.class);
                    FormBeanResetType ft = fb.resetType();
                    
                    if(FormBeanResetType.NULL.name().equalsIgnoreCase(ft.name())){
                        f.set(test, null);
                        continue;
                    }
    
                    Class clazz = f.getType();
                    System.out.println("[testResetBean return clazz] : " + clazz.getName());
                    Method[] ms = test.getClass().getMethods();
                    for(Method m : ms){
                        System.out.println("[testResetBean Method] : " + m.getName());
          
                        if(("set"+f.getName()).equalsIgnoreCase(m.getName())){
                            System.out.println("[testResetBean Map Method] : " + m.getName());
                            m.invoke(test, clazz.newInstance());
                            break;
                        }
                    }
                }
            } 
            System.out.println("[testResetBean TestBean] : " + test.getTestBean());
            System.out.println("[testResetBean TestBean.Code] : " + test.getTestBean().getCode());
     }
    
    จากตัวอย่าง Code นี้ จะทำการ Clear testBean ให้มีค่าเป็น Null เมื่อ Run จะเห็น Log ดังนี้
    [testResetBean TestBean] : TEST_CODE_001
    [testResetBean FieldName]:testBean
    [testResetBean Annotation FieldName]:testBean
    [testResetBean TestBean] : null
    Exception in thread "main" java.lang.NullPointerException
        at test.annotation.TestAnnotation.testResetBean(TestAnnotation.java:81)
        at test.annotation.TestAnnotation.main(TestAnnotation.java:23)
    แต่หากเราไม่ต้องการให้ค่าเป็น Null เราจะสามารถเปลี่ยนในส่วน Configure ได้ดังนี้
        ...
    
        @FormBeanReset(resetType=FormBeanResetType.NEW_INSTANCE)
        public TestBean testBean;
    
        ...
    
    เมื่อแก้ดังนี้แล้ว ค่าที่เคยได้ เป็น Null ก็จะกลายเป็น New Instance เมื่อ Run จะเห็น Log ดังนี้
    [testResetBean TestBean] : TEST_CODE_001
    [testResetBean FieldName]:testBean
    [testResetBean Annotation FieldName]:testBean
    [testResetBean return clazz] : test.bean.TestBean
    [testResetBean Method] : getTestBean
    [testResetBean Method] : setTestBean
    [testResetBean Map Method] : setTestBean
    [testResetBean TestBean] : test.bean.TestBean@18eb9e6
    [testResetBean TestBean.Code] : null
    ในกรณีที่ได้แก้ไข Constructor ไว้ก่อนหน้านี้แล้ว ค่าก็จะเป็นไปตาม ที่ Constructor กำหนด เพราะในส่วน Code ที่เขียนกำหนดนี้มันไปทำการเรียก Default Constructor คือ method ชื่อเดียวกับ Class ที่ไม่ทำการใส่ argument นั่นเอง ประโยชน์ที่ได้หลักจากนี้คือ เมื่อเราต้องมีการเพิ่ม ตัวแปรใน Class ที่กำหนด และ มีการ Clear ค่าตัวแปรทุกครั้งที่มีการทำงาน เราก็แค่เพิ่ม Annotation กำหนดเข้าไป โดยไม่ต้องเขียน Code เพิ่มเลย บทความนี้ก็มีเท่านี้แหละ จบ

    วันพฤหัสบดีที่ 5 เมษายน พ.ศ. 2555

    Richfaces4 : Button add icon and text

    Support for richfaces4

    ตัวอย่าง สร้างปุ่ม บน richfaces4 ให้มีทั้ง icon และข้อความบนปุ่ม
    เนื่องจาก richfaces4 นั้น ไม่ได้ทำ support ทั้งสองแบบนี้ในปุ่มเดียว
    แต่ ถ้าใช้ primefaces นั้นจะมีให้แล้ว

    และอีกจุดประสงค์คือ เราต้องการให้ stylesheet เดิมยังคงอยู่เพื่อ
    ไม่ต้องทำการเพิ่มงานในส่วน design อีก

    ในที่นี้เราจึงเอา link ครอบปุ่ม เนื่องจาก ณ ตอนนี้ ปุ่มที่เป็น a4j:commandButton
    ของ richfaces4 ยังไม่ support เรื่องแสดง Icon พร้อม text

    เขียนปุ่มบน Html file
    <a4j:commandLink 
        actionListener="#{remainStatusAction.add}" 
        oncomplete="#{rich:component('enr006_01_01_Modalpanel_add')}.show()">
            <button type="button">
                <div class="btn_add_icon">
                </div>
                <div class="btn_text">
                    <h:outputText value="#{msg['btn.msg.add']}">
                    </h:outputText>
                </div>
            </button>
    </a4j:commandLink>
    
    <a4j:commandLink 
        actionListener="#{remainStatusAction.add}" 
        oncomplete="#{rich:component('enr006_01_01_Modalpanel_add')}.show()">
            <button type="button">
                <div class="btn_save_icon">
                </div>
                <div class="btn_text">
                    <h:outputText value="#{msg['btn.msg.save']}">
                    </h:outputText>
                </div>
            </button>
    </a4j:commandLink>
    

    กำหนด css เพื่อใส่ Icon image และการวางข้อมูลบนปุ่ม
    
    .btn_text{
     float:left;
    }
    
    .btn_save_icon{
     float:left; 
     width:20px;
     height:16px; 
     background-image: url(/EDUPTWeb/resources/images/save.png); 
     background-repeat: no-repeat; 
     background-position: left;
    }
    
    .btn_add_icon{
     float:left; 
     width:20px;
     height:16px; 
     background-image: url(/EDUPTWeb/resources/images/icon_add.gif); 
     background-repeat: no-repeat; 
     background-position: left;
    }
    
    

    reference site : https://community.jboss.org/message/718169#718169

    วันพุธที่ 20 กรกฎาคม พ.ศ. 2554

    taglib Authorization by spring security on spring3 richfaces4 jsf2

    ในส่วน authorization นี้ พูดในเรื่องของ tag ที่ใช้ในการกำหนดสิทธิ บนหน้า web เป็นหลัก โดยต่อยอดมาจากบนความก่อนหน้าในเรื่อง authentication

    authentication by spring security on spring3 richfaces4 jsf2

    สิ่งสำคัญในของหน้าที่ authorization คือ การให้สิทธิ ที่ไม่เท่าเทียมกันของ ผู้เข้ามาใช้ระบบ ซึ่ง บางคนสามารถทำการสร้างลบข้อมูลได้ ส่วนบางคน ควรจะมีหน้าที่ดู เพื่อรับข้อมูลอย่างเดียว เป็นต้น

    ในที่นี้ ต่อจากหน้า login.xhtml ในคราวที่แล้ว ให้เราสร้างหน้า home.xhtml ขึ้นมา

    == home.xhtml ==
    <!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:a4j="http://richfaces.org/a4j"
          xmlns:rich="http://richfaces.org/rich"
          xmlns:ui="http://java.sun.com/jsf/facelets"
          xmlns:sec="http://www.springframework.org/security/facelets/tags">
       
       status : 
       <sec:isAnonymous > anonymous user -_- </sec:isAnonymous > 
       <sec:isAuthenticated > authenticate user ^_^ </sec:isAuthenticated > 
       
       role : 
       <sec:ifNotGranted roles="ROLE_ADMIN">not admin</sec:ifNotGranted >
       <sec:ifAnyGranted roles="ROLE_ADMIN">admin</sec:ifAnyGranted >
       
       <sec:ifAllGranted roles="ROLE_ADMIN, ROLE_USER">both role (user, admin)</sec:ifAllGranted >
       
    </html>
    

    ในส่วนนี้เป็นตัวอย่างง่าย ๆ คือ เราต้องประกาศ

    xmlns:sec="http://www.springframework.org/security/facelets/tags"

    ขึ้นมาก่อน เพื่อใช้ tag ในการทำเรื่อง authorization ได้
    หลังจากนั้น เราก็ใช้งาน หลักการง่าย ๆ คือ ถ้า roles ที่กำหนดไว้ ตรงกับ ชื่อ sec:? นั้น ก็จะทำการแสดงข้อความที่อยู่ด้านใน tag sec:? นั้นๆ ได้ โดยในที่นี้ ลองดูมีอยู่ 5 tag ได้แก่


    <sec:isAnonymous />

    ใช้เพื่อถามว่าเป็น บุคคลทั่วไป หรือเปล่า

    <sec:isAuthenticated />

    ใช้เพื่อถามว่าเป็น บุคคลในระบบ หรือเปล่า

    <sec:ifAllGranted roles="?" />

    ใช้เพื่อถามว่าเป็น บุคคลที่อยู่ในตำแหน่งต่าง ๆ เหล่านี้ทั้งหมดหรือเปล่า

    <sec:ifNotGranted roles="?" />

    ใช้เพื่อถามว่าเป็น บุคคลที่ไม่ได้อยู่ในตำแหน่งต่าง ๆ เหล่านี้ทั้งหมดหรือเปล่า

    <sec:ifAnyGranted roles="?" />

    ใช้เพื่อถามว่าเป็น บุคคลที่อยู่ในตำแหน่งใดตำแหน่งหนึ่ง ในที่นี้หรือเปล่า

    authentication by spring security on spring3 richfaces4 jsf2

    authentication กล่าวเกริ่นแบบไม่แปลให้งงเล่นได้ว่า ในส่วนของ web หรือ application ที่เราได้สร้างขึ้นมานั้น ส่วนที่ทำให้ระบบเราปลอดภัย และจำกัดผู้ไม่เกี่ยวข้องในการใช้งานอันดับแรกเลยก็คนเป็นเรื่องของการแสดงตัวตนผู้ใช้ที่เข้ามาในระบบนั่นเอง

    ในส่วนนี้จะมี library ที่จำเป็น และ ใช้งานได้ง่ายดังนี้ โดยจะขอยกยอดมาจาก เนื้อเรื่อง spring framework บทก่อนๆ นะครับ ไม่ค่อยได้เริ่มอะไรใหม่เพราะยาวแล้วจะไม่อยากอ่านกัน
    JSF 2.0 and Spring Framework annotation example

    ขอแสดงเป็น xml maven แล้วกันนะครับ เพื่อความสะดวก โดยเพิ่มส่วนดังต่อไปนี้เข้าไป (ถ้ามีแล้วก็ไม่ต้องเพิ่มซ้ำนะ)
    == pom.xml ==
    
     org.springframework.security
     spring-security-core
     3.0.5.RELEASE
    
    
     org.springframework.security
     spring-security-web
     3.0.5.RELEASE
    
    
    
     org.springframework.security
     spring-security-config
     3.0.5.RELEASE
    
    
     org.springframework.security
     spring-security-taglibs
     3.0.5.RELEASE
    
    
     org.springframework.security
     taglib-core
     0.4
    
    
     org.springframework.security
     facelets-taglib-jsf20-spring-3
     0.5
    
    

    และเพิ่มในส่วนของ repository เข้าไป เพราะ jar file บางตัว ไม่ได้อยู่ center

    
     org.springframework.security.taglibs.facelets
     Repository for library Library[spring-security-facelets-taglib-googlecode]
     http://spring-security-facelets-taglib.googlecode.com/svn/repo/
    
    

    หลังจากนั้น ก็เพิ่ม code ใน file web.xml เพื่อให้ spring security เริ่มทำงานได้

    == web.xml ==
    
     springSecurityFilterChain
     org.springframework.web.filter.DelegatingFilterProxy
    
    
     springSecurityFilterChain
     /*
    
    

    หลังจากนั้น ก็ทำการ setup บน application context file แต่ในที่นี้ เราแยกออกมาเป็นอีก file โดย file applicationContext.xml เราเพิ่ม code ไปว่า

    == applicationContext.xml ==
    <import resource="/security.xml"  />
    

    == security.xml ==
    
    
    
        
        
            <intercept-url pattern="/login.xhtml" filters="none" />
            <intercept-url pattern="/images/**" filters="none" />
            <intercept-url pattern="/javascript/**" filters="none" />
            <intercept-url pattern="/css/**" filters="none" />
            <intercept-url pattern="/javax.faces.resource/**" filters="none" />
            <intercept-url pattern="/rfRes/**" filters="none" />
            <intercept-url pattern="/**"  access="ROLE_USER" />
            <form-login login-page="/login.xhtml" default-target-url='/home.xhtml'
                always-use-default-target='true' />
            <logout/>
        
        
        
    
        
            
                
                    
                    
                
            
        
    
    

    ในครั้งนี้เราเปลี่ยน tag beans ที่เคยเป็น default มาเป็น tag security เป็น default แทน เพิ่มทำให้ tag ดูง่ายขึ้น แต่ tag beans เปลี่ยนไปนิดหน่อย
    เอาเป็นว่า ในที่นี้เรา สร้าง user มาสองคน ชื่อว่า nai_a กับ nai_b ใน tag authentication-manager
    ในส่วน tag http เอาไว้กำหนด หน้าที่ต้องการให้สิทธิใดเข้าถึง รายละเอียดคงต้องหาอ่านกันต่อไปนะ

    หลังจากนั้น เราก็มาสร้างหน้า login กันดีกว่า เพื่อให้ครบวงจรซะที

    == login.xhtml ==
    <!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:a4j="http://richfaces.org/a4j"
          xmlns:rich="http://richfaces.org/rich"
          xmlns:ui="http://java.sun.com/jsf/facelets"
          xmlns:p="http://primefaces.prime.com.tr/ui">
    
        <f:view locale="#{localizationBean.locale}">
    
            <h:head>
                <title>Project Tracking.</title>
                <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
                <link type="text/css" rel="stylesheet" href="css/default.css" />
            </h:head>
    
            <h:body onload='document.f.j_username.focus();'>
    
                <center>
                    <p:panel header="Login with Username and Password" style="width:400px;">
                        <form name='f' action='j_spring_security_check' method='POST'>
                            <table>
                                <tr><td colspan="2">  </td></tr>
                                <tr><td>User:</td><td><input type='text' name='j_username' value=''/></td></tr>
                                <tr><td>Password:</td><td><input type='password' name='j_password'/></td></tr>
                                <tr><td colspan="2">  </td></tr>
                                <tr align="center">
                                    <td colspan="2">
                                        <input name="submit" type="submit"/> 
                                        <input name="reset" type="reset"/>
                                    </td>
                                </tr>
                            </table>
                        </form>
                    </p:panel>
                </center>
                <br/>
                ROLE_ADMIN nai_a : jimispassword<br/>
                ROLE_USER nai_b : bobspassword<br/>
            </h:body>
        </f:view>
    </html>
    

    ในส่วนนี้จะเป็น หน้า login ที่ทำการกรอก user, password ธรรมดา แต่มีการใช้ tag primefaces เข้ามาช่วย ก็ไม่ต้องสนใจ ในที่นี้ แค่ต้องการนำเอา user, password ส่งไปยัง j_spring_security_check เท่านั้น ถ้าถูกต้องก็จะเข้าไปยังหน้า home.xhtml ดังที่เราได้กำหนดไว้
    และถ้า กรอกข้อมูลผิด ก็จะยังอยู่ในหน้า login อีกครั้ง เนื่องจากหน้า login ถูกกำหนดเป็นหน้า default กรณียังไม่ทำการ login ที่ file security.xml

    วันศุกร์ที่ 24 มิถุนายน พ.ศ. 2554

    Hibernate Annotation generate id from oracle function

    แก้ไขการ Generate ID จาก ORACLE Sequence มาเป็น เรียก ORACLE Function
    แนะนำความต้องทำความรู้จัก Hibernate, Spring Framework เบื้องต้น ก็จะเป็นการดีครับผม

    - spring framework 3 quickstart
    - Spring Framework and Hibernate on Annotation

    เรามาเข้าจุดที่เราสนใจกันจริง ๆ ดีกว่า ในส่วนของ Hibernate Map Class Model กับ Database นั้น
    เราจะมีการเขียน Class ในลักษณะนี้ครับ

    โดยในตัวอย่างแรกนี้ จะเป็นเรื่องการ Map ID ของ Class ให้มีการดึง Sequence Oracle
    มาเป็น id ตอน insert ข้อมูล

    Process.java
    package x.y.z.projecttracking.model;
    
    import javax.persistence.Column;
    import javax.persistence.Entity;
    import javax.persistence.GeneratedValue;
    import javax.persistence.GenerationType;
    import javax.persistence.Id;
    import javax.persistence.SequenceGenerator;
    import javax.persistence.Table;
    
    @Entity
    @Table(name="PROCESS")
    public class Process extends BaseEntity {
        
        public Process(){}
        
        @Id
        @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_PROJ_GEN")
        @SequenceGenerator(name = "SEQ_PROJ_GEN", sequenceName = "SEQ_PROJ")
        private Long id;
        
        @Column
        private String code;
        
        @Column
        private String description;
    
        public Long getId() {
            return id;
        }
    
        public void setId(Long id) {
            this.id = id;
        }
        
        public String getCode() {
            return code;
        }
    
        public void setCode(String code) {
            this.code = code;
        }
    
        public String getDescription() {
            return description;
        }
    
        public void setDescription(String description) {
            this.description = description;
        }
    

    ในส่วนนี้จะเป็นเครื่องมือที่ Hibernate มีให้ใช้งานได้ทันที

    ส่วนที่สำคัญที่มีผลต่อการ generate id ก็เป็นดังนี้

    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_PROJ_GEN")
    ในส่วน GeneratedValue นี้จะบอกชนิดของการสร้าง id โดยระบุ strategy คือ ลักษณะการสร้าง id เป็น
    Sequence Oracle
    (NOTE : กรณี MySQL นั้น ไม่มี Sequence จะใช้แค่ @GeneratedValue เปล่า ๆ เลยแล้วทาง MySQL จะรู้เองว่า ต้องการขอ id ใหม่ ก็จะสร้างเลขใหม่ไม่ซ้ำให้ แต่ ORACLE ไม่มี auto generate เช่นนั้น โดยทั่วไปถึงต้องเรียกใช้ sequence ที่เป็นอีก function เฉพาะใน oracle ให้ทำการสร้าง running -number เพื่องานใด ๆ)

    ส่วน generator นั้นก็จะเป็นตัวบอกว่าไปหา ตัวที่ทำการสร้างนั้นที่ใหน ด้วยชื่อ ตามด้านล่างนี้เลยครับ

    @SequenceGenerator(name = "SEQ_PROJ_GEN", sequenceName = "SEQ_PROJ")
    ในส่วน SequenceGenerator ตัวนี้ถือเป็นเครื่องมือในการดึงข้อมูลจาก Sequence ORACLE ที่เราได้เตรียมไว้แล้ว
    โดย name ก็คือชื่อแทนตัว ไว้ให้สำหรับเรียกใช้จากที่อื่น
    ส่วน sequenceName ก็จะเป็นตัวระบุว่า Sequence ORACLE ที่เราต้องการใช้งานนั้น มีชื่อว่าอะไร

    จบในส่วน เกรินนำ ID Generate By Sequence ไว้เท่านี้ก่อน

    เราไปเริ่มแก้ให้เป็น ID Generate By Function กันดีกว่า

    เริ่มจาก เราเปลี่ยน Generator ใหม่เป็นดังนี้

    Process.java
    package x.y.z.projecttracking.model;
    
    import javax.persistence.Column;
    import javax.persistence.Entity;
    import javax.persistence.GeneratedValue;
    import javax.persistence.Id;
    import javax.persistence.Table;
    import javax.persistence.Temporal;
    import org.hibernate.annotations.GenericGenerator;
    
    @Entity
    @Table(name="PROCESS")
    public class Process extends BaseEntity {
        
        public Process(){}
        
        @Id
        @GeneratedValue(generator = "projIdSeqGen")
        @GenericGenerator(
            name = "projIdSeqGen", 
            strategy = "x.y.z.projecttracking.generator.ProjIdSequenceGenerator")
        private Long id;
        
        @Column
        private String code;
        
        @Column
        private String description;
    
        public Long getId() {
            return id;
        }
    
        public void setId(Long id) {
            this.id = id;
        }
        
        public String getCode() {
            return code;
        }
    
        public void setCode(String code) {
            this.code = code;
        }
    
        public String getDescription() {
            return description;
        }
    
        public void setDescription(String description) {
            this.description = description;
        }
    

    มีส่วนที่เปลี่ยนแปลง 2 ส่วน ได้แก่
    @GeneratedValue(generator = "projIdSeqGen")
    ในส่วน strategy เราไม่ต้องระบุแล้ว เพราะเราจะปรับแต่งเอาเอง โดยใช้ตัว genreator พื้นฐานคือ GenericGenerator

    @GenericGenerator(
    name = "projIdSeqGen",
    strategy = "x.y.z.projecttracking.generator.ProjIdSequenceGenerator")
    ในส่วนนี้เราใช้ GenericGenerator แล้วนำมาปรับแต่ง เพิ่มเติมโดย เราจะเห็นว่ามีการระบุ strategy ขึ้นมา
    แล้วเราก็เขียน class ซึ่งจะมี code ตามด้านล่างนี้

    package x.y.z.projecttracking.generator;
    
    import java.io.Serializable;
    import java.lang.reflect.Constructor;
    import java.lang.reflect.InvocationTargetException;
    import java.sql.CallableStatement;
    import java.sql.Types;
    import java.util.Properties;
    import org.hibernate.Hibernate;
    import org.hibernate.HibernateException;
    import org.hibernate.MappingException;
    import org.hibernate.dialect.Dialect;
    import org.hibernate.engine.SessionImplementor;
    import org.hibernate.id.Configurable;
    import org.hibernate.id.PersistentIdentifierGenerator;
    import org.hibernate.id.SequenceHiLoGenerator;
    import org.hibernate.type.Type;
    
    /**
     *
     * @author Kan.s
     */
    public abstract class FunctionIdSequenceGenerator implements PersistentIdentifierGenerator, Configurable {
    
        protected final SequenceHiLoGenerator delegate = new SequenceHiLoGenerator();
        protected Constructor constructor;
    
        @Override
        public String[] sqlCreateStrings(Dialect dialect) throws HibernateException {
            return delegate.sqlCreateStrings(dialect);
        }
    
        @Override
        public String[] sqlDropStrings(Dialect dialect) throws HibernateException {
            return delegate.sqlDropStrings(dialect);
        }
    
        @Override
        public Object generatorKey() {
            return delegate.generatorKey();
        }
    
        @Override
        public Serializable generate(SessionImplementor session, Object object) throws HibernateException {
            try {
                String sqlCallFunc = "{ ? = call "+getFunctionName()+"() }";
                System.out.println("SQL call Function() : " + sqlCallFunc);
                CallableStatement call = session.connection().prepareCall(sqlCallFunc);
                call.registerOutParameter(1, Types.VARCHAR);
                call.execute();
                String result = call.getString(1);
                return typedIdForValue(result);
            } catch (Exception e) {
                throw new HibernateException("delegate didn't return a string", e);
            }
        }
    
        @Override
        public void configure(Type type, Properties params, Dialect d) throws MappingException {
            delegate.configure(Hibernate.STRING, params, d);
            try {
                constructor = type.getReturnedClass().getConstructor(String.class);
            } catch (NoSuchMethodException e) {
                throw new HibernateException("FunctionIdSequenceGenerator is only applicable for types deriving from AbstractPersistenLongId", e);
            }
        }
    
        protected Serializable typedIdForValue(String val) {
            try {
                return (Serializable) constructor.newInstance(val);
            } catch (InstantiationException e) {
                throw new HibernateException("Cannot create strongly typed id", e);
    
            } catch (IllegalAccessException e) {
                throw new HibernateException("Cannot create strongly typed id", e);
    
            } catch (InvocationTargetException e) {
                throw new HibernateException("Cannot create strongly typed id", e);
            }
        }
    
        public abstract String getFunctionName();
    }
    

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

    id ส่วนนี้ ผมจะ return String เลยจัดการแก้ในส่วน Method configure()

    ส่วนสำคัญที่ทำหน้าที่สร้าง ID คือ Method generate ในส่วนนี้ผมได้แก้ให้เป็น เรียก Function ORACLE โดย Class ที่นำไปใช้งานจริง จะถูกบังคับให้สร้าง Method getFunctionName() เพื่อระบุชื่อ Function ORACLE ที่ต้องการทำการเรียกใช้งาน

    หากต้องการใส่ parameter เข้าไปยัง function ก็ใช้ประมาณนี้ครับ
        String sqlCallFunc = "{ ? = call "+getFunctionName()+"(?, ?) }";
        String param1 = "test";
        Long param2 = "test";
        CallableStatement call = session.connection().prepareCall(sqlCallFunc);
        call.registerOutParameter(1, Types.VARCHAR);
        call.setString(2, param1);
        call.setLong(3, param2 );
        call.execute();
        String result = call.getString(1);
    
    ตามตัวอย่างนี้ก็หวังว่าจะนำไป apply ใช้ประโยชน์ได้นะครับ

    ส่วน Class ที่นำไปใช้งานจริงก็เป็นแบบนี้ครับ

    package x.y.z.projecttracking.generator;
    
    /**
     *
     * @author Kan.s
     */
    public class ProjIdSequenceGenerator extends FunctionIdSequenceGenerator {
    
        @Override
        public String getFunctionName() {
            return "getProjId";
        }
    }
    

    เรามาดูตัวอย่าง PL/SQL ที่ Generate function ORACLE กันบ้างดีกว่า
    ในที่นี้เขียนง่าย ๆ เพราะเขียนได้แค่นี้ครับ T_T
    ขั้นตอน คือ นำเอาวันเวลาปัจจุบัน มาแล้วเติม prefix เป็น 'PJ'
    CREATE OR REPLACE FUNCTION getProjId(
    ) RETURN VARCHAR2 IS
        dttm_curr VARCHAR2(20);
        cursor c1 IS 
        SELECT TO_CHAR(CURRENT_DATE, 'DDMMYYYYHHMISSSSS') FROM DUAL;
    BEGIN
       open c1;      
       fetch c1 into dttm_curr;
       close c1;
       return CONCAT('PJ', dttm_curr); 
    END getId4DttmCurr  ;
    

    Result : PJXXXXXXXXXXXXXXXXX
    X is number 0-9


    กรณี เขียนแบบให้รับ parameter
    CREATE OR REPLACE FUNCTION getProjId(prefix IN varchar2
    ) RETURN VARCHAR2 IS
        dttm_curr VARCHAR2(20);
        cursor c1 IS 
        SELECT TO_CHAR(CURRENT_DATE, 'DDMMYYYYHHMISSSSS') FROM DUAL;
    BEGIN
       open c1;      
       fetch c1 into dttm_curr;
       close c1;
       return CONCAT(prefix, dttm_curr); 
    END getId4DttmCurr  ;
    

    ------------------------------------------------
    Thank you for...
    credit :
    - Custom Id Generators For Typed Id Values With Hibernate
    - stackoverflow : How to call a Oracle function from hibernate with return parameter?