Memakai Gradle Untuk Mem-build Griffon Dari Source

Selama ini saya memakai Maven yang sudah mature.   Dan kini, bertambah satu lagi tool untuk manajemen proyek yang saya temukan, yaitu Gradle.   Salah satu proyek yang menggunakan Gradle adalah framework Griffon.  Contoh lain, misalnya proyek Android SDK  yang  beralih memakai Gradle.  Sejujurnya, saya lebih memilih Maven sebagai satu-satunya produk manajemen proyek yang diterapkan secara universal.   Tapi bila proyek yang ingin saya modifikasi tidak mendukung Maven, maka cara tergampang mungkin adalah dengan mengikuti ‘arus’ kehendak si pembuat proyek 🙂

Apa kelebihan Gradle? Gradle tidak memakai XML melainkan memakai Groovy sebagai DSL (Domain Specific Language)-nya. Dengan demikian, saya bisa menyertakan ‘kode program‘ di Gradle.   Penggunaan Groovy di script Gradle bahkan memungkinkan pengguna untuk membuat task secara dinamis, sebuah hal yang sulit dicapai dengan hanya berbekal XML.   Gradle juga dapat memanggil Ant task bila diperlukan.   Gradle tetap dapat memakai repository Maven yang saat ini sudah sangat banyak dipakai.

Untuk memakai Gradle, saya dapat men-download-nya di www.gradle.org/downloads.   Saya akan men-extract folder yang ada di dalam file Zip tersebut.   Setelah itu, saya mengatur environment variable GRADLE_HOME agar berisi lokasi folder tersebut.   Tidak lupa, saya juga menambahkan lokasi %GRADLE_HOME%\bin ke dalam environment variable PATH.

Untuk menguji apakah Gradle ter-install dengan baik, saya membuka Command Prompt dan memberikan perintah seperti berikut ini:

C:\>gradle -version

------------------------------------------------------------
Gradle 1.3
------------------------------------------------------------

...

Tidak lama kemudian, saya men-pull (mengambil kode program) Griffon dari GitHub.   Saya bisa melihat informasi mengenai proyek tersebut dengan masuk (baca: cd) ke lokasi folder yang berisi kode program Griffon, lalu memberikan perintah seperti ini:

C:\projects\griffon-1-2.0>gradle -q projects

------------------------------------------------------------
Root project
------------------------------------------------------------

Root project 'griffon'
+--- Project ':griffon-cli'
+--- Project ':griffon-guide'
+--- Project ':griffon-resources'
+--- Project ':griffon-rt'
+--- Project ':griffon-scripts'
+--- Project ':griffon-shell'
\--- Project ':griffon-wrapper'

To see a list of the tasks of a project, run gradle :tasks
For example, try running gradle :griffon-cli:tasks

Untuk melihat task yang dapat dipanggil, saya memberikan perintah seperti berikut ini:

C:\projects\griffon-1.2.0>gradle -q tasks

------------------------------------------------------------
All tasks runnable from root project
------------------------------------------------------------

Build tasks
-----------
assemble - Assembles the outputs of this project.
build - Assembles and tests this project.
buildDependents - Assembles and tests this project and all projects that depend
on it.
buildNeeded - Assembles and tests this project and all projects it depends on.
classes - Assembles the main classes.
clean - Deletes the build directory.
jar - Assembles a jar archive containing the main classes.
testClasses - Assembles the test classes.

Documentation tasks
-------------------
...

Help tasks
----------
...

IDE tasks
---------
cleanEclipse - Cleans all Eclipse files.
cleanIdea - Cleans IDEA project files (IML, IPR)
eclipse - Generates all Eclipse files.
idea - Generates IDEA project files (IML, IPR, IWS)

Installation tasks
------------------
izPackCreateInstaller - Creates an IzPack-based installer

Upload tasks
------------
uploadArchives - Uploads all artifacts belonging to configuration ':griffon-cli:
archives'

Verification tasks
------------------
check - Runs all checks.
test - Runs the unit tests.

Other tasks
-----------
...

Rules
-----
...

Untuk melihat ketergantungan proyek, misalnya untuk melihat dependency subproyek griffon-scripts, saya memberikan perintah seperti berikut ini:

C:\projects\griffon-1.2.0>gradle -q dependencies griffon-scripts:dependencies

------------------------------------------------------------
Root project
------------------------------------------------------------

archives - Configuration for archive artifacts.
No dependencies

default - Configuration for default artifacts.
No dependencies

izpack - The IzPack standalone compiler libraries to be used for this project.
No dependencies

------------------------------------------------------------
Project :griffon-scripts
------------------------------------------------------------

archives - Configuration for archive artifacts.
No dependencies

checkstyle - The Checkstyle libraries to be used for this project.
No dependencies

clover
\--- com.cenqua.clover:clover:3.1.2

cobertura
+--- net.sourceforge.cobertura:cobertura:1.9.4.1
|    +--- oro:oro:2.0.8
|    +--- asm:asm:3.0
|    +--- asm:asm-tree:3.0
|    |    \--- asm:asm:3.0 (*)
|    +--- log4j:log4j:1.2.9
|    \--- org.apache.ant:ant:1.7.0
|         \--- org.apache.ant:ant-launcher:1.7.0
\--- junit:junit:4.10
     \--- org.hamcrest:hamcrest-core:1.1

codenarc - The CodeNarc libraries to be used for this project.
No dependencies

compile - Classpath for compiling the main sources.
+--- org.codehaus.griffon:griffon-rt:1.2.0
|    +--- org.codehaus.groovy:groovy-all:2.0.6
|    +--- log4j:log4j:1.2.17
|    +--- org.slf4j:slf4j-log4j12:1.7.2
|    +--- org.slf4j:slf4j-api:1.7.2
|    +--- org.slf4j:jcl-over-slf4j:1.7.2
|    |    \--- org.slf4j:slf4j-api:1.7.2 (*)
|    \--- org.slf4j:jul-to-slf4j:1.7.2
+--- org.codehaus.griffon:griffon-cli:1.2.0
|    +--- org.codehaus.griffon:griffon-rt:1.2.0 (*)
|    +--- org.codehaus.griffon:griffon-resources:1.2.0
|    +--- junit:junit:4.10
|    |    \--- org.hamcrest:hamcrest-core:1.1
|    +--- asm:asm:3.2
|    +--- commons-cli:commons-cli:1.2
|    +--- commons-io:commons-io:2.4
|    +--- commons-lang:commons-lang:2.6
|    +--- commons-collections:commons-collections:3.2.1
|    +--- commons-codec:commons-codec:1.6
|    +--- org.apache.httpcomponents:httpcore:4.1.2
|    +--- com.jcraft:jzlib:1.1.1
|    +--- xml-resolver:xml-resolver:1.2
|    +--- org.fusesource.jansi:jansi:1.9
|    +--- jline:jline:0.9.94
|    |    \--- junit:junit:3.8.1 -> 4.10 (*)
|    +--- org.yaml:snakeyaml:1.9
|    +--- radeox:radeox:1.0-b2
|    +--- org.apache.ant:ant-launcher:1.8.4
|    +--- biz.aQute:bndlib:1.50.0
|    +--- org.apache.ant:ant:1.8.4
|    +--- org.apache.ant:ant-junit:1.8.4
|    +--- org.springframework:org.springframework.core:3.2.0.RELEASE
|    +--- org.springframework:org.springframework.beans:3.2.0.RELEASE
|    +--- org.springframework:org.springframework.context:3.2.0.RELEASE
|    +--- org.springframework:org.springframework.context.support:3.2.0.RELEASE
|    +--- org.xhtmlrenderer:core-renderer:R8
|    +--- com.lowagie:itext:2.0.8
|    +--- org.grails:grails-docs:2.2.0
|    |    +--- org.yaml:snakeyaml:1.8 -> 1.9 (*)
|    |    +--- org.grails:grails-gdoc-engine:1.0.1
|    |    |    \--- org.slf4j:jcl-over-slf4j:1.6.1 -> 1.7.2 (*)
|    |    +--- commons-lang:commons-lang:2.6 (*)
|    |    \--- org.slf4j:jcl-over-slf4j:1.6.2 -> 1.7.2 (*)
|    +--- org.grails:grails-gdoc-engine:1.0.1 (*)
|    +--- org.apache.ivy:ivy:2.2.0
|    +--- org.codehaus.gant:gant_groovy2.0:1.9.8
|    +--- org.codehaus.groovy.modules.http-builder:http-builder:0.5.2
|    +--- net.sf.json-lib:json-lib:2.4
|    +--- net.sf.ezmorph:ezmorph:1.0.6
|    +--- commons-beanutils:commons-beanutils:1.8.3
|    +--- org.apache.httpcomponents:httpclient:4.1.2
|    +--- com.jcraft:jsch:0.1.48
|    +--- xerces:xercesImpl:2.9.1
|    \--- org.codehaus.groovy:groovy-all:2.0.6 (*)
+--- org.codehaus.griffon:griffon-resources:1.2.0 (*)
\--- org.codehaus.groovy:groovy-all:2.0.6 (*)

...

Bagian penting dari sebuah proyek yang memakai Gradle adalah file script yang bernama build.gradle (setara dengan pom.xml di Maven). Pada bagian paling awal dari build.gradle milik source code Griffon, saya akan menemukan baris seperti berikut ini:

import java.text.SimpleDateFormat
import org.apache.ivy.plugins.resolver.URLResolver

Yup, terlihat sangat mirip kode program, bukan? Karena script ini memang adalah bentuk khusus dari kode program Groovy; bukan XML seperti di Maven.

Setelah itu, saya akan menemukan baris seperti berikut ini:

apply plugin: 'base'
apply plugin: 'idea'
apply plugin: 'eclipse'

Baris di atas menunjukkan bahwa apa saja plugin yang dipakai oleh build.gradle ini.   Plugin base menambahkan beberapa tasks untuk membuat dan menghasilkan file arsip dari proyek.   Plugin idea dan eclipse memungkinkan Gradle untuk menghasilkan file proyek IntelliJ IDEA dan Eclipse.   Beberapa cotnoh software development plugins lain adalah checkstyle, codenarc, eclipse-wtp, findbugs, jdepend, pmd, project-report, signing, dan sonar.

Setelah itu, saya akan menemukan baris seperti berikut ini:

evaluationDependsOn(':griffon-rt')
evaluationDependsOn(':griffon-cli')
evaluationDependsOn(':griffon-resources')
evaluationDependsOn(':griffon-scripts')
evaluationDependsOn(':griffon-shell')
evaluationDependsOn(':griffon-wrapper')
evaluationDependsOn(':griffon-guide')

evaluationDependsOn() dipakai untuk menandakan configuration dependencies, dimana file build.gradle yang ada di subproyek griffon-crt, griffon-cli, griffon-resources, griffon-scripts, griffon-shell, griffon-wrapper, dan griffon-guide akan di-evaluasi terlebih dahulu secara berurutan.

Subproyek? Yup!   Proyek Griffon merupakan sebuah contoh yang memakai multi-project builds di Gradle.   Bila saya membuka file settings.gradle, saya akan menemukan isinya seperti berikut ini:

include 'griffon-rt'
include 'griffon-cli'
include 'griffon-resources'
include 'griffon-scripts'
include 'griffon-wrapper'
include 'griffon-guide'
include 'griffon-shell'

rootProject.name = 'griffon'
rootProject.children.each {project ->
    String fileBaseName = project.name
    String projectDirName = "subprojects/$fileBaseName"
    project.projectDir = new File(settingsDir, projectDirName)
    project.buildFileName = "${fileBaseName}.gradle"
    assert project.projectDir.isDirectory()
    assert project.buildFile.isFile()
}

Secara sederhana, file tersebut akan mendaftarkan subproyek yang ada, dimana setiap subproyek berada di dalam folder subprojects.   Masing-masing subproyek memiliki file konfigurasi sesuai dengan nama subproyek tersebut, misalnya griffon-cli.gradle, griffon-guide.gradle, griffon-resources.gradle, dan sebagainya.

Kembali ke build.gradle yang ada di root project, saya akan menemukan baris seperti berikut ini:

subprojects { subproj ->
    apply plugin: 'idea'
    if(plugins.hasPlugin('java')){
        sourceCompatibility = 1.5
        targetCompatibility = 1.5
        group = 'org.codehaus.griffon'

        apply from: "${rootDir}/gradle/coverage.gradle"
        apply from: "${rootDir}/gradle/codeQuality.gradle"
    }

    repositories {
        mavenRepo name: 'Codehaus',       url: 'http://repository.codehaus.org'
        mavenRepo name: 'SpringSource',   url: 'http://repository.springsource.com/maven/bundles/release'
        mavenRepo name: 'Gradle',         url: 'http://gradle.artifactoryonline.com/gradle/libs-releases-local'
        mavenCentral()
        mavenRepo name: 'Sonatype',       url: 'http://repository.sonatype.org/content/groups/public'
        mavenRepo name: 'Grails Central', url: 'http://repo.grails.org/grails/core/'
    }
}

Bagian tersebut akan dikerjakan untuk seluruh subproyek yang ada.

Berikutnya, saya menemukan bagian seperti berikut ini:

Date buildTimeAndDate = new Date()
ext {
    buildTime = new SimpleDateFormat('dd-MMM-yyyy').format(buildTimeAndDate)
    buildDate = new SimpleDateFormat('hh:mm aa').format(buildTimeAndDate)
}

Ini adalah salah satu metode untuk memberikan variabel global di Gradle (melalui extra properties).

Lalu terdapat bagian seperti berikut ini:

configure(mavenizedProjects()) { proj ->
    proj.apply from: "${rootDir}/gradle/maven.gradle"
    proj.task('checkManifest') {
        dependsOn proj.classes
        doLast {
            proj.tasks.withType(Jar).each { jarfile ->
                jarfile.manifest {
                    attributes(
                        'Built-By': System.properties['user.name'],
                        'Created-By': System.properties['java.version'] + " (" + System.properties['java.vendor'] + " " + System.getProperty("java.vm.version") + ")",
                        'Build-Date': buildTime,
                        'Build-Time': buildDate,
                        'Specification-Title': proj.name,
                        'Specification-Version': project.version,
                        'Specification-Vendor': 'griffon-framework.org',
                        'Implementation-Title': proj.name,
                        'Implementation-Version': project.version,
                        'Implementation-Vendor': 'griffon-framework.org'
                    )
                }
            }
        }
    }
    proj.jar.dependsOn proj.checkManifest
}

Closure pada method configure() hanya akan diberlakukan pada subproyek yang diberikan sebagai parameternya.   Lalu, apa isi parameter mavenizedProjects() pada kode program di atas?  Saya dapat menemukannya di baris paling terakhir dari build.gradle:

def mavenizedProjects() {
    [
        project(':griffon-rt'),
        project(':griffon-cli'),
        project(':griffon-resources'),
        project(':griffon-scripts'),
        project(':griffon-shell')
    ]
}

Sekarang, saya akan mencoba melihat isi file konfigurasi untuk salah satu subproyek yang ada, yaitu griffon-cli. Saya akan menemukan baris seperti berikut ini di file griffon-cli.gradle (terletak di folder subprojects/griffon-cli):

apply plugin: 'groovy'
...
dependencies {
    groovy "org.codehaus.groovy:groovy-all:$groovyVersion"

    compile project(':griffon-rt'),
            project(':griffon-resources')
    compile 'junit:junit:4.10',
            'asm:asm:3.2',
            'commons-cli:commons-cli:1.2',
            'commons-io:commons-io:2.4',
            'commons-lang:commons-lang:2.6',
            'commons-collections:commons-collections:3.2.1',
            'commons-codec:commons-codec:1.6',
            'org.apache.httpcomponents:httpcore:4.1.2',
            'com.jcraft:jzlib:1.1.1',
            'xml-resolver:xml-resolver:1.2',
            'org.fusesource.jansi:jansi:1.9',
            'jline:jline:0.9.94',
            'org.yaml:snakeyaml:1.9',
            'radeox:radeox:1.0-b2',
            "org.apache.ant:ant-launcher:$antVersion",
            'biz.aQute:bndlib:1.50.0'
    ...
}

Method dependencies() menentukan apa saja yang dibutuhkan untuk bisa men-build subproyek griffon-cli.   groovy dan compile adalah salah satu configuration yang dapat dipakai bila subproyek mendaftarkan plugin groovy (atau plugin bahasa lainnya).

Bagian compile project(':griffon-rt'), project(':griffon-resources') menunjukkan bahwa subproyek griffon-cli bergantung pada subproyek griffon-rt dan griffon-resources.   Selain ketergantungan pada proyek, Gradle juga memungkinkan melakukan definisi ketergantungan terhadap file lain dengan files() atau isi pada folder dengan fileTree().

Kemana Gradle akan mencari dependencies yang dibutuhkan?   Gradle dapat mencari di repository Maven, Ivy, atau sebuah direktori yang ditentukan.   Bila saya kembali memperhatikan isi method subprojects() di build.gradle, saya akan menemukan lokasi pencarian, seperti yang terlihat berikut ini:

subprojects { subproj ->
    ...
    repositories {
        mavenRepo name: 'Codehaus',       url: 'http://repository.codehaus.org'
        mavenRepo name: 'SpringSource',   url: 'http://repository.springsource.com/maven/bundles/release'
        mavenRepo name: 'Gradle',         url: 'http://gradle.artifactoryonline.com/gradle/libs-releases-local'
        mavenCentral()
        mavenRepo name: 'Sonatype',       url: 'http://repository.sonatype.org/content/groups/public'
        mavenRepo name: 'Grails Central', url: 'http://repo.grails.org/grails/core/'
    }
}

Setelah memahami DSL yang dipakai Gradle, langkah berikutnya adalah men-import proyek Griffon agar dapat dipakai di IntelliJ IDEA.   Cara yang paling gampang adalah dengan membuka IntelliJ IDEA, memilih Import Project, kemudian pilih file build.gradle yang berada dalam folder griffon-1.2.0, kemudian men-klik tombol Next.   IntelliJ IDEA akan menampilkan struktur proyek yang ada disertai dengan pilihan untuk melakukan perubahan bila perlu.  Klik tombol Finish untuk selesai.

Struktur proyek yang dihasilkan berdasarkan file build.gradle Griffon akan terlihat seperti berikut ini:

Struktur Proyek Griffon

Struktur Proyek Griffon

IntelliJ IDEA juga memiliki sebuah window khusus bernama JetGradle project khusus untuk proyek Gradle, seperti yang terlihat pada gambar berikut ini:

Tampilan JetGradle Projects

Tampilan JetGradle Projects

Bagaimana cara menjalankan proyek?  Proyek Griffon tidak dijalankan, melainkan harus di-copy ke sebuah folder yaitu %GRIFFON_HOME% untuk dipakai oleh proyek lain.   Jika saya membuka file package.gradle di folder gradle, saya akan menemukan definisi task seperti berikut ini:

task installBinary(type: Sync, dependsOn: checkGriffonHome) {
    description = 'Installs the binary distribution at $GRIFFON_HOME.'
    destinationDir = griffonHomeDir as File
    with distBinSpec
    doLast {
        ant.chmod(dir: "${griffonHomeDir}/bin", excludes: '*.bat', perm: 'ugo+x')
    }
}

Dengan demikian, yang perlu saya lakukan setelah  melakukan perubahan kode program Griffon adalah memanggil task installBinary untuk men-copy hasil perubahan ke folder %GRIFFON_HOME% (sehingga seluruh proyek lain yang memakai framework Griffon akan memakai kode program Griffon hasil perubahan).

Untuk menjalankan perintah Gradle di IntelliJ IDEA, saya men-klik kanan file build.gradle kemudian memilih Create ‘build’…   Pada dialog yang muncul, saya mengisi Script parameters dengan installBinary, dan men-klik tombol OK. Sekarang, saya bisa men-klik icon Run atau menekan Shift+10 untuk mengerjakan task installBinary di IntelliJ IDEA.

Tapi ternyata tidak segampang ini! Build pertama gagal..   Berikut ini adalah pesan kesalahan yang saya peroleh:

Execution failed for task ':griffon-guide:java2htmlGenerateOverview'.
> Illegal/unsupported escape sequence near index 3
  C:\Projects\griffon-1.2.0\subprojects\griffon-guide\build\java2html
     ^

Saya sepertinya familiar dengan bug ini (kesalahan yang sama dengan yang saya temui di plugin CloudFoundry untuk Eclipse?!)  Di Windows, pemisah direktori adalah tanda “\” yang sekaligus adalah escape characters untuk String di Java.   Dengan demikian, karakter yang berusaha diterjemahkan adalah “\P” (tentunya tidak ada dan salah!).   Bila seandainya tidak ada developer Griffon yang mengalami kesalahan ini, berarti saya satu-satunya yang mencoba men-build Griffon di Windows :p

Salah satu solusi cepat yang saya tempuh untuk memperbaiki situasi di atas adalah dengan menghilangkan ketergantungan ke subproyek griffon-guide (sepertinya ini untuk dokumentasi, sehingga saat ini tidak begitu penting!). Caranya adalah dengan memberikan komentar (atau menghapus) bagian evaluationDependsOn(':griffon-guide') di build.gradle dan include 'griffon-guide' di settings.gradle. Tidak lupa, pada package.gradle, saya juga menghapus bagian from(project(':griffon-guide')) { ... | dan seluruh bagian into('doc') {..} into ('guide') {..}.

Setelah itu, saya kembali menklik icon Run di IntelliJ IDEA. Kali ini, build berjalan dengan lancar, yang ditandai dengan tulisan BUILD SUCCESSFUL.   Agar lebih yakin, saya dapat memeriksa lokasi %GRIFFON_HOME% apakah sudah berisi atau masih kosong.

Memakai Spring Security Di Grails

Untuk menambahkan fitur keamanan di Grails, saya dapat memakai Spring Security.  Yup!  Benda yang sama seperti yang saya pakai di Spring Framework sehingga saya tidak perlu mempelajari sesuatu yang sangat baru.

Saya akan mulai dengan menambah plugin spring-security-core pada proyek latihan.  Btw, plugin adalah sebuah mekanisme untuk menambah fitur yang tidak ada pada Grails bawaan.  Tidak seperti pada istilah di CMS, plugin Grails ditargetkan untuk dipakai oleh developer bukan pengguna (lebih mirip seperti komponen).  Jadi, ada peluang bagi seorang developer untuk membuat plugin yang nantinya bisa dipakai oleh developer lain yang tidak ingin repot.  Halaman http://grails.org/plugins berisi seluruh daftar plugin yang tersedia untuk Grails saat ini.

Untuk memakai plugin spring-security-core, saya menambahkan baris berikut ini di file BuildConfig.groovy:

...
plugins {
  ...
  compile ":spring-security-core:1.2.7.3"
  ...
}

Setelah itu, saya memberikan perintah berikut ini agar Grails men-download file yang dibutuhkan:

refresh-dependencies

Setelah proses download selesai, saya akan menemukan sebuah baris dengan isi berupa Installed plugin spring-security-core-1.2.7.3.  Sekarang, saya dapat mulai melakukan konfigurasi Spring Security.

Saya perlu membuat domain class yang dibutuhkan oleh Spring Security.  Untuk itu, saya memberikan perintah:

s2-quickstart com.latihan User Role

Plugin Spring Security secara otomatis akan membuat domain class bernama com.latihan.Role, com.latihan.User, dan com.latihan.UserRole.  Selain itu, ia juga akan membuat controller dengan nama LoginController.groovy dan LogoutController.groovy.

Selanjutnya, saya membuat sebuah domain class Mahasiswa dan menghasil scaffolding sama seperti yang saya lakukan di tulisan Memakai Grails’ Object Relation Mapping (GORM).  Saya ingin siapa saja boleh melihat daftar mahasiswa yang ada, tetapi hanya user admin yang boleh melakukan modifikasi data mahasiswa.

Saya akan menambahkan data percobaan setiap kali aplikasi dijalankan.  Hal ini supaya saya tidak perlu men-klik tombol Create untuk membuat semua domain object yang terlibat.  File BootStrap.groovy di bagian conf (bila dilihat dari plugin STS) berisi kode program yang akan dikerjakan setiap kali aplikasi dijalankan.  Oleh sebab itu,  saya mengubah file ini sehingga terlihat seperti berikut ini:

import com.latihan.*;

class BootStrap {

  def init = { servletContext ->
    def adminRole = new Role(authority: 'ROLE_ADMIN').save(flush: true)
    def testUser = new User(username: 'snake', enabled: true, password: 'password').save(flush: true)
    UserRole.create(testUser, adminRole, true)		

    new Mahasiswa(nim: "1122334455", nama: "Solid Snake", usia: 27).save(flush: true)
    new Mahasiswa(nim: "1122334466", nama: "Jocki Hendry", usia: 28).save(flush: true)
    new Mahasiswa(nim: "1122334477", nama: "Liquid Snake", usia: 25).save(flush: true)

    assert User.count()==1
    assert Role.count()==1
    assert Mahasiswa.count()==3
  }
}

Saya akan mendefinisikan keamanan dalam bentuk annotation, oleh sebab itu, saya perlu menambahkan baris berikut ini di Config.groovy:

grails.plugins.springsecurity.securityConfigType = "Annotation"

Lalu, saya menambahkan annotation @Secured pada action di controller yang ingin saya lindungi.  Karena yang boleh melalukan modifikasi CRUD hanya user dengan role ROLE_ADMIN, maka saya memberika annotation seperti berikut ini:

class MahasiswaController {
  def index() { ... }

  def list() { ... }

  @Secured(['ROLE_ADMIN'])
  def create() { ... }

  @Secured(['ROLE_ADMIN'])
  def save() { ... }

  def show(Long id) { ... }

  @Secured(['ROLE_ADMIN'])
  def edit(Long id) { ... }

  @Secured(['ROLE_ADMIN'])
  def update(Long id, Long version) { ... }

  @Secured(['ROLE_ADMIN'])
  def delete(Long id) { ... }
}

Saya akan menjalankan aplikasi dengan memberikan perintah run-app.  Setelah itu, saya akan membuka url http://localhost:8080/latihan/mahasiswa/list.  Siapa saja bisa melihat daftar mahasiswa yang ada.  Tetapi begitu saya men-klik tombol New Mahasiswa, saya akan dibawa ke halaman login (dibuat oleh Spring Security secara otomatis) seperti yang terlihat pada gambar berikut ini:

Halaman Login Yang Dibuat Spring Security

Halaman Login Yang Dibuat Spring Security

Untuk mengerjakan action di controller yang khusus untuk role ROLE_ADMIN, saya dapat login dengan menggunakan user snake dan password berupa password.

Saya dapat menambahkan beberapa elemen di view supaya pengguna bisa logout dengan memakai Security TagLib.  Saya akan menambahkan kode berikut ini tepat sebelum definisi <table>:

<div style="margin: 10px;">
  <sec:ifLoggedIn>
    Selamat datang, <sec:loggedInUserInfo field="username" /> |
    <g:link controller="logout">Logout</g:link>
  </sec:ifLoggedIn>
  <sec:ifNotLoggedIn>
    <g:link controller="login">Login</g:link>
  </sec:ifNotLoggedIn>
</div>

Isi tag <sec:ifLoggedIn> hanya akan ditampilkan bila user sudah login, sebaliknya, isi tag <sec:ifNotLoggedIn> hanya akan ditampilkan bila user belum login.  Didalam masing-masing tag, saya memakai <g:link> untuk memanggil LoginController atau LogoutController yang isinya dihasilkan oleh Spring Security plugin.

Sekarang, bila saya sudah login dan membuka halaman list, saya akan menemukan pilihan untuk logout seperti pada tampilan seperti berikut ini:

Tampilan Dengan Pilihan Logout Bila User Sudah Login

Tampilan Dengan Pilihan Logout Bila User Sudah Login

Memakai Grails’ Object Relation Mapping (GORM)

Grails’ Object Relation Mapping (GORM) pada dasarnya adalah Hibernate 3 yang di-akses melalui Groovy.  Karena Groovy adalah bahasa dinamis, GORM menawarkan cara berbeda dalam memakai Hibernate sehingga rasanya unik bagi pengguna Hibernate di Java seperti saya.   Seperti apa?

Saya akan mulai dengan membuat sebuah proyek Grails di STS yang saya beri nama latihan.  Setelah itu, saya memberikan perintah untuk membuat sebuah domain object Mahasiswa:

create-domain-class mahasiswa

Kemudian, saya mengubah class Mahasiswa.groovy yang dihasilkan menjadi seperti berikut ini:

package latihan

class Mahasiswa {

  String nim
  String nama
  Integer usia
  Date tanggalDaftar

  static constraints = {
    nim maxSize: 10, blank: false, unique: true
    nama size: 5..45, blank: false
    usia range: 10..50
    tanggalDaftar max: new Date()
  }
}

Pada kode program di atas, terlihat bahwa saya melakukan pengaturan domain class yang akan dikelola oleh Hibernate melalui DSL GORM, bukan melalui annotation seperti di Java.  Selain itu, GORM secara otomatis akan menambahkan atribut id dengan tipe Long sebagai primary key (yang akan di-auto increment secara otomatis oleh Hibernate) dan version untuk optimistic locking.  Dengan demikian, nim disini adalah surrogate primary key atau primary key ‘palsu’.

Berikutnya, saya ingin Grails membuat scaffolding.  Pada konstruksi bangunan, scaffolding adalah struktur sementara yang berfungsi sebagai pembantu/pendukung dalam membuat struktur yang lebih rumit.  Sebagai contoh, scaffolding bisa ditemukan dalam bentuk bambu-bambu yang disatukan yang memungkinkan para pekerja bangunan menjangkau bagian sulit.  Dalam bidang pengembangan software, terutama di Grails, scaffolding yang dimaksud adalah membuat view atau controller secara otomatis berdasarkan isi domain object.  View dan controller hasil scaffolding ini umumnya bersifat sementara sebagai prototype dan perlu dikembangkan lebih lanjut.  Untuk menghasilkan scaffolding, saya memberikan perintah:

generate-all latihan.Mahasiswa

Grails akan menghasilkan controller dengan nama MahasiswaController.groovy dan view untuk operasi CRUD di folder mahasiswa.  Salah satu contoh view  adalah list.gsp  yang akan menampilkan seluruh mahasiswa yang ada di database.  Untuk merasakan pengalaman coding Groovy dan GORM,  saya akan mencoba menambahkan sebuah fitur filtering di halaman tersebut.  Btw, view di Grails memakai teknologi Groovy Server Page (GSP) yang mirip seperti JSP.   Saya menambahkan bagian berikut ini tepat sebelum <table> di list.gsp:

<g:form>
  <g:select name="kataKunci" from="${['NIM', 'Nama', 'Usia', 'Tanggal Daftar']}"
     keys="${['nim','nama', 'usia', 'tanggalDaftar']}" />
  <g:textField name="nilaiDicari" />
  <g:actionSubmit value="Cari" action="list" />
</g:form>

Tag GSP di atas akan membuat sebuah drop-down berisi field yang akan dicari, sebuah textbox untuk mengetik nilai yang dicari, dan sebuah tombol submit.  Bila tombol submit ini di-klik, maka method list (nilai dari atribut action di tag <g:actionSubmit>) yang ada di-controller yang menampilkan halaman ini akan dikerjakan.

Dengan kata lain, bila tombol submit di-klik, maka method list yang ada di file MahasiswaController.groovy akan dikerjakan.  Oleh sebab itu, saya mengubah method tersebut agar berisi kode program yang melakukan penyaringan seperti yang terlihat berikut ini:

def list(Integer max) {
  params.max = Math.min(max ?: 10, 100)

  def results
  if (params.kataKunci && params.nilaiDicari) {
    switch (params.kataKunci) {
       case "nim":
          results = Mahasiswa.where {
             nim =~ "%${params.nilaiDicari}%"
          }.list(params)
          break
       case "nama":
          results = Mahasiswa.where {
             nama =~ "%${params.nilaiDicari}%"
          }.list(params)
          break
       case "usia":
          results = Mahasiswa.where {
             usia == params.nilaiDicari as Integer
          }.list(params)
          break
       case "tanggalDaftar":
          results = Mahasiswa.where {
             tanggalDaftar == new java.text.SimpleDateFormat("dd/MM/yyyy").parse(params.nilaiDicari)
          }.list(params)
          break
    }
  } else {
     results = Mahasiswa.list(params)
  }

  [mahasiswaInstanceList: results, mahasiswaInstanceTotal: Mahasiswa.count()]
}

Pada kode program di atas, saya memakai fitur where queries yang telah ada sejak Grails 2.  Pada perbandingan untuk String, saya menggunakan operator =~ yang akan melakukan pencarian like secara case-insensitive (misalnya pencarian “SOLID SNAKE” dan “solid snake” akan mengembalikan hasil yang sama).

Sekarang, saya akan menjalankan aplikasi latihan ini dengan memberikan perintah:

run-app

Saya segera membuka url “localhost:8080/latihan/mahasiswa/list“.  Saya bisa menambahkan data mahasiswa dengan men-klik tombol New Mahasiswa.  Setelah menambahkan beberapa data, tampilan akan terlihat seperti pada gambar berikut ini:

Tampilan View Hasil Scaffolding Grails

Tampilan View Hasil Scaffolding Grails

Kode program di halaman ini masih singkat, tetapi saya sudah memperoleh sebuah situs dengan fitur CRUD lengkap dengan fungsi pencarian.  Grails sepertinya berhasil menyederhanakan proses pengembangan untuk web yang sederhana sehingga terasa cepat dan agile.

Terintegrasi Dengan Memakai Eclipse Grails Plugin Di STS

Grails adalah sebuah framework untuk pengembangan web secara agile dengan memakai Groovy.   Untuk memakai Grails, saya perlu memberikan perintah di Command Prompt, misalnya selalu dimulai dengan perintah grails create-app untuk membuat proyek baru.  Untuk menjalankan proyek, saya perlu mengetik perintah grails run-app.  Dan seterusnya.

Saat pertama kali memakai Grails, saya langsung teringat pada apa yang dilakukan oleh teman saya yang sedang membuat tesis yang melibatkan Symfony, sebuah framework agile untuk PHP.  Keduanya memiliki kemiripan yang serupa: harus sering-sering memberikan perintah lewat Command Prompt (sebenarnya bukan hanya Symfony, tapi masih banyak lagi, seperti Ruby on Rails dan Zend Framework).

Pada beberapa kali kesempatan, sang teman mencari saya untuk berdiskusi mengenai kode programnya, sehingga terkadang saya harus mengubah kode program di notebook-nya.   Dia tidak memakai sebuah Integrated Development Environment (IDE), melainkan memakai sebuah editor yaitu Komodo Edit.  Dalam hal ini, editor berfungsi memberikan nilai tambah berupa syntax highlightning.  Ada juga project tree untuk melihat folder secara cepat.  Tapi untuk melakukan operasi lain, misalnya menjalankan pengujian, saya perlu menekan ALT+TAB untuk berpindah ke command prompt dan mengetik perintah disana.  Setelah membaca hasil output dari command prompt, saya perlu kembali ke editor, mencari file yang failed di project tree untuk di-edit.

Saya berusaha agar lingkungan pengembangan saya tidak seperti itu saat memakai Grails.  Mungkin hal ini terlihat sepele, tapi pada saat saya sedang ‘aktif’ coding, saya ingin semua referensi yang saya butuhkan muncul pada saat itu juga. Misalnya  bila saya ingin tahu apa isi file tertentu sebelum melanjutkan coding, maka saya ingin isi file tersebut bisa langsung muncul dalam hitungan beberapa detik.  Bila terlalu lama, maka bayangan kode program yang tadinya mengalir dengan lancar bisa sirna, dan saya jadi bertanya dalam hati,  ‘tadi mau ngapain ya?’  Selain itu, kadang-kadang saya menghabiskan waktu berjam-jam karena sebuah bug yang timbul hanya karena saya membuat sebuah asumsi atau perkiraan yang salah; padahal saya bisa memastikan kebenaran asumsi saya dengan membuka file lain yang terkait, tetapi karena repot mencari jarum di tumpukan jerami, saya memilih untuk bergantung sepenuhnya pada asumsi.   Tapi ada kasusnya saya memakai editor, misalnya saat melakukan perubahan minor pada proyek yang sudah jadi di server.

Saya beruntung karena ada plugin Eclipse Grails untuk Spring Tool Suite (berbasis Eclipse).  Setelah meng-install plugin ini, yang saya lakukan adalah menentukan lokasi Grails yang akan dipakai dengan memilih menu Window, Preferences, Groovy, Grails seperti yang terlihat di gambar berikut ini:

Menentukan Instalasi Grails Yang Dipakai

Menentukan Instalasi Grails Yang Dipakai

Untuk membuat sebuah proyek Grails baru, saya tinggal memilih menu File, New, Grails Project, seperti yang terlihat pada gambar berikut ini:

Membuat Proyek Grails Baru

Membuat Proyek Grails Baru

Struktur proyek yang ditampilkan di Project Explorer tidak sepenuhnya sesuai dengan struktur folder proyek, melainkan ada pengkategorian secara logika, misalnya domain, controllers, views, dan sebagainya seperti yang terlihat pada gambar berikut ini:

Struktur Proyek Grails

Struktur Proyek Grails

Lalu bagaimana caranya memberikan perintah yang sebelum diberikan melalui Command Prompt?  Saya bisa men-klik icon Grails Command History seperti yang terlihat pada gambar berikut ini:

Grails Command History

Grails Command History

Akan muncul sebuah popup dimana saya bisa mengetik perintah Grails (dan tetap ada Ctrl+Space untuk completion) seperti yang terlihat pada gambar berikut ini:

Memberikan perintah Grails

Memberikan perintah Grails

Untuk menjalankan aplikasi, bisa juga langsung dari tombol Run As..  yang memiliki shortcut untuk perintah run-app seperti yang terlihat pada gambar berikut ini:

Menjalankan Proyek

Menjalankan Proyek

Pada saat membuat kode program, saya dapat menahan tombol Ctrl dan men-klik  sebuah identifier untuk menampilkan file dimana ia didefinisikan.  Shortcut Ctrl+Shift+R untuk mencari file yang ada di proyek juga bisa bekerja dengan baik, seperti yang terlihat pada gambar berikut ini:

Shortcut Ctrl+Shift+R di Eclipse

Shortcut Ctrl+Shift+R di Eclipse

Hasil test reports selain dapat dibaca dengan mudah versi HTML-nya,  juga dapat ditampilkan dalam bentuk  JUnit View yang  familiar, seperti yang terlihat pada gambar berikut ini:

Melihat Test Reports

Melihat Test Reports