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.

Perihal Solid Snake
I'm nothing...

Apa komentar Anda?

Please log in using one of these methods to post your comment:

Logo WordPress.com

You are commenting using your WordPress.com account. Logout / Ubah )

Gambar Twitter

You are commenting using your Twitter account. Logout / Ubah )

Foto Facebook

You are commenting using your Facebook account. Logout / Ubah )

Foto Google+

You are commenting using your Google+ account. Logout / Ubah )

Connecting to %s

%d blogger menyukai ini: