You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 6 Current »

 

ในบทเรียนนี้เราจะทำตาม guideline of developing a plugin เพื่อพัฒนาปลั๊กอิน JDBC Form Store Binder ของเรา โปรดอ้างอิงถึงบทช่วยสอนแรกสุด วิธีการพัฒนา Bean Shell Hash Variable และปลั๊กอินที่เกี่ยวข้องกับ JDBC ต่อไปนี้สำหรับขั้นตอนรายละเอียดเพิ่มเติม

1. ปัญหาคืออะไร?

เพื่อจุดประสงค์ในการรวมเราต้องการจัดเก็บข้อมูลฟอร์มของเราลงในตารางฐานข้อมูลอื่นแทนตารางข้อมูล Joget.

2. ความคิดของคุณในการแก้ปัญหาคืออะไร?

Joget Workflow จัดทำประเภทปลั๊กอินที่เรียกว่า Form Store Binder Pluginเราจะพัฒนาเพื่อรองรับการเชื่อมต่อ JDBC และ query ที่กำหนดเองเพื่อเก็บข้อมูลแบบฟอร์ม

3. อินพุตที่จำเป็นสำหรับปลั๊กอินของคุณ?

เพื่อการพัฒนาปลั๊กอิน JDBC Store binder, เราจะต้องมีการตั้งค่าการเชื่อมต่อ JDBC และ query แบบกำหนดเองเพื่อเก็บข้อมูลฟอร์มตามข้อมูลฟอร์มที่รวบรวม

  1. Datasource: การใช้แหล่งข้อมูลที่กำหนดเองหรือแหล่งข้อมูลเริ่มต้นของ Joget
  2. Custom JDBC Driver: ไดรเวอร์ JDBC สำหรับแหล่งข้อมูลที่กำหนดเอง
  3. Custom JDBC URL: URL การเชื่อมต่อ JDBC สำหรับแหล่งข้อมูลที่กำหนดเอง
  4. Custom JDBC Username: ชื่อผู้ใช้สำหรับแหล่งข้อมูลที่กำหนดเอง
  5. Custom JDBC Password: รหัสผ่านสำหรับแหล่งข้อมูลที่กำหนดเอง
  6. SQL Check Exist Query: The query เพื่อตรวจสอบว่าแบบสอบถามแทรกหรือปรับปรุงควรจะดำเนินการ
  7. SQL Insert Query: The query เพื่อแทรกข้อมูลแบบฟอร์ม
  8. SQL Update Query: The query เพื่อแทรกข้อมูลแบบฟอร์ม
  9. SQL Delete Query: The query เพื่อลบข้อมูลในแบบฟอร์มที่ถูกลบเมื่อใช้เป็น multirow binder

เราจะต้องสนับสนุน syntax ในการเอาข้อมูลแบบฟอร์มไปยัง query "{foreignKey}" สามารถใช้สำหรับการจัดเก็บหลายแถว

เราจะต้องสนับสนุน syntax ในการเอาข้อมูลแบบฟอร์มไปยัง UUID value. ในกรณีนี้เราจะใช้ "{uuid}".

ตัวอย่าง: INSERT INTO app_fd_test VALUES ({id}, {name}, {email}, {phone}, {foreignKey});

4. ผลลัพธ์และผลลัพธ์ที่คาดหวังจากปลั๊กอินของคุณคืออะไร?

ข้อมูลที่ส่งทั้งหมดจะเก็บตามคำสั่ง check / insert / update

5. มีทรัพยากร / API ใด ๆ ที่สามารถนำมาใช้ซ้ำได้?

เราสามารถอ้างถึงการดำเนินการของอื่น ๆ ที่มีอยู่ใน Form Store Binder plugins ได้ แหล่งข้อมูลเริ่มต้นของ Joget สามารถเรียกดูได้ที่ AppUtil.getApplicationContext().getBean("setupDataSource").

6. เตรียม environment ของคุณเพื่อการพัฒนา

เราจำเป็นต้องเตรียมซอร์สโค้ด Joget Workflow ของเราให้พร้อมและสร้างโดยทำตาม this guideline

บทเรียนนี้จัดทำขึ้นด้วย Macbook Pro และ Joget Source Code version 5.0.0. โปรดอ้างอิงถึง แนวทางสำหรับการพัฒนาปลั๊กอิน สำหรับคำสั่งแพลตฟอร์มอื่น ๆ

ให้กล่าวว่าไดเรกทอรีโฟลเดอร์ของเราดังต่อไปนี้

- Home
  - joget
    - plugins
    - jw-community
      -5.0.0

ไดเรกทอรี "ปลั๊กอิน" คือโฟลเดอร์ที่เราจะสร้างและจัดเก็บปลั๊กอินทั้งหมดของเราและไดเรกทอรี "jw-community" เป็นที่เก็บซอร์สโค้ด Joget Workflow

เรียกใช้คำสั่งต่อไปนี้เพื่อสร้างโครงการ maven ในไดเรกทอรี "ปลั๊กอิน"

cd joget/plugins/
~/joget/jw-community/5.0.0/wflow-plugin-archetype/create-plugin.sh org.joget.tutorial jdbc_store_binder 5.0.0

จากนั้น the shell script จะขอให้เราใส่ปลั๊กอินสำหรับรุ่นของคุณและขอให้เรายืนยันก่อนที่จะสร้างโครงการ Maven

Define value for property 'version':  1.0-SNAPSHOT: : 5.0.0
[INFO] Using property: package = org.joget.tutorial
Confirm properties configuration:
groupId: org.joget.tutorial
artifactId: jdbc_store_binder
version: 5.0.0
package: org.joget.tutorial
Y: : y

เราควรได้รับข้อความ "BUILD SUCCESS" ที่ปรากฏในเครื่องของเราและโฟลเดอร์ "jdbc_store_binder" ที่สร้างในโฟลเดอร์ "ปลั๊กอิน"

เปิดโครงการ maven ด้วย IDE ที่คุณโปรดปราน เราแนะนำให้ใช้ NetBeans.  

7. เริ่มโค้ด!

a. Extending the abstract class of a plugin type

สร้างคลาส "JdbcStoreBinder" ภายใต้ "org.joget.tutorial" package. จากนั้นขยายคลาสด้วย org.joget.apps.form.model.FormBinder abstract class.

เพื่อให้มันทำงานเป็น Form Store Binder, เราจะต้องใช้อินเตอร์เฟสของ  org.joget.apps.form.model.FormStoreBinder. จากนั้นเราจำเป็นต้องใช้อินเตอร์เฟส org.joget.apps.form.model.FormStoreElementBinder เพื่อให้ปลั๊กอินนี้แสดงเป็นตัวเลือกใน Select Box และเลือกใช้อินเตอร์เฟสของ org.joget.apps.form.model.FormStoreMultiRowElementBinder เพื่อแสดงรายการภายใต้ Select Box ของ grid element.

โปรดอ้างอิงถึง Form Store Binder Plugin. 

b. Implement all the abstract methods

เช่นเคย เราต้องใช้ abstract methods ทั้งหมด เราจะใช้ AppPluginUtil.getMessage method เพื่อสนับสนุน i18n และใช้ตัวแปรคงที่ MESSAGE_PATH สำหรับ message resource bundle directory.

 

Implementation of all basic abstract methods
package org.joget.tutorial;
 
import org.joget.apps.app.service.AppPluginUtil;
import org.joget.apps.app.service.AppUtil;
import org.joget.apps.form.model.Element;
import org.joget.apps.form.model.FormBinder;
import org.joget.apps.form.model.FormData;
import org.joget.apps.form.model.FormRowSet;
import org.joget.apps.form.model.FormStoreBinder;
import org.joget.apps.form.model.FormStoreElementBinder;
import org.joget.apps.form.model.FormStoreMultiRowElementBinder;
 
public class JdbcStoreBinder extends FormBinder implements FormStoreBinder, FormStoreElementBinder, FormStoreMultiRowElementBinder {
    
    private final static String MESSAGE_PATH = "messages/JdbcStoreBinder";
    
    public String getName() {
        return "JDBC Store Binder";
    }
 
    public String getVersion() {
        return "5.0.0";
    }
    
    public String getClassName() {
        return getClass().getName();
    }
 
    public String getLabel() {
        //support i18n
        return AppPluginUtil.getMessage("org.joget.tutorial.JdbcStoreBinder.pluginLabel", getClassName(), MESSAGE_PATH);
    }
    
    public String getDescription() {
        //support i18n
        return AppPluginUtil.getMessage("org.joget.tutorial.JdbcStoreBinder.pluginDesc", getClassName(), MESSAGE_PATH);
    }
 
    public String getPropertyOptions() {
        return AppUtil.readPluginResource(getClassName(), "/properties/jdbcStoreBinder.json", null, true, MESSAGE_PATH);
    }
 
    public FormRowSet store(Element element, FormRowSet rows, FormData formData) {
        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
    }
}

จากนั้นเราต้องทำ UI สำหรับผู้ใช้ผู้ดูแลระบบเพื่อให้อินพุตสำหรับปลั๊กอินของเรา ใน getPropertyOptions method, เราได้เตรียม ตัวเลือกคุณสมบัติปลั๊กอิน ไว้ที่ไฟล์ "/properties/jdbcStoreBinder.json". ให้เราสร้าง directory "resources/properties" ภายใต้ "jdbc_store_binder/src/main" directory. หลังจากสร้าง directory, ให้สร้างไฟล์ชื่อ "jdbcStoreBinder.json" ในโฟลเดอร์ "properties"

ในไฟล์ตัวเลือกคุณสมบัติเราจะต้องเลือกตัวเลือกดังนี้ โปรดทราบว่าเราสามารถใช้ "@@message.key@@" syntax เพื่อสนับสนุน i18n ในตัวเลือกคุณสมบัติของเรา

[{
    title : '@@form.jdbcStoreBinder.config@@',
    properties : [{
        name : 'jdbcDatasource',
        label : '@@form.jdbcStoreBinder.datasource@@',
        type : 'selectbox',
        options : [{
            value : 'custom',
            label : '@@form.jdbcStoreBinder.customDatasource@@'
        },{
            value : 'default',
            label : '@@form.jdbcStoreBinder.defaultDatasource@@'
        }],
        value : 'default'
    },{
        name : 'jdbcDriver',
        label : '@@form.jdbcStoreBinder.driver@@',
        description : '@@form.jdbcStoreBinder.driver.desc@@',
        type : 'textfield',
        value : 'com.mysql.jdbc.Driver',
        control_field: 'jdbcDatasource',
        control_value: 'custom',
        control_use_regex: 'false',
        required : 'true'
    },{
        name : 'jdbcUrl',
        label : '@@form.jdbcStoreBinder.url@@',
        type : 'textfield',
        value : 'jdbc:mysql://localhost/jwdb?characterEncoding=UTF8',
        control_field: 'jdbcDatasource',
        control_value: 'custom',
        control_use_regex: 'false',
        required : 'true'
    },{
        name : 'jdbcUser',
        label : '@@form.jdbcStoreBinder.username@@',
        type : 'textfield',
        control_field: 'jdbcDatasource',
        control_value: 'custom',
        control_use_regex: 'false',
        value : 'root',
        required : 'true'
    },{
        name : 'jdbcPassword',
        label : '@@form.jdbcStoreBinder.password@@',
        type : 'password',
        control_field: 'jdbcDatasource',
        control_value: 'custom',
        control_use_regex: 'false',
        value : ''
    },{
        name : 'check_sql',
        label : '@@form.jdbcStoreBinder.check_sql@@',
        description : '@@form.jdbcStoreBinder.check_sql.desc@@',
        type : 'codeeditor',
        mode : 'sql',
        required : 'true'
    },{
        name : 'insert_sql',
        label : '@@form.jdbcStoreBinder.insert_sql@@',
        description : '@@form.jdbcStoreBinder.insert_sql.desc@@',
        type : 'codeeditor',
        mode : 'sql',
        required : 'true'
    },{
        name : 'update_sql',
        label : '@@form.jdbcStoreBinder.update_sql@@',
        description : '@@form.jdbcStoreBinder.update_sql.desc@@',
        type : 'codeeditor',
        mode : 'sql',
        required : 'true'
    },{
        name : 'delete_sql',
        label : '@@form.jdbcStoreBinder.delete_sql@@',
        description : '@@form.jdbcStoreBinder.delete_sql.desc@@',
        type : 'codeeditor',
        mode : 'sql',
        required : 'true'
    }],
    buttons : [{
        name : 'testConnection',    
        label : '@@form.jdbcStoreBinder.testConnection@@',
        ajax_url : '[CONTEXT_PATH]/web/json/app[APP_PATH]/plugin/org.joget.tutorial.JdbcStoreBinder/service?action=testConnection',
        fields : ['jdbcDriver', 'jdbcUrl', 'jdbcUser', 'jdbcPassword'],
        control_field: 'jdbcDatasource',
        control_value: 'custom',
        control_use_regex: 'false'
    }]
}]

เช่นเคย JDBC Options Binder, เราจะต้องเพิ่มปุ่มเพื่อทดสอบการเชื่อมต่อของ JDBC. โปรดอ้างอิงถึง วิธีการพัฒนา JDBC Options Binder บน Web Service Plugin.

เมื่อเราทำตัวเลือกคุณสมบัติเพื่อรวบรวมอินพุตและบริการเว็บเพื่อทดสอบการเชื่อมต่อเราสามารถทำงานกับวิธีการหลักของปลั๊กอินซึ่งเป็นวิธีการจัดเก็บ

    public FormRowSet store(Element element, FormRowSet rows, FormData formData) {
        Form parentForm = FormUtil.findRootForm(element);
        String primaryKeyValue = parentForm.getPrimaryKeyValue(formData);
            
        Connection con = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        
        try {
            DataSource ds = createDataSource();
            con = ds.getConnection();
            
            //check for deletion
            FormRowSet originalRowSet = formData.getLoadBinderData(element);
            if (originalRowSet != null && !originalRowSet.isEmpty()) {
                for (FormRow r : originalRowSet) {
                    if (!rows.contains(r)) {
                        String query = getPropertyString("delete_sql");    
                        pstmt = con.prepareStatement(getQuery(query));
                        int i = 1;
                        for (String obj : getParams(query, r, primaryKeyValue)) {
                            pstmt.setObject(i, obj);
                            i++;
                        }
                        pstmt.executeUpdate();
                    }
                }
            }
            
            if (!(rows == null || rows.isEmpty())) {
            
                //run query for each row
                for (FormRow row : rows) {
                    //check to use insert query or update query
                    String checkSql = getPropertyString("check_sql");
                    pstmt = con.prepareStatement(getQuery(checkSql));
                    int i = 1;
                    for (String obj : getParams(checkSql, row, primaryKeyValue)) {
                        pstmt.setObject(i, obj);
                        i++;
                    }
                    String query = getPropertyString("insert_sql");
                    rs = pstmt.executeQuery();
                    //record exist, use update query
                    if (rs.next()) {
                        query = getPropertyString("update_sql");
                    }
                    pstmt = con.prepareStatement(getQuery(query));
                    i = 1;
                    for (String obj : getParams(query, row, primaryKeyValue)) {
                        pstmt.setObject(i, obj);
                        i++;
                    }
                    pstmt.executeUpdate();
                }
            }
        } catch (Exception e) {
            LogUtil.error(getClassName(), e, "");
        } finally {
            try {
                if (rs != null) {
                    rs.close();
                }
                if (pstmt != null) {
                    pstmt.close();
                }
                if (con != null) {
                    con.close();
                }
            } catch (Exception e) {
                LogUtil.error(getClassName(), e, "");
            }
        }
        
        return rows;
    }
    
    /**
     * Used to replaces all syntax like {field_id} to question mark
     * @param query
     * @return 
     */
    protected String getQuery(String query) {
        return query.replaceAll("\\{[a-zA-Z0-9_]+\\}", "?");
    }
    
    /**
     * Used to retrieves the value of variables in query 
     * @param query
     * @param row
     * @return 
     */
    protected Collection<String> getParams(String query, FormRow row, String primaryKey) {
        Collection<String> params = new ArrayList<String>();
        
        Pattern pattern = Pattern.compile("\\{([a-zA-Z0-9_]+)\\}");
        Matcher matcher = pattern.matcher(query);
        
        while (matcher.find()) {
            String key = matcher.group(1);
            
            if (FormUtil.PROPERTY_ID.equals(key)) {
                String value = row.getId();
                if (value == null || value.isEmpty()) {
                    value = UuidGenerator.getInstance().getUuid();
                    row.setId(value);
                }
                params.add(value);
            } else if ("uuid".equals(key)) {
                params.add(UuidGenerator.getInstance().getUuid());
            } else if ("foreignKey".equals(key)) {
                params.add(primaryKey);
            } else {
                String value = row.getProperty(key);
                params.add((value != null)?value:"");
            }
        }
        
        return params;
    }
    
    /**
     * To creates data source based on setting
     * @return
     * @throws Exception 
     */
    protected DataSource createDataSource() throws Exception {
        DataSource ds = null;
        String datasource = getPropertyString("jdbcDatasource");
        if ("default".equals(datasource)) {
            // use current datasource
            ds = (DataSource)AppUtil.getApplicationContext().getBean("setupDataSource");
        } else {
            // use custom datasource
            Properties dsProps = new Properties();
            dsProps.put("driverClassName", getPropertyString("jdbcDriver"));
            dsProps.put("url", getPropertyString("jdbcUrl"));
            dsProps.put("username", getPropertyString("jdbcUser"));
            dsProps.put("password", getPropertyString("jdbcPassword"));
            ds = BasicDataSourceFactory.createDataSource(dsProps);
        }
        return ds;
    }

c. จัดการ dependency libraries ของปลั๊กอินของคุณ

ปลั๊กอินของเราใช้ dbcp, javax.servlet.http.HttpServletRequest และ javax.servlet.http.HttpServletResponse class, ดังนั้นเราต้องเพิ่ม jsp-api และ commons-dbcp library ไปที่ไฟล์ POM ของเรา

<!-- Change plugin specific dependencies here -->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>jsp-api</artifactId>
    <version>2.0</version>
</dependency>
<dependency>
    <groupId>commons-dbcp</groupId>
    <artifactId>commons-dbcp</artifactId>
    <version>1.3</version>
</dependency>
<!-- End change plugin specific dependencies here -->

d. เตรียมปลั๊กอิน internationalization (i18n) ของคุณให้พร้อม

เรากำลังใช้ i18n message key ใน getLabel และ getDescription method. และเรายังใช้ i18n message key ในตัวเลือกคุณสมบัติของเราด้วย. ดังนั้นเราต้องสร้างไฟล์คุณสมบัติ message resource bundle เพื่อปลั๊กอินของเรา

สร้าง directory "resources/messages" ภายใต้ "jdbc_store_binder/src/main" directory. จากนั้นสร้างไฟล์ "JdbcStoreBinder.properties" ในโฟลเดอร์. ในไฟลืคุณสมบัติให้เราเพิ่ม message keys และ label ดังนี้

org.joget.tutorial.JdbcStoreBinder.pluginLabel=JDBC Binder
org.joget.tutorial.JdbcStoreBinder.pluginDesc=Used to store form data using JDBC
form.jdbcStoreBinder.config=Configure JDBC Binder
form.jdbcStoreBinder.datasource=Datasource
form.jdbcStoreBinder.customDatasource=Custom Datasource
form.jdbcStoreBinder.defaultDatasource=Default Datasource
form.jdbcStoreBinder.driver=Custom JDBC Driver
form.jdbcStoreBinder.driver.desc=Eg. com.mysql.jdbc.Driver (MySQL), oracle.jdbc.driver.OracleDriver (Oracle), com.microsoft.sqlserver.jdbc.SQLServerDriver (Microsoft SQL Server)
form.jdbcStoreBinder.url=Custom JDBC URL
form.jdbcStoreBinder.username=Custom JDBC Username
form.jdbcStoreBinder.password=Custom JDBC Password
form.jdbcStoreBinder.check_sql=SQL SELECT Query
form.jdbcStoreBinder.check_sql.desc=Used  to decide an insert or update operation. Use syntax like {field_id} in query to inject submitted form data.
form.jdbcStoreBinder.insert_sql=SQL INSERT Query
form.jdbcStoreBinder.insert_sql.desc=Use syntax like {field_id} in query to inject submitted form data.
form.jdbcStoreBinder.update_sql=SQL UPDATE Query
form.jdbcStoreBinder.update_sql.desc=Use syntax like {field_id} in query to inject submitted form data.
form.jdbcStoreBinder.delete_sql=SQL DELETE Query
form.jdbcStoreBinder.delete_sql.desc=Used to delete deleted form data in Grid element. Use syntax like {id} in query to inject form data primary key.
form.jdbcStoreBinder.testConnection=Test Connection
form.jdbcStoreBinder.connectionOk=Database connected
form.jdbcStoreBinder.connectionFail=Not able to establish connection.

e. ลงทะเบียนปลั๊กอินของคุณที่ Felix Framework

เราจะต้องลงทะเบียนคลาสปลั๊กอินของเราในคลาส Activator (Auto generated in the same class package) เพื่อบอก Felix Framework ว่านี่เป็นปลั๊กอิน

    public void start(BundleContext context) {
        registrationList = new ArrayList<ServiceRegistration>();
        //Register plugin here
        registrationList.add(context.registerService(JdbcStoreBinder.class.getName(), new JdbcStoreBinder(), null));
    } 

f. สร้างและทดสอบ

ให้สร้างปลั๊กอินของเรา เมื่อกระบวนการสร้างเสร็จสิ้นเราจะพบไฟล์ "jdbc_store_binder-5.0.0.jar" ภายใต้ไดเรกทอรี "jdbc_store_binder / target"

จากนั้นให้อัปโหลด jar ปลั๊กอินไปที่ Manage Plugins หลังจากอัปโหลดไฟล์ jar ตรวจสอบอีกครั้งว่าปลั๊กอินถูกอัปโหลดและเปิดใช้งานอย่างถูกต้อง

ให้สร้างแบบฟอร์มเพื่อสร้างและอัปเดตผู้ใช้เป็นตาราง dir_user

จากนั้นกำหนดค่า binder เก็บของฟอร์มด้วย query ต่อไปนี้

Check Select Query
select username from dir_user where username = {id}
Insert Query
insert into dir_user
(id, username, firstName, lastName, email, active)
values
({id}, {id}, {firstName}, {lastName}, {email}, 1)

note: {uuid} สามารถใช้สร้าง unique id

Update Query
update dir_user set firstName = {firstName}, lastName = {lastName},
email = {email}
where username = {id}

 

Delete Query
delete from TABLE_NAME where id = {id}

ตอนนี้ให้ทดสอบเพื่อเพิ่มผู้ใช้

ตรวจสอบผู้ใช้ที่ถูกสร้างขึ้นในตาราง dir_user

ให้อัปเดตระเบียนเดียวกันโดยส่งรหัสในพารามิเตอร์ URL

ตรวจสอบการปรับปรุงผู้ใช้

มันได้ผล! โปรดอย่าลืมทดสอบคุณสมบัติอื่น ๆ ของปลั๊กอินด้วย (big grin)

8. ขั้นต่อไป แชร์หรือขาย

คุณสามารถดาวน์โหลด source code จาก jdbc_store_binder_src.zip

หากต้องการดาวน์โหลด jar ปลั๊กอินที่พร้อมใช้งานโปรดค้นหา http://marketplace.joget.org/.

 

  • No labels