24 Jan 2008
Di milis Java sedang dibahas tentang kombinasi teknologi yang digunakan untuk membuat aplikasi Java.
Ada beberapa kategori teknologi, yaitu:
-
Presentation Layer. Yaitu teknologi untuk membangun User Interface
-
Business Layer. Yaitu teknologi untuk menyediakan logika proses bisnis.
-
Data Access Layer. Yaitu teknologi untuk mengakses penyimpanan data, misalnya database.
-
Security Framework. Teknologi untuk mengelola otentikasi dan otorisasi.
-
Build System. Sistem untuk mengotomasi proses build.
-
Testing System. Sistem untuk mengotomasi pengetesan aplikasi.
-
Application Server
-
Database Server
-
Project Management Tools. Perangkat pembantu untuk mengelola project.
Berikut adalah stack yang saya gunakan, per Januari 2008. Sesuai perkembangan jaman, mungkin sekali stack ini akan berubah.
Presentation Layer
Tergantung tujuan developmentnya. Kalau butuh rapid, saya pakai JSF 1.2 dengan komponen RichFaces. Kalau ingin sederhana dan fleksibel, saya pakai Spring MVC.
Sebagai tambahan, sekarang saya sedang mengeksplorasi Spring Web Flow untuk mengelola state aplikasi. Framework ini bisa digunakan baik untuk JSF maupun Spring MVC.
Baik dalam Spring MVC maupun JSF, kita dapat memilih View Engine yang digunakan. Kebanyakan orang tidak tahu, atau tidak mau repot, atau mungkin juga merasa nyaman menggunakan JSP. Tapi saya tidak. Saya lebih suka pakai Velocity atau Freemarker untuk Spring MVC, dan pakai Facelets di JSF.
Business Layer
Saya menggunakan Spring Framework untuk mengorkestrasi transaction, integrasi dengan berbagai data access layer, messaging system, dan sebagainya.
Data Access Layer
Bila ingin rapid development, saya pakai Hibernate. Bila ingin main low level SQL, saya pakai Spring JDBC.
Saya tidak pakai JPA. Dulu pernah pakai iBatis, tapi belakangan sepertinya sudah cukup dengan Spring JDBC saja.
Security Framework
Pakai Spring Security. Fiturnya cukup lengkap dan desainnya sudah teruji di skenario dunia nyata.
Build System
Orang-orang pada pakai Maven. Saya sudah merasa cukup pakai Ant saja.
Testing System
Saat ini pakai JUnit 4 dan DBUnit. Tapi sedang eksplorasi TestNG. Saya tertarik dengan kemampuan test-grouping. Ini penting apabila kita ingin memisahkan test yang harus dieksekusi programmer sebelum dia commit (sehingga harus cepat), dan test yang dieksekusi oleh tools Continuous Integration saja (karena butuh waktu lama).
Test sebelum commit contohnya adalah unit test dan integration test. Selain itu, ada test yang butuh waktu lama, misalnya test End of Day/Month/Year process, dan test simulasi kinerja aplikasi dengan data satu tahun kedepan.
Untuk coverage test, saya gunakan Cobertura. Sedangkan untuk Static Code Analysis saya gunakan PMD.
Ke depan, saya sedang menganalisa efektifitas penggunakan JDepend untuk memastikan dependensi antar modul yang sudah ditentukan di awal tidak dilanggar programmer lain. Misalnya, tidak ada urusannya presentation layer melakukan import HibernateException. Nah, ini sepertinya bisa dienforce oleh JDepend, tapi belum sempat saya coba.
Application Server
Saya gunakan Winstone untuk tutorial dan pelatihan. Untuk development saya gunakan Tomcat. Untuk production saya gunakan Glassfish.
Database Server
Untuk development dan production, saya menggunakan PostgreSQL. Untuk pelatihan dan tutorial, saya gunakan MySQL.
Untuk melakukan tracking terhadap:
saya menggunakan Redmine.
Untuk membantu proses Configuration Management, saya menggunakan Subversion. Kadang-kadang dibantu dengan SVK bila ingin kerja offline.
Sebelum saya tutup artikel ini, pasti nanti ada yang ingin berkomentar seperti ini,
Wuah, banyak sekali persenjataannya. Kok repot-repot sekali sih?
Di sebelah rumah saya, tetangga sedang membangun rumah. Para pekerjanya (2-3 orang) cukup bermodalkan cangkul untuk mengaduk semen. Bila mereka ingin mengecor lantai dua, cukup membuat bekisting dari triplek dan papan bekas, yang setelah pakai bisa langsung dibuang. Sekali-sekali, saya melihat mereka menggotong perkakas dan bahan baku bila lokasi kerjanya pindah.
Di depan jendela saya di kantor, sedang dibangun gedung perkantoran yang baru. Untuk mengaduk semen, tidak pakai cangkul, melainkan dituang dari truk molen. Tim pekerjanya (200-300 orang) punya cetakan/bekisting khusus yang reusable untuk mengecor tiang dan lantai. Bila ada perkakas atau bahan baku yang perlu dipindahkan, mereka tidak gotong sendiri. Untuk keperluan itu, ada alat sendiri yang namanya tower crane. Bahkan ada pilotnya khusus.
Nah, itu analoginya. Silahkan simpulkan sendiri, kenapa harus repot-repot.
19 Jan 2008
Pada artikel kali ini, kita akan mengurusi masalah sepele tapi penting, yaitu Internationalization (i18n) dan Localization (l10n). i18n adalah menyiapkan aplikasi kita supaya bisa beradaptasi dengan berbagai bahasa, format penomoran, mata uang, dan hal-hal lain yang berkaitan dengan globalisasi.
Para pembaca mungkin ada yang bertanya,
Ah, aplikasi saya tidak perlu multibahasa kok. Saya bikin dalam bahasa Inggris, dan tidak akan diubah-ubah lagi.
Baiklah, mungkin aplikasi kita tidak akan berganti bahasa. Tapi mungkin sekali akan terjadi banyak revisi tampilan. Kita sebagai programmer mungkin mahir berbahasa PHP, Java, Ruby, atau bahasa-bahasa komputer lainnya. Tetapi belum tentu kita dapat menyaingi seorang JS Badudu dalam urusan Bahasa Indonesia.
Oleh karena itu, penting untuk menyiapkan aplikasi kita agar setidaknya tulisan yang dilihat user bisa diganti dengan mudah.
Mari kita lihat template sederhana berikut.
personlist.html
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>:: List of All Person ::</title>
</head>
<body>
<h1>List of All Person</h1>
<table border="0" cellpadding="2" cellspacing="2">
<tr>
<th>Name</th>
<th>Email</th>
<th> </th>
</tr>
#foreach($person in $personList)
<tr>
<td>$person.Name</td>
<td>$person.Email</td>
<td><a href="personform?person_id=$person.Id">edit</a> | <a
href="persondetail?person_id=$person.Id">view</a></td>
</tr>
#end
</table>
</body>
</html>
Seperti kita lihat, ada beberapa tulisan yang muncul di situ, yaitu:
- Judul halaman
- Nama kolom pada tabel : Nama, Email
- link untuk edit dan lihat
Spring sudah memiliki dukungan built-in untuk mengeluarkan tulisan tersebut ke file text. Nantinya kita bisa edit file tersebut dengan mudah. Kita juga bisa punya beberapa file dalam berbagai bahasa, sehingga aplikasi dapat mendeteksi setting regional komputer, dan kemudian menampilkan bahasa yang sesuai.
Tapi untuk artikel ini, cukuplah kita mengeluarkan tulisan itu ke file text. Fitur-fitur tambahan lainnya silahkan dieksplorasi sendiri oleh pembaca.
Kita menggunakan tag #springMessage
untuk mengeluarkan tulisan tersebut. Tag ini khusus untuk Velocity. Bila Anda menggunakan JSP atau Freemarker, silahkan lihat referensi Spring.
Template yang sudah diubah terlihat menjadi seperti ini.
personlist.html
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>:: #springMessage("personlist.title") ::</title>
</head>
<body>
<h1>#springMessage("personlist.title")</h1>
<table border="0" cellpadding="2" cellspacing="2">
<tr>
<th>#springMessage("personlist.name")</th>
<th>#springMessage("personlist.email")</th>
<th> </th>
</tr>
#foreach($person in $personList)
<tr>
<td>$person.Name</td>
<td>$person.Email</td>
<td><a href="personform?person_id=$person.Id">#springMessage("list.edit")</a> | <a
href="persondetail?person_id=$person.Id">#springMessage("list.view")</a></td>
</tr>
#end
</table>
</body>
</html>
Tag #springMessage
membutuhkan satu argumen, yaitu nama yang kita berikan untuk tulisan tersebut. Ini akan menjadi jelas setelah kita lihat file text yang menampung tulisan tersebut, misalnya saya beri nama messages.properties
.
messages.properties
list.edit = edit
list.view = lihat
personlist.title = Daftar Orang
personlist.name = Nama
personlist.email = Alamat Email
Setelah file tersebut siap, kita konfigurasi Spring agar membaca file tersebut. Lokasi konfigurasinya ada di tutorial-servlet.xml
. Berikut isinya.
tutorial-servlet.xml
<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource"
p:basename="messages" />
Nah, silahkan deploy ulang aplikasinya. Sekarang semua tulisan yang tampil sudah bisa dikonfigurasi melalui text file.
Selanjutnya, bagaimana mengakses file tulisan tersebut dari kode Java? Kita membutuhkannya untuk menampilkan pesan error dari hasil import data.
Mudah saja, langsung saja gunakan object messageSource
yang sudah kita deklarasikan tadi. Kita dapat mendapatkan object tersebut dengan menggunakan dependency injection. Berikut adalah kode PersonCSVParser yang akan menggunakan messageSource
.
PersonCSVParser.java
package tutorial.spring25.helper;
@Component
public class PersonCSVParser {
private MessageSource messageSource;
@Autowired(required=true)
public void setMessageSource(MessageSource messageSource) {
this.messageSource = messageSource;
}
@SuppressWarnings("unchecked")
public void parse(List<String> data, List<Person> result, List<ParseError> errors) {
-- snip --
// check for malformed data
if(tokenizer.countTokens() != PERSON_NUM_FIELDS) {
errors.add(new ParseError(
counter, text,
messageSource.getMessage(
"parseerror.reason.malformed",
new Object[]{2,tokenizer.countTokens()},
Locale.getDefault()
)
));
}
-- snip --
if(bindErrors.hasErrors()) {
List<ObjectError> bindErrorContent = bindErrors.getAllErrors();
for (ObjectError objectError : bindErrorContent) {
errors.add(new ParseError(
counter, text,
messageSource.getMessage(
objectError.getCode(),
objectError.getArguments(),
Locale.getDefault()
)
));
}
}
result.add(person);
}
}
Nah, mudah bukan? Karena itu, dari hari pertama coding, langsung saja siapkan aplikasi Anda untuk i18n.
18 Jan 2008
Aplikasi web –berbeda dengan aplikasi desktop– ditakdirkan untuk stateless. Artinya dia tidak menyimpan data untuk masing-masing user yang sedang aktif. Keputusan ini menyebabkan aplikasi web bisa diakses jutaan user sekaligus. Tapi juga menyebabkan perlu teknik khusus agar data user yang sedang aktif dapat disimpan dengan baik.
Tanpa kemampuan penyimpanan state, semua data yang dikirim user akan hilang setelah dia pindah halaman. Misalnya user:
- mengisi form 1, kemudian menekan tombol Next
- mengisi form 2, kemudian menekan tombol Next
- tiba di form 3. Pada saat ini, data yang diisi di form 1 sudah hilang
Sebelum kita melihat kode program dengan Spring 2.5, terlebih dulu kita bahas konsepnya.
Data user yang kita bicarakan di sini adalah data sementara. Contoh klasik dari state user yang harus disimpan antara lain:
- kita berbelanja online. Barang-barang yang sudah kita pilih, tapi belum kita pesan, ini harus disimpan oleh aplikasi. Data ini sering disebut dengan istilah shopping cart (atau troli belanja)
- kita sudah login untuk melihat email. Selama kita belum menyatakan selesai (dengan cara logout), kita ingin aplikasi menyimpan username dan password kita. Sehingga kita tidak perlu memasukkan username dan password setiap kali pindah dari satu email ke email lain.
- kita mengisi form secara bertahap (wizard-style). Keseluruhan rangkaian proses baru berakhir pada saat form terakhir diisi. Tanpa state management yang baik, kita tidak bisa menggunakan rangkaian form. Kita terpaksa menggunakan satu form yang sangat panjang, dan menyuruh user mengisinya sekaligus dalam satu kali proses.
Contoh yang kita sebutkan di atas membahas tentang data sementara yang harus dikelola sebelum disimpan ke database. Umurnya relatif lebih pendek daripada data permanen yang disimpan di database. Misalnya, username dan password selama user membaca email, disimpan dalam jangka waktu beberapa jam saja. Data wizard mungkin hanya disimpan beberapa menit saja, sampai form terakhir selesai dan keseluruhan data disimpan ke database. Walaupun beberapa situs, seperti Amazon, mungkin saja menyimpan data shopping-cart kita selama berbulan-bulan.
Ada beberapa cara yang dapat kita pilih untuk menyimpan state. Setidaknya ada dua pilihan lokasi :
- di client. Dengan menggunakan mekanisme cookie, kita dapat menyimpan data di browser client. Cara ini tidak membebani server, tapi kapasitas cookie di client relatif kecil. Kita tidak bisa menyimpan data yang terlalu banyak di cookie.
- di server. Mekanismenya mirip dengan kalau kita belanja di hypermarket. Tas dan jaket kita titipkan di pintu masuk, kemudian kita akan diberikan token. Pada waktu kita keluar, token kita tukarkan dengan barang yang kita titipkan.
Demikian juga dengan penyimpanan state. Ketika user pertama kali datang, dia akan mendapat token atau session ID. Aplikasi menyimpan state user di lokasi tertentu yang ditandai dengan session ID tersebut. Setiap request yang datang akan dilihat session IDnya dan datanya diambil untuk diproses.
Ada beberapa cara untuk menitipkan token pada user, beberapa yang sering digunakan antara lain:
- masukkan ke cookie. Ya kita pakai cookie lagi, tapi tidak menyimpan keseluruhan data state, melainkan cuma session ID saja.
- disisipkan di setiap link dan form. URL yang tadinya seperti ini :
http://localhost/aplikasi/personlist
menjadi seperti ini: http://localhost/aplikasi/personlist?session_id=12a75tj67
.
- dikirim ke server sebagai HTML input. Biasanya tipe yang dipilih adalah hidden. Cara ini memiliki kelemahan, karena tidak semua request bisa dibungkus dalam form.
Data state yang disimpan di server juga memiliki beberapa pilihan lokasi penyimpanan, antara lain:
- di webserver. Webserver menulis satu file per satu session. Semua data disimpan di sini. Cara ini paling mudah dikonfigurasi, sehingga banyak webserver menjadikan cara ini sebagai default. Kelemahannya, metode ini menyulitkan kita untuk mengcluster webserver, karena kita kemudian harus memikirkan bagaimana mensinkronisasikan data session di masing-masing anggota cluster
- di database. Webserver dikonfigurasi agar menyimpan datanya di database. Cara ini cukup sulit, mengingat dialek SQL masing-masing database berbeda. Banyak vendor webserver tidak mau menulis adapter untuk masing-masing merek database, sehingga kita harus periksa dulu apakah database yang kita gunakan didukung atau tidak. Bila tidak, kita terpaksa menulis sendiri kode program untuk state management.
- di distributed cache. Ada beberapa aplikasi cache yang mengkhususkan diri untuk menyimpan data secara terdistribusi, misalnya memcached yang open source atau Oracle Coherence yang berbayar. Penyimpanan ini bisa dicluster sendiri, terpisah dari webserver. Dengan pendekatan ini, kita bisa mengcluster webserver sesuka hati, karena statenya tidak disimpan webserver, melainkan dititipkan di distributed cache. Kelemahan cara ini sama dengan metode database. Tidak semua webserver menyediakan support. Kadang kita harus tulis kodenya sendiri.
- di upstream layer. Beberapa orang menggunakan arsitektur empat layer : client - web layer - application layer - database. Dengan arsitektur ini, semua state disimpan di application layer. Biasanya server yang digunakan untuk application layer memiliki mekanisme state management yang lebih baik daripada webserver. Terutama jika kita ingin melakukan clustering.
Sekarang setelah kita mengerti konsepnya, mari kita implementasikan pada contoh aplikasi kita. Kita akan menggunakan state management untuk menampilkan hasil upload data Person.
Kode upload kita kemarin bekerja sebagai berikut :
- user melakukan upload dengan mensubmit form
- server memproses, kemudian mengirim redirect ke halaman berikut
- browser menerima perintah redirect, kemudian melakukan request GET ke URL lain sesuai perintah redirect
- server menerima request GET, kemudian melakukan proses dan merender hasilnya
- browser menampilkan respon dari server
Teknik ini sering disebut Post-Redirect-Get (PRG) pattern.
Kesalahan umum programmer web pemula adalah memproses form sebagai berikut:
- user melakukan upload dengan mensubmit form
- server memproses, kemudian merender hasilnya
- browser menampilkan respon dari server
Bila user merefresh halaman hasil, maka yang dilakukan browser adalah mengulang dari langkah 1. Ini akan mengakibatkan form tersubmit dua kali. Berbeda dengan teknik PRG, kalau user merefresh halaman hasil, maka browser akan mengulangi dari langkah ketiga, yaitu GET untuk halaman hasil. Dengan demikian form tidak disubmit dua kali.
Kita menghadapi tantangan untuk mengimplementasikan PRG pada halaman upload result. Kalau hanya sekedar pesan sukses, kita bisa membuat redirect ke http://localhost/aplikasi/personuploadresult?msg=Sukses
. Tapi yang ingin kita tampilkan adalah daftar yang memuat mana data yang sukses diimport, dan mana data yang gagal. Sulit untuk dikirim melalui URL.
Untuk itu, kita harus simpan data tersebut di session. Berikut adalah kode pemrosesan upload yang sudah dimodifikasi.
PersonUploadController.java
package tutorial.spring25.ui.springmvc;
@Controller
@RequestMapping("/personuploadform")
public class PersonUploadController {
private static final Log LOG = LogFactory.getLog(PersonUploadController.class);
private static final String REDIRECT_PERSONUPLOADRESULT = "redirect:personuploadresult";
@RequestMapping(method=RequestMethod.POST)
public String processForm(@RequestParam("persondata") MultipartFile file, final HttpSession session) {
// parse file into list of strings
List<String> contents = new ArrayList<String>();
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(file.getInputStream()));
String content = reader.readLine();
while(content != null) {
contents.add(content);
content = reader.readLine();
}
reader.close();
} catch (IOException e) {
LOG.warn(e.getMessage(), e);
}
// parse list of strings into list of Persons
List<Person> persons = new ArrayList<Person>();
List<ParseError> errors = new ArrayList<ParseError>();
personDataParser.parse(contents, persons, errors);
for (Person person : persons) {
personDao.save(person);
}
session.setAttribute(Constants.PERSONUPLOAD_ERRORS_KEY, errors);
session.setAttribute(Constants.PERSONUPLOAD_SUCCESS_KEY, persons);
return REDIRECT_PERSONUPLOADRESULT;
}
}
Perhatikan dua baris session.setAttribute
di bagian bawah.
Setelah menyimpan data di session, kita redirect ke halaman hasil.
Pada halaman hasil, terlebih dulu kita pindahkan data hasil upload dari session ke request. Kemudian data yang ada di session kita hapus, agar tidak membebani memori ataupun kapasitas penyimpanan server. Lalu baru kita render tampilannya.
Berikut adalah kode untuk memindahkan data dari session ke request. Terletak di class PersonController
, dalam method uploadResult
.
PersonController
package tutorial.spring25.ui.springmvc;
@Controller
public class PersonController {
@RequestMapping("/personuploadresult")
public ModelMap uploadResult(final HttpSession session){
final ModelMap modelMap = new ModelMap();
if (session.getAttribute(Constants.PERSONUPLOAD_SUCCESS_KEY) != null) {
modelMap.addAttribute(
session.getAttribute(Constants.PERSONUPLOAD_SUCCESS_KEY)
);
session.removeAttribute(Constants.PERSONUPLOAD_SUCCESS_KEY);
}
if (session.getAttribute(Constants.PERSONUPLOAD_ERRORS_KEY) != null) {
modelMap.addAttribute(
session.getAttribute(Constants.PERSONUPLOAD_ERRORS_KEY)
);
session.removeAttribute(Constants.PERSONUPLOAD_ERRORS_KEY);
}
return modelMap;
}
}
Dan ini adalah template yang digunakan untuk menampilkan hasil upload.
personuploadresult.html
<html>
<head><title>:: Person Upload Result ::</title></head>
<body>
<h1>Person Upload Result</h1>
<h2>Successfully Imported Person Data</h2>
#if (!$personList || ${personList.isEmpty()})
<h3>No Data</h3>
#else
<table border="0" cellpadding="2" cellspacing="2">
<tr>
<th>Name</th>
<th>Email</th>
<th> </th>
</tr>
#foreach($person in $personList)
<tr>
<td>$person.Name</td>
<td>$person.Email</td>
<td>
<a href="personform?id=$person.Id">edit</a> |
<a href="persondetail?id=$person.Id">view</a>
</td>
</tr>
#end
</table>
#end
<h2>Erroneous Data</h2>
#if (!$parseErrorList || ${parseErrorList.isEmpty()})
<h3>No Data</h3>
#else
<table border="0" cellpadding="2" cellspacing="2">
<tr>
<th>Row</th>
<th>Data</th>
<th>Reason</th>
</tr>
#foreach($error in $parseErrorList)
<tr>
<td>$error.Line</td>
<td>$error.Text</td>
<td>$error.Reason</td>
</tr>
#end
</table>
#end
</body>
</html>
Di framework Ruby on Rails, sudah ada dukungan untuk kegiatan ini, namanya flash message. Flash message akan menyimpan data di session, kemudian menghapusnya pada request berikutnya.
Pada artikel ini kita sudah melakukan state management sederhana. Untuk kemampuan yang lebih canggih seperti pengelolaan shopping cart atau wizard, kita bisa menggunakan framework Spring Web Flow. Dengan SWF, flow aplikasi bisa diedit secara grafis. Ini akan memudahkan kita untuk mendokumentasikan flow navigasi aplikasi.
Gambar diambil dari situsnya SWF.
17 Jan 2008
Dulu saya pernah menulis tentang backup script Subversion untuk Linux, maupun untuk Windows. Sayangnya, script tersebut hanya bisa digunakan untuk satu repository saja.
Biasanya saya menghosting beberapa Subversion repository sekaligus, dipublikasikan menggunakan Apache dengan konfigurasi SVNParentPath
. Setidaknya ada 10 repository yang saya kelola, sehingga untuk mengkonfigurasi backup otomatisnya cukup melelahkan juga.
Oleh karena itu, saya membuat backup script lagi. Kali ini mampu menangani satu folder yang berisi banyak repository. Berikut scriptnya, masih dalam bahasa Ruby.
subversion-repos-full-backup.rb
require 'zlib'
if ARGV.length < 2
puts "Usage : ruby full-backup.rb <SVNParentPath folder> <backupfolder>"
exit
end
# some configuration
svn_parent_path = ARGV[0]
backup_folder = ARGV[1]
# variable initialization
current_date = Time.now.strftime("%Y%m%d")
Dir.foreach(svn_parent_path) { |repo|
next if('.' == repo || '..' == repo)
puts "Start to process folder : "+repo
puts "Performing svndump"
repo_name = svn_parent_path + File::SEPARATOR + repo
dumpfile_name = repo + '-' +current_date + '.dmp'
dumpfile = backup_folder + File::SEPARATOR + dumpfile_name
`svnadmin dump #{repo_name} > #{dumpfile}`
puts "Compressing dumpfile"
Zlib::GzipWriter.open(dumpfile+".gz") do |gz|
gz.write(File.read(dumpfile))
end
puts "Deleting uncompressed file"
File.delete(dumpfile)
}
Cara menjalankannya tidak sulit, cukup panggil dia melalui command prompt:
ruby subversion-repos-full-backup.rb /path/ke/repos /path/ke/backup-folder
Semoga bermanfaat
17 Jan 2008
Belajar membuat aplikasi web belum lengkap tanpa tahu caranya mengupload file dan mengelola state. Pada artikel ini kita akan belajar tentang cara menangani upload file dengan Spring MVC versi 2.5. Di artikel selanjutnya baru kita akan bahas tentang state management.
Studi kasus kita kali ini sederhana saja. Kita sudah punya aplikasi buku alamat sederhana pada rangkaian artikel sebelumnya. Kita sudah bisa menampilkan daftar data orang, mengedit data yang sudah ada atau menambah data baru, serta menggunakan template untuk header dan footer. Kali ini kita akan membuat fasilitas import data berupa text file berformat Comma Separated Value (CSV).
File yang akan kita import kira-kira berbentuk seperti ini:
Endy Muhardin,endy.muhardin@gmail.com
Hadikusuma Wahab,dhiku@artivisi.com
Ifnu Bima,ifnubima@gmail.com
Setelah file tersebut diupload, kita akan memasukkan masing-masing data ke dalam database. Setelah itu, user akan kita arahkan ke halaman daftar data orang, sehingga data yang baru saja diupload bisa dilihat.
Untuk melakukan upload, kita harus membuat HTML form dengan encoding type multipart form data. Artinya, data kita akan dikirim melalui HTTP POST dalam beberapa bagian (multipart). Kira-kira mekanismenya mirip dengan mengirim attachment melalui email.
Berikut adalah kode program HTMLnya.
<html>
<head><title>:: Upload Person Data ::</title></head>
<body>
<h1>Upload Person Data</h1>
<form method="POST" enctype="multipart/form-data">
<table>
<tr>
<td><label for="file">Person Data</label></td>
<td><input type="file" name="persondata"></td>
</tr><tr>
<td> </td>
<td><input type="submit" class="inputbutton" value="Upload"/></td>
</tr>
</table>
</form>
</body>
</html>
Perhatikan tag form. Di sana ada satu hal yang berbeda, yaitu enctype="multipart/form-data"
. Semua aplikasi web yang ingin mengupload file harus membuat tag form seperti itu, apapun bahasa pemrograman yang digunakan. Ini sering menimbulkan kebingungan di kalangan pemula.
Saya sudah ikuti semua instruksi, tapi kenapa file yang diupload tidak terbaca?
Untuk menerima file, kita gunakan input type="file"
.
Selanjutnya masuk ke kode Java. Untuk menampilkan dan memproses form ini, kita buatkan class PersonUploadController. Bentuknya mirip dengan PersonFormController yang kemarin sudah kita buat. Ada method untuk menampilkan halaman form, dan ada method untuk memproses data yang dikirim user. Berikut class PersonUploadController.
PersonUploadController.java
package tutorial.spring25.ui.springmvc;
@Controller
@RequestMapping("/personuploadform")
public class PersonUploadController {
private static final Log LOG = LogFactory.getLog(PersonUploadController.class);
private PersonCSVParser personDataParser;
private PersonDao personDao;
@Autowired(required=true)
public void setPersonDao(PersonDao personDao) {
this.personDao = personDao;
}
@Autowired(required=true)
public void setPersonDataParser(PersonCSVParser personDataParser) {
this.personDataParser = personDataParser;
}
@RequestMapping(method=RequestMethod.GET)
public ModelMap displayForm(){
return new ModelMap();
}
@RequestMapping(method=RequestMethod.POST)
public String processForm(@RequestParam("persondata") MultipartFile file) {
// parse file into list of strings
List contents = new ArrayList();
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(file.getInputStream()));
String content = reader.readLine();
while(content != null) {
if("".equals(content)) {
content = reader.readLine();
continue;
}
contents.add(content);
content = reader.readLine();
}
reader.close();
} catch (IOException e) {
LOG.warn(e.getMessage(), e);
}
// parse list of strings into list of Persons
List persons = new ArrayList();
List errors = new ArrayList();
personDataParser.parse(contents, persons, errors);
for (Person person : persons) {
personDao.save(person);
}
return "redirect:personlist"
}
}
Logika kode di atas tidak sulit. Untuk mendisplay form, praktis tidak ada yang perlu dilakukan. Kita cukup memberikan model kosong, karena form upload tidak membutuhkan data apa-apa.
Pada saat memproses form, kita menerima parameter MultipartFile. Ini adalah file yang sudah diparsing oleh Spring. Parameter ini diambil dari input form dengan nama persondata
.
Supaya Spring bisa memisahkan file yang diupload dari keseluruhan HttpRequest, kita harus menyediakan resolver. Ada dua library yang biasa digunakan orang untuk memproses file upload, yaitu:
Spring mendukung kedua library. Saya biasanya menggunakan Jakarta Commons. Untuk mengaktifkan dukungan ini, kita perlu menambahkan resolver di konfigurasi DispatcherServlet.
tutorial-servlet.xml
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="1000000"/>
</bean>
Setelah file didapat, kita lalu membaca isinya. Hanya operasi I/O standar di sini. Kita membuka InputStream
yang dibungkus dengan BufferedReader
yang memiliki method readLine
yang praktis. Kemudian kita looping setiap baris. Jangan lupa untuk memeriksa baris kosong. Hasil pembacaan file kita simpan ke List untuk pemrosesan selanjutnya.
Pemrosesan String menjadi Person kita lakukan di class PersonCSVParser supaya ada pembagian tanggung jawab yang jelas. Memisahkan parser di class tersendiri akan memudahkan kita untuk mengetes kode parser tersebut.
PersonCSVParser tidak ditampilkan di sini. Bagi yang ingin melihat kode programnya dapat langsung pergi ke Github. Demikian juga kelengkapannya, seperti:
Setelah data diproses menjadi kumpulan Person, kita looping lagi untuk menyimpan hasilnya ke database dengan menggunakan personDao
.
Terakhir, setelah semua selesai, kita redirect ke halaman daftar orang. Seharusnya kita menampilkan hasil upload. Mana data yang error, dan mana data yang sukses diimpor. Tapi materi ini membutuhkan pengetahuan tentang session, yang akan kita bahas pada artikel mendatang.
Selamat mencoba.