Deploy jBPM-3.2.3 di JBoss-4.2.2.GA

November 10th, 2009

Pendahuluan

Dua minggu ini saya dapet tugas untuk explore XForm dan jBPM. XForm sudah saya bahas sedikit-sedikit di postingan saya terdahulu, tapi belum ada tutorialnya, hehehe, nunggu benar-benar sudah bikin sesuatu baru deh nanti saya buatkan tutorialnya.

jBPM adalah framework dari JBoss untuk memanage Bussiness Process Management. BPM sendiri merupakan alat untuk mendokumentasikan flow aliran data dalam aplikasi. Tipikal aplikasi yang mempunyai BPM kompleks adalah proses Purchase Order dan settlement. Misalnya di sistem supply chain management perusahaan retail seperti Giant atau Hypermart, proses PO sangat panjang. Dimulai dari ERP perusahaan akan membuat PO, kemudian PO “somehow” harus sampe ke inbox supplier, kemudian supplier akan mengirim barang bersamaan dengan despatch advice, kemudian dilanjutkan dengan settlement jumlah barang yang dikirim dan harganya hingga disepakati invoice oleh kedua belah pihak. Kenapa perlu settlement? karena ternyata nggak semua barang yang dikirim supplier diterima, mungkin karena rusak atau nggak sesuai standard, harga juga masih perlu ada settlement mungkin karena ada perubahan atau karena ada kondisi tertentu di kontrak yang menyebabkan harga yang sudah disepakati berubah.

BPM digunakan dalam proses seperti diatas untuk mengatur aliran flow data (dokumen) dan action apa yang bisa dikenakan terhadap data yang sedang aktif. Misalnya di data (dokumen) despatch advice bisa ditolak atau diterima, sedangkan dokumen invoice tidak bisa diapa2kan karena sudah final. Nah semua action dan flow data ini diatur dalam BPM engine.

Agar tidak penasaran, saya kasih sedikit screenshoot dari jBPM console :

Gambar diatas memperlihatkan flow diagram dari sebuah Process didalam jBPM. Lebih jauh tentang konseptual jBPM bisa baca buku dari packt jBPM practical guide

Persiapan

Sebelum bisa menjalankan jBPM console, ada beberapa file yang harus didownload, antara lain:
1. JBoss 4.2.2.GA.zip
2. Jbpm-jpdl-3.2.3.zip
3. Mysql
4. MySQL connector (JDBC Driver)
5. Java JDK
Link mysql download silahkan dicari di mysql.com atau cukup install mysql untuk OS yang digunakan. Java yang digunakan adalah JDK versi 1.4 keatas, saya sendiri menggunakan versi 1.6.0_12 dan berjalan dengan lancar.

Setelah proses download selesai, install mysql dan Java JDK. Kemudian extract jboss 4.2.2.GA dan jbpm-jpdl-3.2.3 ke folder yang diinginkan.

Konfigurasi

1. Instalasi dan persiapan mysql.
Setelah mysql berhasil diinstall, buat sebuah database baru untuk menyimpan data jbpm. Setelah itu buat user yang diberi akses ke database baru tersebut. Jangan dibiasakan menggunakan user root dalam konfigurasi aplikasi. Gunakan satu user khusus yang diberi akses hanya ke database yang digunakan aplikasi.

Login ke mysql dan jalankan perintah ini :

mysql> create database jbpmdb;
mysql> grant all on jbpmdb.* to jbpm@localhost identified by 'jbpm';

2. Create table jBPM
jBPM memerlukan table-table dalam database untuk menyimpan semua data yang diperlukanya. Setelah extract file jbpm-jpdl-3.2.3.zip, buka folder db dan cari file jbpm.jpdl.mysql.sql. Di dalam file tersebut terdapat DDL untuk membuat schema, nah anehnya ternyata DDL tersebut tidak benar secara sintaks, harus dilakukan manipulasi agar DDLnya bisa dijalankan di mysql. Pertama hapus semua statement alter table dan drop table di bagian atas kira-kira ada 122 baris. Kemudian untuk sisanya, tambahkan ; di bagian belakang setiap barisnya. Yang terakhir adalah ganti statement type=InnoDB menjadi engine=InnoDB. hfff, sepertinya pada waktu dibuat versi 3.2.3 ini mysql masih versi < 5, jadi sintaksnya banyak yang error kalau digunakan di mysql-5.
Setelah selesai diedit, simpan file jbpm.jpdl.mysql.sql dan flush ke mysql. Caranya, pertama buka console (command prompt) dan cd ke folder di mana file jbpm.jpdl.mysql.sql berada, lalu jalankan perintah di bawah ini dari console:

$ mysql -u root -p jbpmdb < jbpm.jpdl.mysql.sql

Pastikan proses flush berhasil tanpa ada pesan error. Setelah proses flush table ke database selesai, coba test hasilnya dengan login ke mysql dan periksa jumlah tablenya :

$ mysql -u jbpm -p jbpmdb
mysql> show tables;

Gunakan password jbpm dan pastikan ada 33 table di dalam database jbpmdb.

3. Menyiapkan Mysql Data Souce dan Drivernya
Sebelum bisa mendeploy jbpm di jboss, persiapan berikutnya adalah membuat Data Source agar jbpm bisa akses database dan table yang sudah dipersiapkan tadi.
Periksa folder hasil extract file jbpm-jpdl-3.2.3.zip dan cari file jbpm-console.war, nah file war inilah yang nantinya akan dideploy di dalam jboss. File war sebenarnya adalah file zip biasa, coba gunakan winzip atau zip tools lain untuk mengextract file war tersebut.
Setelah berhasil mengextract file war, coba cari file jboss-web.xml, disana terdapat konfigurasi nama datasource yang digunakan jbpm, kira-kira seperti ini konfigurasinya:

<resource-ref>
  <res-ref-name>jdbc/JbpmDataSource</res-ref-name>
  <jndi-name>java:JbpmDS</jndi-name>
</resource-ref>

Terlihat dalam konfigurasi diatas, jbpm memerlukan JbpmDS yang dibind ke JNDI name. Nah di JBoss 4.2.2 harus disiapkan datasource yang dimaksud. Caranya tidak susah, karena jboss sudah menyediakan contoh bagaimana membuat mysql-ds, file contohnya ada di ${jboss_home}/docs/examples/jca/mysql-ds.xml
Buka file mysql-ds.xml kemudian edit isinya menjadi :

    <jndi-name>JbpmDS</jndi-name>
    <connection-url>jdbc:mysql://mysql-hostname:3306/jbpmdb</connection-url>
    <driver-class>com.mysql.jdbc.Driver</driver-class>
    <user-name>jbpm</user-name>
    <password>jbpm</password>

Copy mysql-ds.xml yang sudah diedit ke folder ${JBOSS_HOME}/server/default. Konfigurasi diatas memerlukan driver mysql, oleh karena itu copy mysql jdbc driver yang sudah didownload dalam langkah persiapan ke folder ${JBOSS_HOME}/server/default/lib

4. Menyiapkan Security untuk login
jbpm-console menggunakan JAAS sebagai security mechanismnya. Form-based authentication merupakan teknik autentikasinya. Tutorial kali ini hanya membahas konfigurasi JAAS yang digunakan oleh jbpm-console, mungkin penjelasan saya malah bikin bingung, ya mohon maklum, ini cuma tutorial howto saja, kalau mau lebih jelas lagi tentang mekanisme security JAAS di JBoss silahkan membaca buku JBoss in Action.
Konfigurasi security JAAS untuk jboss tersebar di beberapa file :
- jboss-web.xml
Letaknya di dalam WEB-INF dari jbpm-console.war, konfigurasinya :

<security-domain>java:/jaas/jbpm</security-domain>

Di dalam konfigurasi ini disebutkan bahwa jbpm-console menggunakan mekanisme security JAASdengan domain jbpm. Nah untuk sekarang cukup ingat-ingat bahwa nama domain securitynya adalah jbpm, nama ini nanti digunakan di file konfigurasi login-config.xml.

- web.xml
Letaknya di WEB-INF dalam file jbpm-console.war dan kita tidak perlu melakukan modifikasi apa-apa.

    <security-role>
        <role-name>admin</role-name>
    </security-role>
    <security-role>
        <role-name>user</role-name>
    </security-role>
    <security-constraint>
        <web-resource-collection>
            <web-resource-name>Secure Area</web-resource-name>
            <url-pattern>/sa/*</url-pattern>
            <http-method>GET</http-method>
            <http-method>POST</http-method>
        </web-resource-collection>
        <auth-constraint>
            <role-name>user</role-name>
        </auth-constraint>
    </security-constraint>
    <login-config>
        <auth-method>FORM</auth-method>
        <form-login-config>
            <form-login-page>/ua/login-example.jsf</form-login-page>
            <form-error-page>/ua/login-example.jsf?error=true</form-error-page>
        </form-login-config>
    </login-config>

Konfigurasi dalam Web.xml ini menandakan url apa saja yang diamankan, dalam hal ini adalah /sa/*, kemudian role user bisa masuk ke dalam url /sa/*, sedangkan user dengan role lain bisa masuk juga, konfigurasinya ada di server.xml, nanti kita bahas di bagian selanjutnya.

- server.xml
Letaknya ada di ${JBOSS_HOME}/server/default/deploy/jboss-web.deployer. Konfugurasi yang ada dalam file ini ada konfigurasi yang mengijinkan user lain dalam role selain user role bisa mengakses /sa/*

<Realm className="org.jboss.web.tomcat.security.JBossSecurityMgrRealm"
            certificatePrincipal="org.jboss.security.auth.certs.SubjectDNMapping"
            allRolesMode="authOnly"
            />

Lihat atribute allRolesMode, disana isinya “authOnly”, konfigurasi ini mengjinkan user lain dengan role selain role user bisa login, sedangkan user lain yang tidak masuk ke role apapun tidak bisa login. Selain mode authOnly, JBoss menyediakan 2 mode lainya: strict dan strictAuthOnly. Penjelasan lebih lanjut silahkan cari di buku JBoss In Action tentang 2 mode lainya ini.
Selain itu di dalam server.xml terdapat konfigurasi agar JAAS menggunakan https (SSL/TSL) dalam aplikasi, silahkan lihat bagian <Connecto port=”8443″ yang di-remark secara default. Connector inilah yang digunakan sebagai provider Https di JBoss.

- login-config.xml
Letaknya di ${JBOSS_HOME}/server/default/conf. Di bagian web.xml ada konfigurasi untuk mendefinisikan security domain, dalam hal ini namanya jbpm. Security domain jbpm harus didefinisikan dalam login-config.xml. Konfigurasi security domain ini hanya digunakan kalau data user dan rolenya disimpan di dalam file properties. Sedangkan kalau menggunakan user dan role yang ada dalam database, tidak perlu mendefinisikan security domain di login-config.xml. Pertama saya akan menjelaskan konfigurasi kalau menggunakan file properties dan nanti saya akan menjelaskan konfigurasi untuk menggunakan data user dan role yang ada di dalam database.
Tambahkan konfigurasi ini ke dalam login-config.xml :

<application-policy name="jbpm">
      <authentication>
        <login-module code="org.jboss.security.auth.spi.UsersRolesLoginModule"
          flag="required">
          <module-option name="usersProperties">props/jbpm-users.properties</module-option>
          <module-option name="rolesProperties">props/jbpm-roles.properties</module-option>
        </login-module>
      </authentication>
    </application-policy>

Konfigurasi ini menyaratkan adanya file jbpm-users.properties dan jbpm-roles.properties di dalam folder props yang sejajar dengan file login-config.xml. Isi dari file jbpm-users.properties adalah :

user=user
manager=manager
admin=admin
shipper=shipper

Konfigurasi diatas adalah pasangan antara username dan passwordnya.
File jbpm-roles.properties :

manager = user,manager,admin
user = user
shipper = user
admin = user,admin

Konfigurasi diatas adalah pasangan antara username di sebelah kiri dan role yang dipunyai user tersebut.

Kalau ingin user dan role mengambil entry dari database, konfigurasi di login-config.xml diubah. Entrinya menjadi :

    <application-policy name = "jbpm">
       <authentication>
         <login-module code="org.jboss.security.auth.spi.DatabaseServerLoginModule"
                       flag="required">
           <module-option name="dsJndiName">java:/JbpmDS</module-option>
           <module-option name="principalsQuery">
             SELECT PASSWORD_ FROM JBPM_ID_USER WHERE NAME_=?
           </module-option>
           <module-option name="rolesQuery">
             SELECT g.NAME_ ,'Roles'
             FROM JBPM_ID_USER u,
                  JBPM_ID_MEMBERSHIP m,
                  JBPM_ID_GROUP g
             WHERE g.TYPE_='security-role'
               AND m.GROUP_ = g.ID_
               AND m.USER_ = u.ID_
               AND u.NAME_=?
           </module-option>
         </login-module>
       </authentication>
    </application-policy>

Kemudian perlu insert user, role dan membership di dalam table, nah berikut ini statement untuk insertnya, masukkan ke database jbpmdb yang sudah dibuat di proses sebelumnya:

insert  into `JBPM_ID_GROUP`(`ID_`,`CLASS_`,`NAME_`,`TYPE_`,`PARENT_`) values
(1,'G','sales','organisation',NULL),
(2,'G','manager','security-role',NULL),
(3,'G','hr','organisation',NULL),
(4,'G','admin','security-role',NULL),
(5,'G','user','security-role',NULL);
insert  into `JBPM_ID_USER`(`ID_`,`CLASS_`,`NAME_`,`EMAIL_`,`PASSWORD_`) values
(1,'U','user','user@sample.domain','user'),
(2,'U','manager','manager@sample.domain','manager'),
(3,'U','admin','admin@sample.domain','admin'),
(4,'U','shipper','shipper@sample.domain','shipper');
insert  into `JBPM_ID_MEMBERSHIP`(`ID_`,`CLASS_`,`NAME_`,`ROLE_`,`USER_`,`GROUP_`) values
(1,'M',NULL,NULL,2,2),
(2,'M',NULL,NULL,2,4),
(3,'M',NULL,NULL,3,4),
(4,'M',NULL,NULL,2,5),
(5,'M',NULL,NULL,1,5),
(6,'M',NULL,NULL,4,3),
(7,'M',NULL,NULL,4,5),
(8,'M',NULL,NULL,3,5),
(9,'M',NULL,NULL,3,3),
(10,'M',NULL,NULL,2,3),
(11,'M',NULL,'boss',2,1),
(12,'M',NULL,NULL,1,1);

5. Deploy
Setelah selesai konfigurasi yang sangat melelahkan, langkah berikutnya adalah mendeploy jbpm-console.war di JBoss. Cukup copy file jbpm-console.war dan letakkan di folder ${JBOSS_HOME}/server/default/deploy
Jalankan file ${JBOSS_HOME}/bin/run untuk linux atau ${JBOSS_HOME}/bin/run.bat untuk windows.
Buka browser dan akses url http://localhost:8080/jbpm-console/ kemudian masukkan user dan password yang ada di jbpm-users.properties atau yang ada di table JBPM_ID_USER (tergantung mana datastore yang digunakan, properties atau table di database).

hff, lanjut lagi kapan2 dengan topik development JBPM dengan JBoss tools di Eclipse, what the, eclipse? yak yak, untuk sementara babay dulu netbeans :(

XForm

November 4th, 2009

Hari ini saya mendapatkan sesuatu yang tak terduga dan membuat saya syok: XForm

Ternyata ada teknologi untuk meggantikan HTML Form yang dianggap sudah kuno dan uzur. Salah satu implementasi dari XForm yang cukup sukses adalah Orbeon Form. Saya masih teringat bagaimana sulitnya mengimplementasikan Validation Framework di Struts jaman dulu. Atau membuat form dengan JSF atau dengan ExtJS. Nah ternyata ada framework yang khusus menangani masalah Form ini.

Karena masih sedikit syok, tutorialnya dilanjutkan nanti saja ;)

Membaca XML Bagian 3 : Xanot

October 3rd, 2009

Artikel ini adalah bagian ketiga dari seri membaca XML.

Kali ini kita akan menggunakan Xanot untuk membaca XML, jika 2 metode sebelumnya masih melakukan parsing manual, metode xanot sedikit berbeda. Xanot adalah singkatan dari XML Annotation, dibuat oleh salah satu member jug Ferdinan Neman.

Xanot menggunakan metode yang sering disebut dengan XML binding. Untuk menggunakan Xanot, Class-class yang diperlukan disiapkan terlebih dahulu kemudian diberi anotasi untuk memnentukan bagaimana cara melakukan binding antara XML tag dengan properti class-class tersebut.

Kelemahan xanot, dan semua XML binding, adalah semua tag XML harus ada representasi class-nya. Misalkan untuk melakukan mapping XML yang sama dengan 2 artikel sebelumnya :

<?xml version="1.0" encoding="UTF-8"?>
<Users>
	<User>
		<Name>badu</Name>
		<TglLahir>09-09-2009</TglLahir>
	</User>
	<User>
		<Name><![CDATA[fulan]]></Name>
		<TglLahir>13-01-1976</TglLahir>
	</User>
</Users>

Harus dibuat 2 class, yaitu class Users dan class User. Berikut ini kedua class tersebut beserta mapping Annotationya :

@TagInstance(path="Users")
public class Users {
    private List users = new ArrayList();
    private Users usersParent;
    @XmlCollectionProperty(xmltag=”User”,element=User.class,methodName=”addUser”)
    public List getUsers() {
        return users;
    }
    public void setUsers(List users) {
        this.users = users;
    }
    public void addUser(User user){
        users.add(user);
    }
    @ParentReference
    public Users getUsersParent() {
        return usersParent;
    }
    public void setUsersParent(Users usersParent) {
        this.usersParent = usersParent;
    }
}

Class User menjadi seperti ini :

@TagInstance(path="User")
public class User {
    private String name;
    private Date tglLahir;
    @XmlSingleProperty(name="Name")
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @XmlSingleProperty(name="TglLahir",dateFormat="dd-MM-yyyy")
    public Date getTglLahir() {
        return tglLahir;
    }
    public void setTglLahir(Date tglLahir) {
        this.tglLahir = tglLahir;
    }
}

Terlihat ada 4 buah Annotation yaitu :

@TagInstance
Digunakan untuk menandai classs tersebut dibanding dengan Tag XML tertentu. @TagInstance ini diperlukan agar class Users bisa digunakan sebagai root.

@XmlSingleProperty
Digunakan untuk mapping single value atau single class. Contoh diatas digunakan untuk memapping tag Name dengan property name dari class User. Property tglLahir juga dimap dengan tag TglLahir, karena tglLahir adalah Date maka value dari dateFormat harus diisi.

@XmlCollectionProperty
Digunakan untuk memapping Collection value, ada beberapa setting harus diisi:

  • xmltag diisi dengan XML tag yang akan dimapping dengan class User. Tetapi nilai dari xmltag ini dioverride dengan nilai yang ada di @TagInstance yang ada di atas deklarasi class User. nilai xmltag akan digunakan jika misalnya class User tidak mempunyai @TagInstance
  • element diisi dengan User.class
  • method name diisi dengan method yang disiapkan untuk menambahkan satu nilai User ke dalam Users, dalam hal ini saya kasih nama addUser

@ParentReference
Digunakan untuk menandai bahwa properti usersParent adalah parent dari class User

Setelah menyiapkan class dan mappingnya, sekarang waktunya Xanot in action. Lihat kodenya, dan anda akan terkejut dengan begitu sederhananya Xanot in Action.


import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.parsers.ParserConfigurationException;
import org.xanot.Xanot;
import org.xanot.XanotException;
import org.xml.sax.SAXException;

/**
 *
 * @author ifnu
 */
public class XmlReaderXanot {

    public static void main(String[] args) {
        try {

            Xanot xanot = new Xanot();
            xanot.setRoot(Users.class);
            Users users = (Users) xanot.parse(new File("users.xml"));
            for(User user : users.getUsers()){
                System.out.println(user.getName());
            }
        } catch (SAXException ex) {
            Logger.getLogger(XmlReaderXanot.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(XmlReaderXanot.class.getName()).log(Level.SEVERE, null, ex);
        } catch (ParserConfigurationException ex) {
            Logger.getLogger(XmlReaderXanot.class.getName()).log(Level.SEVERE, null, ex);
        } catch (XanotException ex) {
            Logger.getLogger(XmlReaderXanot.class.getName()).log(Level.SEVERE, null, ex);
        }

    }

}

Satu hal yang belum dicapai xanot adalah bagaimana memvalidasi document xml menggunakan XML schema. Ferdinand bilang akan diimplementasikan di versi 2, tetapi sepertinya belum sempat terealisasi beliaunya sudah terlanjur meninggalkan dunia perkodingan java ;)

Di artikel berikutnya, saya akan membahas tentang bagaimana menggunakan XMLBeans untuk membaca dokumen berdasarkan XML Schema. Selain itu XML Bean dapat mengenerate class mapping (Users dan User) tanpa harus kita membuat sendiri, cool eh?

Membaca XML Bagian 2 : Apache Commons Configuration

October 3rd, 2009

Artikel saya sebelumnya menerangkan bagaimana membaca XML menggunakan SAX. Metode tersebut adalah yang paling sederhana dan paling manual. Kode yang diperlukan cukup banyak, kalau XML dokumenya cukup panjang dan rumit maka kode-nya juga makin rumit pula.

Cara kedua ini sangat sederhana, namun Apache Commons Configuration ini performancenya masih kalah jauh dibanding SAX. Jadi sebaiknya, sesuai namanya, apache commons configuration digunakan untuk membaca file konfigurasi XML saja. Atau bisa juga digunakan untuk membaca XML data tetapi load-nya kecil. Kalau anda membutuhkan parser XML yang performance critical sebaiknya jangan menggunakan Apache Commons Configuration.
Commons Configuration sebenarnya bisa membaca file :

  • Properties files
  • XML documents
  • Windows INI files
  • Property list files (plist)
  • JNDI
  • JDBC Datasource
  • System properties
  • Applet parameters
  • Servlet parameters

Nah artikel kali ini sedikit membahas trik menggunakan commnons configuration untuk membaca file XML. Seperti sudah saya tekankan dari awal, commons configuration hanya tepat digunakan untuk membaca konfigurasi xml atau data XML yang kecil-kecil saja. Performa apache configuration bukanlah issue penting karena bukan merupakan tujuan utama.

Commons configuration untuk XML mempunyai cara yang sangat mudah untuk mendapatkan nilai dari XML. Misalnya untuk mendapatnkan String “badu” kita gunakan kode :

  configuration.getString("User(0).Name"));

Perhatikan bahwa Tag Users yang merupakan Root dari XML dokumen diatas tidak disertakan dalam String path diatas.
Sebelum mulai coding, sebaiknya download dulu library Apache Commons Configuration dari websitenya : http://commons.apache.org/configuration/

Jar yang diperlukan adalah

Misalnya XML yang akan diparsing sama seperti di artikel sebelumnya :

<?xml version="1.0" encoding="UTF-8"?>
<Users>
	<User>
		<Name>badu</Name>
		<TglLahir>09-09-2009</TglLahir>
	</User>
	<User>
		<Name><![CDATA[fulan]]></Name>
		<TglLahir>13-01-1976</TglLahir>
	</User>
</Users>

Kode untuk mendapatkan List of user adalah seperti berikut ini :

import java.io.File;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.XMLConfiguration;

/**
 *
 * @author ifnu
 */
public class XmlReaderApacheCommonsConfig {
    public static void main(String[] args) {
        try {
            XMLConfiguration configuration = new XMLConfiguration(new File("users.xml"));
            List users = new ArrayList();
            //mendapatkan banyaknya tag User di dalam tag Users
            int userLength = ((Collection)configuration.configurationsAt(”User”)).size();
            for(int i = 0;i<userLength;i++){
                User user = new User();
                users.add(user);
                user.setName(configuration.getString(”User(” + i +”).Name”));
                System.out.println(user.getName());
                String tglLahir = configuration.getString(”User(” + i +”).TglLahir”);
                try {
                    user.setTglLahir(new SimpleDateFormat(”dd-MM-yyyy”).parse(tglLahir));
                } catch (ParseException ex) {
                    Logger.getLogger(XmlReaderApacheCommonsConfig.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        } catch (ConfigurationException ex) {
            Logger.getLogger(XmlReaderApacheCommonsConfig.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}

Membaca XML bagian 1: SAX

October 3rd, 2009

Hari ini dapet pertanyaan dari Yoga gimana caranya baca XML dan mapping ke dalam Object.

Nah beberapa waktu yang lalu saya sudah menyampaikan training tentang IO di java, kebetulan salah satu materinya adalah membaca XML. Jadi sekalian saja saya share kodenya di sini.

XML yang akan kita baca adalah seperti ini :

<?xml version="1.0" encoding="UTF-8"?>
<Users>
	<User>
		<Name>badu</Name>
		<TglLahir>09-09-2009</TglLahir>
	</User>
	<User>
		<Name><![CDATA[fulan]]></Name>
		<TglLahir>13-01-1976</TglLahir>
	</User>
</Users>

Salah satu metode untuk membaca XML diatas adalah dengan SAX (Simple API for XML). Metode ini adalah yang paling cepat dan sederhana. Untuk membaca XML yang sedikit rumit sebaiknya hindari menggunakan SAX karena proses parsingnya masih sangat manual dan akan menyebabkan kodenya cukup rumit untuk dipahami.

Ada library lain yang bisa digunakan untuk membaca XML dengan lebih mudah, misalnya menggunakan XML-binding library seperti XMLBean. Dimana kita cukup menyediakan XML Schema dari dokumenya, maka Object mappingnya akan digenerate sekaligus parsernya.

Selain itu ada juga XML library yang dapat digunakan untuk memapping dari XML ke object, antara lain Xanot (yand didevelop oleh Ferdinand) dan Apache Commons Configuration. Lain kali saya akan coba bikin tutorial mengenai ginama menggunakan libary Xanot dan Apache Commond Configuration.

Langkah pertama dalam menggunakan SAX adalah dengan membuat class yang extends DefaultHandler. Untuk membaca XML diatas, begini classnya :

public class UserXmlHandler extends DefaultHandler{

        private User currentUser;
        private Set users = new HashSet();
        private String currentString;

        public Set getUsers() {
            return users;
        }

        @Override
        public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
            if(qName.equals(”User”)){
                currentUser = new User();
            }
        }

        @Override
        public void characters(char[] ch, int start, int length) throws SAXException {
            currentString = new String(ch,start,length);
        }

        @Override
        public void endElement(String uri, String localName, String qName) throws SAXException {
            if(qName.equals(”User”)){
                users.add(currentUser);
            } else if(qName.equals(”Name”)){
                currentUser.setName(currentString);
            } else if(qName.equals(”TglLahir”)){
                try {
                    currentUser.setTglLahir(new SimpleDateFormat(”dd-MM-yyy”).parse(currentString));
                } catch (ParseException ex) {
                    Logger.getLogger(XmlReader.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        }
    }

Ada 3 method penting yang harus dioverride dari class DefaultHandler, yaitu startTag, endTag dan characters.

Method startTag akan dipanggil oleh SAX parser ketika ada start tag dari xml ditemukan. Dalam class UserXmlHandler diatas, kodenya sangat sederhana, yaitu kalau ketemu start tag User ( if(qName.equals(”User”)) ) maka buat user baru ( currentUser = new User());

Method character digunakan untuk menyimpan isi dari tag. Misalnya dalam tag <Name>badu</Name> isi dari character adalah “badu”.

Method endTag dipanggil SAX parser ketika end tag dari xml ditemukan. Di end tag ini biasanya ada kode-kode untuk memasukkan string yang diperoleh dari method character ke tag yang bersesuaian.

Setelah siap kodenya, kita coba untuk membaca Xml, nah kode untuk membaca XML kira-kira seperti ini :

import java.io.File;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

/**
 *
 * @author ifnu ifnubima@gmail.com;ifnu@artivisi.com
 * blog : ifnu.artivisi.com
 * company : artivisi intermedia
 * site : artivisi.com
 */
public class XmlReader {

    public static void main(String[] args) {

        UserXmlHandler handler = new UserXmlHandler();

        SAXParser parser;
        try {
            parser = SAXParserFactory.newInstance().newSAXParser();
            parser.parse(new File("users.xml"), handler);

            Set users = handler.getUsers();
            for(User u : users){
                System.out.println(u.getName());
            }

        } catch (IOException ex) {
            Logger.getLogger(XmlReader.class.getName()).log(Level.SEVERE, null, ex);
        } catch (ParserConfigurationException ex) {
            Logger.getLogger(XmlReader.class.getName()).log(Level.SEVERE, null, ex);
        } catch (SAXException ex) {
            Logger.getLogger(XmlReader.class.getName()).log(Level.SEVERE, null, ex);
        }

    }
}

file users.xml berada di dalam user.dir yaitu folder yang sama dimana aplikasi java-nya dijalankan. kalau di NetBeans project ya di dalam folder projectnya, sejajar dengan file build.xml

Selamat mencoba

Deploy Aplikasi Desktop sebagai Java Webstart/JNLP

August 31st, 2009

Aplikasi yang sedang saya develop adalah aplikasi desktop menggunakan java, arsitekturnya menggunakan 3 thier dimana ada satu bagian aplikasi yang menjadi server-side. Untuk memudahkan development, kita putuskan untuk mengadopsi teknologi Spring.

Aplikasi didevelop seperti biasa mengguanakan stack Spring-Hibernate-Swing, kemudian bagian service-dao-Entity hibernate dideploy di sisi server, kemudian interface service diexpose sebagai Servlet menggunakan protokol HTTP Invoker dari Spring. Lebih lanjut mengenai arsitektur ini bisa baca di sini dan di sana

Client hanya membutuhkan interface service, Entity (diabuse jadi DTO) dan UI code (Swing). Sampai di sini saya sudah cukup puas, karena aplikasi jadi cukup fleksible untuk ditambah-tambahkan feature, misalnya scheduler di sisi server.

Kemudian ada masalah cukup pelik di sisi deployment. Aplikasi client harus di install di setiap workstation, kemudian kalau ada update, ya diupdatelah semua workstation, bete abis!!

Pemecahanya ternyata gampang kok, gunakan JNLP/Webstart. Aplikasi client diletakkan di sisi server, kemudian install web server agar bisa dibrowse file jnlp dan jar dari client. Kemudian akses file jnlp dari browser client, misalnya :

http://192.168.0.1/app/pos.jnlp

Nah sekarang, aplikasi cleint tidak perlu diinstall lagi, cukup akses file jnlp maka semua jar yang diperlukan akan didownload. Jika ada update di sisi server, maka jar yang baru akan didownload ke client, praktis bukan?.

Selain dari browser, file jnlp bisa dieksekusi langsung dari console, perintahnya seperti ini :

javaws http://192.168.0.1/app/pos.jnlp

Isi file JNLP tidak terlalu rumit, yang perlu didefinisikan adalah url dimana file jnlp dan jar diletakkan. Kemudian daftar dari semua jar yang dibutuhkan dan main class aplikasi. Contoh file JNLP kira-kira seperti ini :

<?xml version="1.0" encoding="UTF-8"?>
<jnlp spec="1.0+" codebase="http://192.168.0.1/app/" href="pos.jnlp">
    <information>
        <title>Termos Application</title>
        <vendor>Artivisi Intermedia</vendor>
        <homepage href="http://www.artivisi.com"/>
        <description>Aplikasi POS</description>
        <description kind="short">Aplikasi POS</description>
        <offline-allowed/>
    </information>
    <resources>
        <j2se version="1.6+"/>
        <jar eager="true" href="pos.jar" main="true"/>
        <jar href="jasypt-1.5.jar"/>
        <jar href="jcalendar-1.3.2.jar"/>
        <jar href="joda-time-1.5.2.jar"/><
        <jar href="log4j-1.2.12.jar"/>
        <jar href="commons-collections-3.1.jar"/>
        <jar href="commons-codec-1.3.jar"/>
        <jar href="commons-lang-2.1.jar"/
        <jar href="commons-beanutils-1.7.jar"/>
        <jar href="commons-digester-1.7.jar"/>
        <jar href="commons-logging-1.0.2.jar"/>
        <jar href="itext-1.3.1.jar"/>
        <jar href="jasperreports-3.1.4.jar"/>
        <jar href="poi-3.0.1-FINAL-20070705.jar"/>
        <jar href="ejb3-persistence.jar"/>
        <jar href="AbsoluteLayout.jar"/>
        <jar href="termos-config.jar"/>
        <jar href="spring.jar"/>
        <jar href="MartinSwingUtil.jar"/>
        <jar href="hibernate3.jar"/>
    </resources>
    <application-desc main-class="com.artivisi.pos.client.Main">
    </application-desc>
</jnlp>

Cara paling mudah mengekspose file jar dan jnlp agar bisa diakses dari client lewat protokol http adalah dengan menginstall apache webserver. Di ubuntu caranya sangat gampang tinggal jalankan perintah :

sudo apt-get install apache2

Kalau di windows bisa menggunakan package instalasi AMP seperti XAMPP. Setelah instalasi apache2 selesai, kemudian buat folder /var/www/app dan letakkan file jar dan jnlp yang dibutuhkan di folder tersebut. Nah sekarang siap sudah jnlp diakses dari client.

Kadang kala kita gagal dalam proses deployment jnlp, ada sintaks yang salah atau daftar jar yang tidak lengkap, sehingga perlu proses download ulang jnlp dari client. Nah, rumitnya, javaws melakukan penyimpanan semua jnlp di cachenya, jadi kadang-kadang saya nggak mengerti kenapa kok jnlp udah diupdate di server tapi dari cleint kok masih tetep yang lama?

Ada langkah tambahan untuk menghapus cache ini, jalankan perintah ini di workstation client :

javaws -viewer

Akan tampil dialog java console seperti ini :

kemudian pilih tab network dan tekan tombol “view cache files…”, akan tampil daftar aplikasi java webstart yang pernah dieksekusi, kemudian pilih aplikasi yang akan dihapus cachenya :

Selamat mencoba

Generate database schema dari Hibernate Entity

August 31st, 2009

Project di artivisi sebagian besar menggunakan Hibernate sebagai back-end ke database. Sering sekali project ini merupakan project baru yang belum ada struktur table-nya, sehingga dari Entity hibernate perlu digenerate tablenya.

Cara generate table dari entity hibernate tidaklah sulit, hanya perlu tambahin setting berikut ini di hibernate.cfg.xml :

<property name="hbm2ddl.auto">create</property>

Cara ini cukup efektif, namun tidak efisien, karena setiap kali mau generate table harus pasang setting diatas kemudian dihapus atau diganti valuenya menjadi “none” kalau tidak ingin tablenya didrop-create lagi.

Cara kedua adalah dengan kode java tanpa harus menambahkan konfigurasi seperti diatas. Nah kira-kira begini kodenya :

public class GenerateDDL {
    public static void main(String[] args) throws SQLException {
        AbstractApplicationContext ctx =
                new ClassPathXmlApplicationContext("classpath:applicationContext.xml");

        DataSource dataSource = (DataSource) ctx.getBean("dataSource");

        Configuration cfg = new AnnotationConfiguration().configure("hibernate.cfg.xml")
                .setProperty("hibernate.dialect",
                "org.hibernate.dialect.MySQLInnoDBDialect");

        Connection conn = dataSource.getConnection();
        new SchemaExport(cfg, conn).create(true, true);

        conn.close();
        ctx.registerShutdownHook();
    }
}

Kode diatas menyaratkan hibernate digunakan bersamaan dengan Spring. Kalau menggunakan Hibernate murni tanpa Spring, kodenya seperti ini :

public class GenerateDDL {

    public static void main(String[] args) throws SQLException, ClassNotFoundException {

        Configuration cfg = new AnnotationConfiguration().configure("hibernate.cfg.xml")
                .setProperty("hibernate.dialect",
                "org.hibernate.dialect.MySQLInnoDBDialect");

        Class.forName("com.mysql.jdbc.Driver");

        MysqlDataSource dataSource = new MysqlDataSource();
        dataSource.setUser("root");
        dataSource.setPassword("");
        dataSource.setURL("mysql:jdbc://localhost:3306/latihan");

        Connection conn = dataSource.getConnection();
        new SchemaExport(cfg, conn).create(true, true);

        conn.close();

    }
}

Nah dengan adanya class ini, ketika akan men-generate struktur table, cukup jalankan class GenerateDDL saja, tidak perlu mengotak-atik konfigurasi Hibernate.cfg.xml.

Deploy aplikasi Spring-Hiberante-Spring HTTP Invoker di Tomcat6 Ubuntu 9.04

August 31st, 2009

Hari ini dapet kerjaan tambahan gara-gara servernya berlagak mencla-mencle, setiap kali direstart koneksi internetnya ngadat.

Server lama diinstall dengan Ubuntu 08.04 dan instalasi tomcat menggunakan package tomcat (sudo apt-get install tomcat), hasilnya cukup bagus. Instalasi tomcatnya diletakkan di /opt/apache-tomcat, kemudian ada script di /etc/init.d/tomcat untuk start,stop dan restart tomcat. Selain itu tomcat juga akan distart ketika server restart. Jadi jauh lebih menyenangkan menginstall tomcat dari apt dibanding install sendiri tomcat.

Server baru diinstall dengan ubuntu 09.04, pas dicari-cari eh ternyata tidak ada package tomcat. Adanya tomcat6 dan tomcat5.5, oke kita coba pake tomcat6, eng-ing-eng, ternyata konfigurasinya berubah total.

konfigurasi tomcat : /etc/tomcat6/
deploy aplikasi : /var/lib/tomcat6/webapps
log : /var/log/tomcat6/

Selain itu di package tomcat6 ini tidak ada lagi log/catalina.out adanya log/Catalina-dd-MM-yyyy.log untuk melihat log dari tomcat dan log/Localhost-dd-MM-yyyy.log untuk melihat log dari semua aplikasi yang dideploy di dalam tomcat6.

Oke, instalasi beres sekarang kita deploy aplikasinya, and again, saya dikejutkan dengan error yang saaangat tidak lazim :

java.lang.reflect.ReflectPermission suppressAccessChecks

Tebakan awal ini karena Security Manager tomcat di package tomcat6 dinyalakan, setelah googling sebentar, wah ternyata tebakan benar. Ok, sekarang gimana cara pemecahanya? Ada 2 cara : setting security policy tomcat6 atau matikan saja security manager tomcat6.

Mari kita coba cara pertama, karena saya belum pernah menggunakan application server dengan security manager dinyalakan, berarti harus lihat dulu manual dari tomcat :

http://tomcat.apache.org/tomcat-6.0-doc/security-manager-howto.html

Nah untuk menerapkan security manager policy di tomcat6, buka folder /etc/tomcat6/policy.d/ nanti akan terlihat beberapa file untuk menerapka security policy.

01system.policy
02debian.policy
03catalina.policy
04webapps.policy
50local.policy

File yang diperlukan untuk setting security policy adalah 04webapps.policy, file ini digunakan untuk mengatur security policy bagi semua aplikasi yang jalan di tomcat6. Aplikasi saya perlu beberapa security policy agar bisa jalan, salah satunya adalah error yang ada diatas. Error tersebut terjadi karena saya menggunakan injection seperti ini di spring :

@Repository
public class UserDao {
    @Autowired private SessionFactory sessionFactory
    //more code here
}

Kode diatas meminta spring untuk menginject object sessionFactory yang mempunyai access modifier private, makanya muncul exception :

java.lang.reflect.ReflectPermission suppressAccessChecks

Untuk mengatasi hal ini, buka file 04webapps.policy, nanti akan terlihat isinya kira-kira seperti ini :

.........
    // Precompiled JSPs need access to this system property.
    permission java.util.PropertyPermission "org.apache.jasper.runtime.BodyContentImpl.LIMIT_BUFFER", "read";

    // java.io.tmpdir should be usable as a temporary file directory
    permission java.util.PropertyPermission "java.io.tmpdir", "read";
    permission java.io.FilePermission "${java.io.tmpdir}/-", "read,write,delete";

Ubah menjadi :

.........
    // Precompiled JSPs need access to this system property.
    permission java.util.PropertyPermission "org.apache.jasper.runtime.BodyContentImpl.LIMIT_BUFFER", "read";

    // java.io.tmpdir should be usable as a temporary file directory
    permission java.util.PropertyPermission "java.io.tmpdir", "read";
    permission java.io.FilePermission "${java.io.tmpdir}/-", "read,write,delete";

    //setting untuk spring
    permission java.lang.reflect.ReflectPermission "suppressAccessChecks";

Saya restart tomcatnya dan saya jalankan aplikasinya, laaah masih ada error lagi, error dengan tipe yang sama, setalah bergulat ke sana sini akhirnya saya menyerah oprek2 security managernya… >:)

Cara kedua lebih gampang lagi, matikan saja Security Managernya!!! :)) :))

Buka file /etc/init.d/tomcat6, kemudian cari baris dengan isi seperti ini :

TOMCAT6_SECURITY=yes

ubah menjadi

TOMCAT6_SECURITY=no

die you bitch (Security Manager) … diee :))

Restart tomcatnya, deploy aplikasinya, horeeee… pulang deh…

Bug aneh ubuntu 09.04 di Acer Aspire 5500

August 26th, 2009

Minggu kemaren nganterin Martinus beli laptop kereen, Dell inpiron 1320, trus laptop saya yang lama, aspire 5500, dipake Adi. Karena martinus pakenya OpenSolaris, jadi diinstall lagi pake Ubuntu 09.04, setelah selesai install ternyata ada masalah serius dengan kernel 2.6.28-11, touchpad tidak jalan dan keyboard kadang jalan kadang gak jalan.

Setelah browsing ke sana ke mari, ternyata ada link menarik nih :

https://bugs.launchpad.net/ubuntu/+bug/365382

menang ada bug di kernel 2.6.28-11 yang gagal mendetek touchpad acer secara benar. Pada waktu restart keyboard akan berjalan lancar, ketika salah satu tombol di touchpad ditekan, keyboard akan berhenti bekerja sama sekali. Nah anehnya, kalau kita tekan tombol di touchpad 1 atau 2 kali, keyboard akan berjalan normal lagi.

Sampe sekarang belum ada workaround masalah ini, ya caranya seperti saya terangkan di atas atau ganti ke kernel 2.6.27-11 (ubuntu 08.10).

Update 2 September 2009 :

Adi berhasil mencari pemecahan masalah ini di sini

Membaca file excell (xls) dari kode Java

August 20th, 2009

Sebuah aplikasi bisnis pasti akan membutuhkan modul untuk import atau export data dari satu format ke format yang lainya. Salah satu yang paling sering dilakukan adalah export-import data dari file Excell.

Tutorial kali ini, saya akan menunjukkan kode sederhana untuk membaca file excell, di tutorial serupa nanti saya juga akan menunjukkan cara membaca dokumen OpenOffice Spreadsheet (ods), menulis file Excell dan menulis file ods, soo… stay tuned ya! ;)

Apache POI adalah project yang ditujukan untuk membaca format file Excell, sejauh pengetahuan saya, Apache POI belum bisa membaca xlsx, sedangkan semua file xls dari jaman windows 95 bisa dibaca dengan baik.

Contoh kasus, saya mempunyai data motor yang berisi: atpm, nama motor, tanggal stnk, tahun motor dan harga jual. Dari data excell ini akan dimapping ke dalam List.

public class DataMotor {
    private String atpm;
    private String motor;
    private String tahun;
    private Date tanggalStnk;
    private BigDecimal hargaJual;
    //getter setter di sini
}

Berikut ini contoh datanya :

yamaha

Jupiter mx

2009

21-09-2010

15000000

suzuki

Spin

2009

21-08-2010

13000000

Kode untuk membaca file diatas :

public class Main {
    public static void main(String[] args) throws IOException {
        JFileChooser chooser = new JFileChooser();
        chooser.setCurrentDirectory(new File(System.getProperty("user.home")));
        chooser.setFileFilter(new FileNameExtensionFilter("Excell File (*.xls)", "xls","XLS"));
        chooser.showOpenDialog(null);
        if(chooser.getSelectedFile()!=null){
            File selectedFile = chooser.getSelectedFile();
            HSSFWorkbook workbook = new HSSFWorkbook(new FileInputStream(selectedFile));
            HSSFSheet sheet = workbook.getSheetAt(0);
            HSSFRow row = null;
            List dataMotors = new ArrayList();
            for(int i =0;i<=sheet.getLastRowNum();i++){
                row = sheet.getRow(i);
                DataMotor dataMotor = new DataMotor();
                dataMotor.setAtpm(row.getCell((short)0).getRichStringCellValue().getString());
                dataMotor.setMotor(row.getCell((short)1).getRichStringCellValue().getString());
                dataMotor.setTahun(String.valueOf((int)row.getCell((short)2).getNumericCellValue()));
                dataMotor.setTanggalStnk(row.getCell((short)4).getDateCellValue());
                dataMotor.setHargaJual(new BigDecimal(row.getCell((short)5).getNumericCellValue()));
                dataMotors.add(dataMotor);
            }
            //kode untuk mengolah List di sini
        }
    }
}

Cukup sederhana kan?

ada beberapa trik yang harus dilihat dalam kode ini :

HSSFSheet sheet = workbook.getSheetAt(0);

Kode diatas mengasumsikan bahwa file xls hanya terdiri dari satu sheet saja, kalau multiple sheet perlu sedikit modifikasi dengan melakukan iterasi untuk setiap sheet.

Perhatikan kode di bawah ini :

for(int i =0;i<=sheet.getLastRowNum();i++)

Ternyata nilai dari sheet.getLastRowNum() tidak sama dengan banyaknya baris (rowNum), nilainya ternyata adalah index terakhir dari row yang ada isinya, jadi for diatas menggunakan operator <= bukanya <

Download semua kode dan contoh file xls-nya di sini