December 17, 2016

Dependency Injection - Part 2

ပြီးခဲ့တဲ့ Blog Post မှာတော့ Dependency Injection ဆိုတာ ဘယ်လိုဟာမျိုးလဲ။ ပြီးတော့ CDI ကို သုံးပြီး Injection ကို ရေးမလဲဆိုတာကို ဖေါ်ပြခဲ့ပါတယ်။ တဆက်ထဲ စဉ်းစားသွားစေလိုတာကတော့ Injection Point မှာ သုံးလို့ရတဲ့ Class အမျိုးအစားတွေကိုပါ။ Injection Poing မှာ Concrete Class တွေကိုလဲ အသုံးပြုနိုင်သလို၊ Abstract Class ကော Interface တွေကိုပါ အသုံးပြုနိုင်ပါတယ်။ Concrete Class တွေဆိုရင် ပြဿနာမရှိပေမဲ့ Abstract Class တွေ Interface တွေကို သုံးထားရင် အဲ့ဒီနေရာမှာ ဘယ် Class ကို လာ Inject လုပ်မလဲ စိုးရိမ်စရာရှိပါတယ်။

အထက်ပါ Class Diagram ထဲမှာလို ExamRegistor ထဲက Injection Point မှာ Model ဆိုတဲ့ Type ကို အသုံးပြုထားပါတယ်။ Model ဆိုတာက Interface တစ်ခုဖြစ်ပြီး ExamModel Class က Implement လုပ်ထားတာဖြစ်ပါတယ်။ ဒါ့ကြောင့် ExamModel Class ကို Model Interface ရဲ့ Type အနေနဲ့ အသုံးပြုနိုင်မှာ ဖြစ်ပါတယ်။

အထက်ပါအတိုင်း Model Interface ကို Implement လုပ်ထားတဲ့ Class ဟာ တစ်ခုထဲရှိနေမယ်ဆိုရင် Container ကနေ ExamModel Class ကနေ Objectကို ဆောက်ပြီး Model ရဲ့ နေရာမှာ လာပြီး Inject လုပ်ပေးနိုင်မှာ ဖြစ်ပါတယ်။

Injection Point မှာ Inject လုပ်လို့ရတဲ့ Class ဟာ တစ်မျိုးထဲ ဖြစ်နေလို့သာ Container ကနေ အလိုအလျောက် Inject လုပ်ပေးနိုင်တာဖြစ်ပါတယ်။ ထိုကဲ့သို့ Inject လုပ်နိုင်ခြင်းကို Default Injection လို့ခေါ်ပါတယ်။


Injection Point မှာ Inject လုပ်လို့ရတဲ့ Bean တစ်ခုထက်မကရှိနေခဲ့ရင်



အထက်ပါပုံအတိုင်း Injection Point ဖြစ်တဲ့ Model နေရာမှာ Implement လုပ်ထားတဲ့ Class တွေ တစ်ခုထက်မကရှိလာပြီဆိုရင် ဒီအတိုင်းဆို Container ကနေ ဘယ် Class ကနေ Object ဆောက်ပြီး Inject လုပ်ရမယ်ဆိုတာကို ဆုံးဖြတ်လို့မရပဲ ဖြစ်နေပါလိမ့်မယ်။

ထိုအခါမျိုးဆိုရင် Application ထဲကနေ ဘယ် Class ကို သုံးမယ်ဆိုတာ သိသာအောင် Qualifier တွေကို ရေးသားပေးဖို့ လိုအပ်ပါတယ်။


@Default & @Any Qualifier


အထက်ပါ အနေအထာ:မျို:မှာ Injection Point မှာ ဘယ် Bean ကို Inject လုပ်ရမယ်ဆိုတာ သိသာအောင် ရေ:ထာ:ဖို့ လိုအပ်ပါတယ်။ Inject လုပ်လို့ရတဲ့ Bean တွေက ၂ ခုထဲသာဆိုရင်တော့ Built In ပါဝင်တဲ့ @Default နဲ့ @Any Annotation တို့ကို အသုံ:ပြုနိုင်ပါတယ်။

ExamModel Class ကို Default အနေနဲ့ သုံ:ချင်ရင် Class Definition ရဲ့ရှေ့မှာ @Default Annotation ကို ရေ:သာ:ပါမယ်။ ပြီ:မှ FileModel မှာတော့ @Any ကို ရေ:သာ:ပါမယ်။

ExamModel.java
@Default
public class ExamModel implements Model {
    // business methods
}

FileModel.java
@Any
public class FileModel implements Model {
    // business methods
}
Injection Point နေရာမှာတော့ အသုံ:ပြုလိုတဲ့ Type အလိုက် @Default နဲ့ @Any တို့ကို ရေ:သာ:ရုံပါပဲ။ နမူနာထဲမှာတော့ Default ကို သုံ:စေလိုတဲ့အတွက် @Default လို့ရေ:သာ:ထာ:ပါတယ်။

ExamRegistration.java
public class ExamRegistration {
    
    @Inject
    @Default
    private Model model;

    // business methods
}
Default နဲ့ Any ကို သုံ:မယ်ဆိုရင် Inject လုပ်လို့ရတဲ့ Bean က ၂ ခုထဲဆိုရင် အစဉ်ပြေပေမဲ့ ၂ ခုထက်မကရှိရင် ဘယ် Any ကို သုံ:မလဲဆိုတာ မသိတော့ပဲ နေပါမယ်။ အဲ့ဒီလိုအခါမျို:မှာ အသုံ:ပြုနိုင်တာက @Named Qualifier နဲ့ မိမိကိုယ်တိုင် ရေ:သာ:အသုံ:ပြုနိုင်တဲ့ Custom Qualifier တို့ပဲ ဖြစ်ပါတယ်။


@Named Qualifier

Injection Point မှာ Inject လုပ်လို့ရတဲ့ Type တွေ တစ်ခုထက်မက ရှိနေရင် နောက်တနည်းရေးလို့ရတာက @Named Annotation ပဲ ဖြစ်ပါတယ်။ Inject လုပ်မည့် Class တွေမှာ @Named Annotation ကို ရေးထားပြီး Injection Point မှာ အသုံးလိုတဲ့ Name ကို ရေးလိုက်ရုံပါပဲ။

ExamModel.java
@Named
public class ExamModel implements Model {
    // business methods
}

FileModel.java
@Named
public class FileModel implements Model {
    // business methods
}

@Named Annotation ဟာ Bean တွေကို နာမည်ပေးခွဲခြားနိုင်တဲ့ Annotation ဖြစ်ပါတယ်။ Default အတိုင်းဆိုပါက class name ရဲ့ အစစာလုံးကို lower case နဲ့ စပြီး ပေးမှာ ဖြစ်ပါတယ်။ အထက်ပါနမူနာ အရ FileModel ဆိုရင် name ဟာ fileModel ဖြစ်ပါလိမ့်မယ်။ တကယ်လို့ Default Name အတိုင်း မသုံးလိုဘူးဆိုရင် @Named Annotation ရဲ့ value attribute တန်ဖိုးကို ပြောင်းပြီး အသုံးပြုနိုင်ပါတယ်။ @Named(“file”) ဆိုရင် အမည်ဟာ file ဖြစ်သွားပါမယ်။

Injection Point နေရာမှာတော့ @Named နဲ့ @Inject ကို သုံးပြီး Inject လုပ်နိုင်မှာ ဖြစ်ပါတယ်။ Injection Point မှာရှိတဲ့ Instance Name ဟာ Inject လုပ်မည့် name နဲ့ အတူတူဆိုရင်တော့ ဒီအတိုင်းကို Inject လုပ်နိုင်မှာ ဖြစ်တယ်။

ExamRegister.java
public class ExamRegistration {
    
    @Named
    @Inject
    private Model examModel;

    // business methods
}

တကယ်လို့ Instance Name နဲ့ Inject လုပ်မည့် Bean ရဲ့ name ဟာ မတူဘူးဆိုရင်တော့ Injection Point မှာ ရေးထားတဲ့ @Name ရဲ့ value မှာ အသုံးပြုလိုတဲ့ Bean ရဲ့ name ကို ရေးသားပေးရမှာ ဖြစ်ပါတယ်။

ExamModel.java
@Named("exams")
public class ExamModel implements Model {
    // business methods
}

ExamRegister.java
public class ExamRegistration {
    
    @Named("exams")
    @Inject
    private Model model;

    // business methods
}

@Named Annotation ကို အသုံးပြုပြီး Bean တွေကို ခွဲခြားသတ်မှတ်နိုင်တာမှန်ပေမဲ့ name တွေကို String တွေနဲ့ သတ်မှတ်နေရတဲ့အတွက် ရေးထားတာမှားသွားတာတို့ ဖြစ်နိုင်ပါတယ်။ Type Safe ဖြစ်တယ်လို့ မဆိုနိုင်ပါဘူး။

Type Safe ဖြစ်စေချင်ရင်တော့ Custom Qualifier တွေကိုရေးသားအသုံးပြုနိုင်မှာ ဖြစ်ပါတယ်။


Custom Qualifiers

Java EE specifications တွေ အတော်များများဟာ Annotation တွေကို အသုံးပြုပြီး သူတို့ရဲ့ Service တွေကို အလွယ်တကူရေးသားနိုင်အောင် ဆောင်ရွက်ထားပါတယ်။ JPA မှာ ဆိုရင် Bean တစ်ခုကို Entity Object တွေ အဖြစ်အသုံးပြုနိုင်အောင် @Entity တို့ EntityManager ကို Inject လုပ်နိုင်အောင် @PersistenceContext တို့ EntityManagerFactory အတွက်ဆိုရင် @PersistenceUnit တို့ကို ပြင်ဆင်ထားပါတယ်။ EJB မှာဆိုရင်လဲ @Stateless, @Statefull, @Singleton တို့ @EJB တို့ကို အလွယ်တကူ အသုံးပြုနိုင်အောင် ပြင်ဆင်ထားလေ့ရှိပါတယ်။

CDI နဲ့ Beans Validation တွေမှာလဲ မိမိတို့ အသုံးပြုနိုင်အောင် ပြင်ဆင်ထားတာတွေရှိပြီး လိုအပ်လာရင် မိမိကိုယ်ပိုင် Annotation တွေကိုလဲ ရေးသား အသုံးပြုနိုင်မှာ ဖြစ်ပါတယ်။

Qualifier ဆိုတာကတော့ မိမိကိုယ်တိုင်ရေးသားရမည့် Annotation တစ်မျိုးဖြစ်ပါတယ်။ ဘယ်လိုနေရာမှာသုံးလဲ ဆိုတော့ @Named တွေလိုပဲ Injection Point မှာ Inject လုပ်လို့ရတဲ့ Bean တွေ တစ်ခုထက်မက ရှိနေတဲ့အခါမှာ ခွဲခြားပေးဖို့ အသုံးပြုပါတယ်။ ရှေ့မှာ ဖေါ်ပြခဲ့သလိုပဲ @Named ကို သုံးရင် Bean တွေကို String တွေနဲ့ သတ်မှတ် နေတဲ့အတွက် စာလုံးပေါင်းမှားတာတို့ဖြစ်နိုင်ပါတယ်။ ဒါပေမဲ့ Qualifier ဟာ Annotation ဖြစ်တဲ့ အတွက် Type Safe ဖြစ်စွာနဲ့ ခွဲခြားပေးနိုင်ပါမယ်။

Qualifier ကို အသုံးပြုမယ်ဆိုရင်တော့ အရင်ဆုံး အသုံးပြုမည့် Qualifier Annotation ကို ရေးသားရပါမယ်။ ပြီးမှ Inject လုပ်မည့် Class မှာကော Injection Point မှာပါ Qualifier Annotation ကို ရေးသားပေးရမှာပါ။
@Qualifier ဆိုတာ Annotation ရေးတဲ့နေရာမှာ အသုံးပြုရတဲ့ Meta Annotation တစ်ခုပါ။ Qualifier တစ်ခုရေးမယ်ဆိုရင် @Qualifier ကို တပ်ဆင် ရေးသားထားရပါမယ်။ ဒါမှ မိမိရေးသားထားတဲ့ Annotation ဟာ Qualifier တစ်ခုဖြစ်ကြောင်း Container ကို ပြောပြနိုင်မှာ ဖြစ်ပါတယ်။

File.java
@Qualifier
@Target({TYPE, METHOD, PARAMETER, FIELD})
@Retention(RUNTIME)
public @inerface File {
 
}

@Target နဲ့ @Retention တို့ကတော့ Annotation ရေးပြီဆိုရင် မဖြစ်မနေရေးသားရမည့် Meta Annotation တွေ ဖြစ်ပါတယ်။ @Target ဆိုတာကတော့ ရေးထားတဲ့ Annotation ကို ဘယ်နေရာမှာ ရေးလို့ရလဲ ဆိုတာကို သတ်မှတ်ပေးတာပါ။ @Retention ကတော့ ရေးထားတဲ့ Annotation ကို ဘယ်မှာ သုံးမလဲ ဆိုတာကို ဖေါ်ပြပေးမှာပါ။ Qualifier တွေကို Runtime မှာ Container ကနေ အသုံးပြုမှာ ဖြစ်တဲ့အတွက် RUNTIME လို့ ရေးပေးထားရခြင်းဖြစ်ပါတယ်။

FileModel.java
@File
public class FileModel implements Model {
    // business methods
}

ပြီးရင်တော့ ရေးသားထားတဲ့ @File Annotation ကို Qualifier အနေနဲ့ Inject လုပ်မည့် Bean ကို ရေးသားပေးရပါမယ်။ အလားတူပဲ Injection Point နေရာမှာလဲ @File Annotation ကို ရေးသားထားမယ်ဆိုင် Container ကနေ FileModel Bean ကို Injection Point နေရာမှာ Inject လုပ်ပေးနိုင်မှာ ဖြစ်ပါတယ်။

FileRegister.java
public class ExamRegistration {
    
    @File
    @Inject
    private Model model;

    // business methods
}

Qualifier Annotation တွေကို တစ်ခုထက်မက ရေးသား အသုံးပြုနိုင်ပါတယ်။

XmlFileModel.java
@File
@XMLFile
public class XmlFileModel implements Model {
    // business methods
}

FileRegister.java
public class ExamRegistration {
    
    @File
    @XMLFile
    @Inject
    private Model model;

    // business methods
}

အထက်ပါ အတိုင်း Bean တစ်ခုမှာ Qualifier တွေကို တစ်ခုထက်မက ရေးသားနိုင်ပါတယ်။ နမူနာထဲမှာတော့ XmlFileModel Bean ရဲ့ ရှေ့မှာ @File နဲ့ @XMLFile ဆိုပြီး Qualifier နှစ်ခုကို ရေးသားထားပါတယ်။ အဲ့ဒီ Bean ကို Inject လုပ်ချင်ရင်တော့ Injection Point မှာလဲ @File နဲ့ @XMLFile တို့ကို @Inject နဲ့ တွဲပြီး ရေးသားရမှာ ဖြစ်ပါတယ်။


Qualifier with Parameters

တကယ်လို့ Injection Point မှာ Inject လုပ်နိုင်တဲ့ Bean တွေ ၁၀ခုလောက်ရှိနေပြီဆိုရင် ဘယ်လိုလုပ်မလဲ။ Bean တစ်ခုအတွက် Qualifier တစ်ခုရေးနေရရင် မလွယ်ဘူး။ ပိုပြီး ရှုပ်ထွေးသွားနိုင်ပါတယ်။ အဲ့ဒီလို အခါမျိုးဆိုရင်တော့ Qualifier ကို တစ်ခုထဲရေးပြီး Qualifier ထဲကနေ လုပ်အပ်သလို Parameter တွေကို ယူပြီး သုံးသွားနိုင်ပါတယ်။

Qualifier Annotation တွေကို Enum နဲ့ တွဲပြီးအသုံးပြုမယ်ဆိုရင် Type Safe မဖြစ်မှာ စိတ်ပူစရာမလိုပါဘူး။
ModelType.java
@Qualifier
@Target({TYPE, METHOD, PARAMETER, FIELD})
@Retention(RUNTIME)
public @inerface ModelType {

    public enum Type {File, XMLFile, RemoteFile, SessionCache}

    Type value();
}
အထက်ပါအတိုင်း ModelType Qualifier ထဲမှာ Type ဆိုတဲ့ enum ကို ရေးသားထားပါမယ်။ ပြီးရင် Type ကို Attribute အနေနဲ့ ပြန်ယူနိုင်မှာ ဖြစ်ပါတယ်။ method name ကို value လို့ပေးထားတဲ့အတွက် Annotation ကို ရေးသားတဲ့နေရာမှာ attribute name ကို မရေးပဲနေမယ်ဆိုလဲ ရပါမယ်။

အသုံးပြုလိုတဲ့ အမျိုးစားရှိသလောက် enum ထဲ ထည့်ရေးထားမယ်ဆိုရင် Qualifier တစ်ခုထဲနဲ အားလုံးမှာ အသုံးပြုနိုင်မှာ ဖြစ်ပါတယ်။
XmlFileModel.java
@ModelType(XMLFile)
public class XmlFileModel implements Model {
    // business methods
}
ExamRegister.java
public class ExamRegistration {
    
    @Inject
    @ModelType(XMLFile)
    private Model model;

    // business methods
}
အထက်ပါအတိုင်း XmlFileModel ကို @ModelType ကို သုံးပြီး Parameter အနေနဲ့ XMLFile enum တန်ဖိုးကို သတ်မှတ်ရေးသားပြီး၊ Injection Point မှာလဲ အလားတူရေးသားပြီး Inject လုပ်နိုင်မှာ ဖြစ်ပါတယ်။


@Alternatives

Java EE ဆိုတာက Enterprise Application ကို ရေးသားဖို့အတွက်ပါ။ Enterprise Application ဆိုတာ အတော်လေးကို ကြီးမားလေ့ရှိပါတယ်။ Application တစ်ခုကို နှစ်နဲ့ချီ လူဦးရေ ရာနဲ့ချီပြီး ရေးသားရတဲ့ Application တွေလဲ ရှိပါတယ်။ နည်းပညာတစ်ခုထဲမဟုတ်ပဲ Business Domain တွေရဲ့ Knowledge တွေလဲ အများကြီး လိုအပ်ပါတယ်။ ရေးသားလိုတဲ့ Business က ဘယ်လိုရှိတယ် ဆိုတာကို နားမလည်ပဲ ရေးလို့ရမှာ မဟုတ်ပါဘူး။

ဆိုလိုတာက တစ်ယောက်ထဲရေးရတဲ့ Application မျိုးမဟုတ်ပါဘူး။ အဲ့ဒီလို လူပေါင်းများစွာ Team တွေနဲ့ အလုပ်လုပ်တဲ့အခါမှာ ကိုယ်ရေးနေတဲ့ Module ကို အခြား Team တွေကလဲ အသုံးပြုနေသလို ကိုယ်ကလဲ အခြား Team တွေ ရေးထားတဲ့ Module တွေကို သုံးနေမှာပါပဲ။ ပြီးတော့ Development လုပ်တဲ့နေရာမှာ Team အားလုံးက ရေးနေကြမှာပဲ။

အဲ့ဒီလို အနေအထားမျိုးမှာ Module တစ်ခုမပြီးသေးလို့ အခြား Module တွေကို ရေးလို့မရဘူးဆိုရင် အတော်လေးကို ပြဿနာတက်မှာပဲ။ ပုံမှန်အားဖြင့်တော့ Moak Class တွေကို အစားထိုးပြီး ရေးကြ ပြီးရင် Test လုပ်ကြ လုပ်နေလေ့ရှိပါတယ်။

ဒီလိုနေရာမျိုးမှာ အသုံးဝင်တာကတော့ CDI ရဲ့ @Alternatives Qualifier Annotation ပဲ​ဖြစ်ပါတယ်။ @Alternatives ဟာ CDI ရဲ့ Built In Annotation တစ်ခုပါပဲ။ အဲ့ဒီအတွက် အသုံးပြုလိုတဲ့ Moak Clas မှာ ဒီအတိုင်း ရေးသားလို့ရပါတယ်။ @Alternatives ကို အသုံးပြုမယ်ဆိုရင် တစ်ခုသတိထားရမှာက CDI ရဲ့ Deployment Descriptor ဖြစ်တဲ့ beans.xml ထဲမှာ သွားရောက်ရေးသားထားဖို့လိုတဲ့ အချက်ပါပဲ။

လက်တွေ့နမူနာ တစ်ခုကို ရေးသားကြည့်ပါမယ်။ ကျွန်တော်တို့က PayRoleService ဆိုတဲ့ Class ကို ရေးနေပါတယ်။ အဲ့ဒီအထဲမှာ Holiday တွေကို သိအောင် CalendarService ကို ယူသုံးနေတယ်။ ဒါပေမဲ့ အခြား Team တစ်ခုကနေ CalendarService ကို ရေးနေတာ မပြီးသေးဘူး။ အဲ့ဒီလို အနေအထားမျိုးမှာ ကျွန်တော်တို့က CalendarServiceMoak ဆိုတဲ့ Class ကို ရေးပြီး ကိုယ်ရေးနေတဲ့ Module ကို ရေးနေ စမ်းနေလို့ရပါတယ်။
CalendarService.java
@Local
public interface CalendarService {
    List<Date> getHoliday(Date startOfMonth);
}
ဒါကတော့ ကျွန်တော်တို့အသုံးပြုလိုတဲ့ Interface ပါ။ အဲ့ဒီ Implementation ကို အခြား Team တစ်ခုက ရေးနေတာ မပြီးသေးပါဘူး။ ကျွန်တော်တို့ ရေးရမည့် Module ကတော့ အောက်က အတိုင်းပါ။ Inject တွေ သုံးပြီး ရေးလို့ရတာ မှန်ပေမဲ့ Implementation Class မပြီးမချင်း Test လုပ်လို့မရဘူးဖြစ်နေပါမယ်။
PayRolService.java
@Stateless
public class PayRolService implements PayRolServiceLocal {
    
    @Inject
    private CalendarService calendarService;

    // business logic methods
}
အဲ့ဒီလို အနေအထားမျိုးမှာ ကျွန်တော်တို့တွေ Alternatives ကို သုံးပြီး Test လုပ်နေလို့ရပါတယ်။ Alternative ကို သုံးဖို့ဆိုရင် Alternatives အနေနဲ့ အသုံးပြုလိုတဲ့ Class မှာ Alternatives Annotation ကို ရေးသားရပါမယ်။
CalendarServiceMoak.java
@Alternatives
@Stateless
public class CalendarServiceMoak implements CalendarService {
    
    public List<Date> getHolidays(Date startOfMonth) {

        List<Date> result = new ArrayList<>();

        // adding test data

        return result;
    }
}
ပြီးရင် beans.xml မှာ Alternative အနေနဲ့ အသုံးပြုမည့် Class ကို သွားရေးထားရပါမယ်။
beans.xml
<?xml version="1.0"?>
<beans bean-discovery-mode="all" version="1.1"
 xmlns="http://xmlns.jcp.org/xml/ns/javaee"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee 
  http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd">

 <alternatives>
  <class>com.jdc.payrol.alter.CalendarServiceMoak</class>
 </alternatives>

</beans>
အထက်ပါ​အတိုင်း ရေးသားထားမယ်ဆိုရင် PayRolService ကို အသုံးပြုတိုင်းမှာ Alternative အနေနဲ့ ရေးသားထားတဲ့ CalendarServiceMoak ကို အသုံးပြုသွားမှာ ဖြစ်ပါတယ်။


Conclusion


ဒီ Post ထဲမှာ Injection Point တစ်ခုမှာ Inject လုပ်နိုင်တဲ့ Beans တွေ တစ်ခုထက်မကရှိခဲ့ရင် Qualifier ကို သုံးပြီး ဘယ်လို ရေးသားရမယ်ဆိုတာကို အဓိကထားဖေါ်ပြခဲ့ပါတယ်။ Qualifier တွေထဲမှာမှ Built In ပါဝင်တဲ့ @Default, @Any နဲ့ @Named Qualifier တွေ အပြင် Customer Qualifier တွေကို ဘယ်လိုရေးရမယ်ဆိုတာကိုလဲ ဖေါ်ပြခဲ့ပါတယ်။ တဖန် @Alternatives ရဲ့အသုံးပြုပုံကိုလဲ ဖေါ်ပြထားပါတယ်။

ဆက်လက်ပြီး CDI Bean တွေရဲ့ Lifecycle နဲ့ Callbacks တွေရဲ့ အသုံးပြုပုံတို့ CDI Bean မဟုတ်တဲ့ Beans တွေကို Inject လုပ်ချင်ရင် ဘယ်လိုရေးရမယ်ဆိုတာကို နောက် Blog ဖြင့် ဖေါ်ပြပါဦးမယ်။

ဆက်ပါဦးမယ်
မင်းလွင်

No comments:

Post a Comment