September 7, 2020

Custom Converters

ရှေ့အခန်းမှာ ဖေါ်ပြခဲ့သလို JSF Framework ဟာ Primitive Data Type နဲ့ သူတို့ရဲ့ Wrapper Type တွေ၊ Date တွေ၊ Enum တွေ နဲ့ Number တွေဆိုရင်တော့ Standard Converter တွေကို အသုံးပြုပြီး၊ Convert လုပ်တာတွေ Format လုပ်တာတွေကို လုပ်ဆောင်နိုင်ပါတယ်။ မိမိရေးသားထားတဲ့ Custom Object တွေကို Format လုပ်ချင်တဲ့ အခါ၊ ဒါမှမဟုတ် Request Parameter တွေကနေ ပြန်ပြီး Convert လုပ်ချင်တဲ့ အခါမျိုးတွေမှာ Custom Converter တွေကို ရေးသားအသုံးပြုနိုင်အောင်လဲ ပြင်ဆင်ထားပါတယ်။

Converter Interface

Custom Converter ကို ရေးသားတဲ့ နေရာမှာ JSF ရဲ့ အရင်ရေးသားပုံနဲ့လဲ​ ရေးသားနိုင်သလို၊ CDI Beans အနေနဲ့လဲ ရေးသားအသုံးပြုနိုင်ပါတယ်။ ဘယ်လိုပဲ ရေးရေး Converter အနေနဲ့ ရေးသားလိုတဲ့ Class ဟာ javax.faces.convert.Converter Interface ကို Implement လုပ်ပြီး ရေးသားရမှာ ဖြစ်ပါတယ်။

public interface Converter<T> {

	public T getAsObject(FacesContext context,
   	 	UIComponent component, String value);

	public String getAsString(FacesContext context,
   	 	UIComponent component, T value);   

}

Converter Interface ဟာ Generics Type Interface ဖြစ်တဲ့ အတွက်၊ Implement လုပ်တဲ့ အခါမှာ ဘယ် Type ကို Convert လုပ်ချင်လဲ ဆိုတာကို Type Parameter အနေနဲ့ သတ်မှတ်ရေးသားပေးရမှာ ဖြစ်ပါတယ်။ Converter Interface ထဲမှာ Abstract Method အနေနဲ့ ၂ ခုပါဝင်ပါတယ်။ အဲ့ဒီ Method တွေကတော့ getAsObject() Method နဲ့ getAsString() Method တို့ပဲဖြစ်ပါတယ်။


Parsing in Process Validations Phase

JSF View တစ်ခုကနေ Form ကို Submit လုပ်လိုက်တဲ့အခါမှာ Input Parameter တွေဟာ Request Parameter တွေအနေနဲ့ JSF Framework ထဲကို ရောက်ရှိလာမှာ ဖြစ်ပါတယ်။ JSF Life-cycle ရဲ့ Process Validation Phase ကို ရောက်တဲ့ အခါမှာ Request Parameter ထဲက String တန်ဖိုးတွေကနေ Component Value ရဲ့ Type အဖြစ်ပြောင်းဖို့ အတွက် Component တွေမှာ သတ်မှတ်ရေးသားထားတဲ့ Converter ရဲ့ getAsObject() Method ကို Invoke လုပ်ပြီး Spring ကနေ Component Value ဖြစ်အောင် Convert လုပ်ယူမှာ ဖြစ်ပါတယ်။

Converter ရဲ့ getAsObject() Method မှာ Argument ၃ ခုပါဝင်ပါတယ်။ ပထမ Argument ကတော့ FacesContext Object ဖြစ်ပြီး၊ Request Liife-cycle တစ်ခုလုံးမှာ သုံးဖို့လိုအပ်တဲ့ အရာအားလုံးကကို FacesContext Object ကနေ ရယူနိုင်ပါတယ်။ ဒုတိယ Argument ကတော့ UIComponent Object ဖြစ်ပြီး၊ လက်ရှိ Converter ကို အသုံးပြုနေတဲ့ UI Component Object ကို ကိုယ်စားပြုပါတယ်။ တတိယ Argument ကတော့ String Type ဖြစ်ပြီး Servlet Request Parameter ကို ကိုယ်စားပြုပါတယ်။

Return Type ကတော့ Generic Type ဖြစ်ပြီး Component Value ရဲ့ Type ဖြစ်လာပါမယ်။ ဒါ့ကြောင့် getAsObject() Method ထဲမှာတော့ Request Parameter ကို အသုံးပြုပြီး Component Value နေရာမှာ အသုံးပြုလိုတဲ့ Type အဖြစ် ဘယ်လို ပြောင်းမလဲ ဆိုတာကို ရေးသားပေးရမှာ ဖြစ်ပါတယ်။


Formatting in Render Response Phase

Converter ရဲ့ getAsString() Method ကိုတော့ JSF Runtime ဟာ Render Response Phase မှာ အသုံးပြုပါမယ်။ UI Component တွေရဲ့ Value တွေကနေ String အဖြစ် ပြောင်းတဲ့ အခါမှာလဲ Converter ကို အသုံးပြုပါတယ်။

Converter ရဲ့ getAsString() Method မှာ Argument ၃ ခုပါဝင်ပါတယ်။ ရှေ့မှာပါတဲ့ Argument ၂ ခုကတော့ FacesContext နဲ့ UiComponent Object တွေဖြစ်ကြပြီး getAsObject() Method မှာ ပါဝင်ခဲ့တာနဲ့ အတူတူပဲ​ဖြစ်ပါတယ်။ တတိယ Argument ကတော့ Type Parameter မှာ သတ်မှတ် ထားခဲ့တဲ့ Type ဖြစ်ပြီး၊ Component Value ကို ကိုယ်စားပြုပါတယ်။ ဒီ Method ထဲမှာတော့ Component Value ကနေ String အဖြစ်ပြောင်းတာကို ရေးသားပေးရမှာ ဖြစ်ပါတယ်။

ဒါ့ကြောင့် UI Output Component တွေမှာ Component Value တွေကို Format လုပ်ပြီး ပြချင်တယ် ဆိုရင်လဲ Converter တွေကိုအသုံးပြုရမှာ ဖြစ်ပါတယ်။ ပြီးရင် getAsString() Method ကို Override လုပ်ပြီး Component Value ကနေ String အဖြစ် ဘယ်လို Format ချမယ် ဆိုတာကို ရေးသားပေးရမှာ ဖြစ်ပါတယ်။

Custom Converter in Action

ရှေ့မှာ ဖေါ်ပြခဲ့သလို့ Converter တွေကို Input တန်ဖိုးတွေကို Parse လုပ်တဲ့ နေရာမှာလဲ အသုံးပြုနိုင်သလို Output တွေကို Format လုပ်တဲ့ နေရာတွေမှာလဲ အသုံးပြုနိုင်ပါတယ်။ ကဲ ကျွန်တော်တို့ Custom Converter တွေကို ဘယ်လို အခါတွေမှာ ရေးပြီး ဘယ်လို သုံးသွားကြမလဲ ဆိုတာကို ဒီနေရာမှာ လေ့လာသွား ကြပါမယ်။


Formatting Output Value

အရင်ဆုံး Formatter အနေနဲ့ အသုံးပြုသွားတဲ့ ပုံစံကနေ စပြီး ကြည့်သွားကြရအောင်။ ဘယ်လို နေရာမျိုး တွေမှာ Output အတွက် Format ချပေးချင်တာလဲ။ ဆိုကြပါစို့၊ မှတ်ပုံတင်နံပတ် ဒါမျိုးတွေကို ဖေါ်ပြတဲ့ နေရာတွေမှာ Input တုန်းက ယူတဲ့ ပုံစံက တစ်ခုချင်း ယူတာရှိချင်ရှိပါမယ်။ ဒါပေမဲ ပြန်ပြီး ဖေါ်ပြတဲ့ အခါမှာ ပုံစံတစ်မျိုးထဲဖေါ်ပြပေးရမှာ မျိုးတွေ ရှိလာတတ်ပါတယ်။ ဒီလို အခါမျိုးတွေမှာလဲ Converter တွေကို အသုံးပြုချင်လာမှာ ဖြစ်ပါတယ်။

ကျွန်တော်တို့ မှတ်ပုံတင် နံပါတ်ကို နမူနာထားပြီးကြည့်ကြရအောင်။

	12/YKN(N)006789

12 ဟာ တိုင်းဒေသကြီးကုဒ် ဖြစ်ပြီး၊ YKN ဟာ မြို့နယ်ကုဒ်ဖြစ်ပါတယ်။ တဖန် (N) ကတော့ နိုင်ငံသားကို ကိုယ်စားပြုတဲ့ ကုဒ်ဖြစ်ပြီး နောက်က ဂဏန်း ၆ လုံးကတော့ ID Number ဖြစ်ပါမယ်။

ဒါ့ကြောင့် Database ထဲမှာ သိမ်းမယ်ဆိုရင် STATE_CD, CITY_CD, TYPE, REGIST_NUMBER ဆိုပြီး Column လေးခုနဲ့ သိမ်းတာက SELECT လုပ်တဲ့ အခါမှာ ပိုပြီး လွယ်ကူစေမှာ ဖြစ်ပါတယ်။

public class NrcNumber {
    
    private String stateCode;
    private String cityCode;
    private String type;
    private int registNumber;

    // getter and setter
}

Java Class နဲ့ရေးမယ်ဆိုရင်လဲ အထက်ပါအတိုင်း Field တစ်ခုချင်းစီကို ကိုယ်စားပြုတဲ့ ပုံစံနဲ့ ရေးကြပါလိမ့်မယ်။ Program ထဲမှာတော့ အသုံးပြုရလွယ်တဲ့ ပုံစံနဲ့ ရေးကြမှာ ဖြစ်ပေမဲ့ တကယ်တမ်း UI မှာ ပြန်ပြတဲ့ အခါမှာ User က နားလည်လွယ်တဲ့ ပုံစံနဲ့ ပြန်ပြပေးဖို့လိုအပ်ပါတယ်။

@FacesConverter("com.jdc.NrcNumber")
public class NrcNumberFormatter implements Converter<NrcNumber>{

}

NrcNumberFormatter Class ကို Converter တစ်ခုအနေနဲ့ အသုံးပြုနိုင်ဖို့ အတွက် Converter Interface ကို Implement လုပ်ပြီး ရေးသားရမှာ ဖြစ်ပါတယ်။ Type Parameter အနေနဲ့ကတော့ Convert လုပ်လိုတဲ့ NrcNumber Class ကို အသုံးပြုပါတယ်။ နောက်တစ်ခုကတော့ @FacesConverter Annotation ကို တတ်ဆင်ရေးသားရပါမယ်။ “com.jdc.NrcNumber” ကတော့ Converter ID ဖြစ်လာပါမယ်။ ဒါကတော့ JSF ရဲ့ Converter ရေးသားပုံရေးသားနည်းဖြစ်ပါတယ်။

@Named
@Dependent
public class NrcNumberFormatter implements Converter<NrcNumber>{

}

အထက်ပါအတိုင်း CDI Scope တစ်ခုဖြစ်တဲ့ @Dependent Annotation နဲ့ @Named Annotation ကို တတ်ဆင်ရေးသားမယ်ဆိုရင်လဲ ရပါတယ်။ ဒါကတော့ CDI Bean တစ်ခုကို Converter အနေနဲ့ အသုံးပြု သွားတဲ့ ပုံစံဖြစ်ပါတယ်။ CDI ကို အသုံးပြုမယ်ဆိုရင် Dependency Injection ကို အသုံးပြုနိုင်မှာ ဖြစ်တဲ့အတွက် Converter ထဲမှာ Database ကို ချိတ်ဆက် အသုံးပြုလိုတဲ့ အခါမျိုးတွေမှာ CDI ပုံစံနဲ့ ရေးသားသင့်ပါတယ်။

NrcNumberFormatter Class ဟာ Converter Interface ကို Implement လုပ်လိုက်ပြီဆိုတာနဲ့ Converter ထဲမှာပါတဲ့ Abstract Method နှစ်ခုကို Override လုပ်ပြီး ရေးသားပေးရတော့မှာ ဖြစ်ပါတယ်။ လက်ရှိရေးသားနေတဲ့ Class ဟာ Output တွေကို Format လုပ်ဖို့သာ ရည်ရွယ်ရေးသားတဲ့ Class ဖြစ်တဲ့ အတွက် getAsObject() Method ထဲမှာ ဘာမှမရေးထားရင်လဲ ရပါတယ်။

ဒါပေမဲ့ getAsString() Method ထဲမှာတော့ Format လုပ်မည့် Logic တွေကို ရေးသားပေးရမှာ ဖြစ်ပါတယ်။ ဒီနေရာမှာတော့ NrcNumber Object ရဲ့ Fields တွေကို အသုံးပြုပြီး၊ မှတ်ပုံတင်နံပတ် တစ်ခုကို ဘယ်လို Format လုပ်မယ် ဆိုတာကို ရေးသားရပါမယ်။

@Override
public String getAsString(FacesContext context,
    UIComponent component, NrcNumber value) {
    
    if(null != value) {
   	 return String.format("%s / %s (%s) %06d",
   			 value.getStateCode(), value.getCityCode(),
   			 value.getType(), value.getRegistNumber());
    }
    
    return null;
}

Converter Class ကို ရေးသားပြီးပြီဆိုတာနဲ့ Output ပြမည့် Component တွေနေရာမှာ Converter ကို အသုံးပြုလို့ရပါပြီ။ Converter ကို ရေးသားထားတဲ့ ပုံစံအပေါ်မှာ မူတည်ပြီး မှာ သတ်မှတ်ရေးသားပုံဟာလဲ ကွဲပြားမှာ ဖြစ်ပါတယ်။ JSF ရဲ့ နဂိုပုံစံအတိုင်းရေးသားထားတယ်ဆိုရင် UI Converter ရဲ့ converter Attribute တန်ဖိုး၊ ဒါမှမဟုတ် Converter Tag ရဲ့ converterId Attribute တန်ဖိုးတွေမှာ converter id ကို String အနေနဲ့ သတ်မှတ်ရေးသားပေးရမှာ ဖြစ်ပါတယ်။


အထက်နမူနာကတော့ UIOutputText Component မှာ converter id ဖြစ်တဲ့ “com.jdc.NrcNumber” ကို converter attribute မှာ ရေးသားထားတာ ဖြစ်ပါတယ်။


    

အထက်ပါနမူနာကတော့ Converter Tag ရဲ့ converId Attribute ကို အသုံးပြုပြီးရေးသားထားတာ ဖြစ်ပါတယ်။


    

တကယ်လို့ Converter Class ကို CDI ပုံစံနဲ့ ရေးသားထားတယ်ဆိုရင်တော့ Binding Attribute ကို အသုံးပြုရမှာ ဖြစ်ပါတယ်။


Converting Input Values

UI Input Component တွေမှာလဲ Custom Converter တွေကို အသုံးပြုနိုင်ပါတယ်။ Select One Menu Component တွေမှာ Category တွေကို ရွေးချယ်ပြီး နောက်ကွယ်မှာ Category Object နဲ့ Bind ထားချင်တယ် ဆိုရင် Custom Converter တွေကို ရေးသားရတော့မှာ ဖြစ်ပါတယ်။

@Entity
public class Category implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    private String name;

    // getters, setters and serial version uid 

	// Override hashCode and equals method
}

အထက်ပါ Category Class ကတော့ Select One Menu မှာ အသုံးပြုမည့် Entity Class ဖြစ်ပါတယ်။


    

Select One Menu ကို အသုံးပြုမယ်ဆိုရင် Component Value တွေနဲ့လဲ Backing Bean မှာရှိတဲ့ Category Object နဲ့ Bind ဖို့လိုအပ်သလို Select Item မှာ ဖေါ်ပြဖို့လဲ Category List နဲ့ Bind ထားဖို့လိုအပ်ပါတယ်။

@Named
@Dependent
public class CategoryConverter implements Converter<Category>{
    
    @EJB
    private CategoryService service;

    // converter methods

}

Category Converter ကို ရေးသားတဲ့ နေရာမှာလဲ Request ထဲကနေရောက်လာတဲ့ Category ID ကို အသုံးပြုပြီး Database ထဲကနေ Category ကို ရှာဖွေပြီးပြန်ပေးဖို့လိုတဲ့ အတွက် CDI ပုံစံနဲ့ ရေးသား ရပါမယ်။ ဒါမှသာ EJB ဖြစ်တဲ့ CategoryService ကို Inject လုပ်လို့ရမှာ ဖြစ်ပါတယ်။

Product Edit View ကို ပထမဆုံးစပြီးဖေါ်ပြတဲ့ First Request မှာ Model ထဲကတန်ဖိုးတွေနဲ့ HTML ကို Create လုပ်တဲ့ အခါမှာ CategoryConverter ရဲ့ getAsString() Method ကို အသုံးပြုပြီး Select Items ထဲက Category တွေအားလုံးကိုကော Select One Menu ရဲ့ Value ကိုပါ Convert လုပ်ပါမယ်။

@Override
public String getAsString(FacesContext context,
   	 	UIComponent component, Category value) {
    
    if(null != value) {
   	 	return String.valueOf(value.getId());
    }
    
    return null;
}

ဒီနေရာမှာတော့ Category Object ရဲ့ ID ကို String အဖြစ်ပြောင်းပြီး Return ပြန်ပေးလိုက်ပါတယ်။ ဒါ့ကြောင့် HTML Select Tag ထဲမှာရှိတဲ့ Option Values တွေနေရာမှာ Category ID တွေကို String တွေ အနေနဲ့ ဖေါ်ပြပေးနိုင်မှာ ဖြစ်ပါတယ်။ ဒီလိုပဲ Select One Menu ရဲ့ Value ဖြစ်တဲ့ Category ရဲ့ ID နဲ့ တူတဲ့ Option ကိုလဲ Selected လို့ ဖေါ်ပြပေးနိုင်မှာ ဖြစ်ပါတယ်။

Product Edit Form ကို Submit လုပ်တဲ့ အခါမှာ Select မှာ ရွေးထားတဲ့ Option Value တစ်ခုက Requst Parameter အနေနဲ့ JSF Framework ထဲကို ရောက်ရှိလာမှာ ဖြစ်တယ်။ Option Value ဟာ Category ID ဖြစ်တဲ့ အတွက် Category ID ကနေ Category Object ဖြစ်အောင် Convert လုပ်ပေးရပါမယ်။

@Override
public Category getAsObject(FacesContext context,
   	 	UIComponent component, String value) {
   	 
    if(null != value && !value.isEmpty()) {
   	 int id = Integer.parseInt(value);
   	 return service.findById(id);
    }
    
    return null;
}

Request Parameter ကနေ Category Object ကို Convert လုပ်ဖို့အတွက်ကတော့ getAsObject() Method ကို အသုံးပြုပါမယ်။ ဒီနေရာမှာတော့ EJB ဖြစ်တဲ့ service Object ကို အသုံးပြုပြီး Category Object ကို Database ထဲကနေ သွားရှာပေးပါတယ်။

Database ထဲကနေရှာလာတဲ့ Category Object နဲ့ အရင်ပြထားတဲ့ Category List ထဲက Object တွေဟာ Create လုပ်တဲ့ အချိန်မတူပါဘူး။ အဓိပ္ပါယ်အရတူညီပေမဲ့ Object တွေမတူဘူးဆိုတာဖြစ်လာနိုင်တဲ့ အတွက် Category Class မှာ hashCode() နဲ့ equals() Method တွေကို Override လုပ်ထားဖို့လိုအပ်ရတာ ဖြစ်ပါတယ်။

ရေးသားထားတဲ့ CategoryConverter ဟာ CDI Bean ဖြစ်တဲ့ အတွက် UI Component မှာ Converter အနေနဲ့ ပြန်သုံးတဲ့ အခါ Converter Tag ရဲ့ binding attribute မှာ Binding Expression နဲ့ ရေးသားပေးရမှာ ဖြစ်ပါတယ်။


    
    

အထက်ပါအတိုင်း Select One Menu Component ထဲမှာ Nested Tag အနေနဲ့ Converter Tag ကို ရေးသားအသုံးပြုနိုင်ပါတယ်။

Input Component Value နဲ့ Backing Beans ထဲမှာ Bind ထားတဲ့ Type တွေ မတူညီခဲ့ရင်၊ ဒါမှဟုတ် Output မှာ Format လုပ်ပြီးပြသလိုတဲ့ အခါမျိုးမှာ Converter တွေကို ရေးသားအသုံးပြုနိုင်မှာ ဖြစ်ပါတယ်။

ဆက်ပါဦးမည်။
မင်းလွင်

No comments:

Post a Comment