Living life and Make it Better

life, learn, contribute

Endy Muhardin

Software Developer berdomisili di Jabodetabek, berkutat di lingkungan open source, terutama Java dan Linux.

Integrasi aplikasi kantor pusat dan cabang [Bagian 2]

Pada artikel sebelumnya, kita sudah mendiskusikan requirement yang diinginkan. Sekarang kita akan melakukan implementasi menggunakan Spring Integration.

Spring Integration memiliki beberapa abstraksi utama yang perlu kita ketahui agar bisa membuat implementasi, yaitu :

  • Message : ini adalah data yang akan kita kirim, proses, dan terima

  • Channel : ini adalah saluran tempat lewatnya message

  • Endpoint : ini adalah ujung dari channel

  • Transport : mekanisme pengiriman message. Spring Integration mendukung messaging (email,JMS), remoting (Web Service, RMI, HttpInvoker, HTTP), file, dan lainnya (stream)

Ilustrasi hubungan antara message, channel, dan endpoint bisa dilihat di gambar berikut :

Hubungan Message, Channel, dan Endpoint

Message terdiri dari dua bagian utama, yaitu header dan payload (isi message).

Channel

Berdasarkan kemampuan menampung message, channel dibedakan menjadi :

  • Pollable : memiliki buffer untuk menampung message. Dengan adanya kapasitas buffer, pengirim tidak perlu menunggu sampai penerima mendapatkan message, kecuali bila buffernya penuh. Dalam kondisi buffer penuh, pengirim akan menunggu (blocking) sampai ada slot kosong yang bisa digunakan untuk menyimpan message. Penerima dapat memeriksa keberadaan message dalam buffer. Dalam melakukan pemeriksaan, penerima dapat menunggu sampai ada message, atau sampai jangka waktu tertentu (timeout)

  • Subscribable : tidak memiliki buffer, tiap message yang masuk langsung dikirim ke endpoint. Pengirim harus menunggu sampai message diterima.

Berdasarkan perilaku pengiriman message, channel dibedakan menjadi :

  • Point to Point (PTP) : tiap message hanya dikirimkan ke satu penerima saja. Walaupun ada beberapa penerima yang terhubung ke channel PTP, tapi hanya salah satu saja yang akan menerima message

  • Point to Multipoint (PTM) : tiap message akan dikirim ke semua penerima yang terdaftar.

Ada beberapa implementasi channel :

  • Direct Channel : subscribable dan point-to-point. Seluruh proses (kirim, masuk channel, terima) akan dilakukan dalam thread yang sama

  • Queue Channel : pollable dan point to point. Message yang datang duluan akan dikirim duluan juga (FIFO)

  • Priority Channel : mirip dengan Queue Channel, tapi tidak menggunakan FIFO, melainkan melihat field Priority di Message Header untuk menentukan mana message yang harus dikirim terlebih dulu

  • Rendezvous Channel : mirip dengan Direct Channel, tapi pengirim dan penerima menggunakan thread yang berbeda. Pengirim akan menunggu sampai message diterima atau sampai timeout. Atau sebaliknya, penerima akan menunggu sampai ada message yang masuk. Biasanya digunakan untuk mengimplementasikan request-reply.

  • ThreadLocal Channel : pollable dan point-to-point. Message disimpan di thread local, sehingga tidak disharing dengan thread yang berbeda.

  • Publish Subscribe Channel : subscribable dan point-to-multipoint. Setiap penerima yang terdaftar akan menerima message. Tidak bisa menyimpan message, sehingga kalau kita butuh buffer, kita harus merangkainya dengan channel jenis lain yang memiliki buffer.

Endpoint

Endpoint digunakan untuk menghubungkan channel. Pemrosesan message dilakukan dalam endpoint. Ada beberapa jenis endpoint, yaitu:

  • Service Activator : kalau kita ingin memanggil business method kita, gunakan endpoint ini.

  • Channel Adapter : ini adalah penghubung channel dengan transport, baik untuk menerima message (inbound) ataupun mengirim message (outbound).

  • Transformer : digunakan untuk mengubah format message

  • Filter : digunakan untuk memutuskan apakah suatu message akan diterima atau dibuang

  • Router : digunakan untuk memilih channel mana yang akan menerima message

  • Splitter : digunakan untuk memecah message menjadi beberapa bagian untuk diproses secara independen

  • Aggregator : digunakan untuk menggabungkan beberapa message menjadi satu message untuk diteruskan ke channel berikutnya

  • Resequencer : digunakan untuk menyusun urutan message

Implementasi Kantor Pusat

Sekarang, setelah kita memahami berbagai istilah dalam Spring Integration, kita bisa merancang implementasi dari requirement kita. Berikut adalah aliran message di sisi kantor pusat. Kantor pusat mengirim beberapa data produk baru, dan akan dikonversi menjadi JSON.

Flow konversi data menjadi JSON

Setelah menjadi JSON, selanjutnya kita bisa kirim melalui berbagai transport yang disediakan. Untuk tahap development, kita kirim saja melalui file ke folder /tmp, supaya mudah didebug dan tidak butuh internet.

Flow message JSON ke shared folder

Selanjutnya, setelah transport melalui file sudah dipastikan benar, baik format data, isi data, maupun rangkaian filternya, kita bisa mengganti channel adapter untuk mengirim ke GMail.

Flow message JSON ke Email

Kita harus membuat beberapa file sebagai berikut :

  • Produk.java : domain model dari data yang akan kita kirim

  • JsonTransformer.java : kode program untuk mengubah object Produk menjadi JSON

  • JsonTransformerTest.java : kode program untuk mengetes ProdukTransformer

  • PusatSender.java : kode program untuk mengaktifkan Spring Integration dan mengirim data produk

  • Gateway.java : interface untuk mengirim message. Kita harus membuat ini agar tidak ada dependensi ke library Spring Integration

  • pusat-integration-ctx.xml : konfigurasi Spring Integration

Berikut kode programnya.

Produk.java

public class Produk implements Serializable {
    private Integer id;
    private String kode;
    private String nama;
    
    // getter dan setter generate dengan IDE
}

JsonTransformer.java

public class JsonTransformer {
    public Produk jsonToProduk(String json){
        return (Produk) JSONObject.toBean(JSONObject.fromObject(json), Produk.class);
    }

    public String produkToJson(Produk p){
        return JSONObject.fromObject(p).toString();
    }
}

JsonTransformerTest.java

public class JsonTransformerTest {

    @Test
    public void testJsonToProduk() {
        Produk p = new JsonTransformer()
                    .jsonToProduk("{\"id\":99,\"kode\":\"T-001\",\"nama\":\"Produk Test\"}");
        assertEquals(new Integer(99), p.getId());
        assertEquals("T-001", p.getKode());
        assertEquals("Produk Test", p.getNama());
    }

    @Test
    public void testProdukToJson() {
        Produk p = new Produk();
        p.setId(99);
        p.setKode("T-001");
        p.setNama("Produk Test");

        assertEquals("{\"id\":99,\"kode\":\"T-001\",\"nama\":\"Produk Test\"}",
                new JsonTransformer().produkToJson(p));
    }
}

PusatSender.java

public class PusatSender {
    public static void main(String[] args) {
        // 1. Menginstankan Spring Application Context
        AbstractApplicationContext ctx 
            = new ClassPathXmlApplicationContext("pusat-integration-ctx.xml", Gateway.class);
        ctx.registerShutdownHook();
        Gateway gw = (Gateway) ctx.getBean("gateway");

        // 2. Kirim produk ke gateway
        int jumlahProduk = 5;
        for (int i = 0; i < jumlahProduk; i++) {
            Produk p = new Produk();
            p.setId(i);
            p.setKode("PRD-00"+i);
            p.setNama("Produk "+i);
            gw.send(p);
            System.out.println("Kirim produk "+i);
        }
        System.out.println("Produk terkirim");
        System.exit(0);
    }
}

Gateway.java

public interface Gateway {
    public void send(Produk p);
}

pusat-integration-ctx.xml

<beans:beans xmlns="http://www.springframework.org/schema/integration"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:beans="http://www.springframework.org/schema/beans"
       xmlns:file="http://www.springframework.org/schema/integration/file"
       xmlns:mail="http://www.springframework.org/schema/integration/mail"
       xmlns:transformer="http://www.springframework.org/schema/integration/transformer"
       xmlns:stream="http://www.springframework.org/schema/integration/stream"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/integration
           http://www.springframework.org/schema/integration/spring-integration-1.0.xsd
           http://www.springframework.org/schema/integration/file
           http://www.springframework.org/schema/integration/file/spring-integration-file-1.0.xsd
           http://www.springframework.org/schema/integration/mail
           http://www.springframework.org/schema/integration/mail/spring-integration-mail-1.0.xsd
           http://www.springframework.org/schema/integration/stream
           http://www.springframework.org/schema/integration/stream/spring-integration-stream-1.0.xsd
           http://www.springframework.org/schema/integration/transformer
           http://www.springframework.org/schema/integration/transformer/spring-integration-transformer-1.0.xsd">


    <gateway id="gateway"
                service-interface="com.artivisi.explore.spring.integration.pusat.Gateway"
                default-request-channel="outgoingProduk"/>

    <channel id="outgoingProduk" />
    
    <transformer input-channel="outgoingProduk" output-channel="outgoingJson"
    ref="jsonTransformer" method="produkToJson"/>

    <publish-subscribe-channel id="outgoingJson" />
    
    <file:outbound-channel-adapter channel="outgoingJson" directory="/tmp"/>

    <mail:header-enricher
        id="mailHeaderEnricher"
        subject="Spring Integration Demo"
        to="cabang@gmail.com"
        from="pusat@gmail.com"
        reply-to="pusat@gmail.com"
        overwrite="false"
        input-channel="outgoingJson"
        output-channel="outgoingEmail"/>

    <channel id="outgoingEmail" />

    <mail:outbound-channel-adapter
        mail-sender="mailSender"
        channel="outgoingEmail"
        />

    <beans:bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
        <beans:property name="defaultEncoding" value="UTF-8"/>
        <beans:property name="host" value="smtp.gmail.com"/>
        <beans:property name="port" value="465"/>
        <beans:property name="username" value="mygmailaccount"/>
        <beans:property name="password" value="mygmailpassword"/>
        <beans:property name="javaMailProperties">
            <beans:value>
                mail.debug=true
                mail.smtp.starttls.enable=true
              	mail.smtp.auth=true
                mail.smtp.socketFactory.class=javax.net.ssl.SSLSocketFactory
                mail.smtp.socketFactory.fallback=false
            </beans:value>
        </beans:property>
    </beans:bean>

    <beans:bean id="jsonTransformer" class="com.artivisi.explore.spring.integration.model.JsonTransformer"/>
</beans:beans>

Implementasi Kantor Cabang

Di sisi kantor cabang, berikut aliran message dari transport hingga menjadi JSON. Kita mulai dengan transport file.

Flow dari shared folder menjadi JSON

Bila kita menggunakan email, berikut gambarnya

Flow dari Email menjadi JSON

Setelah menjadi JSON, kita proses sampai ke CabangService

Flow pemrosesan JSON menjadi Produk

Kita harus membuat beberapa file sebagai berikut :

  • CabangReceiver.java : kode program untuk mengaktifkan Spring Integration

  • CabangService.java : kode program yang akan dipanggil setelah message diterima, dikonversi dari JSON menjadi Produk

  • cabang-integration-ctx.xml : konfigurasi Spring Integration

Kode programnya.

CabangReceiver.java

public class CabangReceiver {
    public static void main(String[] args) {
        AbstractApplicationContext ctx 
                = new ClassPathXmlApplicationContext("cabang-integration-ctx.xml", CabangReceiver.class);
        ctx.registerShutdownHook();
    }
}

CabangService.java

public class CabangService {
    public void terimaProduk(Produk p){
        // Tampilkan data produk, tapi bisa juga disimpan di database
        System.out.println("Terima produk");
        System.out.println("ID : "+p.getId());
        System.out.println("Kode : "+p.getKode());
        System.out.println("Nama : "+p.getNama());
    }
}

cabang-integration-ctx.xml

<beans:beans xmlns="http://www.springframework.org/schema/integration"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:beans="http://www.springframework.org/schema/beans"
       xmlns:file="http://www.springframework.org/schema/integration/file"
       xmlns:mail="http://www.springframework.org/schema/integration/mail"
       xmlns:transformer="http://www.springframework.org/schema/integration/transformer"
       xmlns:stream="http://www.springframework.org/schema/integration/stream"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/integration
           http://www.springframework.org/schema/integration/spring-integration-1.0.xsd
           http://www.springframework.org/schema/integration/stream
           http://www.springframework.org/schema/integration/stream/spring-integration-stream-1.0.xsd
           http://www.springframework.org/schema/integration/file
           http://www.springframework.org/schema/integration/file/spring-integration-file-1.0.xsd
           http://www.springframework.org/schema/integration/mail
           http://www.springframework.org/schema/integration/mail/spring-integration-mail-1.0.xsd
           http://www.springframework.org/schema/integration/transformer
           http://www.springframework.org/schema/integration/transformer/spring-integration-transformer-1.0.xsd">


    <file:inbound-channel-adapter id="filePoller"
       directory="/tmp"
       filename-pattern="^.*\.msg$"
       channel="incomingFile"/>
    
    <channel id="incomingFile" />

    <file:file-to-string-transformer
       input-channel="incomingFile"
       output-channel="incomingJson"
       delete-files="true" />

    <channel id="incomingJson" />

    <transformer input-channel="incomingJson" output-channel="incomingProduk"
    ref="jsonTransformer" method="jsonToProduk"/>

    <channel id="incomingProduk" />

    <service-activator input-channel="incomingProduk"
                      ref="cabangService"
                      method="terimaProduk"/>
                      
    <poller id="defaultPoller" default="true">
        <interval-trigger interval="3" time-unit="SECONDS"/>
    </poller>


    <beans:bean id="jsonTransformer" class="com.artivisi.explore.spring.integration.model.JsonTransformer"/>
    <beans:bean id="cabangService" class="com.artivisi.explore.spring.integration.cabang.CabangService"/>
</beans:beans>

Untuk menerima email, kita dapat memilih protokol POP3, IMAP, atau IMAP-Idle. POP3 dan IMAP akan mendownload semua email yang masuk, setelah itu, kita harus melakukan polling dengan interval tertentu untuk memeriksa apakah ada email baru. Bila kita menggunakan IMAP-Idle, kita tidak perlu melakukan polling. Mail server akan memberikan notifikasi bila ada email baru yang masuk, setelah itu kita bisa mendownloadnya.

Berikut konfigurasi untuk GMail

Debug Output

    <beans:bean id="javaMailProperty" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
        <beans:property name="properties">
            <beans:value>
                mail.debug=true
            </beans:value>
        </beans:property>
    </beans:bean>

POP3

    <mail:inbound-channel-adapter
    channel="incomingMail"
    store-uri="pop3s://mygmailaccount:mygmailpassword@pop.gmail.com:995/INBOX"
    java-mail-properties="javaMailProperty"/>

IMAP

    <mail:inbound-channel-adapter
    channel="incomingMail"
    store-uri="imaps://mygmailaccount:mygmailpassword@imap.gmail.com:993/INBOX" />

IMAP-Idle

    <mail:imap-idle-channel-adapter channel="incomingMail"
    store-uri="imaps://mygmailaccount:mygmailpassword@imap.gmail.com:993/INBOX"/>

Setelah email masuk, jangan lupa konversi dulu jadi String.

    <channel id="incomingMail" />

    <mail:mail-to-string-transformer 
    input-channel="incomingMail" 
    output-channel="incomingJson"/>

Demikianlah tutorial penggunaan Spring Integration. Dengan menggunakan Spring Integration ini, ada beberapa benefit yang kita dapatkan dibandingkan coding sendiri :

  • Kode program lebih sedikit

  • Kode program lebih mudah ditest. Kita bisa menggunakan JUnit untuk JsonTransformer. Alur pengiriman data juga bisa ditest di komputer lokal tanpa koneksi ke GMail, sehingga kalau ada error bisa diperbaiki dengan lebih cepat.

  • Aliran data lebih mudah dibaca, yaitu dalam konfigurasi Spring Integration

  • Fleksibilitas dalam pemilihan transport. Bukan cuma email, tapi juga banyak opsi lain seperti remoting dan file. Opsi ini cuma butuh beberapa baris konfigurasi saja untuk mengaktifkannya.

Konsumsi effort :

  • Download : 1 jam

  • Browsing tutorial : 2 jam

  • Mempelajari Spring Integration : 8 jam

  • Membuat sample aplikasi : 4 jam

  • Menulis blog dan membuat gambar : 8 jam

  • Total : 23 jam (3 hari)

Apakah dalam 3 hari kita bisa membuat implementasi kirim-terima data produk via email yang bebas bug ??


Integrasi aplikasi kantor pusat dan cabang [Bagian 1]

Integrasi Aplikasi Pusat Cabang - Bagian I

Beberapa waktu yang lalu, kami di ArtiVisi dihadapkan pada suatu tantangan, yaitu sinkronisasi aplikasi antara pusat dan cabang. Aplikasi pusat maupun cabang memiliki database masing-masing, dan tentunya data transaksi masing-masing pula.

Pada artikel ini, saya akan mengelaborasi proses problem solving yang dilakukan, solusi yang dipilih, dan tentunya dilengkapi dengan source code.

Use Case

Ada beberapa use case yang ingin didukung oleh aplikasi, sebagai berikut :

  • Pusat menambahkan jenis produk baru, data ini harus segera diimplementasikan juga di cabang

  • Transaksi yang dilakukan di cabang harus dikirim ke pusat agar bisa dibuatkan laporan keseluruhan

  • Data pelanggan di cabang harus juga dikirim ke pusat

  • Perpindahan stok barang dari pusat ke cabang dan sebaliknya

Constraint

Bicara requirement, tentu tidak bisa dilepaskan dari constraint. Berikut constraint yang ada :

  • Pusat dan cabang sama-sama terhubung ke internet menggunakan Speedy

  • Karena pakai Speedy, kita bisa mengekspose satu mesin ke internet menggunakan IP Public

  • IP Public tidak dedicated, dan mungkin berubah sewaktu-waktu

  • Server aplikasi dan database di pusat/cabang belum tentu hidup. Kalaupun hidup, belum tentu hidup berbarengan. Bisa saja pusat sedang nyala, cabang mati lampu, dan sebaliknya.

Alternatif Solusi

Setelah kita memiliki requirement dan constraint, tahap berikutnya adalah membuat daftar alternatif solusi. Berikut adalah alternatif yang bisa kita lakukan untuk memecahkan masalah di atas.

  • Remoting

    • Web Services

    • RMI

    • HttpInvoker

  • Messaging

    • JMS

    • Email (POP3/IMAP dan SMTP)

  • File

    • Shared Folder

    • FTP

    • SSH/SCP

  • Database

    • Shared Database

    • Replikasi Database

Pemilihan Solusi

Jaringan publik melalui internet sangat tidak reliable dan memiliki banyak keterbatasan. Kita tidak bisa membuka sembarang port, karena mungkin saja diblokir di tengah jalan. Kita juga tidak bisa menggunakan protokol yang terlalu kompleks atau cerewet (membutuhkan transfer data yang sering). Oleh karena itu, kita bisa menyingkirkan alternatif berikut.

  • Remoting dengan RMI : port tidak standar

  • Messaging dengan JMS : port tidak standar

  • Replikasi MySQL : protokol terlalu kompleks dan cerewet, port tidak standar

Kita harus mempertimbangkan konsumsi bandwidth. Protokol yang rakus bandwidth bisa kita singkirkan, yaitu Remoting dengan WS.

Melihat constraint terakhir, kita juga harus mengeliminasi beberapa alternatif yang mengharuskan pusat dan cabang online bersamaan, yaitu Remoting dengan Spring HTTPInvoker dan File Transfer, baik FTP maupun SSH/SCP.

Dengan demikian, yang tersisa adalah Messaging dengan Email. Kita bisa mengirim data dengan attachment. Untuk menghemat bandwidth, attachment bisa dikompresi. Pusat dan cabang bisa online kapanpun mereka mau, dan akan tetap menerima message. Kalau ada message yang hilang di tengah jalan, orang pusat/cabang bisa menelepon untuk minta dikirim ulang datanya.

Sebagai bonus, dengan memilih email sebagai media transfer data, kita tidak perlu repot-repot mengelola infrastruktur sendiri. Kita bisa gunakan layanan GMail yang gratis, berkapasitas besar, dan memiliki implementasi POP3, IMAP, dan SMTP. Ada satu fitur istimewa dari IMAP, yaitu IMAP-Idle. Fitur ini akan sangat berguna kelak di kemudian hari. Kebetulan GMail sudah mendukung fitur ini.

Desain Implementasi

Baiklah, mari kita implementasikan sinkronisasi melalui email. Ada beberapa pertanyaan lanjutan. Pertama, apa yang ingin dikirim? Sebagai contoh sederhana, kita ingin mengirim data produk baru dari pusat ke cabang. Inilah class Produk.

public class Produk {
  private Integer id;
  private String kode;
  private String nama;
  
  // getter dan setter
}

Bila kita membuat object Produk, datanya akan tersimpan di memori. Kita harus mengkonversi format data di memori ini ke bentuk file, supaya bisa dikirim dalam bentuk attachment. Ada beberapa pilihan lagi di sini, yaitu :

  • java.io.Serializable, konversi dengan ObjectInputStream dan ObjectOutputStream

  • JSON

  • XML

Serializable lebih unggul dalam hal kemudahan coding dan kecepatan marshall/unmarshall. Di Java menulis object ke file sangat mudah dan cepat. Prosesnya sama untuk apapun object yang ingin kita konversi. Untuk memproses konversi juga tidak dibutuhkan CPU dan Memori tinggi. Walaupun demikian, file yang dihasilkan berbentuk binary, sehingga menyulitkan debugging.

JSON dan XML berbentuk text, sehingga mudah didebug kalau ada masalah. Walaupun demikian, kita harus membuat atau memilih dari yang ada implementasi generator dan parser untuk proses konversi. Proses konversinya butuh resource CPU dan memori. Dan yang paling penting, kita mungkin harus membuat generator/parser untuk tiap tipe data yang akan kita kirim.

Sementara saya pilih JSON, karena ada json-lib yang bisa mengkonversi object menjadi JSON dan sebaliknya dengan mudah.

Selanjutnya, bagaimana cara mengirim email?

Cara mengirim email dengan Java sudah ditunjukkan Ifnu di sini. Tapi saya tidak mau coding tangan kosong seperti itu. Bukan apa-apa, setiap baris kode yang kita tulis harus ditest dan dimaintain. Belum lagi kalo nanti ada perubahan requirement, bisa coding ulang.

Nah, pertama saya memutuskan menggunakan Spring untuk mengirim email. Ibaratnya kalau coding sendiri itu berkelahi dengan tangan kosong, pakai Spring berkelahi pakai pistol. Jauh lebih cepat dan mudah.

Tapi ternyata, pistol juga tidak cukup, karena kita belum bisa menerima email. Bisa kirim gak bisa terima ya percuma, karena di sisi kantor cabang kita tentu harus mengambil dan memproses data yang dikirim dari pusat. Ifnu sebetulnya sudah membuat implementasi penerima emailnya, tapi nampaknya belum diposting.

Saatnya membuka gudang senjata dan mencari senjata yang lebih besar. Saya menemukan rocket launcher, yaitu Spring Integration.

Bukan saja bisa menerima email, Spring Integration juga bisa menggunakan semua alternatif transport yang disebutkan di atas (WS, HTTPInvoker, RMI, FTP, SSH, JMS, Email) hanya dengan mengganti beberapa baris konfigurasi. Wow …. canggih sekali. Baiklah, saya akan pakai ini saja.

Berhubung artikel ini sudah terlalu panjang. Implementasi Spring Integrationnya diteruskan di bagian kedua.


Mengakses EJB secara Remote

Mengakses EJB secara Remote

Beberapa buku tentang EJB, seperti EJB 3 in Action karangan Reza Rahman maupun Enterprise Java Beans 3.0 karangan Bill Burke, membahas secara detail tentang penggunaan EJB. Akan tetapi, ada satu hal kecil namun penting yang tidak dibahas. Bagaimana cara mengakses EJB secara remote?

Di sisi server, ini mudah. Cukup membuat remote interface saja beres. Tapi bagaimana cara lookupnya dari client? Artikel ini akan membahas cara memanggil EJB dari method main.

Sebelumnya, berikut adalah EJB yang akan kita panggil.

HaloBean.java

package com.artivisi.belajar.ejb.session;

@Stateless
public class HaloBean implements HaloRemote {
   public String halo(String nama) {
     return "Halo "+nama;
   }
}

HaloRemote.java

package com.artivisi.belajar.ejb.session;

@Remote
public interface HaloRemote {
   public String halo(String nama);
}

EJB ini akan kita deploy ke Glassfish menggunakan Netbeans. Bagaimana caranya? Silahkan buka menu Help Netbeans.

Berikut adalah kode untuk memanggil HaloBean secara remote.

HaloClient.java

package com.artivisi.belajar.ejb.client;

public class HaloClient {
  public static void main(String[] args){
    // 0. Konfigurasi host dan port
    String ejbHost = "localhost";
    String ejbPort = 12345;
    String ejbName = HaloRemote.class.getName();
  
    // 1. JNDI Context
    Properties props = new Properties();
    props.put("org.omg.CORBA.ORBInitialHost", ejbHost);
    props.put("org.omg.CORBA.ORBInitialPort", ejbPort);
    
    InitialContext ctx = new InitialContext(props);
    
    // 2. Lookup EJB
    HaloRemote haloBean = (HaloRemote) ctx.lookup(ejbName);
    
    // 3. Panggil methodnya
    System.out.println("Halo EJB : "+haloBean.halo("endy"));
  }
}

Ada beberapa variabel yang perlu diperhatikan di sini, yaitu ejbHost, ejbPort, dan ejbName. Variabel ejbHost tentunya adalah komputer tempat EJB dideploy. Variabel ejbPort adalah port yang digunakan oleh Application Server untuk mempublish EJB. Port ini berbeda-beda tergantung merek application server dan cara deploymentnya. Karena saya menggunakan Netbeans untuk mendeploy ke Glassfish, maka saya harus cari tahu dulu berapa port yang digunakan Netbeans.

Caranya, login ke admin console Glassfish melalui Netbeans. Buka tab Services, klik kanan Glassfish, dan pilih View Admin Console. (/images/uploads/2009/06/netbeans-view-admin-console.png) Browser akan terbuka dan menampilkan halaman login Glassfish. (/images/uploads/2009/06/glassfish-login.png) Setelah login, klik Application Server di panel kiri. Port yang digunakan akan ditampilkan di panel kanan, yaitu dengan nama IIOP Port(s). Coba-coba saja semuanya. Di komputer saya, port yang benar adalah 10275. (/images/uploads/2009/06/glassfish-iiop-port.png) Variabel ejbName juga berbeda tergantung dari application server yang digunakan. Baca dokumentasi server Anda untuk memastikannya.


Backup MySQL

Sebelumnya, saya telah membahas script backup untuk Trac maupun Subversion. Kali ini, kita akan bahas backup script untuk MySQL.

Sama seperti backup script sebelumnya, script ini akan membuat folder sesuai dengan tanggal dan jam backup. Selanjutnya, script akan melakukan backup terhadap database MySQL sesuai dengan nama database yang ditentukan. Backup ini akan disimpan di folder yang kita tentukan.

Berikut backup scriptnya.

[Update - 7 Des 2009] Sudah ditambahkan perintah untuk kompresi hasil backupnya.

#!/bin/sh

test -x /bin/date || exit -1
test -x /usr/bin/mysqldump || exit -1
test -x /bin/tar || exit -1
test -x /bin/bzip2 || exit -1

DBHOST=$1
DBNAME=$2
USERNAME=$3
PASSWORD=$4
BACKUP_FOLDER=$5
CURR_DATE="$(/bin/date +%Y%m%d-%H%M)"


if [ "$1" = '' ]; then
    echo "Usage : $0 <db name> <username> <password> <backup folder>"
    return 1
fi

if [ "$2" = '' ]; then
    echo "Usage : $0 <db name> <username> <password> <backup folder>"
    return 1
fi

if [ "$3" = '' ]; then
    echo "Usage : $0 <db name> <username> <password> <backup folder>"
    return 1
fi

if [ "$4" = '' ]; then
    echo "Usage : $0 <db name> <username> <password> <backup folder>"
    return 1
fi


echo "Create backup folder $BACKUP_FOLDER/$CURR_DATE"
echo "..."

/bin/mkdir "$BACKUP_FOLDER/$CURR_DATE"

echo "Backup $DBNAME schema to $BACKUP_FOLDER/$CURR_DATE/$DBNAME-schema-$CURR_DATE.sql"
echo "..."

/usr/bin/mysqldump $DBNAME -u $USERNAME -p$PASSWORD -h$DBHOST -d > "$BACKUP_FOLDER/$CURR_DATE/$DBNAME-schema-$CURR_DATE.sql"

echo "Backup $DBNAME data to $BACKUP_FOLDER/$CURR_DATE/$DBNAME-data-$CURR_DATE.sql"
echo "..."

/usr/bin/mysqldump $DBNAME -u $USERNAME -p$PASSWORD -h $DBHOST -n -c -t --single-transaction > "$BACKUP_FOLDER/$CURR_DATE/$DBNAME-data-$CURR_DATE.sql"

echo "Compressing folder $CURR_DATE"
echo "..."

/bin/tar cvf - "$BACKUP_FOLDER/$CURR_DATE" | /bin/bzip2 -c9 > "$BACKUP_FOLDER/$CURR_DATE.tar.bz2"

echo "Removing folder $BACKUP_FOLDER/$CURR_DATE"
echo "..."

/bin/rm -rf "$BACKUP_FOLDER/$CURR_DATE"

echo "Completed"

Script di atas dapat dijalankan setiap Jumat malam jam 23.00 dengan konfigurasi sebagai berikut.

0 23 * * 5 /bin/sh /path/ke/mysql-backup.sh db_user db_pass db_name backup_folder_mysql

SSH dengan JSch

Mengakses Remote Server dengan Java

Kita sudah biasa mengakses komputer lain menggunakan SSH. Cukup ketikkan ssh username@namaserver, tekan enter, dan lakukan apa yang kita mau. Nah, bagaimana kalau kita ingin membuat aplikasi yang butuh mengakses komputer lain? Misalnya, kita ingin membuat aplikasi sederhana yang menampilkan input IP Address, Username, Password serta tombol Shutdown. Begitu input field kita isi dan tombol Shutdown ditekan, aplikasi kita akan melakukan ssh ke komputer tersebut dan menjalankan perintah shutdown -h now.

Di Java, hal ini dapat dengan mudah dilakukan menggunakan pustaka JSch. Berikut langkah-langkah untuk menggunakan JSch.

Mendapatkan Pustaka JSch

Tentunya yang pertama kita lakukan adalah membuka website JSch dan mengunduh rilis terbaru. Pilih yang ZIP, jangan yang JAR, karena di dalamnya ada beberapa contoh penggunaan yang akan kita butuhkan untuk mencontek nantinya.

Paket ZIP ini belum dikompilasi, dan kita membutuhkan Ant untuk melakukan proses build. Jika Anda belum pernah menggunakan Ant, baca dulu tutorial ini. Setelah proses build selesai dilakukan, akan muncul file *jar di dalam folder dist.

Template Aplikasi Java

Buatlah project Java menggunakan editor yang Anda sukai. Pastikan jar JSch sudah terdaftar di project Java yang barusan dibuat. Setelah itu, buatlah Java Class sebagai berikut

public class JschDemo {
    public static void main(String[] args) throws Exception {

    }
}

Untuk selanjutnya, kode program akan ditulis di dalam method main.

Parameter koneksi

Untuk melakukan koneksi ke komputer lain, kita memerlukan beberapa variabel, ditunjukkan dengan kode sebagai berikut.

String host = "localhost";
String user = "endy";
String pass = "java";
String command = "shutdown -h now";

Kita akan melakukan koneksi ke komputer kita sendiri (localhost) dengan username endy dan password java. Setelah berhasil terhubung, kita akan menjalankan perintah shutdown -h now.

Pre Requisite

Agar kode program kita bisa dijalankan, ada beberapa persyaratan sebagai berikut:

  • Komputer yang ingin dihubungi harus menjalankan SSH server

  • Komputer yang ingin menghubungi harus melakukan koneksi SSH secara manual terlebih dulu, agar komputer tujuan terdaftar di file known_hosts

  • User yang digunakan untuk koneksi harus memiliki akses yang cukup untuk melakukan perintah yang diinginkan

Menggunakan JSch

Setelah parameter koneksi kita deklarasikan, dan juga prasyarat di komputer tujuan dipenuhi, sekarang kita bisa mulai menggunakan JSch. Pertama, kita instankan dulu object dari class JSch.

JSch jsch = new JSch();

Kemudian, kita berikan database known_hosts, yaitu daftar komputer tujuan beserta public keynya. Ini berguna untuk melakukan verifikasi terhadap komputer tujuan.

jsch.setKnownHosts("/home/endy/.ssh/known_hosts");

File known_hosts ini akan otomatis dibuat bila kita melakukan koneksi SSH ke komputer lain. Itulah sebabnya kita harus melakukan koneksi manual terlebih dulu, sehingga public key komputer tujuan sudah terdaftar dalam file ini. Alternatif dari menggunakan file ini adalah mendaftarkan public key di komputer tujuan dan menggunakan private key untuk login.

Selanjutnya, kita membuka koneksi ke komputer tujuan

Session session = jsch.getSession(user, host);
session.setPassword(pass);
session.connect();

Lalu, kita membuka channel. Channel menggambarkan bentuk interaksi kita dengan komputer tujuan. JSch menyediakan beberapa jenis channel, misalnya exec dan shell. Kita menggunakan exec untuk mengeksekusi satu perintah saja. Sedangkan shell bisa digunakan untuk membuka terminal interaktif yang bisa kita berikan perintah sebanyak kita mau.

ChannelExec channel = (ChannelExec) session.openChannel("exec");
channel.setCommand(command);
channel.connect();

Kita bisa membaca output dari perintah yang kita jalankan menggunakan I/O Stream seperti biasa.

BufferedReader reader = new BufferedReader(new InputStreamReader(channel.getInputStream()));
String output = reader.readLine();
while (output != null) {
    System.out.println(output);
    output = reader.readLine();
}
reader.close();

Segera setelah perintah selesai dijalankan, kita mengakhiri channel dan session.

channel.disconnect();
session.disconnect();

Berikut adalah keseluruhan kode program.

public class JschDemo {

    public static void main(String[] args) throws Exception {
	String host = "localhost";
	String user = "endy";
	String pass = "java";
	String command = "shutdown -h now";

        JSch jsch = new JSch();
        jsch.setKnownHosts("/home/endy/.ssh/known_hosts");

        Session session = jsch.getSession(user, host);
        session.setPassword(pass);
        session.connect();

        ChannelExec channel = (ChannelExec) session.openChannel("exec");
        channel.setCommand(command);
        channel.connect();

        BufferedReader reader = new BufferedReader(new InputStreamReader(channel.getInputStream()));
        String output = reader.readLine();
        while (output != null) {
            System.out.println(output);
            output = reader.readLine();
        }
        reader.close();

        channel.disconnect();
        session.disconnect();

    }
}

Selamat mencoba. Bila percobaan Anda sukses, komputer yang sedang Anda gunakan akan shutdown setelah aplikasi selesai dijalankan. :D