29 March 2016

Integrasi Spring-Boot dan DataTables

Presentation layer dalam sebuah aplikasi web, merupakan suatu hal yang sangat diperhatikan. Bahkan terkadang tingkat keberhasilan sebuah aplikasi web diukur dari puas tidaknya, mudah susahnya, atau bagus tidaknya user interface aplikasi web tersebut. Tidak heran dalam industri IT, user interface diperhatikan secara khusus, bahkan ada kualifikasi karir tersendiri untuk menangani user interface ini.  Dalam aplikasi web, user interface adalah sebuah media yang menjadi jembatan bagi para user untuk meng-input-kan data ke dalam sebuah aplikasi web. Meskipun pada dasarnya tampilan sebuah aplikasi web hanyalah representasi dari CRUD (create, read, update, dan delete) data dari database. Namun para developer aplikasi web dituntut untuk membuat sebuah tampilan yang menarik.  
Salah satu halaman standar aplikasi web adalah halaman untuk menampilkan data berupa list dalam sebuah tabel. Dan biasanya halaman ini dilengkapi dengan fitur pagination, pencarian data, sorting berdasarkan kolom, dan masing-masing baris data bisa disisipkan tombol action seperti delete atau edit data. Kemajuan teknologi javascript dan css memungkinkan berkembangnya project-project yang khusus menangani tampilan-tampilan data yang dikemas dalam bentuk tabulasi ini. Hasil dari project-project tersebut bisa saja dijadikan sebuah freeware, open source project, sampai produk yang komersil. 
Saat ini banyak sekali project-project atau hasil-hasil dari project-project tersebut yang bisa kita temukan di internet.  Salah satunya adalah datatables. Datatables adalah sebuah framework javascript yang bertujuan menjadi template suatu mekanisme tampilan data yang berupa tabulasi. Template ini kemudian diletakkan dalam sebuah halaman aplikasi web. Oleh karena framework ini menggunakan javascript maka penggunaan atau integrasi datatables tidak terbatas hanya pada suatu jenis bahasa pemrograman saja, entah itu java, php, python, pearl, asp, .net atau apapun platform pemrogramannya sepanjang itu adalah sebuah aplikasi web yang juga bisa menggunakan javascript, maka datatables bisa digunakan atau diintegrasikan.
Saya memilih freemarker sebagai presentasion layer dalam development stack yang biasa saya lakukan, dikarenakan freemarker merupakan template language yang akan menghasilkan html. Dengan keunggulan ini integrasi framework atau project-project seperti datatables ini bisa saya integrasikan dalam rangkaian aplikasi web saya , dan tentunya integrasi tersebut terletak pada layer presentasi, layaknya twitter-bootstrap yang pernah kita integrasikan dengan freemarker pada tutorial-tutorial sebelumnya. 
Untuk aktivitas kita kali ini, kita akan mengintegrasikan datatables ke dalam aplikasi web kita. Source code awal untuk latihan kita bisa di check-out dari link berikut ini, source code tersebut merupakan source code dasar aplikasi web yang kita pernah buat pada artikel-artikel sebelumnya tentang spring-boot, freemarker, dan bootstrap. Kemudian silahkan download dahulu file zip datatables dari link berikut ini, pada link download datatables tersebut sudah saya pilihkan sedemikian rupa sehingga hanya beberapa file yang kita butuhkan saja yang ada dalam file zip tersebut. Kemudian ekstrak file zip datatables tersebut sesuai dengan folder yang tersedia dalam source code, maksudnya file-file yang ada dalam folder css dalam file zip diekstrak ke dalam folder css pada source code, demikian juga dengan file-file yang ada pada folder js pada file zip diekstrak ke dalam folder js yang ada pada source code, sehingga akan terlihat seperti pada gambar berikut ini:
Sebagai langkah pertama, kita akan membuat sebuah file template yang bisa kita pakai dalam halaman list data, file ini adalah file yang berisi referensi file-file datatables yang akan kita pakai nantinya dalam halaman list data. File template ini sengaja kita pisahkan dengan tujuan agar tidak semua referensi datatables kita libatkan dalam setiap halaman, kita hanya ingin supaya referensi datatables hanya dimuat pada halaman list data yang menggunakan datatables saja, pada halaman edit kita tidak perlu melibatkan datatables. Sebab file-file statis seperti css dan javascript akan mengkonsumsi resource tersendiri dalam implementasi aplikasi web, jadi sudah selayaknya kita lebih bijak menggunakan resources agar aplikasi web kita dapat lebih efisien dan efektif ketika sedang digunakan oleh user, dalam artian tidak terlalu lama pada saat loading page
File yang akan kita buat ini kita beri nama sebagai “_datatables_script.ftl” dan kita letakkan pada foldersrc/main/resources/templates/include/”, adapun isi file ini adalah sebagai berikut:
<link rel="stylesheet" href="../css/jquery.dataTables.min.css"/>
<link rel="stylesheet" href="../css/dataTables.bootstrap.min.css"/>
<script type="text/javascript" src="../js/jquery.dataTables.min.js"></script>
<script type="text/javascript" src="../js/dataTables.bootstrap.min.js"></script>
Seperti yang kita lihat bersama-sama bahwa isi file tersebut hanyalah referensi-referensi yang berhubungan dengan datatables saja, sehingga file-file ini hanya akan dimuat pada halaman dimana kita meng-include file ini saja tentunya. Keuntungan lain dari pemisahan file referensi datatables ini adalah nantinya kita akan bisa menambahkan fungsi-fungsi javascript khusus datatables, atau css style khusus untuk datatables disini.
Sebelum kita melangkah ke tahap berikutnya ada baiknya kita mengenal sedikit bagaimana datatables bisa bekerja dalam sebuah aplikasi web. Datatables sebetulnya merupakan sebuah plugin jquery library, seiring perkembangannya datatables menjadi populer karena banyak sekali digunakan dalam aplikasi web karena sifatnya yang fleksibel. Ketika diintegrasikan dengan sebuah halaman web, datatables bisa bekerja dengan menggunakan konsep ajax. Pada load pertama kali sebuah halaman web, datatables akan membuat sebuah template tabel di atas halaman tersebut, kemudian list data yang terformat dalam bentuk json diambil dari sebuah link dengan menggunakan fungsi javascript, setelah list data tersebut selesai diambil, datatables akan mem-parsing list data tersebut dan kemudian mengisinya dalam template tabel yang sudah dibuat sebelumnya. 
Jadi pada dasarnya datatables bekerja dengan menggunakan konsep ajax yang menghasilkan format dengan bentuk json yang sudah ditentukan oleh datatables itu sendiri. Format json ini merupakan format baku yang dibuat oleh datatables sehingga core datatables bisa mem-parsing list data tersebut dan kemudian memasukkannya ke dalam template table yang sudah disiapkan sebelumnya oleh datatables dalam sebuah halaman web. Adapun contoh format json yang disyaratkan oleh datatables seperti contoh gambar berikut ini:

Dan pada implementasi aplikasi web yang akan kita buat, integrasi datatables ini kita wujudkan dengan cara membuat dua buah url map, url pertama adalah url yang merupakan halaman yang berisi template tabel list data tersebut, dan url kedua adalah url yang memproduksi json data seperti format di atas. Kita akan mengimplementasikan fitur datatables ini pada data Author, dan sudah tentu kedua url mapping yang kita rencanakan ini ada pada class AuthorController. Untuk url map yang pertama, berikut ini adalah kode yang bisa kita buat pada class AuthorController.
@RequestMapping(value = "/author/get_list", method = RequestMethod.GET)
public String getDataTableList(Map<String, Object> objectMap){
    objectMap.put("pageTitle","Author");
    return "author/dt_list";
}
Pada baris kode tersebut terlihat kita membuat sebuah url dengan alamat “author/get_list” dan kembalian dari fungsi tersebut akan mengarah pada file template freemarkerdt_list.ftl” yang kita letakkan pada foldersrc/main/resources/templates/author/”. Adapun isi dari filedt_list.ftl” ini adalah sebagai berikut.
<#import "../layout/main_layout.ftl" as layout>
<@layout.mainLayout>
<div class="container">
    <table id="authorDT" class="table table-striped table-hover table-bordered table-condensed">
        <thead>
        </thead>
        <tbody></tbody>
    </table>
</div>
</@layout.mainLayout>
<#include "../include/_datatables_script.ftl"/>
<script type="text/javascript" src="../js/author/author_app.js"></script>
<script type="text/javascript">
    $(document).ready(function () {
        jsApp("../service/json/author/datatable_list");
    })
</script>
Seperti yang terlihat bahwa file template freemarker tersebut sangat singkat, namun yang harus kita perhatikan adalah baris dimana kita membuat sebuah template tabel dengan idauthorDT”. Kemudian ada deklarasi dimana kita meng-include file_datatables_script.ftl” yang kita buat sebelumnya. Lalu ada juga deklarasi yang melibatkan file javascriptauthor_app.js” yang diambil dari lokasi foldersrc/main/templates/static/js/author/”, file ini berisi fungsi javascript untuk mem-parsing json data dari format json datatables dan kemudian memasukkan data hasil parsing tersebut ke template tabel yang sudah dibuat. Dan yang terakhir ada sebuah fungsi javascript yang berfungsi memerintahkan halaman tersebut untuk mengambil data dari url../service/json/author/datatable_list” dan kemudian mem-parsing data dengan format json. Fungsi utama ini sendiri sebenarnya terletak pada fileauthor_app.js”, dan pada file freemarker template inilah, fungsi tersebut diperintahkan untuk dijalankan.

Seperti yang kita ketahui bahwa url map yang kedua memiliki output dengan format json seperti gambar di atas, format json seperti ini bisa dibentuk dengan membuat sebuah object model java. Dan output dari url map kedua merupakan @ResponseBody  object model java ini. Maka yang harus kita buat adalah sebuah object model yang bisa merepresentasikan bentuk output tersebut. Mudah saja kita langsung membuat sebuah class yang bernama “DataTableObject” dan kita letakkan dalam packageorg.josescalia.blog.simple.util” 
package org.josescalia.blog.simple.util;
import java.util.List;
public class DataTableObject<T> {
    private int iTotalRecords;
    private int iTotalDisplayRecords;
    private String sEcho;
    private String sColumns;
    private String sSortBy;
    private String sSortOrder;
    private List<T> aaData;
    /*getter setter here*/
    
    /**
     * in spring data jpa library the Page Class need page no for paramater to get result set 
     * from database
     *
     * @return int
     * @param iDisplayStart int startRow from
     * @param iDisplayLength int startRow from
     * */
    public static int getPageFromStartAndLength(int iDisplayStart, int iDisplayLength) {
        int expectedCalc = 0;
        if (iDisplayStart > 1) {
            expectedCalc = (iDisplayStart / iDisplayLength);
        }
        return expectedCalc;
    }
}
Seperti yang kita lihat pada kode-kode tersebut, class ini merupakan class pojo (plain old java object)  biasa yang ditambahkan sebuah fungsi static untuk mempermudah kalkulasi dalam pencarian suatu bilangan. Class ini nantinya akan kita pakai sebagai template output dari fungsi controller yang akan di-parsing oleh datatables. Salah satu properti pada class tersebut adalah list of object yang bisa diisi dengan object model secara dinamis, perhatikan penggunaan huruf T pada nama class dan properti aaData, inilah yang disebut dengan “named parameter” dalam pemrograman java. Jadi jika kita hendak menggunakan class DataTableObject ini untuk men-display class model Author, maka yang kita lakukan cukup dengan mendeklarasikan object baru DataTableObject ini dengan class Author sebagai named paramater-nya. 
Kemudian kita akan membuat url mapping yang kedua yang  @RequestMapping value-nya kita namakan sesuai dengan file freemarker template dt_list.ftl” yang kita buat sebelumnya. Adapun fungsi pada class AuthorController ini adalah seperti di bawah ini.
@RequestMapping(value = "/service/json/author/datatable_list")
public @ResponseBody DataTableObject<Author> 
getDataTableList(@RequestParam(required = true) String sEcho,
                @RequestParam(required = true) String sColumns,
                @RequestParam(required = true) String sSearch,
                @RequestParam(required = true) Integer iDisplayStart,
                @RequestParam(required = true) Integer iDisplayLength,
                @RequestParam(required = true) Integer iSortCol_0,
                @RequestParam(required = true) String sSortDir_0){
    String[] columns = sColumns.split(",");
    Sort sort = new Sort(Sort.Direction.fromString(sSortDir_0), sColumns.split(",")[iSortCol_0]);
    Page<Author> modelList = null;
    DataTableObject<Author> dtObject = new DataTableObject<Author>();
    int page = DataTableObject.getPageFromStartAndLength( iDisplayStart, iDisplayLength);
    try {
        modelList = authorRepository.getPaginatedList(sSearch, new PageRequest(page,iDisplayLength,sort));
        dtObject.setsEcho(sEcho);
        dtObject.setAaData(modelList.getContent());
        dtObject.setiTotalDisplayRecords(authorRepository.getTotalData(sSearch));
        dtObject.setiTotalRecords((int) authorRepository.count());
        dtObject.setsColumns(sColumns);
        dtObject.setsSortBy(sColumns.split(",")[iSortCol_0]);
        dtObject.setsSortOrder(sSortDir_0);
        return dtObject;
    }catch (Exception e){
        logger.error(e.getMessage());
    }
    return null;
}
Perhatikan kode fungsi di atas, value dari request mapping telah kita sesuaikan, kemudian fungsi tersebut memiliki parameter yang lumayan banyak, harap diketahui parameter ini adalah paramater yang akan di lempar oleh mekanisme datatables ketika mengambil data dengan menggunakan konsep ajax. Dalam kode tersebut kita juga melihat kembalian dari fungsi ini adalah class DataTableObject, dan class DataTableObject ini kita assign class model Author sebagai named parameter object-nya. Pada bagian utama kode tersebut, setiap properti yang ada pada class DataTableObject di-set nilai-nilainya dengan menggunakan resources yang sudah ada sebelumnya, seperti properti aaData yang di-isi dengan list data Author, totalRecord yang di-isi dengan jumlah seluruh data dalam tabel mysql, dan seterusnya.
Sampai disini, kita hampir selesai mengimplementasikan datatables ke dalam halaman list pada aplikasi web kita, tinggal satu langkah lagi yaitu membuat sebuah fungsi javascript yang akan mem-parsing json data hasil dari pemanggilan ajax tadi. Jika kita  melihat kembali file freemarker template dt_list.ftl”, kita melihat ada sebuah file javascript yang di-include dalam file tersebut, yaitu fileauthor_app.js” yang terletak dalam foldersrc/main/resources/template/js/author/”, jika folder author belum ada dalam source code, silahkan buat sendiri. Dan kemudian buat lagi sebuah file dengan nama “author_app.js” di dalam folder tersebut. Adapun isi dari fileauthor_app.js” adalah sebagai berikut.
(function($) {
   jsApp = function(dataTableJsonURL) {
      $(document).ready(function() {
         $('#authorDT').dataTable({
             sPaginationType:"full_numbers", //remove this to delete first
            // and last button
            bProcessing : true,
            bServerSide : true,
            bStateSave : true, // to save state in cookie
            iCookieDuration : 60, // cookie will save for about 60 seconds
            sAjaxSource : dataTableJsonURL,
            iDisplayLength : 10,
            aoColumns : [ {
               "mData" : "id",
               "sClass" : "text-center",
               "sName" : "id",
               "sTitle" : "Action",
               "sWidth" : "12%"
            }, {
               "mData" : "authorName",
               "sName" : "authorName",
               "bSortable" : true,
               "sWidth" : "25%",
               "sTitle" : "Author Name"
            }, {
               "mData" : "authorAddress",
               "sName" : "authorAddress",
               "bSortable" : true,
               "sWidth" : "45%",
               "sTitle" : "Address"
            },{
               "mData" : "createdDate",
               "sName" : "createdDate",
               "bSortable" : true,
               "sWidth" : "15%",
               "sTitle" : "Date Created"
            } ]
         })
      });
   };
})(jQuery);
Ya, fungsi javascript inilah yang akan melakukan request http ke url map kedua yang sudah kita buat, dan kemudian hasil dari http request tersebut di-parsing ke dalam masing-masing field. Perhatikan baris aoColumns yang tertulis diatas, baris ini merupakan array javascript yang akan menempatkan masing-masing kolom pada template tabel yang dibuat oleh Datatables. Uniknya kolom-kolom template yang dibuat oleh datatables juga menggunakan referensi array javascript ini, maksudnya begini, jika kita membuat 4 object dalam array javascript tersebut, maka DataTables juga akan membuatkan sebanyak 4 kolom sebagai template tabelnya, jika kita membuatkan 3 object, maka Datatables juga membuatkan 3 kolom sebagai template tabelnya, dan seterusnya, dan ini dilakukan secara dinamis. Disinilah letak kekuatan datatables sebagai plugin jquery dalam bidang tabulasi.

Sampai disini implementasi datatables ke dalam sebuah halaman list pada aplikasi web kita telah selesai, silahkan anda tes hasil implementasi tersebut. Pada aplikasi web yang saya miliki untuk kegiatan ini, hasil dari saya seperti gambar berikut ini, dan jika tidak sama persis tampilannya dengan anda, saya memang menambahkan beberapa styling css yang ringan untuk menyesuaikan tampilan agar lebih enak dipandang mata. Dan styling css yang saya tambahkan tentunya saya letakkan pada file_datatables_script.ftl” yang ada pada foldersrc/main/resources/templates/include/”, sehingga tampilan ini tentu saja hanya berlaku untuk halaman-halaman yang menggunakan datatables saja.
Anda juga saya sarankan untuk menggali lebih dalam lagi penggunaan datatables dengan membaca dokumentasi datatables pada website resminya, banyak sekali yang bisa kita lakukan atau terapkan untuk mendapatkan tampilan tabulasi yang menarik yang bisa kita manfaatkan dalam halaman aplikasi web kita.
Saya rasa sampai disini dulu tutorial implementasi datatables untuk pembahasan kali ini, jika masih diberikan kesempatan kita akan terus belajar bersama melalui tulisan-tulisan dalam blog ini.

Semoga Bermanfaat
Depok, 26 Maret 2016



Josescalia.

14 March 2016

Spring-Boot Hot Swap

Ketika dulu saya menggunakan Grails Framework, ada suatu fitur yang sangat saya sukai dalam Grails, yaitu fitur dimana saya tidak perlu me-restart aplikasi yang saya buat ketika saya memodifikasi source code aplikasi web tersebut. Saya hanya perlu me-refresh browser ketika saya ingin melihat perubahan dalam kode-kode yang telah saya modifikasi. Fitur ini disebut dengan fitur hot-swap
Seperti yang sama-sama kita ketahui bahwa java merupakan programming language, yaitu suatu bahasa yang memerlukan kompilasi source code sebelum program tersebut bisa di jalankan. Dan kita juga sama-sama mengetahui bahwa jika ada suatu aplikasi yang bisa dieksekusi tanpa harus mengkompilasi source code-nya, pada hakikatnya aplikasi tersebut bukanlah terbuat dari programming language melainkan scripting language. Scripting language adalah suatu pemrograman yang tidak memerlukan kompilasi untuk bisa menjalankan aplikasi tersebut. Perbedaan mutlak antar programming language dan scripting language ada pada masalah kompilasi source code ini. Dengan pengertian ini tentunya anda bisa membedakan yang mana programming language dan yang mana scripting language.
Meskipun Grails  mampu terintegrasi dengan baik terhadap JVM (Java Virtual Machine) dengan kemampuannya membaca API-API java, namun Grails bukanlah java, komponen utama Grails adalah Groovy yang menurut wikipedia memiliki paradigma sebagai sebuah scripting. Dalam pemakaiannya Groovy atau Grails sedikit menarik sebab terkadang groovy harus dikompilasi untuk bisa beroperasi, namun juga bisa berlaku layaknya script (tanpa harus kompilasi). Groovy script bisa dieksekusi langsung tanpa kompilasi apabila kode-kode yang tertulis berada di luar definisi sebuah class.
Demikian juga dengan Grails framework, framework yang dikhususkan untuk pembuatan aplikasi web ini memiliki fitur hot-swap, yaitu suatu fitur yang memungkinkan kita tidak perlu me-restart sebuah aplikasi web jika kita ingin melihat perubahan dari kode-kode yang kita modifikasi dalam aplikasi yang dibuat dengan menggunakan Grails.
Bagaimana dengan spring-boot?, ketika pertama kali menggunakan spring-boot terus terang saya tidak banyak tahu kalo ternyata spring-boot juga memiliki fitur seperti grails diatas, fitur yang mampu memperlihatkan suatu perubahan dari kode-kode yang kita modifikasi tanpa harus me-restart sebuah aplikasi. Ya, spring-boot ternyata memiliki fitur hot-swap ini, namun fitur ini terbatas hanya pada perubahan-perubahan yang dilakukan oleh kode-kode non java. Jadi kalo kita memodifikasi kode-kode yang termasuk java tetap harus melakukan restart aplikasi. Sebab hakikatnya java adalah programming language yaitu sebuah pemrograman yang memerlukan kompilasi sebelum kode-kodenya bisa digunakan. 
Yah, kalo gitu jadi percuma dong?.  Hmm, menurut saya tidak juga. Fitur ini akan sangat berguna jika kita melakukan integrasi scripting language dalam pemrograman aplikasi web menggunakan spring-boot framework. Contohnya seperti freemarker. Seperti yang sudah kita pelajari sebelumnya bahwa freemarker adalah sebuah template language framework yang bisa kita gunakan sebagai layer viewer (UI) dalam sususan development aplikasi web kita. Ketika kita menggunakan freemarker sebagai presentation layer maka freemarker adalah sebuah script yang tidak perlu dikompilasi lagi untuk bisa melihat perubahan-perubahan yang kita lakukan. 
Dalam membuat sebuah aplikasi web, saya pribadi sangat sering menggunakan design pattern MVC (Model, Viewer, Controller) dimana untuk presentation layer atau layer viewer-nya menggunakan konsep hybrid, yaitu perpaduan antara html pages dengan restful services. Untuk kondisi-kondisi yang memerlukan kembalian berupa object data, saya menggunakan konsep pages, sementara untuk kondisi-kondisi crud action seperti save, update, atau delete saya menggunakan konsep restful services. Kondisi dimana sebuah fungsi dalam web aplikasi memerlukan viewer seperti menampilkan list data atau menampilkan suatu data terpilih, saya menggunakan konsep html pages. Dan kondisi dimana fungsi sebuah aplikasi hanya menampilkan konfirmasi seperti “data sudah tersimpan”, “data sudah ter-update” atau “data telah sukses di hapus”, saya menggunakan konsep restful service. Inilah yang saya sebut dengan konsep hybrid dalam aplikasi web yang saya buat.
Ketika memasuki tahap pembuatan presentation layer atau viewer layer, biasanya saya akan lebih sering memodifikasi kode-kode pada bagian presentation tersebut, terutama di halaman html-nya. Hal ini saya lakukan untuk mendapatkan suatu tampilan yang tentunya maksimal, sesuai dengan estetika sebuah aplikasi web. Nah di saat inilah yang saya katakan sebelumnya bahwa fitur hot-swap dalam spring-boot akan menjadi sangat berguna. Saya tidak perlu me-restart aplikasi web jika saya melakukan perubahan dari sisi presentation layer atau viewer layer-nya. Baik itu halaman html, fungsi-fungsi javascript dalam halaman html tersebut, bahkan styling dengan css untuk mendapatkan tampilan presentation layer yang maksimal.
Dalam spring-boot, fitur hot-swap dilakukan dengan meng-include suatu library yang disediakan oleh spring-boot, dan cara menggunakan fitur ini pun harus mengikuti petunjuk yang disediakan oleh library tersebut. Jadi ketika kita menggunakan fitur hot-swap dalam spring-boot ini, kita tidak bisa menjalankan aplikasi dengan cara seperti biasa, sebab dengan cara itu fitur ini tidak akan bisa kita dapatkan, kita harus menggunakan cara spring-boot agar fitur ini bisa kita aktifkan. Lebih jelasnya mari kita implementasikan saja fitur ini.
Agar lebih mudah, silahkan check-out sebuah project yang pernah saya buat untuk tulisan-tulisan mengenai spring-boot sebelumnya di sini. Project tersebut adalah sebuah project aplikasi web yang menggunakan maven sebagai automatisasi development dan tentunya menggunakan spring-boot dan freemarker dalam sususan development-nya. Bukalah file pom.xml pada source code tersebut dan pada file pom.xml perhatikan baris-baris seperti dibawah ini:

<profiles>
    <profile>
        <id>default</id>
        <build>
            <plugins>
                <plugin>
                    <groupId>org.codehaus.mojo</groupId>
                    <artifactId>exec-maven-plugin</artifactId>
                    <version>1.2.1</version>
                    <executions>
                        <execution>
                            <goals>
                                <goal>java</goal>
                            </goals>
                        </execution>
                    </executions>
                    <configuration>
                        <mainClass>org.josescalia.blog.simple.SpringApps</mainClass>
                    </configuration>
                </plugin>
                <plugin>
                    <!--make application have hot swap capability but this should be run with mvn spring-boot:run-->
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <version>1.2.2.RELEASE</version>
                    <dependencies>
                        <dependency>
                            <groupId>org.springframework</groupId>
                            <artifactId>springloaded</artifactId>
                            <version>1.2.1.RELEASE</version>
                        </dependency>
                        <dependency>
                            <groupId>org.springframework.boot</groupId>
                            <artifactId>spring-boot-starter-tomcat</artifactId>
                            <version>1.2.2.RELEASE</version>
                        </dependency>
                    </dependencies>
                </plugin>
            </plugins>
        </build>
    </profile>
</profiles>
Baris tersebut merupakan definisi sebuah profile dalam suatu konfigurasi maven dalam development aplikasi web yang saya buat. Dalam profile tersebut saya menggunakan dua buah plugin maven, yang pertama plugin exec-maven-plugin dan yang kedua plugin spring-boot-maven-plugin. Plugin yang pertama mungkin biasa kita pakai dalam development aplikasi java, namun yang saya ingin bahas dalam tulisan ini adalah plugin yang kedua yaitu spring-boot-maven-plugin. Dalam plugin tersebut kita melihat ada dua buah dependency library, yaitu library springloaded dan library spring-boot-starter-tomcat. Library springloaded inilah yang sebetulnya membawa fitur hot-swap ke dalam development web aplikasi kita, sementara library lainnya adalah library pelengkap untuk bisa menjalankan aplikasi web, sebab seperti yang kita tahu tomcat merupakan application server java programming language.

Kemudian bukalah file application.properties yang ada pada folder src/main/resources/ dalam source code tersebut. Perhatikan di dalamnya ada baris seperti dibawah ini:
#server apps
spring.freemarker.cache=false
Baris tersebut merupakan perintah kepada web aplikasi supaya tidak menyimpan cache template freemarker. Baris ini harus dituliskan dalam file tersebut agar perubahan-perubahan yang dilakukan dalam freemarker akan bisa segera ada efeknya jika ada perubahan-perubahan yang kita lakukan di sisi freemarker.

Spring-boot-maven-plugin ini memiliki perintah-perintah maven seperti : spring-boot:help, spring-boot:repackage, dan spring-boot:run. Perintah yang terakhir inilah yang akan bisa mengaktifkan fitur hot-swap yang ada dalam maven plugin tersebut. Dengan kata lain, jika kita tidak menggunakan perintah ini, tentunya fitur tersebut tidak akan aktif.
Sekarang mari kita jalankan aplikasi web tersebut tentunya dengan menggunakan perintah yang tersedia, atau jika kita menggunakan konsol sistem operasi maka perintah untuk menjalankan aplikasi web tersebut menjadi seperti berikut ini:
mvn -P default spring-boot:run
Perhatikan pada konsol ketika pertama kali perintah ini di jalankan, kita akan mendapatkan baris-baris seperti gambar dibawah ini:
Baris inilah yang mengaktifkan fitur hot-swap dalam spring-boot dan bisa kita gunakan dalam development aplikasi web. Silahkan anda coba fitur ini dengan merubah salah satu file ftl (freemarker template language) yang ada pada folder src/main/templates/ kemudian refresh browser anda terhadap halaman tersebut. Dan lakukanlah itu tanpa me-restart aplikasi web yang sedang kita develop.
Semoga bermanfaat.


Depok, 14 Maret 2016

Josescalia

01 March 2016

Spring-boot, MVC, Freemarker, dan Twitter-Bootstrap (bag 7 - Habis)

Pada tulisan lalu kita mempelajari implementasi keamanan data pada aplikasi web dengan menggunakan spring-security. Kita belajar mengkonfigurasi spring-security tersebut dengan menggunakan metode otentikasi yang nantinya bisa kita pilih berdasarkan tingkat kebutuhan aplikasi web kita. Dan juga kita belajar bagaimana para developer umumnya menggunakan trik-trik tertentu agar deployment aplikasi web siap dengan “predefined data”. 
Pemanfaatan spring-security selain untuk melindungi data aplikasi web yang kita buat dari tangan-tangan jahil, juga ada fungsi lain yang tidak kalah penting, yaitu penggunaan level hak akses untuk melindungi suatu data tertentu, seperti ada user yang hanya boleh melihat atau mengedit suatu data tertentu dan ada user lain yang tidak boleh melakukannya. Pengaturan semacam itu sangat mungkin dilakukan dengan spring-security berdasarkan prinsip-prinsip hak akses. Dan ini akan menjadi salah satu pembahasan kita selanjutnya dalam artikel ini. 
Selain pembelajaran mengenai implementasi level hak akses, kita juga akan mencoba membuat beberapa kustomisasi, antara lain kustomisasi form-login, kustomisasi error page untuk memberitahu user bahwa mungkin user tersebut tidak memiliki hak akses yang cukup untuk mengakses suatu data. 
Sebagai bahasan pertama kita akan belajar menggunakan fitur level hak akses untuk membatasi suatu data yang boleh di akses. Kita akan membuat sebuah url baru, dan url ini hanya boleh diakses oleh user-user yang memiliki role sebagai ROLE_ADMIN, artinya user-user yang tidak memiliki ROLE_ADMIN ini tidak boleh mengakses data tersebut.
Url baru yang akan kita buat adalah “http://localhost:8080/admin/**”, url ini merupakan suatu url path pertama yang boleh diakses oleh user-user dengan ROLE_ADMIN. Dalam url ini nanti akan ada path contohnya “/admin/user/list” atau “/admin/user/edit?u=user” dan lain-lain. Path kedua dalam contoh url tersebut merupakan path url untuk melakukan fungsi penambahan, modifikasi atau penghapusan data user standar, hanya hak akses saja yang membedakan url ini. Langkah pertama yang akan kita lakukan adalah memodifikasi class SecurityConfig agar url baru ini terlindungi dari user-user yang memiliki role selain ROLE_ADMIN. Adapun modifikasi class SecurityConfig ini adalah sebagai berikut:
@Override
protected void configure(HttpSecurity http)throws Exception{
    http.authorizeRequests()
            .antMatchers("/").permitAll() //izinkan semua
            .antMatchers("/css/**").permitAll() //izinkan semua
            .antMatchers("/js/**").permitAll() //izinkan semua
            .antMatchers("/fonts/**").permitAll() //izinkan semua
            .antMatchers("/login").permitAll() //izinkan semua (termasuk default spring-security login page
            .antMatchers("/admin/**").hasRole("ADMIN")
            .anyRequest().authenticated() //selain yang di atas harus authenticated
            .and()
            .formLogin()
            .and()
            .logout() //logout config
            .logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
            .logoutSuccessUrl("/");
}
Perhatikan penambahan baris .antMatchers(“/admin/**”).hasRole(“ADMIN”) pada fungsi configure di atas, dengan cara seperti ini maka kita sudah melindungi url tersebut dari user-user selain user dengan ROLE_ADMIN. Untuk mengujinya kita akan membuat sebuah controller baru dengan nama UserManagementController dan berikut ini isi dari class tersebut:
package org.josescalia.blog.simple.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
/**
 * Created by josescalia on 27/02/16.
 */
@Controller
@RequestMapping("/admin/")
public class UserManagementController {
    
    @RequestMapping(value = "/user/test_auth", method = RequestMethod.GET)
    public @ResponseBody String testAuth(){
        return "Halaman ini hanya boleh diakses oleh user yang memiliki ROLE_ADMIN";
    }
    
}
Perhatikan anotasi @RequestMapping pada deklarasi class UserManagementController tersebut, kita meletakkan path pertama sebagai url/admin/”, dengan cara seperti ini maka nantinya setiap fungsi dalam controller ini hanya menambahkan yang perlu-perlu saja, sebab url dengan awalan “/admin/” sudah kita definisikan secara global pada deklarasi class UserManagementController ini. Untuk fungsi testAuth pada class controller tersebut, sederhana saja jika urlhttp://localhost:8080/admin/user/test_auth” dipanggil dalam browser, maka akan menampilkan sebuah teks dengan tulisan “Halaman ini hanya boleh diakses oleh user yang memiliki ROLE_ADMIN”. Sekarang silahkan jalankan aplikasi web tersebut, dan eksekusi urlhttp://localhost:8080/admin/user/test_auth” pada browser anda, jika kita disodorkan form-login masukkanlah usernameadministrator” dan passwordadmin123” dan lihatlah apakan yang terjadi seperti gambar saya dibawah ini:
Jika iya, sekarang eksekusi url "http://localhost:8080/logout" kemudian eksekusi lagi url sebelumnya “http://localhost:8080/admin/user/test_auth” tapi kali ini dalam form-login masukkan user dengan usernameuser” dan password-nya “password”, apakah hasilnya seperti gambar dibawah ini?
Jika ya, maka sesungguhnya kita sudah selesai menerapkan hak akses berdasarkan level atau role, sederhana dan mudah bukan?. Ketika kita memasukkan usernameadministrator” pada tes pertama dan jika username dan password benar, maka kita diijinkan untuk melihat halaman tersebut, sebab salah satu role dari useradministrator” adalah ROLE_ADMIN, dan ketika kita memasukkan usernameuser” pada percobaan kedua, jika login berhasil maka user ini hanya memiliki ROLE_USER saja, itulah yang menyebabkan halaman tersebut tidak bisa diakses seperti gambar kedua. Dan perhatikan benar-benar tulisan tersebut, ada tulisan “Access is Denied” dan status=403 di dalamnya. Dan halaman pada gambar kedua tersebut merupakan halaman default error untuk tipe http status error 403 (Access Denied Page).
Untuk pembahasan kedua kita akan melakukan kustomisasi dua halaman default aplikasi web kita, yaitu form-login spring-security dan halaman 403 itu sendiri. Kustomisasi form-login bisa kita mulai dengan memodikasi class SecurityConfig sebagai langkah pertama. Berikut modifikasi class SecurityConfig tersebut.
@Override
protected void configure(HttpSecurity http)throws Exception{
    http.csrf().disable() //disable cross site request forgery (csrf) protection
            .authorizeRequests()
            .antMatchers("/").permitAll() //izinkan semua
            .antMatchers("/css/**").permitAll() //izinkan semua
            .antMatchers("/js/**").permitAll() //izinkan semua
            .antMatchers("/fonts/**").permitAll() //izinkan semua
            .antMatchers("/login").permitAll() //izinkan semua (termasuk default spring-security login page
            .antMatchers("/admin/**").hasRole("ADMIN")
            .anyRequest().authenticated() //selain yang di atas harus authenticated
            .and()
            .formLogin()
            .loginPage("/login")
            .and()
            .logout() //logout config
            .logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
            .logoutSuccessUrl("/");
}
Bisakah anda mencari tahu baris manakah yang kita tambahkan pada fungsi configure di atas?, ya kita menambahkan baris .loginPage('login') setelah baris .formLogin() pada fungsi configure tersebut, dan ada juga baris .csrf().disable() pada baris-baris awal konfigurasi tersebut. Untuk baris .csrf().disabled(), sebetulnya ini adalah fitur keamanan yang disediakan oleh spring-security untuk membuat keamanan data lebih ketat lagi berkaitan dengan Cross Site Request Forgery, tapi untuk latihan ini kita disable saja terlebih dahulu, sebab bila kita enable akan banyak perubahan yang kita lakukan dalam form-form yang sebelumnya kita buat, lain kali akan kita bahas mengenai fitur .csrf ini. 
Kemudian langkah selanjutnya adalah membuat sebuah fungsi baru pada class IndexController yang berfungsi memanggil form-login yang kita kustomisasi. Berikut ini fungsi baru pada class IndexController.
…
@RequestMapping(value = "/login", method = RequestMethod.GET)
public String loginPage(){
    return "login";
}
…
Fungsi baru yang kita buat pada class IndexController tersebut memiliki mapping url sebagai “/login” dan menggunakan sebuah template bernama login.ftl yang ada dalam foldersrc/main/resources/templates/”. Dan pastinya kita mesti buat file login.ftl tersebut, adapun isi dari file login.ftl ini adalah sebagai berikut:
<html>
<head>
    <link rel="stylesheet" href="${springMacroRequestContext.contextPath}/css/bootstrap.min.css">
    <link rel="stylesheet" href="${springMacroRequestContext.contextPath}/css/style.css">
    <title>Login Form</title>
</head>
<body>
<#if springMacroRequestContext.getQueryString()?exists >
<div class="alert alert-danger alert-dismissible" role="alert" style="width:45%;margin:10px auto">
    <button type="button" class="close" data-dismiss="alert"><span aria-hidden="true">&times;</span><span
            class="sr-only">Close</span></button>
    <strong>Login Failed</strong><br>Please Check your username and password
</div>
</#if>
<div class="container">
    <div class="row">
        <div class="col-lg-4 col-lg-offset-4 col-md-6 col-md-offset-2  col-sm-12">
            <div class="panel panel-primary" style="margin:50px auto;">
                <div class="panel-heading text-center">
                <h1 class="text-center"
                    style="background-color:white;color:#a9a9a9;font-size:60px;width:90px;height:85px;border-radius:50%;padding-top:8px;margin:30px auto;  ">
                    <span class="glyphicon glyphicon-user"></span>
                </h1>
                    <h3 class="text-center">Login Page</h3>
                </div>
                <div class="panel-body">
                    <form name='f' action="login" method='POST' class="form-inline">
                        <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}">
                        <div class="input-group">
                            <span class="input-group-addon"><span class="glyphicon glyphicon-user"></span></span>
                            <input type='text' id="username" name='username' value='' placeholder="username" class="form-control">
                        </div>
                        <p>&nbsp;</p>
                        <div class="input-group">
                            <span class="input-group-addon"><span class="glyphicon glyphicon-lock"></span></span>
                            <input type='password' class='form-control' name='password' id='password' placeholder="Password"/>
                        </div>
                        <p>&nbsp;</p>
                        <div>
                            <input name="submit" type="submit" value="Login" class="btn btn-primary"/>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
<script type="text/javascript" src="${springMacroRequestContext.contextPath}/js/jquery-1.9.1.min.js"></script>
<script type="text/javascript" src="${springMacroRequestContext.contextPath}/js/bootstrap.min.js"></script>
<script type="text/javascript" ></script>
</body>
</html>
Ada beberapa catatan untuk kode-kode pada file login.ftl yang kita buat ini, diantaranya adalah penggunaan beberapa directive dan variable default freemarker seperti ${springMacroRequestContext.contextPath} atau ${springMacroContextRequest.getQueryString()?exists}. Directives dan variabel freemarker yang kita buat ini adalah freemarker predefined directives, sementara penggunaan ?exists pada salah satu directives tersebut adalah trik untuk memeriksa apakah variabel query string pada directives springMacroContextRequest ada atau tidak.
Silahkan jalankan aplikasi web kita ini dan bukalan alamat url http://localhost:8080/login pada browser anda, seharusnya tampilan halaman login sudah bukan lagi milik spring-security secara default, melainkan form-login yang sudah kita kustomisasi seperti gambar dibawah ini:
Bukankah tampilan form-login tersebut lebih menarik dari tampilan form-login default milik spring-security?. Ok kita akan jabarkan dahulu apa-apa yang telah kita buat sebelumnya. Jadi begini “jika ada user yang me-request form-login, maka spring-security akan mengarahkan ke url “/login” dimana url tersebut telah kita mapping di dalam class IndexController dan ujungnya akan menampilkan template form (login.ftl) seperti gambar diatas, demikian juga dengan jika belum ada session security, namun user berusaha untuk mengakses suatu halaman yang diproteksi oleh spring-security, spring-security akan mengarahkan request tersebut ke form ini, sebab memang konfigurasi spring -security membuat url/login” menjadi halaman default form-login”.

Pembahasan selanjutnya kita akan melakukan kustomisasi error page Access is Denied (403), sehingga tampilan error page tersebut tidak lagi menggunakan halaman default spring-security. Langkah pertama yang kita lakukan adalah memodifikasi lagi class SecurityConfig menjadi seperti berikut ini:
@Override
protected void configure(HttpSecurity http)throws Exception{
    http.csrf().disable()
            .authorizeRequests()
            .antMatchers("/").permitAll() //izinkan semua
            .antMatchers("/css/**").permitAll() //izinkan semua
            .antMatchers("/js/**").permitAll() //izinkan semua
            .antMatchers("/fonts/**").permitAll() //izinkan semua
            .antMatchers("/login").permitAll() //izinkan semua (termasuk default spring-security login page
            .antMatchers("/admin/**").hasRole("ADMIN")
            .anyRequest().authenticated() //selain yang di atas harus authenticated
            .and()
            .formLogin()
            .loginPage("/login")
            .and()
            .logout() //logout config
            .logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
            .logoutSuccessUrl("/")
            .and()
            .exceptionHandling().accessDeniedPage("/403");
}
Yap, perhatikan dua baris paling bawah pada kode-kode diatas, kita menambahkan suatu statement yang berfungsi mengalihkan exception Access Denied ke url/403”, jadi jika ada user yang tidak berhak mengakses suatu url dalam aplikasi web kita, maka spring-security akan mengalihkan user tersebut ke halaman “/403”. Kemudian kita harus membuat mapping url ini dalam controller, sekali lagi kita modifikasi class IndexController dengan menambahkan sebuah fungsi untuk menangani url tersebut, adapun tambahan fungsi tersebut adalah sebagai berikut:  
...
@RequestMapping("/403")
public String get403Page(){
    return "error/access_denied";
}
...
Sederhana bukan?, kita akan menggunakan file freemarker template bernama access_denied.ftl yang ada pada foldersrc/main/resources/templates/error/”, jika belum ada folder dan file tersebut dalam source aplikasi web anda, maka buatlah. Adapun isi dari file access_denied.ftl ini adalah sebagai berikut:
<!DOCTYPE html>
<html lang="en">
<head>
    <link rel="stylesheet" href="${springMacroRequestContext.contextPath}/css/bootstrap.min.css">
    <link rel="stylesheet" href="${springMacroRequestContext.contextPath}/css/style.css">
    <meta charset="UTF-8">
    <title>Access Denied</title>
</head>
<body>
    <div class="container">
    <h1 class="text-center text-danger">403</h1>
    <hr>
    <h4 class="text-center text-danger">You're not authorize to view this page</h4>
    <p class="text-center"><a href="/">Back</a> </p>
    </div>
</body>
</html>
Jika sudah, maka mari kita tes aplikasi web kita ini, jalankan aplikasi web ini dan buka alamat "http://localhost:8080/admin/user/test_auth", namun pas pada saat login form yang sudah kita kustomisasi tersebut muncul, masukkan usernameuser” dan passwordpassword”, kita ingin melihat apakah halaman 403 tersebut akan muncul karena usernameuser” tidak memiliki akses sebagai ROLE_ADMIN, pada browser saya pun terlihat seperti gambar berikut ini:
Hmm, lebih baik tampilannya dari pada tampilan default milik spring-security bukan?. Sampai disini kita telah selesai mempelajari bagaimana meng-kustomisasi halaman-halaman default spring-security dalam hal ini tentunya form-login dan halaman Access Denied. Silahkan teruskan berlatih dan mengembangkan untuk aplikasi web yang lebih bagus, efektif dan efisien. Semoga apa-apa yang telah kita lakukan pada tulisan ini menjadi bermanfaat buat kita semua.

Depok, 28 Februari 2016

Salam 

Josescalia