Memakai Java JSON API Untuk Membaca Facebook Graph API Di Aplikasi Desktop

Java API for JSON Processing (JSR-353) adalah API standar dari Java untuk membaca dan menulis format JSON. JSON (JavaScript Object Notation) adalah format data yang sangat populer di dunia pemograman web. JSON lebih mudah dipakai dan lebih fleksibel bila dibandingkan dengan XML. Karena ia dapat dipakai langsung di JavaScript, pertukaran data pada AJAX sering kali memakai format JSON. Sebelum JSR-353 dirilis pada bulan April 2013, saya harus memakai library pihak ketiga seperti Jackson JSON Processor untuk membaca dan menulis JSON di Java. JSR-353 sekarang merupakan bagian dari Java EE (Enterprise Edition) 7. Bila memakai application server yang mendukung Java EE 7 seperti GlassFish 4, developer dapat membaca dan menulis JSON dengan class yang berada di package javax.json. Catatan: Yang disediakan oleh Java EE 7 hanya API-nya, developer tetap perlu menambahkan implementasi JSR ini.

Pada kesempatan ini, saya tidak akan menggunakan JSR-353 di aplikasi web melainkan di aplikasi desktop. Karena JSR tersebut bukan bagian dari Java SE (Standard Edition), maka saya harus menambahkan API maupun implementasinya secara manual. Karena saya memakai Gradle, maka isi build.gradle di proyek saya akan terlihat seperti berikut ini:

apply plugin: 'java'
apply plugin: 'application'

mainClassName = 'com.wordpress.thesolidsnake.facebookword.Main'

repositories {
    mavenCentral()
}

dependencies {
    compile 'javax.json:javax.json-api:1.0'
    compile 'org.glassfish:javax.json:1.0.4'
}

Saya akan membuat program desktop yang mengakses Facebook Graph API. Dengan Graph API, sebuah program dapat mengakses berbagai informasi dari pengguna Facebook (selama pengguna tersebut memberikan izin). Informasi lebih lanjut mengenai Facebook Graph API dapat dibaca di https://developers.facebook.com/docs/graph-api/. Cara kerja Graph API secara garis besar adalah program mengirim request HTTP ke server Facebook. Kemudian, server Facebook akan mengembalikan hasil dalam format JSON. Dengan demikian, Facebook Graph API bisa disebut sebagai web services yang ‘mirip’ sepertiREST.

Untuk memberikan request HTTP ke server Facebook, saya dapat menggunakan class di package java.net dan javax.net yang merupakan bawaan dari Java SE.

Sebelum memulai memakai Graph API, saya perlu mendapatkan access token untuk user bersangkutan. Karena saya hanya ingin mencoba membaca JSON, maka saya akan mengakses profil pribadi saya. Untuk mendapatkan access token guna keperluan pribadi, saya mengunjungi situs https://developers.facebook.com/tools/explorer. Disini saya men-klik tombol Get Access Token yang terletak di kanan atas. Pada dialog yang muncul, saya memilih Friends Data Permissions dan memberi tanda centang pada friends_status seperti yang terlihat pada gambar berikut ini:

Permission untuk mengakses status friends

Permission untuk mengakses status friends

Setelah itu, saya men-klik tombol Get Access Token. Sekarang saya dapat men-copy paste Access Token yang muncul ke kode program Java.

Sebagai contoh, kode program sederhana berikut akan menampilkan nama user dan id dari si pemilik access token:

package com.wordpress.thesolidsnake.facebookword;

import javax.json.Json;
import javax.json.JsonObject;
import javax.json.JsonReader;
import javax.json.JsonStructure;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;

public class Main {

    public static String ACCESS_TOKEN = "...";  // SESUAIKAN!

    public static JsonStructure getResult(String node, String params) {
        URLConnection cn;
        try {
            URL url = new URL("https://graph.facebook.com/" + node +  "?" + params + "&access_token=" + ACCESS_TOKEN);
            cn = url.openConnection();
            try (InputStream httpInputStream = cn.getInputStream();
                 JsonReader reader = Json.createReader(httpInputStream)) {
                    return reader.read();
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return null;
    }

    public static void main(String[] args) {
        JsonObject identitas = (JsonObject) getResult("me", "fields=id,name");
        System.out.println("JSON = " + identitas);
        System.out.println("Nama = " + identitas.getJsonString("name").getString());
        System.out.println("Id = " + identitas.getJsonString("id").getString());
    }
}

Contoh hasil dari kode program di atas adalah:

JSON = {"name":"Solid Snake","id":"1122334455"}
Nama = Solid Snake
Id = 1122334455

Untuk membaca JSON dari sebuah InputStream, saya menggunakan Json.createReader() yang hasilnya berupa sebuah JsonReader. Disini, saya memiliki beberapa alternatif dalam membaca. Method JsonReader.readArray() akan membaca JSON yang berupa array yang mengembalikan sebuah instance JsonArray. Method JsonReader.readObject() akan membaca JSON yang berupa object yang mengembalikan sebuah intance JsonObject. Bila saya tidak yakin, saya dapat menggunakan method JsonReader.read() yang akan mengembalikan sebuah JsonStructure. Class JsonStructure adalah parent dari JsonObject dan JsonArray. Ia memiliki method getValueType() untuk menentukan jenisnya.

Turunan dari JsonStructure

Turunan dari JsonStructure

Karena bila tidak terjadi kesalahan, hasil kembalian dari pemanggilan Graph API di atas adalah sebuah JSON object, maka saya dengan yakin melakukan casting ke JsonObject. Dari sini, saya dapat memanggil method-method untuk mengakses elemen dalam JsonObject tersebut. Perhatikan bahwa dalam sebuah JsonObject boleh mengandung JsonObject lainnya (nested).

Method pada JsonObject

Method pada JsonObject

Langkah berikutnya, saya akan mencoba mengambil daftar teman Facebook untuk user bersangkutan beserta status mereka. Saya perlu memanggil URL seperti /me/friends?fields=name,statuses.fields(message)&limit=10. Graph API akan mengembalikan JSON dalam format seperti:

{
  "data": [
     {"name": ..., "statuses": { "data": [ ... ], "paging": {...} },
     {"name": ..., "statuses": { "data": [ ... ], "paging": {...} },
     ...
  ],
  "paging": { ... }
}

Untuk membaca format JSON di atas, saya dapat membuat kode program Java seperti berikut ini:

...
System.out.println("\nDaftar Teman:");
JsonObject hasil = (JsonObject) getResult("me/friends",
"fields=name,statuses.fields(message)&limit=10");
JsonArray friends = hasil.getJsonArray("data");
for (JsonObject friend: friends.getValuesAs(JsonObject.class)) {
  System.out.println("Nama = " + friend.getJsonString("name").getString());
  JsonObject hasilStatus = friend.getJsonObject("statuses");
  if (hasilStatus!=null) {
    JsonArray statuses = hasilStatus.getJsonArray("data");
    for (JsonObject status: statuses.getValuesAs(JsonObject.class)) {
      JsonString pesanStatus = status.getJsonString("message");
      if (pesanStatus!=null) {
        System.out.println("\t" + pesanStatus);
      }
    }
  }
}
...

Untuk membaca nilai array dari sebuah JsonObject, saya memanggil method getJsonArray() miliknya yang akan mengembalikan sebuah JsonArray. Class JsonArray mengimplementasikan dua interface, yaitu JsonStructure dan List. Dengan begitu saya dapat memakai class ini sebagai sebuah List yang berisi JsonValue. Agar lebih spesifik, saya dapat memanggil method getValueAs() untuk mendapatkan List dengan tipe data tertentu (selama compatible, tentunya).

Interface yang diimplementasikan JsonArray

Interface yang diimplementasikan JsonArray

Walaupun program di atas sudah bekerja, ada satu hal yang saya abaikan: Graph API memiliki pagination! Setiap object yang dikembalikan memiliki atribut paging yang berupa sebuah object. Bila object paging ini memiliki nilai next, maka terdapat ‘halaman’ atau data berikutnya yang perlu di-proses.

Selain itu, bukankah lebih baik bila output tidak langsung ditampilkan di layar, melainkan disimpan dalam file CSV guna diolah lebih lanjut (misalnya di-sortir melalui Excel)? Oleh sebab itu, saya melakukan perubahan sehingga kode program terlihat seperti berikut ini:

package com.wordpress.thesolidsnake.facebookword;

import javax.json.*;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;

public class Main {

    public static String ACCESS_TOKEN = "..."; // UBAH DISINI!

    public static int JUMLAH_TEMAN;
    public static int JUMLAH_STATUS;
    public static StatistikKata STATISTIK;
    static {
        JUMLAH_TEMAN = 0;
        JUMLAH_STATUS = 0;
        STATISTIK = new StatistikKata();
    }

    public static JsonStructure getResult(String node, String params) {
        return getResult("https://graph.facebook.com/" + node + "?" + params + "&access_token=" + ACCESS_TOKEN);
    }

    public static JsonStructure getResult(String urlString) {
        URLConnection cn;
        try {
            URL url = new URL(urlString);
            cn = url.openConnection();
            try (InputStream httpInputStream = cn.getInputStream();
                 JsonReader reader = Json.createReader(httpInputStream)) {
                return reader.read();
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return null;
    }

    public static void prosesStatus(JsonObject hasilStatus) {
        if (hasilStatus!=null) {
            JsonArray statuses = hasilStatus.getJsonArray("data");
            for (JsonObject status: statuses.getValuesAs(JsonObject.class)) {
                JsonString pesanStatus = status.getJsonString("message");
                if (pesanStatus!=null) {
                    JUMLAH_STATUS++;
                    STATISTIK.prosesKalimat(pesanStatus.getString());
                }
            }
            if (hasilStatus.get("paging")!=null) {
                JsonString next = hasilStatus.getJsonObject("paging").getJsonString("next");
                if (next!=null) {
                    prosesStatus((JsonObject)getResult(next.getString()));
                }
            }
        }
    }

    public static void prosesFriends(JsonObject hasilFriends) {
        JsonArray friends = hasilFriends.getJsonArray("data");
        for (JsonObject friend: friends.getValuesAs(JsonObject.class)) {
            JUMLAH_TEMAN++;
            STATISTIK.setUserAktif(friend.getJsonString("name").getString());
            System.out.println("Nama = " + friend.getJsonString("name").getString());
            prosesStatus(friend.getJsonObject("statuses"));
        }
        if (hasilFriends.get("paging")!=null) {
            JsonString next = hasilFriends.getJsonObject("paging").getJsonString("next");
            if (next!=null) {
                prosesFriends((JsonObject)getResult(next.getString()));
            }
        }
    }

    public static void main(String[] args) {
        JsonObject identitas = (JsonObject) getResult("me", "fields=id,name");
        System.out.println("Identitas:");
        System.out.println("Nama = " + identitas.getJsonString("name").getString());
        System.out.println("Id = " + identitas.getJsonString("id").getString());

        System.out.println("\nDaftar Teman:");
        JsonObject hasil = (JsonObject) getResult("me/friends", "fields=name,statuses.fields(message)&limit=10");
        prosesFriends(hasil);

        System.out.println("\n");
        System.out.println("====================================");
        System.out.println("Jumlah Teman = " + JUMLAH_TEMAN);
        System.out.println("Jumlah Status = " + JUMLAH_STATUS);
        System.out.println("====================================");

        System.out.println("\n");
        STATISTIK.buatFile("C:\\Users\\User\\Desktop\\statistik.csv");
    }
}

Kode program di atas membutuhkan sebuah class baru, StatistikKata, yang bertugas menghitung jumlah kata, dimana isinya terlihat seperti berikut ini:

package com.wordpress.thesolidsnake.facebookword;

import java.io.OutputStream;
import java.io.PrintWriter;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.HashMap;
import java.util.Map;

public class StatistikKata {

    public Map data;
    private String userAktif;

    public StatistikKata() {
        data = new HashMap();
    }

    public void setUserAktif(String userAktif) {
        this.userAktif = userAktif;
    }

    public void tambahKata(String kata) {
        if (userAktif==null) throw new RuntimeException("Nama user tidak boleh kosong!");
        Item item = data.get(kata);
        if (item==null) {
            item = new Item();
            data.put(kata, item);
        }
        item.tambah(userAktif);
    }

    public void prosesKalimat(String kalimat) {
        String[] katas = kalimat.split(" ");
        for (String kata: katas) {
            kata = kata.toLowerCase().replaceAll("[^a-zA-Z0-9]", "");
            if (!kata.trim().isEmpty()) {
                tambahKata(kata);
            }
        }
    }

    public void buatFile(String namaFile) {
        try (OutputStream os = Files.newOutputStream(Paths.get(namaFile), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
             PrintWriter pw = new PrintWriter(os)) {

            pw.println("Kata;Jumlah;Jumlah_Teman;Teman");
            for (String key: data.keySet()) {
                Item item = data.get(key);
                pw.printf("%s;%d;%d;%s\n", key, item.getJumlahKata(), item.getJumlahTeman(), item.getDaftarTeman());
            }

            pw.flush();

        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    class Item {

        private Map data;

        public Item() {
            data = new HashMap();
        }

        public void tambah(String namaUser) {
            if (data.containsKey(namaUser)) {
                Long jumlah = data.get(namaUser);
                data.put(namaUser, jumlah+1);
            } else {
                data.put(namaUser, 1l);
            }
        }

        public long getJumlahKata() {
            long jumlah = 0;
            for (String key: data.keySet()) {
                jumlah += data.get(key);
            }
            return jumlah;
        }

        public long getJumlahTeman() {
            return data.size();
        }

        public String getDaftarTeman() {
            StringBuffer out = new StringBuffer();
            for (String key: data.keySet()) {
                out.append(",");
                out.append(key);
            }
            return out.substring(1);
        }

    }
}

Sebelum mulai menjalankan program, ada baiknya bila saya menguji terlebih dahulu apakah kode program di class StatistikKata bekerja sesuai harapan. Dengan melakukan unit testing, saya dapat menguji kode program tanpa harus terkoneksi ke internet atau mengakses server Facebook secara langsung. Walaupun membuat unit test terkadang terasa repot, tapi ia dapat membuat saya lebih percaya diri. Biasanya disaat seorang programmer sudah tidak yakin dengan kode programnya tapi ia masih lanjut membuat kode program berikutnya, maka sangat besar kemungkinan ia membuat kesalahan berantai. Sebuah unit test akan membuat sang programmer yakin dengan apa yang barusan dibuatnya (tanpa harus menjalankan keseluruhan program).

Saya akan memakai library JUnit untuk melakukan pengujian, oleh sebab itu saya mengubah build.gradle saya menjadi seperti berikut ini:

...
dependencies {
    ...
    testCompile 'junit:junit:4.11'
}
...

Saya kemudian membuat sebuah file baru src\test\java\com\wordpress\thesolidsnake\facebookword\StatistikKataTest.java yang isinya seperti berikut ini:

package com.wordpress.thesolidsnake.facebookword;

import junit.framework.TestCase;

public class StatistikKataTest extends TestCase {

    public void testProsesKalimat() {
        StatistikKata stat = new StatistikKata();
        stat.setUserAktif("SolidSnake");
        stat.prosesKalimat("Nama saya adalah TheSolidSnake");
        stat.prosesKalimat("Hari ini biasa-biasa saja");
        stat.setUserAktif("LiquidSnake");
        stat.prosesKalimat("Kalau begitu, nama kamu adalah?");

        StatistikKata.Item itemNama = stat.data.get("nama");
        assertEquals(2, itemNama.getJumlahKata());
        assertEquals(2, itemNama.getJumlahTeman());
        assertEquals("SolidSnake,LiquidSnake", itemNama.getDaftarTeman());

        StatistikKata.Item itemSaya = stat.data.get("saya");
        assertEquals(1, itemSaya.getJumlahKata());
        assertEquals(1, itemSaya.getJumlahTeman());
        assertEquals("SolidSnake", itemSaya.getDaftarTeman());

        StatistikKata.Item itemBegitu = stat.data.get("begitu");
        assertEquals(1, itemBegitu.getJumlahKata());
        assertEquals(1, itemBegitu.getJumlahTeman());
        assertEquals("LiquidSnake", itemBegitu.getDaftarTeman());

        StatistikKata.Item itemBiasa = stat.data.get("biasabiasa");
        assertEquals(1, itemBiasa.getJumlahKata());
        assertEquals(1, itemBiasa.getJumlahTeman());
        assertEquals("SolidSnake", itemBiasa.getDaftarTeman());

        StatistikKata.Item itemAdalah = stat.data.get("adalah");
        assertEquals(2, itemAdalah.getJumlahKata());
        assertEquals(2, itemAdalah.getJumlahTeman());
        assertEquals("SolidSnake,LiquidSnake", itemAdalah.getDaftarTeman());

        assertNull(stat.data.get("dia"));
    }

}

Lalu untuk melakukan pengujian, saya mengerjakan task test di Gradle. Bila tidak ada masalah, saya akan menemukan pesan BUILD SUCCESSFUL. Saya juga bisa menemukan laporan HTML di folder build/reports/tests yang isinya akan terlihat seperti berikut ini:

Hasil laporan pengujian

Hasil laporan pengujian

Unit test dalam dijalankan dalam waktu singkat karena unit test tidak mengakses Graph API. Keakuratan sebuah unit test tentu saja ditentukan dari seberapa banyak kombinasi kasus yang diujikan.

Sekarang adalah saatnya menjalankan program dengan menggunakan task run. Program akan membutuhkan waktu lama tergantung pada jumlah teman dan jumlah status mereka serta kecepatan koneksi internet. Setelah selesai, program akan menghasilkan sebuah file berisi daftar kata dan tingkat frekuensinya. Saya kemudian dapat menganalisa file ini dengan membukanya di Excel.

Iklan

Memakai Reverse Ajax “Comet” Dengan Spring Web MVC 3.2

Hujan turun tanpa henti di malam yang ceriah ini.   Karena hari ini adalah hari terakhir di tahun 2012.  Dan tulisan ini akan menjadi tulisan terakhir di tahun 2012.  Selamat merayakan tahun baru dan selamat datang, 2013!  Kode program terakhir yang saya ubah di tahun 2012 berkaitan dengan sebuah aplikasi web yang saya buat dimana tampilannya seperti berikut ini:

Permintaan Perubahan

Permintaan Perubahan

Halaman tersebut menampilkan data termasuk status data yang ada. Perubahan status data, seperti “Belum Disajikan” menjadi “Sudah Disajikan” dilakukan oleh pengguna lain di komputer lain. Perubahan ini perlu diperbaharui oleh halaman tersebut.

Tetapi yang terjadi adalah bila pengguna membuka halaman ini, maka sampai selama-lamanya halaman akan terlihat sama, walaupun pengguna lain telah men-update status data.   Informasi yang ditampilkan menjadi tidak up-to-date lagi.

Lalu bagaimana cara memperbaharui tampilan? Pengguna bisa men-klik tombol Refresh atau menekan tombol F5!!   Bayangkan jika pengguna harus memantau halaman ini terus menerus selama jam kerja (misalnya seorang kasir), maka tombol F5 bisa jadi tombol yang paling cepat pudar di keyboard 😉

Lalu apakah ada solusi untuk membuat halaman ini terlihat lebih profesional dimana perubahan data oleh user lain bisa langsung diperbaharui?  Yup!  Saya bisa menggunakan apa yang disebut sebagai teknik Reverse Ajax.   Bila biasanya client yang duluan menghubungi server, maka pada Reverse Ajax, seolah-olah server yang akan menghubungi client.   Solusi lain adalah dengan memakai WebSocket (bagian dari HTML5) dengan syarat browser pengguna sudah mendukung.

Teknik Reverse Ajax terdiri atas polling, piggyback, dan Comet (long polling).

Polling adalah cara yang paling sederhana, yaitu dengan membuat sebuah timer JavaScript yang memeriksa perubahan data di server secara periodik. Cara ini akan sangat membebani server karena selalu ada data yang dikirim dari server biarpun tidak ada perubahan.

Piggyback akan mengembalikan status perubahan bersamaan dengan sebuah request normal. Misalnya, pada saat user men-klik sesuatu di halaman, sekalian ikut kembalian event perubahan.   Cara ini lebih hemat bandwidth dibandingkan dengan teknik polling. Kelemahannya adalah harus ada sebuah request yang normal terlebih dahulu, baru event perubahan yang terakumulasi di server dikirim balik bersamaan dengan reponse.   Saya tidak bisa menggunakan piggyback karena saya ingin halaman tetap diperbaharui biarpun pengguna hanya duduk diam menatap monitor.

Teknik Reverse Ajax yang lebih efisien adalah Comet atau sering disebut juga dengan long polling. Pada Comet, client akan melakukan request, tetapi server tidak akan langsung mengembalikan nilai.   Request ini akan terbuka untuk waktu yang lama.   Selama selang waktu tersebut, server dapat mengirim data ke client kapan saja.   Teknik ini adalah teknik yang paling efisien karena bandwidth hanya akan dipakai bila server perlu mengembalikan data ke client.

Saya akan memakai teknik Comet.   Untuk itu, saya harus memenuhi persyaratan, yaitu memiliki server yang mendukung asynchronous reponse.  Selain itu saya juga perlu memakai bahasa pemograman yang mendukung multithreading. Sebagai contoh, bila saya memakai Apache + PHP ‘murni’ dan berusaha menghentikan request dengan sleep(), maka selama thread di-sleep(), seluruh client lain tidak akan bisa mengakses web!!   Kinerja web malah akan jauh lebih buruk dibanding memakai teknik polling.  Server apa yang sudah mendukung asynchronous response?   Tomcat 6 ke atas, Jetty 6 ke atas, Glassfish dengan Grizzly, dan sebagainya.   Saya akan memakai Tomcat 7.

Apa contoh teknologi bahasa pemograman yang mendukung asynchronous response?   Servlet 3 di Java EE!   Ada juga framework yang mempermudah seperti CometD, DWR (Direct Web Remoting), dan Atmosphere.   Selain itu,  Spring Web MVC sejak versi 3.2 telah mendukung asynchronous response.   Karnea aplikasi saya dari awal dikembangkan dengan Spring Web MVC, maka yang perlu saya lakukan adalah men-upgrade versi Spring yang dipakai ke versi 3.2.   Dengan memakai Apache Maven, yang perlu saya lakukan hanya mengganti nilai property org.springframework-version menjadi 3.2.0.RELEASE, dan Maven akan mendownload JAR yang dibutuhkan.

Setelah men-upgrade versi Spring ke 3.2.0.RELEASE, saya perlu mengubah file web.xml. Perubahan yang saya lakukan adalah:

<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0">

Selain itu, saya perlu menambahkan <async-supported> di definisi org.springframework.web.servlet.DispatcherServlet milik Spring Web MVC seperti yang terlihat berikut ini:

<servlet>
    <servlet-name>appServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>    
    <init-param>      
      <param-name>contextConfigLocation</param-name>
      <param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
    </init-param>    
    <load-on-startup>1</load-on-startup>
    <async-supported>true</async-supported>
</servlet>

Karena saya memakai Spring Security yang mendefinisikan sebuah filter di web.xml yaitu org.springframework.web.filter.DelegatingFilterProxy, maka saya juga perlu menambahkan <async-supported> pada definisi filter tersebut. Berikut ini adalah perubahan yang saya lakukan:

<filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    <async-supported>true</async-supported>    
  </filter>
  <filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
    <dispatcher>REQUEST</dispatcher>
    <dispatcher>ASYNC</dispatcher>
  </filter-mapping>
</filter>

Setelah ini, saya bisa mulai membuat kode program.  Saya akan mulai dari service layer.  Saya sedikit frustasi karena tidak menemukan pendekatan yang lebih rapi yang tidak mengotori service layer dengan objek DeferredResult milik presentation layer.  Saya selalu berusaha menghindari coupling seperti ini, tapi sepertinya tidak ada pilihan yang lebih gampang.   Pada service layer, saya mendefisinikan sebuah  struktur untuk menampung DeferredResult yang mewakili asynchronous request seperti berikut ini:

private List<DeferredResult<Boolean>> listDeferredResult = new Vector<DeferredResult<Boolean>>();

Saya memakai Vector karena class tersebut thread safe.  Pada asynchronous request yang diberikan halaman kasir, saya hanya mengembalikan sebuah nilai true bila halaman perlu diperbaharui atau false bila tidak.  Hal ini karena tabel dibuat dengan jqGrid dan saya tidak ingin repot mengelola data.

Pada satu atau lebih service yang menyebabkan perubahan di halaman kasir, saya memberikan kode program seperti berikut ini:

public Pemesanan prosesTambahPemesanan(Pemesanan pemesanan) {
   ...
   prosesNotifikasiPembayaran();
}

public Pemesanan prosesUpdatePemesanan(Pemesanan pemesanan) {
   ...
   prosesNotifikasiPembayaran();
}

private void prosesNotifikasiPembayaran() {
   for (DeferredResult<Boolean> deferredResult: listDeferredResult) {
      deferredResult.setResult(Boolean.TRUE);
   }
}

Method prosesNotifikasiPembayaran() akan menyebabkan asynchronous response selesai dengan nilai kembali berupa true.

Tidak lupa saya juga menyediakan method untuk mendaftar dan menghapus setiap DeferredResult yang ada di Vector, seperti berikut ini:

public void daftarNotifikasiPembayaran(DeferredResult<Boolean> deferredResult) {
  listDeferredResult.add(deferredResult);
}

public void hapusNotifikasiPembayaran(DeferredResult<Boolean> deferredResult) {
  listDeferredResult.remove(deferredResult);
}

Ok, service layer sudah selesai, sekarang saya akan ke presentation layer.  Saya menambahkan method berikut ini di controller:

@RequestMapping(value="pembaharuan")
@ResponseBody
public DeferredResult<Boolean> notifikasiPembayaran() {
  final DeferredResult<Boolean> deferredResult = new DeferredResult<Boolean>(30000l, Boolean.FALSE);
  pemesananService.daftarNotifikasiPembayaran(deferredResult);
  deferredResult.onCompletion(new Runnable() {
    @Override
    public void run() {
      pemesananService.hapusNotifikasiPembayaran(deferredResult);
    }
  });
  return deferredResult;
}

Pada kode program controller tersebut, saya memberikan waktu tunggu 30.000 ms atau 30 detik.  Bila setelah 30 detik, belum ada yang memanggil method DeferredResult.setResult(), maka nilai false akan dikembalikan.

Langkah terakhir, menambahkan kode program ini di view JSP:

...
<spring:url value="/pembayaran/pembaharuan" var="urlPembaharuan" />
...
function comet() {
  $.getJSON("${urlPembaharuan}", function(data) {
     if (data==true) {
        gridUtama.trigger('reloadGrid');
     }
     comet();
  });
}
...

Pada kode program di atas, bila hasil asynchronous request adalah true, maka dialog jqGrid akan di-refresh.   Setelah itu, tidak peduli nilai kembali adalah true atau false, fungsi tersebut akan kembali memanggil dirinya sendiri.

Untuk mencobanya, saya membuka tiga browser yang berbeda, dimana pada 2 browser, saya membuka halaman untuk kasir.  Satu browser-nya lagi untuk perubahan yang harus di-update oleh halaman kasir, seperti pada gambar berikut ini:

Pengujian

Pengujian

Pada gambar di atas, pada saat tombol “Iya” di-klik, maka tabel di browser Kasir A maupun browser Kasir B akan di-reload secara otomatis.

Teknik Comet (long polling) memang agak mirip polling, dimana client harus melakukan request secara periodik.  Tetapi penggunaan asynchronous request membuat Comet jauh lebih efisien dibanding polling:

  1. Pada  polling,  bila interval adalah 30 detik, maka client benar-benar harus menunggu selama 30 detik baru ada hasil yang diperoleh.  Pada Comet, bila interval adalah 30 detik, lalu pada detik ke-15 sudah ada event, maka hasil akan segera dikembalikan pada saat itu juga.
  2. Karena alasan di atas, untuk memperoleh hasil yang real-time, maka polling biasanya memiliki interval yang singkat, misalnya 10ms.   Hal ini menyebabkan semakin banyak request yang dilakukan ke server secara periodik dan akan membebani server.

Memakai Jetty sebagai embedded web server

Jetty adalah sebuah web server yang unik.  Hal ini karena Jetty dapat dipakai sebagai embedded web server.  Apa maksudnya?  Biasanya pada aplikasi web, program yang telah kita buat perlu di-‘copy‘ ke sebuah container (web server).  Tapi embedded web server adalah kebalikannya.   Aplikasi kita didalamnya telah mengandung web server, sehingga kita tidak perlu memindahkan kode program ke web server lagi.  Lalu apa gunanya? Embedded web server dapat dipakai pada pengujian aplikasi dimana aplikasi akan dijalankan  pada embedded web  server yang tidak membutuhkan banyak setup serta tidak ‘berat.

Sebagai contoh, saya akan menggunakan Jetty untuk front-end testing secara otomatis.  Proyek saya berjalan pada Cloud Foundry yang memakai Tomcat 6.  Dengan demikian, versi Jetty yang paling mendekati adalah Jetty 7 yang masih memakai spesifikasi Servlet 2.5 sama seperti Tomcat 6.  Sebagai informasi, Jetty sudah mencapai versi 9 yang mendukung spesifikasi Servlet 3.0 seperti Tomcat 7.

Karena memakai Apache Maven, saya dapat menambahkan dependency ke Jetty secara mudah.  Agar mudah, saya menambahkan seluruh JAR yang dibutuhkan (versi aggregate), dengan informasi seperti berikut ini:

Group Id:  org.eclipse.jetty.aggregate
Artifact Id: jetty-all
Version: 7.6.7.v20120910
Scope: test

Disini saya memakai scope test,  yang menunjukkan bahwa artifact JAR Jetty hanya dibutuhkan untuk pengujian saja, bukan untuk menjalankan aplikasi saya.

Saya juga memastikan bahwa servlet-api dan jsp-api sudah ada di dependency Maven:

Group Id: javax.servlet
Artifact Id: servlet-api
Version: 2.5
Scope: provided

Group Id: javax.servlet.jsp
Artifact Id: jsp-api
Version: 2.1
Scope: provided

Saya memakai scope provided, yang menunjukkan bahwa kedua artifact tersebut dibutuhkan untuk melakukan kompilasi kode program, tetapi tidak perlu disertakan sebagai bagian dari proyek karena mereka telah disediakan oleh server.

Dan terakhir, karena implementasi JSP yang saya pakai adalah Jasper,  saya perlu menambahkan artifact Juli (untuk logging di Tomcat) pada dependency Maven:

Group Id: org.apache.tomcat
Artifact Id: juli
Version: 6.0.36
Scope: test

Setelah memastikan  Maven telah men-download semua artifact JAR yang dibutuhkan, saya bisa membuat kode program.  Sebagai contoh, berikut ini adalah penggalan kode program yang menjalankan Jetty sebagai embedded web server:

...
Server server = new Server (8080);
WebAppContext context = new WebAppContext();
context.setDescriptor("../FolderProyek/src/main/webapp/WEB-INF/web.xml");
context.setResourceBase("../FolderProyek/src/main/webapp");
context.setContextPath("/proyek");
context.setParentLoaderPriority(true);
server.setHandler(context);
server.start();
...

Semua proyek web Java selalu memiliki file web.xml.  Yang saya lakukan adalah memberitahu lokasi web.xml yang akan dijalankan pada Jetty.  Selain itu, saya juga memberi tahu lokasi folder yang berisi file HTML, JSP, dan sebagainya (resource base).  Dengan setContextPath(“/proyek”) maka Jetty dapat dipanggil dengan URL seperti http://localhost:8080/proyek.

Sekarang, bila kode program di atas selesai dikerjakan, saya dapat langsung membuka http://localhost:8080/proyek tanpa perlu meng-install  server seperti Tomcat atau GlassFish.

Proyek lain yang cukup menarik adalah Cargo (informasi ada di http://cargo.codehaus.org).  Dengan Cargo, saya bisa memberikan deskripsi server yang akan saya pakai  (misalnya Tomcat, Jetty, GlassFish, dan sebagainya) di kode program.  Nantinya, Cargo yang akan men-download dan men-install server yang dibutuhkan bila belum ada di komputer dimana program saya berjalan, kemudian Cargo akan memindahkan hasil build program secara otomatis.  Jadi bila saya ingin beralih dari Tomcat ke GlassFish, saya hanya perlu mengubah deskripsi server yang saya pakai.  Contoh kegunaan Cargo adalah pada server Continous Integration (CI).  Server CI akan men-checkout hasil kerja masing-masing programmer yang telah digabungkan, lalu menjalankan function testing secara otomatis.  Server CI bekerja secara periodik dan otomatis sehingga bila ditemui kesalahan, developer akan secepat mungkin mendapat notifikasi.

Mengakses Web Services Dari Aplikasi Java Micro Edition (Mobile)

Artikel ini adalah request dari Erna

Seorang mahasiswi bertanya bagaimana cara mengakses database yang ada di server melalui aplikasi Java Micro Edition (JME) di ponsel.  Salah satu cara yang dapat ditempuh adalah dengan menggunakan web services.  Pada tulisan ini, saya akan memakai WS-* Web Services.  Alternatif lain adalah memakai web service RESTfulyang lebih ringan.

Membuat Web Service dengan Java Di Sisi Server

Web Service akan dijalankan pada komputer server yang terhubung dengan database.   Saya akan menggunakan NetBeans IDE. Langkah pertama adalah memilih menu File, New Project…  Kemudian pilih Java Web, Web Application.  Klik tombol Next. Berikan sebuah nama proyek seperti LatihanWebService, kemudian klik tombol Next.

Pada langkah berikutnya, saya memakai server GlassFish Server 3.1.2 dan versi Java EE 6 Web.  Saya membiarkan context path saya berupa /LatihanWebService.  Karena saya tidak akan memakai framework, maka saya langsung men-klik tombol Finish.

Klik kanan pada nama proyek, kemudian pilih New, Web Service…  Pada Web Service Name, isi dengan AksesDatabase.  Kemudian pada package, isi dengan co.id.jocki.webservice.  Kemudian klik tombol Finish.

Pada nama proyek, akan ada folder Web Services yang didalamnya terdapat node AksesDatabase.  Klik kanan pada AksesDatabase, kemudian pilih Add Operation…  Isi Name dengan lihatIsiTabel, dan isi Return Type dengan java.util.ArrayList<String>.  Klik tombol OK.

Saya akan menambah sebuah operasi lagi.  Untuk itu, saya kembali men-klik kanan pada AksesDatabase, kemudian saya memilih Add Operation…  Kali ini saya mengisi dialog yang muncul sehingga terlihat seperti berikut ini:

Membuat operasi Web Service

Membuat operasi Web Service

Klik tombol OK untuk menutup dialog.

Karena saya akan memakai database MySQL, saya perlu menambahkan MySQL connector terlebih dahulu.  Saya men-klik kanan pada nama proyek (LatihanWebService), lalu memilih Properties.  Kemudian saya memilih Libraries, Add Library…  Pada pilihan yang muncul, saya memilih MySQL JDBC Driver, kemudian saya men-klik tombol Add Library.  Klik tombol OK untuk menutup dialog.

Jika saya men-double klik pada AksesDatabase di Projects, NetBeans akan memunculkan kode program dimana saya bisa menuliskan implementasi.  Saya akan memakai kode program Java 7, sehingga untuk menjalankan program ini dibutuhkan Java JDK minimal versi 7.  Java versi terbaru dapat di-download di http://www.oracle.com/technetwork/java/javase/downloads/index.html.  Selain itu, saya men-klik kanan nama proyek, memilih Properties.  Pada Sources, saya memastikan bahwa JDK 7 sedang dipilih di bagian Source/Binary Format.

Berikut ini adalah isi file AksesDatabase.javayang telah saya modifikasi:

package co.id.jocki.webservice;
import com.mysql.jdbc.jdbc2.optional.MysqlConnectionPoolDataSource;
import java.sql.*;
import java.util.ArrayList;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebService;

@WebService(serviceName = "AksesDatabase")
public class AksesDatabase {
  private MysqlConnectionPoolDataSource dataSource;

  public AksesDatabase() {
    dataSource = new MysqlConnectionPoolDataSource();
    dataSource.setUser("jocki");
    dataSource.setPassword("password");
    dataSource.setURL("jdbc:mysql://localhost:3306/latihan"); 
  }

  private void buatTabelBilaBelumAda(Connection cn) throws SQLException {
    ResultSet rs = cn.getMetaData().getTables(null, null, "namatabel", null);
    if (rs.next()==false) {
      // Tidak ada tabel dengan nama 'namatabel', maka buat tabel tersebut
      cn.createStatement().execute("CREATE TABLE namatabel (data VARCHAR(255))");
    }
  }
  @WebMethod(operationName = "lihatIsiTabel")
  public ArrayList<String> lihatIsiTabel() {
    ArrayList<String> lstReturn = new ArrayList<>(); 
    try (Connection cn = dataSource.getConnection()) {
      buatTabelBilaBelumAda(cn);
      Statement stmt = cn.createStatement();
      ResultSet rs = stmt.executeQuery("SELECT data FROM namatabel");
      while (rs.next()) {
        lstReturn.add(rs.getString("data"));
      }
    } catch (Exception ex) {
      System.out.println("Kesalahan [" + ex.getMessage() + "]");
    } 
    return lstReturn;
  }
  @WebMethod(operationName = "tambah")
  public boolean tambah(@WebParam(name = "nilai") String nilai) { 
    try (Connection cn = dataSource.getConnection()) {
      buatTabelBilaBelumAda(cn);
      PreparedStatement ps = cn.prepareStatement("INSERT INTO namatabel VALUES (?)");
      ps.setString(1, nilai);
      ps.executeUpdate();
    } catch (Exception ex) {
      System.out.println("Kesalahan [" + ex.getMessage() + "]");
      return false;
    }
    return true;
  }
}

Pada kode program di atas, saya mengandaikan bahwa nama user MySQL adalah jocki dan password-nya adalah password.  Bila di database tidak ada user lain, bisa juga menggunakan nama user root dan password dikosongkan (“” / string kosong).   Saya juga mengandaikan nama database yang diakses adalah latihan (ini harus sesuai dengan nama database yang ada di MySQL).

Jalankan web services dengan men-klik kanan pada nama proyek LatihanWebService kemudian memilih menu Run.  Jangan matikan server ini karena nanti akan dipanggil oleh aplikasi JME.

Untuk informasi lebih lanjut mengenai bagaimana cara membuat web services atau ingin membuat implementasi web services dalam bahasa lain, baca artikel yang saya tulis di https://thesolidsnake.wordpress.com/2012/07/04/membuat-web-service-soap-dengan-php-dan-memanggilnya-di-client-java/ , https://thesolidsnake.wordpress.com/2012/05/26/memanggil-web-service-dari-server-java-ee-di-client-php/, atau https://thesolidsnake.wordpress.com/2012/07/08/perbandingan-web-service-soap-antara-java-ee-vs-php/ .

Membuat Aplikasi JME Sebagai Client

Saya akan membuat sebuah aplikasi JME dengan memilih menu File, New Project…  Lalu memilih Java ME, Mobile Application.  Pada Project Name, saya memberi nama LatihanClient.  Saya menghilangkan tanda centang di bagian Create Hello Midlet.  Kemudian saya men-klik tombol Next.  Pada Emulator Platform, saya akan memiliki CLDC Oracle Java(TM) Platform Micro Edition SDK 3.0.5.  Lalu pada Device, saya memilih DefaultCldcMsaPhone1.   Saya memastikan Device Configuration bernilai CLDC-1.1 dan Device Profile bernilai MIDP-2.1.  Kemudian saya men-klik Finish untuk membuat proyek tersebut.

Saya membuat sebuah MIDlet baru dengan men-klik kanan pada nama proyek, kemudian memilih New, MIDlet…  Pada MIDlet Name, saya mengisi LatihanMidlet.  Pada Package, saya mengisi co.id.jocki.midlet.  Lalu saya men-klik tombol Finish.

Langkah berikutnya adalah membuat stub untuk web services.  Saya mulai dengan men-klik kanan pada nama proyek, memilih New, Others…  Pada MIDP, saya memilih Java ME Web Service Client dan men-klik tombol Next. Pada langkah Java ME Web Service Client Information, pilih Running Web Service,  isi WSDL URL dengan http://localhost:8080/LatihanWebService/AksesDatabase.    Ini adalah URL web service yang telah saya buat sebelumnya.  Lalu klik tombol Retrieve WSDL.  Jika tidak terjadi kesalahan, NetBeans akan mengisi field yang ada secara otomatis seperti yang terlihat pada gambar berikut ini:

Menambah Stub WS ke Java ME

Menambah Stub WS ke Java ME

Klik tombol Finish untuk selesai.

Sekarang, di proyek JME LatihanClient, akan ada package AksesDatabase yang dibuat secara otomatis.  Yang perlu saya lakukan sekarang adalah menambahkan kode program pada LatihanMiddlet.java yang akan memakai stub tersebut untuk mengakses web services.

Berikut ini adalah isi LatihanMiddlet.javayang telah saya modifikasi:

package co.id.jocki.midlet;

import aksesdatabase.AksesDatabase;
import aksesdatabase.AksesDatabase_Stub;
import java.rmi.RemoteException;
import javax.microedition.lcdui.*;
import javax.microedition.midlet.MIDlet;

public class LatihanMidlet extends MIDlet {

    private List listData;
    private Form frmTambah;
    private TextField txtData;
    private Command cmdSimpan;
    private Command cmdTambah;

    public void startApp() {
        if (listData==null) {            
            listData = new List("Data Di Database", List.EXCLUSIVE);            
            cmdTambah = new Command("Tambah", Command.ITEM, 0);            
            listData.addCommand(cmdTambah);
            listData.setCommandListener(new CommandListener() {

                public void commandAction(Command c, Displayable d) {
                    if (c==cmdTambah) {
                        Display.getDisplay(LatihanMidlet.this).setCurrent(frmTambah);
                    }
                }

            });

            frmTambah = new Form("Akses Database");
            txtData = new TextField("Data", null, 20, TextField.ANY);
            frmTambah.append(txtData);
            cmdSimpan = new Command("Simpan", Command.OK, 0);
            frmTambah.addCommand(cmdSimpan);
            frmTambah.setCommandListener(new CommandListener() {

                public void commandAction(Command c, Displayable d) {
                    if (c==cmdSimpan) {
                        String data = txtData.getString();
                        simpanDataKeDatabase(data);
                        getDataDariDatabase();
                        Display.getDisplay(LatihanMidlet.this).setCurrent(listData);
                    }
                }

            });

        }
        getDataDariDatabase();
        Display.getDisplay(this).setCurrent(listData);
    }        

    private void getDataDariDatabase() {
        AksesDatabase aksesDatabase = new AksesDatabase_Stub();
        listData.deleteAll();
        try {
            String[] data = aksesDatabase.lihatIsiTabel();
            for (int i=0; i<data.length; i++) {
                listData.append(data[i], null);
            }
        } catch (RemoteException ex) {            
            listData.append("Kesalahan Akses Data [" + ex.getMessage() + "]", null);
        }
    }

    private void simpanDataKeDatabase(String data) {
        AksesDatabase aksesDatabase = new AksesDatabase_Stub();
        try {
            aksesDatabase.tambah(data);
        } catch (RemoteException ex) {
            listData.append("Kesalahan Simpan Data [" + ex.getMessage() + "]", null);
        }
    }

    public void pauseApp() {
    }

    public void destroyApp(boolean unconditional) {
    }
}

Sekarang, saya akan mencoba menjalankan aplikasi JME tersebut dengan men-klik icon Run (F6).  Tampilan awal adalah sebuah List yang harusnya berisi isi tabel (saat ini masih kosong karena belum ada data):

Tampilan Awal (Isi Tabel Masih Kosong)

Tampilan Awal (Isi Tabel Masih Kosong)

Pilih menu “Tambah” (dengan left soft key/tombol untuk menu kiri).  Akan muncul frmTambah yang berisi sebuah TextField.  Saya mengisi TextField ini dengan jocki, kemudian memilih menuSimpan:

Menambah Data Baru

Menambah Data Baru

Hal ini akan menyebabkan nilai “jocki” disimpan di tabel database.   Sekarang saat List mengambil data dari tabel di database, ia akan menemukan sebuah data:

Data Diambil Dari Database

Data Diambil Dari Database

Seandainya saya menambah sebuah data baru dengan nilai “keren”, isi List akan terlihat seperti:

Mengambil Data Dari Database

Mengambil Data Dari Database

Bila ingin lebih yakin lagi, silahkan periksa isi tabel di database dan pastikan terdapat dua record dengan nilai “jocki” dan “keren”.

Hosting JSP Gratis Dengan Google App Engine

Google App Engine adalah layanan hosting aplikasi web  dari Google. Layanan ini bersifat gratis (dengan keterbatasan, yang tentunya bisa dihindari bila membayar) . Untuk menggunakannya, saya harus mendaftar terlebih dahulu di http://appengine.google.com. Setelah mendaftar, saya perlu membuat sebuah aplikasi baru dengan men-klik pada tombol Create Application. Google akan meminta verifikasi SMS terlebih dahulu. Setelah itu, saya memilih sebuah nama unik untuk aplikasi saya, misalnya latihan-java, seperti yang terlihat di gambar berikut ini:

Membuat Aplikasi Baru Di Situs Google App Engine

Membuat Aplikasi Baru Di Situs Google App Engine

Langkah berikutnya adalah menyiapkan lingkungan development untuk membuat kode program. Saya memakai Eclipse IDE for Java EE Developers versi 4.2 (Eclipse Juno) yang dapat di-download di alamat http://www.eclipse.org/downloads. Setelah selesai di-download, saya men-extract file eclipse-jee-juno-win32.zip ke C:\ setelah akan terdapat folder C:\eclipse\. Di dalam folder ini, ada file eclipse.exe yang dapat langsung dijalankan. Setelah men-double click file eclipse.exe, logo Eclipse 4.2 yang berkilau akan muncul menandakan bahwa Eclipse sedang ‘loading‘.

Setelah ini, saya perlu menambahkan plugin Google ke dalam Eclipse. Langkah detail dapat ditemukan di https://developers.google.com/eclipse/docs/install-eclipse-4.2. Secara garis besar, yang perlu saya lakukan di Eclipse adalah:

  1. Membuka menu Help, Install New Software.
  2. Men-klik tombol Add… yang ada disamping Work with:
  3. Saya mengisi Name dengan Google App Engine dan mengisi Location dengan http://dl.google.com/eclipse/plugin/4.2
  4. Setelah menambahkan lokasi baru, di Work with, saya memilih lokasi Google App Engine yang saya tambahkan di lokasi sebelumnya.
  5. Saya memberi centang pada Google Plugin for Eclipse (required) dan SDKs.
  6. Setelah menyetujui lisensi dengan memilih I accept the terms of the license agreements, Eclipse akan men-download file yang dibutuhkan. Ukuran file sekitar lebih dari 180 MB.
  7. Setelah selesai melakukan instalasi, Eclipse akan memberikan pesan untuk melakukan restart.  Saya langsung men-klik tombol Restart Now.

Berikutnya, saya akan membuat sebuah proyek baru di Eclipse dengan memilih File, New, Web Application Project. Pada Project name, saya mengisi dengan latihan. Pada package, saya mengisi dengan co.id.jocki. Saya juga menghilangkan tanda centang di Use Google Web Toolkit serta Generate project sample code, seperti yang terlihat di gambar berikut ini:

Membuat Proyek App Engine Baru

Membuat Proyek App Engine Baru

Pada proyek yang dibuat oleh Eclipse, terdapat folder src dan war.  Folder src adalah tempat saya meletakkan kode program Java.   Sementara itu, folder war adalah tempat saya meletakkan file HTML, CSS, gambar, termasuk file JSP.

Untuk membuat sebuah file JSP di folder war, saya men-klik kanan pada folder war, kemudian memilih New, Other. Pada dialog yang muncul, saya memilih New, JSP File. Lalu saya men-klik Next. Saya mengisi latihan.jsp pada File name, kemudian saya men-klik tombol Finish.

Struktur proyek akan terlihat seperti berikut:

Struktur Proyek App Engine

Struktur Proyek App Engine

Saya melakukan sedikit modifikasi sehingga isi file latihan.jsp terlihat seperti berikut ini:

<%@page import="java.util.Enumeration"%>
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
    <title>Latihan</title>
  </head>

  <body>
    <h1>Nama Servlet: <%=config.getServletName() %></h1>
    <h1>Informasi Context</h1>
    <p>Context Path: <%=config.getServletContext().getContextPath() %></p>
    <p>Versi Servlet: <%=config.getServletContext().getMajorVersion() %>.<%=config.getServletContext().getMinorVersion() %></p>
    <p>Server Info: <%= config.getServletContext().getServerInfo() %>
    <p>Servlet Context Name: <%=config.getServletContext().getServletContextName() %></p>

    <h1>Informasi Request</h1>
    <p>Path Info: <%=request.getPathInfo() %></p>
    <p>Path Translated: <%=request.getPathTranslated() %></p>
    <p>Query String: <%=request.getQueryString() %></p>
    <p>Alamat Kamu: <%=request.getRemoteAddr() %></p>
    <p>Host Kamu: <%=request.getRemoteHost() %></p>
    <p>Port Kamu: <%=request.getRemotePort() %></p>

    <h1>Informasi Request Dari Kamu</h1>
    <%
      Enumeration enumHeader = request.getHeaderNames();
      while (enumHeader.hasMoreElements()) {
       String headerName = (String) enumHeader.nextElement();
       out.println("<p>" + headerName + ": " + request.getHeader(headerName)+"</p>");
      }
    %>
  </body>
</html>

Untuk  menjalankan halaman JSP  ini di komputer lokal, saya men-klik kanan di area editor latihan.jsp lalu memilih menu Run As, Web Application. Window Console akan terbuka dan saya akan menemukan tulisan seperti:

INFO: The server is running at http://localhost:8888/
...
INFO: The admin console is running at http://localhost:8888/_ah/admin

Saya kemudian mem-browse alamat  http://localhost:8888/latihan.jsp untuk melihat seperti apa tampilan JSP pada saat dijalankan.   Setelah memastikan bahwa file JSP dapat ditampilkan dengan baik di komputer lokal, saya perlu memindahkan (deploy) proyek ini ke server Google.  Tujuannya tentu saja agar halaman JSP ini dapat diakses oleh banyak orang.

Saya memulai proses deploy dengan memilih drop-down di GDT pull-down, dan men-klik pilihan Deploy to App Engine… seperti pada gambar berikut ini:

Men-deploy proyek ke App Engine

Men-deploy proyek ke App Engine

Bila ini adalah pertama kalinya saya melakukan proses deploy,  Eclipse akan memunculkan sebuah dialog untuk mengisi nama user dan password account Google App Engine.  Ia akan meminta izin, dan saya perlu memberikan kewenangan dengan men-klik tombol Izinkan Akses.   Setelah proses login selesai,  saya dapat melihat status apakah telah sign-in atau tidak, dengan melihat indikator di kanan bawah layar.  Bila seandainya saya ingin mengganti ke account lain, saya perlu men-klik indikator ini.

Seandainya saya telah sign-in ke Google App Engine di Eclipse, maka pada saat melakukan proses deploy akan muncul sebuah kotak dialog Deploy dengan pesan latihan does not have an application ID.   Untuk memperbaikinya, saya men-klik pada link App Engine project settings…. Pada dialog yang muncul, saya mengisi application ID sesuai dengan yang sebelumnya saya buat di halaman administrasi Google App Engine, misalnya latihan-java, seperti pada gambar berikut ini:

Pengaturan proyek App Engine

Pengaturan proyek App Engine

Di dialog Deploy akan ada status Ready to deploy application ‘latihan-java’, version 1.   Ini menandakan bahwa saya bisa  memulai proses pemindahan isi proyek ke server Google dengan men-klik tombol Deploy.  Eclipse akan menampilkan progress selama proses berlangsung.

Setelah  selesai, saya dapat mencoba melihat isi file JSP saya dengan membuka URL: http://[application_id].appspot.com/latihan.jsp.

Bila application id saya adalah latihan-java, maka saya harus membuka URL berikut:

http://latihan-java.appspot.com/latihan.jsp

Memanggil web service dari server Java EE di client PHP

Seorang mahasiswa yang sedang membuat skripsi bertanya apakah mungkin membuat sebuah web service dengan Java Enterprise Edition kemudian memanggilnya di client PHP?  Pada kesempatan tatap muka yang singkat, saya menjawab secara ringkas dengan merujuk pada definisi web services. Web services adalah sistem yang dirancang secara khusus untuk mendukung interaksi pertukaran data mesin ke mesin melalui jaringan. Selama berkomunikasi melalui metode yang sama, maka proses komunikasi dapat terjadi tanpa membedakan OS maupun bahasa pemograman yang dipakai.   Saat ini ada dua jenis web services, yaitu yang berbasis REST dan berbasis SOAP. Web services berbasis REST cenderung lebih ringan dan lebih mudah dipelajari dibandingkan dengan SOAP. Hal ini menyebabkan terjadinya peralihan dari web services berbasis SOAP ke REST.

Pada kesempatan tertulis ini, saya akan memberikan sebuah contoh halaman PHP yang memanggil web services yang dibuat dengan Java Enterprise Edition. Metode yang dipergunakan adalah metode berbasis SOAP.

Saya akan membuat dua proyek, yaitu:

  1. Server web service yang menggunakan teknologi Java Enterprise Edition. Saya menggunakan NetBeans IDE dan GlassFish bawaannya.
  2. Sebuah halaman PHP yang mengakses layanan web service yang disediakan oleh server di atas.

Membuat Server Web Service dengan Java EE

Langkah-langkah yang saya lakukan adalah:

  1. Memilih menu File, New Project di NetBeans IDE. Kemudian saya memilih Java Web di Categories, dan Web Application di Projects. Setelah memberi nama proyek dan menentukan lokasi penyimpanan, saya men-klik tombol Finish.
  2. Men-klik kanan nama proyek, kemudian memilih menu New, Other... Pada dialog yang muncul, saya memilih Web Services di bagian Categories, dan Web Service di bagian File Typesseperti yang terlihat di gambar berikut ini:

    Membuat Web Services Baru

    Membuat Web Services Baru

  3.  Pada Web Service Name, saya mengisi dengan nama PerhitunganWS. Pada bagian package, saya mengisi dengan nama package co.id.jocki.ws. Setelah itu, saya men-klik tombol Finish.

NetBeans akan membuat sebuah class baru yang berada di folder Web Services seperti yang terlihat pada gambar berikut ini:

Web Services Di Tampilan Project

Web Services Di Tampilan Project

Bila class PerhitunganWS di-buka, NetBeans memungkinkan pengguna untuk melihat dalam bentuk Source atau Design. Bila tampilan Design dipakai, maka layar editor akan terlihat seperti pada gambar berikut ini:

NetBeans Web Service Editor

NetBeans Web Service Editor

Untuk menambahkan sebuah operasi baru, saya melakukan langkah-langkah seperti berikut ini:

  1. Klik tombol Add Operation… Akan muncul sebuah dialog baru.
  2. Untuk menambah parameter, saya dapat men-klik tombol Add. Saya mengisi dialog tersebut seperti yang terlihat pada gambar berikut ini:

    Menambah Operasi Baru Di Web Service

    Menambah Operasi Baru Di Web Service

  3. Setelah itu, saya men-klik tombol OK.

Setelah ini, saya perlu menambahkan kode program yang berisi proses untuk operasi baru tersebut.   Saya men-klik Source di toolbar untuk beralih ke tampilan kode program.   Kemudian, saya mengubah satu-satunya baris di method tambah() menjadi return angka1 + angka2; seperti yang terlihat di gambar berikut ini:

Kode Program Operasi Di Web Service

Kode Program Operasi Di Web Service

Untuk menguji web service tersebut, klik kanan pada nama class PerhitunganWS, kemudian memilih Test Web Service seperti yang terlihat di gambar berikut ini:

Menguji Web Services

Menguji Web Service

NetBeans akan menjalankan browser yang berisi web service tester. Di halaman ini terdapat sebuah link bertuliskan WSDL File. URL disini nantinya akan dipakai oleh client web service. Di percobaan saya, nilai URL ini adalah http://localhost:8080/ServerWebService/PerhitunganWS?WSDL. Saya akan men-copy lokasi URL ini untuk dipakai di PHP nantinya.

Pada bagian methods, saya mencoba mengisi parameter dengan nilai 10 dan 20, kemudian setelah men-klik tombol tambah (nama method), akan muncul halaman tambah Method invocation. Pastikan pada halaman tersebut, terdapat tulisan Method returned int: “30”.

Sekarang server web service telah selesai dibuat. Saya tidak mematikan GlassFish server di NetBeans karena pada langkah berikutnya, saya akan memanggil web service ini di PHP.

Membuat Client Web Service dengan PHP

Sebelum mulai membuat client web service di PHP, saya memastikan apakah extension SOAP telah diaktifkan di PHP saya. Yang saya lakukan adalah membuat sebuah file PHP dengan nama info.php dimana isinya adalah <?php phpinfo(); ?>. Saat menampilkan halaman tersebut di browser, saya memastikan bahwa terdapat baris Soap Client dengan nilai Enabled.

Untuk membuat client web service, saya perlu membuat sebuah object dari class SoapClient seperti pada baris berikut ini:

$client = new SoapClient("http://localhost:8080/ServerWebService/PerhitunganWS?WSDL");

Nilai dari parameter constructur SoapClient adalah URL yang merujuk ke lokasi file WSDL. URL ini dapat dilihat di halaman web service tester pada saat saya menguji server web service di NetBeans.

Secara utuh, kode program PHP yang saya buat adalah:

<?php
function errorHandler($errno, $errstr, $errfile, $errline, array $errcontext) {
print "<h3>Terjadi kesalahan/peringatan:</h3>";
print "Baris $errline [$errstr]";
exit;
}

set_error_handler('errorHandler');

$client = new SoapClient("http://localhost:8080/ServerWebService/PerhitunganWS?WSDL");
$daftarOperasi = $client->__getFunctions();
print "<h3>Daftar Operasi Yang Tersedia Di Server WS:</h3>";
foreach ($daftarOperasi as $operasi) {
print "<p>$operasi</p>";
}
$hasil = $client->tambah(array('angka1'=>20, 'angka2'=>10));
print "<h3>Hasil operasi hitung(10,20): " . $hasil->return . "</h3>";
?>

<!--?php 

Pada kode program tersebut, saya menyertakan pemeriksaan kesalahan dengan set_error_handler(). Bagian tersebut dapat digantikan dengan menggunakan try/catch. Akan tetapi bila menggunakan try/catch, hanya pesan kesalahan yang ditampilkan, sementara pesan warning tidak akan ditampilkan. Bila pengaturan penampilan pesan kesalahan & warning secara otomatis tidak dimatikan (nilai display_errors di file konfigurasi adalah stdout), maka bagian ini tidak diperlukan.

Karena server web service di GlassFish menyediakan file WSDL, maka saya dapat menggunakan method __getFunctions() untuk melihat operasi apa saja yang disediakan oleh server web service tersebut.

Untuk memanggil salah satu operasi yang ada, saya cukup memanggil method dengan nama yang bersesuaian, diikuti dengan parameter yang diletakkan dalam associative array. Sebagai contoh, di kode program di atas, saya memanggil operasi tambah dengan nilai parameter angka1 berupa 20 dan nilai angka2 berupa 10. Hasil yang dikembalikan dari server web service adalah angka 30.

Membuat Album Foto Dengan PrimeFace Di NetBeans 7

PrimeFaces adalah salah satu framework ‘tampilan’ yang kompatibel dengan Java Server Faces 2.0.  Framework ini sudah di-bundle di NetBeans 7 sehingga pengguna NetBeans tidak perlu lagi men-download sendiri.  Artikel ini akan menunjukkan bagaimana memakai PrimeFaces di NetBeans 7.  Artikel ini hanya menunjukkan pemakaian komponen <galleria>.  Seluruh komponen yang disediakan oleh PrimeFaces dapat dilihat di show case yang berada di http://www.primefaces.org/showcase/ui

Langkah pertama yang dilakukan adalah membuat sebuah project baru di NetBeans 7, dengan memilih menu File, New Project.  Kemudian pilih Java Web, Web Application, seperti yang terlihat di gambar berikut:

Klik tombol Next, isi informasi project seperti Project Name dan Project Location.  Klik tombol Next lagi untuk membuka halaman Server and Settings.  Pada halaman ini, pilih Server yang akan dipakai.

Setelah itu, klik tombol Next, halaman Frameworks akan muncul.  Beri tanda centang pada JavaServer Faces.  Kemudian pada tab Components, pilih PrimeFaces 2.2.1 di Components Suite seperti yang terlihat pada gambar berikut:

Memilih PrimeFaces Pada Saat Membuat Project Web Baru
Setelah itu, klik tombol Finish.  Sebuah facelet dengan nama index.xhtml akan dibuat.  Kalau diperhatikan, facelet tersebut secara otomatis akan menyertakan namespace PrimeFaces, seperti yang terlihat di bagian berikut:

<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:p="http://primefaces.prime.com.tr/ui"
xmlns:h="http://java.sun.com/jsf/html">

Web site yang akan dibuat pada artikel ini tidak menyediakan fungsi upload, sehingga diasumsikan gambar sudah berada di lokasi yang ditentukan.  Sebagai informasi, folder yang dipakai adalah folder dengan nama gambar.  Buat sebuah folder baru di Web Pages dengan klik kanan dan memilih New, Other… Pada dialog yang muncul, pilih Other, Folder. Pada Folder Name, isi dengan nama gambar.  Masukkan beberapa file gambar pada folder ini dengan cara copy-paste.

Langkah berikutnya, buat sebuah Managed Bean dengan memilih menu File, New File.  Kemudian pilih JavaServer Faces pada bagian Categories, dan pilih JSF Managed Bean pada bagian File Types.  Klik tombol Next.  Isi Class Name dengan FileAlbum.  Pada combobox Scope, isi dengan session.  Kemudian klik tombol Finish.

Kemudian ganti kode program pada FileAlbum.java yang dihasilkan oleh NetBeans menjadi seperti berikut:

package co.id.snake.mbean;

import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.faces.application.Resource;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
import javax.faces.context.FacesContext;
import javax.servlet.ServletContext;

@ManagedBean
@SessionScoped
public class FileAlbum {

  private List<String> fileLocations;

  public FileAlbum() {
    fileLocations = new ArrayList<>();
    try {
      ServletContext sc = (ServletContext)FacesContext.getCurrentInstance().getExternalContext().getContext();
      Path dirGambar = Paths.get(sc.getRealPath("/gambar"));
      try (DirectoryStream<Path> dir = Files.newDirectoryStream(dirGambar)) {
        Iterator<Path> iter = dir.iterator();
        while (iter.hasNext()) {
          Path file = iter.next();
          fileLocations.add("/gambar/" + file.getFileName());
        }
      }
    } catch (IOException ex) {
      ex.printStackTrace();
    }
  }

  public List<String> getFileLocations() {
    return fileLocations;
  }
}

Kode program di atas memanfaatkan fitur baru di Java 7, sehingga hanya bisa dijalankan bila menggunakan JDK 7 ke atas.  Bila fitur baru belum dikenali oleh editor, klik kanan pada nama project, kemudian pilih Properties.  Pada bagian Source/Binary Format, pastikan JDK 7 sudah dipilih.

Setelah itu, lakukan modifikasi pada index.xhtml menjadi seperti berikut ini:

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:p="http://primefaces.prime.com.tr/ui"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets">
<h:head>
   <title>Latihan PrimeFaces</title>
</h:head>
<h:body>
   <p:growl id="label" showDetail="true"/>
   <p:panel header="Album Foto">
     <p:galleria effect="slide" effectSpeed="1000">
       <ui:repeat value="#{fileAlbum.fileLocations}" var="fileLocation" >
         <p:graphicImage value="#{fileLocation}" />
       </ui:repeat>
     </p:galleria>
   </p:panel>
</h:body>
</html>

Tampilkan facelet tersebut di browser dengan memilih menu Run, Run File.  Berikut ini adalah contoh tampilan di browser:

Contoh Tampilan PrimeFaces

Video yang mempertunjukkan langkah demi langkah dari artikel ini dapat dilihat di http://www.screencast.com/t/C6KdmBmLCMn.

JavaServer Faces: Melihat Wajah Java

Hari ini aku akan mencoba mempelajari bagaimana menggunakan JSF bersama dengan JSP. JSF masih merupakan bagian dari JEE, dan dibuat berdasarkan projek Apache Struts. Yup! JSF memakai design pattern MVC layaknya Struts. Sayangnya, Tomcat tidak menyertakan JSF dalam paket-nya, sehingga aku harus men-download JAR tambahan di situs Sun. Tapi ada ide yang lebih baik: hari ini aku akan memakai NetBeans IDE 6.5 dan application server GlassFish, soalnya mereka sudah punya JSF sehingga aku tidak perlu repot men-download lagi.

Aku memilih Web Application (Java Web) dari New Project di NetBeans. Setelah itu, pada langkah ke-4, aku men-centang pilihan framework JavaServer Faces. Setelah project dibuat, aku memastikan libraries JSF 1.2 telah disertakan di bagian Libraries, yang terdiri atas commons-beanutils.jar, commons-collections.jar, common-digester.jar, commons-logging.jar, jsf-api.jar, dan jsf-impl.jar.

Pada MVC di JSF, Model diwakili oleh class JavaBean, View diwakili oleh JSP, dan Controller diwakili oleh facet servlet (javax.faces.webapp.FacesServlet) yang dapat di-customize melalui file /WEB-INF/faces-config.xml.

Sebagai latihan, aku akan memakai Model ini:

package com.latihan;

public class User {
    private String firstName;
    private String lastName;
    private String password;

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

Aku harus mendaftarkan Model tersebut pada file konfigurasi faces-config.xml terlebih dahulu. File konfigurasi ini dapat ditemukan di bagian “Configuration Files” dari project. Saat pertama kali membukanya, NetBeans akan menampilkan PageFlow, sebuah fitur yang sangat berguna untuk melihat hubungan antar View dalam bentuk diagram. Untuk meng-edit file ini, klik pada bagian “XML“, lalu aku menambahkan baris berikut:

<managed-bean>
  <managed-bean-name>user</managed-bean-name>
  <managed-bean-class>com.latihan.User</managed-bean-class>

  <managed-bean-scope>session</managed-bean-scope>
</managed-bean>

Sekarang aku dapat memakai Model tersebut di View melalui managed-bean-name, yaitu user. Sebagai latihan, berikut ini adalah View yang aku beri nama registrasi.jsp:

<%@taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<%@taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<html>
    <head>
        <title>Latihan JSF</title>

    </head>
    <f:view>
        <body>
            <h1>User Registration</h1>
            <h:form>

                <table>
                    <tr>
                        <td>First Name:</td>
                        <td><h:inputText value="#{user.firstName}" size="30"/></td>
                    </tr>

                    <tr>
                        <td>Last Name:</td>
                        <td><h:inputText value="#{user.lastName}" size="30"/></td>
                    </tr>
                    <tr>

                        <td>Password:</td>
                        <td><h:inputSecret value="#{user.password}" size="30"/></td>
                    </tr>
                </table>
                <h:commandButton value="Register" action="submit"/>

            </h:form>
        </body>
    </f:view>
</html>

Secara default, View di atas dapat di-akses dengan alamat http://localhost:8080/nama_context/faces/registrasi.jsp. Berikutnya, aku membuat sebuah View lagi, yang aku beri nama userinfo.jsp:

<%@taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<%@taglib uri="http://java.sun.com/jsf/html" prefix="h" %>

<html>
    <head>
        <title>Latihan JSF</title>
    </head>
    <f:view>
        <body>

            Welcome, <h:outputText value="#{user.firstName}"/>
            <h:outputText value="#{user.lastName}"/>.
            <p/>
            Your password is <h:outputText value="#{user.password}"/>.
        </body>
    </f:view>

</html>

Sebagai langkah terakhir, aku akan menentukan navigasi View di dalam Controller dengan menambahkan baris berikut di file faces-config.xml:

<navigation-rule>
   <from-view-id>registrasi.jsp</from-view-id>
   <navigation-case>
      <from-outcome>submit</from-outcome>

      <to-view-id>userinfo.jsp</to-view-id>
   </navigation-case>
</navigation-rule>

Sekarang, jika aku membuka halaman registrasi.jsp dan aku menekan tombol Submit, aku akan dibawa ke halaman userinfo.jsp. Tampilan diagram PageFlow di NetBeans saat membuka faces-config.xml akan memperjelas flow navigasi antar-view.

JBoss: Mengakrabkan Diri Dengan Bos

JBoss 5 punya kelebihan di arsitektur microcontainer-nya. Jika aku tidak membutuhkan komponen tertentu, misalnya clustering, aku dapat memilih untuk menjalankan server JBoss tanpa menyertakan komponen tersebut. Pada instalasi standard JBoss, aku dapat menemukan lima konfigurasi server bawaan di lokasi direktori %JBOSS_HOME%\server. Di direktori tersebut terdapat lima subdirektori, yaitu: all, default, minimal, standard, dan web. Seperti yang dapat ditebak dari namanya, minimal adalah konfigurasi yang paling sederhana untuk menjalankan JBoss tanpa dukungan web container, EJB dan JMS. Ini adalah konfigurasi yang paling ringan dan start-up yang paling cepat. Sebaliknya, all adalah konfigurasi yang menjalankan seluruh komponen yang tersedia, termasuk RMI/IIOP dan clustering. Secara default, konfigurasi yang dipakai adalah default yang menyediakan komponen yang umum dipakai oleh aplikasi JEE, dan tidak menyertakan dukungan seperti IIOP dan clustering.

Di dalam masing-masing direktori server configuration, terdapat beberapa direktori seperti direktori conf yang berisi file konfigurasi, direktori deploy yang berisi aplikasi JEE yang ingin aku jalankan, dan direktori lib yang berisi library JAR untuk aplikasi di server configuration ini.

Untuk menjalankan JBoss dengan konfigurasi tertentu, aku dapat menggunakan argument “-c” saat memanggil run.bat (atau run.sh untuk Linux). Misalnya, untuk menjalankan server configuration minimal, aku mengetikkan perintah berikut di command prompt:

run -c minimal

Setelah JBoss menyelesaikan bootstrap-nya, aku akan mendapatkan baris informasi seperti:

Started in 8s:406ms

Sebagai perbandingan, jika aku menjalankan server configuration all, JBoss menampilkan baris ini:

Started in 1m:8s:453ms

Selisih waktu untuk startup-nya lumayan terasa, tapi aku rasa aku tidak akan pernah memakai server configuration all, selain untuk mencoba seluruh kemampuan JBoss.

Untuk membuat konfigurasi server sesuai kebutuhanku, aku harus mengerti bagaimana cara kerja file konfigurasi JBoss yang terdapat di folder conf. File jboss-service.xml akan dijalankan setelah server menjalankan microcontainer. File ini dibutuhkan untuk core services lama yang belum dalam bentuk microcontainer bean atau mbean. Sisanya, service non-core yang hot-deployable terdapat di folder deploy. Cara yang paling mudah untuk membuat server configuration sesuai kebutuhan adalah dengan men-copy dari salah satu folder bawaan JBoss, kemudian meng-edit jboss-service.xml serta menghapus/menambahkan services di folder deploy.

O ya, instalasi JBoss menyertakan database Hypersonic, dimana descriptornya berupa file hsqldb-ds.xml di folder deploy. Umumnya, untuk program dengan kualitas produksi, orang-orang akan memakai database dengan yang lebih baik seperti Oracle. Aku akan mengganti database default Hypersonic dengan database Oracle yang terinstall di komputerku. Aku mulai dengan menghapus hsqldb-ds.xml dari folder deploy. Lalu, aku mencopy library jdbc milik Oracle ke folder lib di folder server configuration yang aku pakai. Kemudian, pada direktori deploy, aku membuat file baru bernama oracle-ds.xml. Isinya seperti berikut:

<?xml version="1.0" encoding="UTF-8"?>
<datasources>
  <local-tx-datasource>
    <jndi-name>DefaultDS</jndi-name>
    <connection-url>jdbc:oracle:thin:@localhost:1521:latihan</connection-url>
    <driver-class>oracle.jdbc.driver.OracleDriver</driver-class>
    <user-name>scott</user-name>
    <password>tiger</password>
    <valid-connection-checker-classname>
       org.jboss.resource.adapter.jdbc.vendor.OracleValidConnectionChecker
    </valid-connection-checker-classname>
    <metadata>
      <type-mapping>Oracle10g</type-mapping>
    </metadata>
  </local-tx-datasource>
</datasources>

Pada konfigurasi di atas, aku memakai Oracle Database dengan SID latihan yang ter-install di komputer yang sama. User name yang aku pakai adalah user name bawaan database Oracle, yaitu scott dan default password-nya tiger.

Aku kemudian me-restart JBoss, dan melihat banyak pesan kesalahan. Untuk sekarang aku akan mengabaikannya dulu. Yang perlu aku lakukan adalah memeriksa apakah koneksi database berlangsung dengan lancar. Caranya adalah dengan membuat direktori baru di direktori deploy dengan nama test.war (ini nama direktori, bukan nama file). Di dalam direktori ini, aku membuat sebuah file baru, dengan nama index.jsp yang isinya:

<%@page contentType="text/html"
  import="java.util.*,javax.naming.*,javax.sql.DataSource,java.sql.*"
%>
<%
DataSource ds = null;
Connection con = null;
PreparedStatement pr = null;
InitialContext ic;
try {
  ic = new InitialContext();
  ds = (DataSource)ic.lookup( "java:/DefaultDS" );
  con = ds.getConnection();
  pr = con.prepareStatement("SELECT ENAME FROM EMP");
  ResultSet rs = pr.executeQuery();
  while (rs.next()) {
    out.println("<br> " +rs.getString("ENAME"));
  }
  rs.close();
  pr.close();
}catch(Exception e){
  out.println("Exception thrown " +e);
}finally{
  if(con != null){
    con.close();
  }
} %>

Program di atas akan menampilkan kolom ENAME dari tabel EMP milik schema SCOTT. Untuk menjalankannya, aku membuka browser dan mengetikkan alamat ini: http://localhost:8080/test/index.jsp. Jika halaman yang muncul adalah nama-nama yang tercantum di tabel EMP, berarti database Oracle telah ter-setup dengan baik dan siap dipakai.

Lalu kenapa banyak pesan kesalahan saat menjalankan JBoss? Itu karena beberapa services mengharapkan tabel-tabel tertentu di database, dan aku belum membuatnya. Database Hypersonic bawaan JBoss menyimpan informasi tabel di file data\hypersonic\localDB.script.

JSTL: Merapikan Halaman JSP

Tujuan utama JavaServer Pages Template Library (JSTL) adalah mempermudah kehidupan perancang halaman JSP. Bagi developer, JSTL membantu menciptakan halaman JSP yang rapi, tidak banyak berbaur antara kode HTML dan kode Java, sehingga halaman JSP lebih mudah dimengerti dan dikelola. Salah satu bagian JSTL (JSR-52), Expression Language (EL), telah menjadi bagian dari spesifikasi JSP 2.1 (JSR-152).

Tag dalam JSTL bisa dikategorikan menjadi empat bagian, yaitu core (http://java.sun.com/jsp/jstl/core, prefix c), XML processing (http://java.sun.com/jsp/jstl/xml, prefix x), I18N formatting (http://java.sun.com/jsp/jstl/fmt, prefix fmt), SQL (http://java.sun.com/jsp/jstl/sql, prefix sql) dan functions (http://java.sun.com/jsp/jstl/functions, prefix fn).

Instalasi Apache Tomcat 6 tidak menyertakan dukungan JSTL, sehingga aku harus mendownload implementasi JSTL secara terpisah, seperti dari Apache Jakarta. Lalu aku juga harus memiliki jar yang berisi API JSTL. Jika mendownload implementasi dari Apache Jakarta, aku dapat menemukan API JSTL di file jstl.jar dan implementasinya di file standard.jar.

Sebagai latihan, aku membuat sebuah halaman yang men-query data dari database dan menampilkannya beserta pilihan untuk menambah/mengedit/menghapus setiap baris data:

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="sql" uri="http://java.sun.com/jsp/jstl/sql"%>
<html>
<head>
<title>Data Individu</title>
</head>

<body>
<sql:query var="individu" dataSource="jdbc/databaseDevelopment">
  SELECT * FROM TBL_INDIVIDU
</sql:query> 
<h1>Daftar Individu</h1>
<c:choose>
  <c:when test="${individu.rowCount==0}">

    <p>Saat ini tidak ada data individu yang terdaftar.</p>
  </c:when>
  <c:otherwise>
    <p>Berikut ini adalah ${individu.rowCount} individu yang terdaftar:</p>
    <table cellspacing="5px">

      <thead>
      <c:forEach var="columnName" items="${individu.columnNames}">
        <th>${columnName}</th>
      </c:forEach>
      </thead>

      <tbody>
      
      <c:forEach var="record" items="${individu.rows}">
        <tr>
        <td>${record.id}</td>
        <td>${record.nama}</td>

        <td>${record.alamat}</td>
        <td>${record.telepon}</td>
        <c:url value="modify.jsp" var="urlUpdate">
          <c:param name="id" value="${record.id}" />
          <c:param name="nama" value="${record.nama}" />

          <c:param name="alamat" value="${record.alamat}" />
          <c:param name="telepon" value="${record.telepon}" />
        </c:url>
        <c:url value="modify.jsp" var="urlHapus">
          <c:param name="id" value="${record.id}" />
          <c:param name="action" value="hapus" />

        </c:url>
        <td><a href="${urlUpdate}">Update</a> <a href="${urlHapus}">Hapus</a></td>
        </tr>

      </c:forEach>
      </tbody>
    </table>
  </c:otherwise>
</c:choose>
<p><a href='modify.jsp'>Tambah Data Baru</a></p>

</body>
</html>
    

Pada halaman JSP di atas, aku tidak menggunakan kode Java sedikitpun, sehingga halaman JSP terlihat lebih mudah dimengerti. Untuk melakukan query, aku menggunakan <sql:query>dengan data source yang telah aku definisikan sebagai JNDI resource di Apache Tomcat. Aku juga menggunakan <c:url> untuk membentuk string URL beserta parameter-parameter-nya secara otomatis. Untuk mencetak setiap baris data sesuai hasil query, aku menggunakan <c:forEach>.

Untuk halaman modify.jsp yang berguna untuk tambah data, update data dan hapus, aku membuat halaman seperti berikut:

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="sql" uri="http://java.sun.com/jsp/jstl/sql" %>
<html>
<head>
<title>Modifikasi Data Individu</title>
</head>
<body>
<h1>Modifikasi Data Individu</h1>

<c:choose>
  <c:when test="${empty param.action}">
    <form method="post" action="modify.jsp">
    <pre>
 Nama   : <input name="nama" type="text" maxlength="20" value="${param.nama}" />
 Alamat : <input name="alamat" type="text" maxlength="20" value="${param.alamat}" />

 Telepon: <input name="telepon" type="text" maxlength="20" value="${param.telepon}" />
 
 <input type="submit" value="Simpan" />
 <input type="hidden" name="id" value="${param.id}" />
 <input type="hidden" name="action" value="${empty param.id?'tambah':'edit'}" />
    </pre>  
    </form>

  </c:when>
  <c:when test="${param.action=='tambah'}">
    <sql:update dataSource="jdbc/databaseDevelopment">
      INSERT INTO TBL_INDIVIDU VALUES (?, ?, ?, ?)
      <sql:param value="<%= java.util.UUID.randomUUID().toString() %>"/>
      <sql:param value="${param.nama}" />

      <sql:param value="${param.alamat}" />
      <sql:param value="${param.telepon}" />
    </sql:update>
    <p>Data berhasil ditambahkan ke database. <a href='latihan1.jsp'>Klik disini untuk kembali.</a></p>

  </c:when>
  <c:when test="${param.action=='edit'}">
    <sql:update dataSource="jdbc/databaseDevelopment">
      UPDATE TBL_INDIVIDU SET NAMA = ?, ALAMAT = ?, TELEPON = ?
      WHERE ID = ?
      <sql:param value="${param.nama}" />
      <sql:param value="${param.alamat}" />
      <sql:param value="${param.telepon}" />

      <sql:param value="${param.id}" />
    </sql:update>
    <p>Data berhasil diupdate. <a href='latihan1.jsp'>Klik disini untuk kembali.</a></p>
  </c:when>

  <c:when test="${param.action=='hapus'}">
    <sql:update dataSource="jdbc/databaseDevelopment">
      DELETE FROM TBL_INDIVIDU WHERE ID = ?
      <sql:param value="${param.id}" />
    </sql:update>
    <p>Data berhasil dihapus. <a href='latihan1.jsp'>Klik disini untuk kembali.</a></p>

  </c:when>
</c:choose>
</body>
</html>
    

Pada halaman ini, aku memakai tag <c:choose> dan<c:when> untuk membuat struktur logika kondisi, yang melakukan aksi tertentu berdasarkan nilai param.action. Object param adalah object implicit dalam EL yang hampir sama seperti session.getParameter(). Satu-satunya kode Java yang aku pakai disini adalah pemanggilan class UUID untuk menghasilkan nilai ID unik setiap kali penambahan data baru.