Rudi Adianto on February 16th, 2011

Beberapa barang di supermarket mempunyai harga yang sederhana, misalnya sebotol air mineral harganya Rp. 2500. Tetapi ada juga yang mempunyai pricing yang rumit, misalnya:

  • kaos kaki 3 pasang seharga Rp. 10.000 (jadi berapa harganya kalau kita membeli 4 atau 5?)
  • daging ayam Rp. 9.999 per pon (jadi berapa harga untuk 4 ons?)
  • minuman kaleng promo beli 2 dapat 3 (apakah kaleng yang ke-3 ada harganya?)

Buatlah domain model yang merepresentasikan uang dan harga yang cukup fleksibel untuk mengakomodasi skenario diatas dan skenario-skenario pricing lain yang mungkin muncul kemudian.

Jawaban boleh dalam bentuk class diagram atau code dalam bahasa pemrograman OOP, dan harus disertai dengan penjelasan singkat.

Have fun !

Rudi Adianto on August 2nd, 2010

Misalkan X adalah sebuah array integer yang isinya diurutkan menurut besarnya, misal: [1,3,12,17,21,50,71,121,461,888,1121,1141,1821,3345].

Salah satu cara tercepat untuk mencari lokasi sebuah bilangan N adalah sebagai berikut:

  1. Ambil bilangan yang berada di tengah array sebagai O. Bila O sama dengan N, maka kita telah menemukan lokasi bilangan yang kita cari dan pencarian dihentikan. Bila tidak maka langkah ke 2.
  2. Operasi nomor 1 telah membagi X menjadi 2 sub array. Pilih 1 dari 2 array tersebut dimana N berada didalamnya, lalu lakukan langkah 1 terhadap array tersebut.

Buatlah sebuah prosedur yang mengimplementasikan algoritma diatas dalam dua bentuk:

  1. menggunakan fungsi rekursif.
  2. menggunakan loop.

Code boleh dalam pseudo code atau dalam bahasa pemrograman imperatif (C, C++, Java, PHP, Ruby atau sejenisnya).

Have fun… :)

Rudi Adianto on July 21st, 2010

Perhatikan struktur data berikut ini,

public class Wadah {
   public String Id;
   public Set<Wadah> anakAnak;
}

Struktur data tersebut dirangkai sedemikian rupa hingga membentuk rangkaian hirarkis seperti ilustrasi ini. Setiap kotak dalam ilustrasi tersebut adalah sebuah instance dari kelas Wadah.

Tuliskan code (boleh pseudo code) yang akan menjelajahi hirarki tersebut sehingga seluruh wadah terjelajahi, dengan aturan sebagai berikut:

  • Untuk setiap wadah print di layar field “Id”-nya.
  • Untuk setiap wadah yang tidak punya anak, print di layar “tidak punya anak.”

Have fun.. :-)

Objective: aplikasi SEAM yang sama dapat di deploy sebagai 2 (atau lebih) aplikasi dalam satu application server yang sama.

How?

  1. Gunakan ant build script ini untuk menggantikan build script asli dari seam gen. Yang perlu Anda ketahui dari script ini adalah property <property name=”application.name” value=”aps2″/>. Disinilah Anda menentukan nama aplikasi yang akan membedakan satu deployment dengan yang lainnya.
  2. Buka file application.xml, ubah semua kata-kata “namaProject” (dimana namaProject adalah nama project yang anda tentukan ketika membuat sebuah project baru melalui seam-gen) menjadi @APPNAME@. Contoh lengkapnya menjadi seperti file ini.
  3. Lakukan langkah diatas untuk file-file berikut ini: jboss-app.xml, components.xml, namaProject-dev-ds.xml, namaProject-prod-ds.xm, components-dev.properties, components-prod.properties
  4. Lakukan langkah diatas untuk persistence-dev.xml dan persistence-prod.xml, dan persistence-test.xml, tapi tinggalkan <persistence-unit name=”namaProject”> seperti aslinya.

Untuk me-deploy aplikasi dengan nama yang berbeda,

  1. ubah property “application.name” di build.xml,
  2. jalankan target “clean”
  3. jalankan deploy/explode

Kunci dari trik ini adalah fitur filterset dari Ant.

Rudi Adianto on September 10th, 2009

Umpamakan skenario berikut ini. Ada dua entity yang mempunyai relasi parent-child dan multiplicity one-to-many. Entity Order mempunyai beberapa OrderItem.

Order 1 —-> 0..* OrderItem

Class untuk entity Order kira-kira seperti berikut.

@Entity
@Table(name = "table_order")
public class Order implements java.io.Serializable {

	private Long id;

        private List<OrderItem> orderItems;

        @Id
	@GeneratedValue(strategy = IDENTITY)
	@Column(name = "id", unique = true, nullable = false)
	public Long getId() {
		return this.id;
	}

	public void setId(Long id) {
		this.id = id;
	}

        public List<OrderItem> getOrderItems() {
                return orderItems;
        }

        public void setOrderItems(List<OrderItem> orders) {
                orderItems = orders;
        }
}

Relasi dari Order ke OrderItem diwakili oleh property “orderItems” yang merupakan sebuah Collection. Pada saat aplikasi dijalankan, code berikut bisa jadi akan melemparkan LazyInitializationException.

order.getOrderItems().get(0);

Atau bisa juga code berikut ini.

for(OrderItem item:order.getOrderItems()) {
  // do something..
}

Kedua code diatas melakukan hal yang sama: mengakses relasi dari sebuah objek entity.

Jadi kenapa Anda mendapatkan LazyInitializationException?

Ada kalanya relasi dari sebuah entity melibatkan ribuan baris data. Dalam kasus kita, misalkan ada beberapa Order yang memiliki OrderItem sebanyak lebih dari 100 ribu. Ini tentu akan mempengaruhi loading time, dan akan sia-sia kalau kita tidak menggunakan data dari relasi tersebut. Hibernate mempunyai cara untuk menyelesaikan hal ini.

Secara default, relasi dari sebuah entity adalah lazy (kecuali kita menyatakan sebaliknya. Ini disebut fetching strategy. Untuk konfigurasi dan cara-cara untuk setting fetching strategy/eagerness silakan lihat disini dan disini). Artinya, relasi tersebut tidak akan di load (tidak disertakan dalam query) pada saat entity induk diload dari database. Relasi tersebut akan diinisiasi pada saat dibutuhkan (pada saat diakses pertama kali) . Pada contoh kasus kita, pada saat order di load pertama kali,berarti hanya Order saja yang diload, sedangkan OrderItem tidak akan di load sampai kita mengakses relasi tersebut (misal order.getOrderItems().get(0) ).

Cara Hibernate melakukan hal tersebut adalah seperti berikut. Umpamakan code berikut ini.

Order order = getOrder();
order.getId();
order.getOrderItems().get(0);

Pada saat runtime, objek entity yang ditunjuk oleh suatu reference (variable) bisa jadi bukan kelas asli dari objek tersebut, tetapi suatu kelas turunan yang disebut proxy. Jadi dalam kasus kita, variable “order” sebenarnya menunjuk ke suatu objek yang classnya bukan  “Order”, tetapi “OrderProxy” (bukan nama class sebenarnya, hanya untuk ilustrasi). OrderProxy ini kebanyakan hanya mendelegasikan pemanggilan method ke objek yang asli. Kecuali untuk method yang mengembalikan suatu relasi yang sifatnya lazy. (…bingung…? tahan sebentar..)

Pada keadaan normal (dimana kita tidak menggunakan Hibernate dan tidak ada proxy yang terlibat), code diatas skemanya adalah seperti dalam diagram berikut (dimulai dari line 2).

seq-no-proxy

Sedangkan bila kita menggunakan Hibernate, maka skemanya akan jadi “sedikit” lebih rumit.

seq-proxy

(Disclaimer: beberapa nama kelas diatas bukan nama yang sesungguhnya. Nama-nama tersebut adalah karangan penulis sekedar untuk menyederhanakan ilustrasi)

Sekarang ada sebuah proxy diantara current thread dan object Order. Proxy tersebut akan mencegat semua pemanggilan method. Bila method tersebut tidak melibatkan akses ke relasi yang lazy, maka pemanggilan method tersebut akan diteruskan ke objek Order yang asli. Bila suatu pemanggilan method melibatkan akses ke suatu relasi yang sifatnya lazy, maka prosedur untuk inisiasi relasi tersebut akan dilakukan. Didalam prosedur tersebut salah satunya adalah melakukan query ke database (untuk meload OrderItem). Untuk melakukan ini, maka Hibernate berusaha mendapatkan referensi ke persistence context (EntityManager) yang bertanggung jawab terhadap EntityTersebut (gampangnya, EntityManager yang meload entity tersebut). Prosedur tersebut ditandai dengan warna merah pada diagram diatas. Bila persistence context yang terkait dengan entity tersebut telah berakhir (atau tidak dapat dijangkau), maka Hibernate tidak dapat melakukan query ke database, dan pada akhirnya gagal untuk melakukan inisiasi relasi yang lazy diatas. Pada saat inilah Anda akan mendapatkan LazyInitializationException.

Entity yang tidak mempunyai persistence context (tidak termanage) ini disebut dengan istilah detached entity.

Jadi bagaimana cara mencegahnya?

Yang harus Anda lakukan adalah mengaitkan detached entity kepada suatu persistence context. Jika Anda menggunakan Hibernate API, salah satu dari line dibawah ini dapat digunakan.

session.update(order); // gunakan bila Anda sekaligus hendak mengupdate
session.merge(order); // gunakan bila Anda sekaligus hendak mengupdate, tapi hati2 karena optimistic locking tidak akan bekerja jika Anda menggunakan fungsi ini
session.lock(order,LockType.NONE); // ini cara yang paling benar

Bila Anda menggunakan JPA, lakukan seperti berikut.

order = entityManager.merge(order);

Perhatikan bahwa kita perlu menampung nilai kembalian dari merge(), karena fungsi tersebut membuat sebuah objek baru di heap. Objek yang lama akan tetap dalam keadaan detached.

Semoga bermanfaat.

Rudi Adianto on September 9th, 2009

Objektif

Ada dua macam validasi di JSF, field-level dan application-level. (lihat disini). Artikel ini akan memuat contoh untuk melakukan validasi application-level di SEAM dan JSF.

Sebagai contoh adalah sebuah halaman dengan 2 field, jumlahSatu dan jumlahDua, masing-masing di bind ke property sebuah bean. Validasi yang dilakukan adalah “bila jumlahSatu < 10, maka jumlahDua harus > 10.”

SEAM SDK yang digunakan adalah versi 2.1.1.GA. Konfigurasi SEAM yang digunakan default dari seam-gen.

Implementasi

Bean yang akan dijadikan penampung value adalah berikut ini.

public class OrderItem implements Serializable {
  private BigDecimal jumlahSatu;
  private BigDecimal jumlahDua;

  public void setJumlahSatu(BigDecimal jml) {
    jumlahSatu = jml;
  }

  public BigDecimal getJumlahSatu() {
    return jumlahSatu;
  }

  public void setJumlahDua(BigDecimal jml) {
    jumlahDua = jml;
  }

  public BigDecimal getJumlahdua() {
    return jumlahDua;
  }
}

Kita memerlukan satu komponen SEAM khusus untuk menempatkan backing bean dari komponen-komponen yang akan kita validasi.

@Name("penampungKomponen")
@Scope(ScopeType.EVENT)
public class PenampungKomponen implements Serializable {
  private UIInput tfJumlahSatu;
  private UIInput tfJumlahDua;

  public void setTfJumlahSatu(UIInput tf) {
    tfJumlahSatu = tf;
  }

  public UIInput getTfJumlahSatu() {
    return tfJumlahSatu;
  }

  public void setTfJumlahDua(UIInput tf) {
    tfJumlahDua = tf;
  }

  public UIInput getTfJumlahDua() {
    return tfJumlahDua;
  }
}

Kenapa kita membutuhkan penampung khusus seperti diatas? Karena kita tidak bisa mem-bind komponen JSF ke komponen SEAM yang scopenya selain EVENT.

Halaman JSF yang harus dibuat adalah seperti ini.

<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
 xmlns:s="http://jboss.com/products/seam/taglib"
 xmlns:ui="http://java.sun.com/jsf/facelets"
 xmlns:f="http://java.sun.com/jsf/core"
 xmlns:h="http://java.sun.com/jsf/html"
 xmlns:a="http://richfaces.org/a4j"
 xmlns:rich="http://richfaces.org/rich"
 template="layout/template.xhtml">

<ui:define name="body">

 <h:form id="purchaseOrder" styleClass="edit">
   <h:inputText id="tfJumlahSatu"
     required="true"
     size="50"
     maxlength="50"
     binding="#{penampungKomponen.jumlahSatu}"
     value="#{orderAction.item.jumlahSatu}"/>
   <s:message styleClass="errors"/>
   <br/>
   <h:inputText id="tfJumlahDua"
     required="true"
     size="50"
     maxlength="50"
     binding="#{penampungKomponen.jumlahDua}"
     value="#{orderAction.item.jumlahDua}"/>
   <s:message styleClass="errors"/>
   <br/>
   <h:commandButton id="save"
     value="Save"
     action="#{orderAction.doAction}"/>
   </h:form>
</ui:define>
</ui:composition>

Perhatikan di line 21 dan 29 terdapat komponen untuk menampilkan message yang berkaitan dengan komponen input sebelumnya.

Berikut ini adalah komponen SEAM dimana terdapat action dan validasi.

@Name("orderAction")
@Scope(ScopeType.SESSION)
public class OrderAction implements Serializable {
  @In(create=true) PenampungKomponen penampungKomponen;
  @In FacesMessages facesMessages;

  private OrderItem item;

  public void doAction {
    if(item.getJumlahSatu().compareTo(new BigDecimal(10)) < 0 &&
       item.getJumlahDua().compareTo(new BigDecimal(10)) < 0) {

       facesMessages.addToControlFromResourceBundle(penampungKomponen.getTfJumlahSatu().getId(), "validation.kurangDariSepuluh", item.getJumlahSatu());
       penampungKomponen.getJumlahSatu().setValid(false);
    }
    else {
       // berhasil
    }
  }

  public void setItem(OrderItem itm) {
    item = itm;
  }

  public OrderItem getItem() {
    return item;
  }
}

Perhatikan di line 13, kita membuat suatu message dan dikaitkan dengan suatu komponen JSF tertentu. Ini akan membuat message tersebut muncul disampingnya (karena di halaman JSF diatas kita menempatkan <s:message/> disamping <h:inputText/>). Kemudian di line 14 kita menandai bahwa komponen tersebut memiliki nilai yang tidak valid. Lalu apa yang akan muncul di layar?

Bagian terakhir adalah file messages_en.properties. Tambahkan entry berikut ini didalamnya.

validation.kurangDariSepuluh=Bila jumlahSatu bernilai #0, maka jumlahDua harus > 10

Selamat mencoba.

Tags: ,

Rudi Adianto on September 9th, 2009

Objektif

Use case yang saya dapatkan adalah seperti berikut ini. Sebuah aplikasi dibangun dengan model SaaS. Setiap klien akan punya database sendiri. Berdasarkan kebutuhan tersebut, maka kira-kira nantinya akan terdapat sekitar 50 database dengan struktur (table-tablenya) yang sama persis. Karena database yang digunakan tergantung login credential, maka database mana yang akan digunakan ditentukan pada saat user login.

Kasusnya jadi tambah menarik karena ada kebutuhan transaksi antar database. Jadi, sebuah transaksi bisa melibatkan posting data ke dua koneksi database yang berbeda. Misalkan, sebuah transaksi bisnis melibatkan perpindahan dana antar klien, yang artinya akun klien A harus didebet dan akun klien B harus dikredit. Akun klien A dan klien berada di database yang berbeda, tapi transaksi tersebut harus diselesaikan sebagai satu kesatuan.

Solusi

(Sebelum Anda baca terlalu jauh, perhatikan bahwa solusi ini spesifik untuk Hibernate 3.x, JPA atau non-JPA)

Dalam melakukan akses data, komponen yang kita buat tidak berhubungan langsung dengan JDBC API. Komponen-komponen tersebut berinteraksi dengan satu lapisan abstraksi yang biasa disebut Object Relational Mapping (ORM).  Bila kita menuruti spek standar JEE 5 (setting standar SEAM menggunakan ini) yang sering disebut dengan JPA, maka interface yang kita gunakan untuk akses data adalah EntityManager. Bila kita menggunakan Hibernate, maka interface yang kita gunakan adalah Session.

data-access-1

Sebuah aplikasi SEAM hasil generate dari seam-gen, pada kondisi default, mempunyai konfigurasi seperti diagram diatas. Komponen-komponen yang kita buat (atau hasil generate dari seam-gen) berinteraksi dengan sebuah EntityManager, yang mana EntityManager tersebut menggunakan sebuah koneksi database. (AWAS: ini adalah penyederhanaan. Buat Anda yang punya pemahaman mendalam tentang JEE tentu tahu jika yang saya bicarakan disini adalah dalam konteks suatu persistence context. EntityManager yang kedua juga berarti ada persistence unit yang kedua. Untuk keterangan lihat disini).

Jadi, bagaimana caranya supaya kita bisa menggunakan beberapa koneksi database yang berbeda, dan menentukan koneksi mana yang dipakai pada saat tertentu? Triknya adalah dengan mengimplementasi interface org.hibernate.connection.ConnectionProvider. ConnectionProvider digunakan oleh Hibernate untuk mendapatkan koneksi database ketika sebuah session baru dibuat. (saya mempelajari trik ini dari sini).

Jadi, pada intinya kita sediakan beberapa koneksi database, dan kita sediakan pula implementasi ConnectionProvider yang akan memilih salah satu dari koneksi tersebut berdasarkan user yang sedang login. Diagram diatas seharusnya berubah menjadi seperti berikut ini.
data-access-2

Masalah lain yang harus diselesaikan adalah, kasus ini melibatkan transaksi antar database. Jadi dalam satu transaksi bisa melibatkan lebih dari satu koneksi database. Kita akan melakukannya dengan menambahkan satu lagi EntityManager kedalam aplikasi.  EntityManager kedua ini akan membungkus koneksi database kedua yang juga harus berpartisipasi dalam transaksi tersebut. Diagram diatas akan berubah lagi menjadi seperti berikut ini.

data-access-3

Lalu bagaimana dengan transaksi database? Dalam kasus ini, dua atau lebih koneksi database harus berpartisipasi dalam sebuah transaksi yang sama. Tidak ada masalah… Kita hanya harus merubah tipe koneksi, dari yang tadinya “local-transaction” menjadi “xa-transaction“. (bagi yang ingin tahu lebih banyak tentang apa itu local-transaction dan xa-transaction, silakan baca ini, ini, dan lihat video ini).

Ok, selanjutnya tinggal bagian mudahnya.. implementasi :)

Implementasi

Definisikan koneksi-koneksi database yang akan digunakan kedalam file xxx-ds.xml. Semua koneksi tersebut boleh berada di lebih dari satu file.

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE datasources
 PUBLIC "-//JBoss//DTD JBOSS JCA Config 1.5//EN"
 "http://www.jboss.org/j2ee/dtd/jboss-ds_1_5.dtd">

<datasources>
 <xa-datasource>
   <jndi-name>SALES_APPDatasource</jndi-name>
   <xa-datasource-class>com.mysql.jdbc.jdbc2.optional.MysqlXADataSource</xa-datasource-class>
   <xa-datasource-property name="URL">jdbc:mysql://localhost:3306/sales_app</xa-datasource-property>
   <user-name>root</user-name>
   <transaction-isolation>TRANSACTION_READ_COMMITTED</transaction-isolation>
   <track-connection-by-tx />
   <max-pool-size>5</max-pool-size>
   <min-pool-size>1</min-pool-size>
   <blocking-timeout-millis>2000</blocking-timeout-millis>
   <idle-timeout-minutes>2</idle-timeout-minutes>
   <no-tx-separate-pools/>
   <exception-sorter-class-name>
       org.jboss.resource.adapter.jdbc.vendor.MySQLExceptionSorter
   </exception-sorter-class-name>
 </xa-datasource>

 <xa-datasource>
   <jndi-name>SALES_APPDatasource2</jndi-name>
   <xa-datasource-class>com.mysql.jdbc.jdbc2.optional.MysqlXADataSource</xa-datasource-class>
   <xa-datasource-property name="URL">jdbc:mysql://localhost:3306/sales_app2</xa-datasource-property>
   <user-name>root</user-name>
   <transaction-isolation>TRANSACTION_READ_COMMITTED</transaction-isolation>
   <track-connection-by-tx />
   <max-pool-size>5</max-pool-size>
   <min-pool-size>1</min-pool-size>
   <blocking-timeout-millis>2000</blocking-timeout-millis>
   <idle-timeout-minutes>2</idle-timeout-minutes>
   <no-tx-separate-pools/>
   <exception-sorter-class-name>
     org.jboss.resource.adapter.jdbc.vendor.MySQLExceptionSorter
   </exception-sorter-class-name>
 </xa-datasource>

 <xa-datasource>
   <jndi-name>SALES_APPDatasource3</jndi-name>
   <xa-datasource-class>com.mysql.jdbc.jdbc2.optional.MysqlXADataSource</xa-datasource-class>
   <xa-datasource-property name="URL">jdbc:mysql://localhost:3306/sales_app3</xa-datasource-property>
   <user-name>root</user-name>
   <transaction-isolation>TRANSACTION_READ_COMMITTED</transaction-isolation>
   <track-connection-by-tx />
   <max-pool-size>5</max-pool-size>
   <min-pool-size>1</min-pool-size>
   <blocking-timeout-millis>2000</blocking-timeout-millis>
   <idle-timeout-minutes>2</idle-timeout-minutes>
   <no-tx-separate-pools/>
   <exception-sorter-class-name>
     org.jboss.resource.adapter.jdbc.vendor.MySQLExceptionSorter
   </exception-sorter-class-name>
 </xa-datasource>
</datasources>

Perhatikan bahwa driver class untuk XA berbeda dengan driver untuk koneksi local transaction. Silakan cari nama XA driver class yang untuk RDBMS yang Anda cari di google :) . Khusus untuk database MySQL yang saya gunakan ketika membuat prototype, property max-pool-size, min-pool-size, dan no-tx-separate-pools harus disertakan (kenapa? belum tau :) ). Untuk keterangan mengenai property-property tersebut silakan lihat disini.

Selanjutnya, tambahkan konfigurasi untuk persistence unit di components.xml dan persistence.xml. Disini kita akan menggunakan 2 persistence unit yang berbeda karena sebuah transaksi paling banyak akan menyertakan 2 koenksi database yang berbeda pula. Jika koneksi database yang diperlukan sebanyak 5 maka kita memerlukan 5 persistence unit yang berbeda.

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
 version="1.0">

 <persistence-unit name="SALES_APP">
   <provider>org.hibernate.ejb.HibernatePersistence</provider>
   <jta-data-source>java:/SALES_APPDatasource3</jta-data-source>
   <properties>
     <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLInnoDBDialect"/>
     <property name="hibernate.hbm2ddl.auto" value="update"/>
     <property name="hibernate.show_sql" value="true"/>
     <property name="hibernate.format_sql" value="true"/>
     <property name="hibernate.connection.provider_class" value="com.rudi.framework.MyConnectionProvider" />
     <property name="jboss.entity.manager.factory.jndi.name" value="java:/SALES_APPEntityManagerFactory"/>
     <property name="mfin.datasource.name" value="dataSourceName"/>
 </properties>
 </persistence-unit>
   <persistence-unit name="SALES_APP2">
   <provider>org.hibernate.ejb.HibernatePersistence</provider>
   <jta-data-source>java:/SALES_APPDatasource3</jta-data-source>
   <properties>
     <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLInnoDBDialect"/>
     <property name="hibernate.hbm2ddl.auto" value="update"/>
     <property name="hibernate.show_sql" value="true"/>
     <property name="hibernate.format_sql" value="true"/>
     <property name="hibernate.connection.provider_class" value="com.rudi.framework.MyConnectionProvider" />
     <property name="jboss.entity.manager.factory.jndi.name" value="java:/SALES_APP2EntityManagerFactory"/>
     <property name="mfin.datasource.name" value="dataSourceName2"/>
   </properties>
 </persistence-unit>
</persistence>

Perhatikan line 15 dan 28. Disitu ditentukan bahwa EntityManager yang akan dihasilkan nantinya (melalui EntityManagerFactory) akan menggunakan ConnectionProvider buatan sendiri.

Perhatikan juga line 17 dan 30. Anda dapat menentukan property karangan sendiri untuk digunakan oleh ConnectionProvider nantinya.

Di file components.xml, tambahkan satu lagi managed-persistence-context, seperti di bawah ini.

<persistence:managed-persistence-context name="entityManager"
 auto-create="true"
 persistence-unit-jndi-name="java:/SALES_APPEntityManagerFactory"/>

 <persistence:managed-persistence-context name="entityManager2"
 auto-create="true"
 persistence-unit-jndi-name="java:/SALES_APP2EntityManagerFactory"/>

Selanjutnya, buat implementasi dari ConnectionProvider, seperti contoh berikut:

package org.rudi.framework;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

import javax.naming.InitialContext;
import javax.naming.NamingException;

import org.hibernate.HibernateException;
import org.hibernate.connection.ConnectionProvider;
import org.jboss.seam.contexts.Contexts;

public class MyConnectionProvider implements ConnectionProvider {
	private InitialContext ctx;

    private String dataSourceNameDefault = "SALES_APPDatasource2";

    public static final String DS_KEY = "rudi.datasource.name";

    private String dataSourceKey;

    public void configure(Properties props) throws HibernateException
    {
        try
        {
            ctx = new InitialContext(props);
            dataSourceKey = props.getProperty(DS_KEY);

        } catch (NamingException e)
        {
            throw new HibernateException(e.getMessage());
        }
    }

    public Connection getConnection() throws SQLException
    {

        try
        {
            if (Contexts.getSessionContext() != null)
            {
                String dataSourceName = (String)Contexts.getSessionContext().get(dataSourceKey);
                javax.sql.DataSource ds = (javax.sql.DataSource)ctx.lookup("java:/" + dataSourceName);
                System.out.println(">>>returning "+dataSourceName);
                return ds.getConnection();
            }
            else
            {
                javax.sql.DataSource ds = (javax.sql.DataSource)ctx.lookup("java:/" + dataSourceNameDefault);
                System.out.println(">>>returning default: "+dataSourceNameDefault);
                return ds.getConnection();
            }
        } catch (NamingException e)
        {
            throw new SQLException(e.getMessage());
        }

    }

    public void closeConnection(Connection conn) throws SQLException
    {
        if (conn != null)
        {
            conn.close();
        }
    }

    public void close()
    {
    }

    @Override
    public boolean supportsAggressiveRelease()
    {
        return false;
    }

}

Perhatikan line 43-44, disitulah inti dari class tersebut.

javax.sql.DataSource ds = (javax.sql.DataSource)ctx.lookup("java:/" + dataSourceName);

Line diatas adalah untuk mendapatkan sebuah DataSource yang di bind dengan alamat JNDI sesuai dengan String variable dataSourceName. Variable dataSourceName sendiri didapatkan dari nilai yang disimpan di session context, dengan line berikut ini.

String dataSourceName = (String)Contexts.getSessionContext().get(dataSourceKey);

Di line 28, kita mengakses property custom yang dijelaskan sebelum ini (lihat penjelasan tentang persistence.xml diatas). Value dari property tersebut digunakan di line 43 sebagai key untuk mendapatkan nama dataSource dari session.

Sampai disini, Anda mungkin sudah sangat bosan baca blog ini. Saya sarankan untuk relaks sejenak, nyalakan sebatang rokok atau buka situs ini. Jika Anda merasa sudah siap, silakan dilanjutkan lagi.

Kemudian, di komponen yang digunakan untuk otentikasi (default hasil generate dari seam-gen adalah class Authenticator) kita tentukan koneksi database mana yang akan digunakan pada saat seorang user login.

package com.rudi.action;

import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Logger;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.contexts.Contexts;
import org.jboss.seam.log.Log;
import org.jboss.seam.security.Credentials;
import org.jboss.seam.security.Identity;

import com.rudi.CONSTANT;

@Name("authenticator")
public class Authenticator {
	@Logger
	private Log log;

	@In
	Identity identity;
	@In
	Credentials credentials;

	public boolean authenticate() {
		log.info("authenticating {0}", credentials.getUsername());
		if ("admin".equals(credentials.getUsername())) {
			identity.addRole("admin");
			Contexts.getSessionContext().set(CONSTANT.DS1_KEY, "SALES_APPDatasource");
			Contexts.getSessionContext().set(CONSTANT.DS2_KEY, "SALES_APPDatasource2");
			return true;
		}else if("admin2".equals(credentials.getUsername())) {
			identity.addRole("admin");
			Contexts.getSessionContext().set(CONSTANT.DS1_KEY, "SALES_APPDatasource2");
			Contexts.getSessionContext().set(CONSTANT.DS2_KEY, "SALES_APPDatasource3");
			return true;
		}else if("admin3".equals(credentials.getUsername())) {
			identity.addRole("admin");
			Contexts.getSessionContext().set(CONSTANT.DS1_KEY, "SALES_APPDatasource3");
			Contexts.getSessionContext().set(CONSTANT.DS2_KEY, "SALES_APPDatasource");
			return true;
		}
		return false;
	}
}

Kelas diatas hanya sebagai contoh. Perhatikan bahwa untuk setiap user (admin, admin2, admin3), kita memasukkan suatu nilai String ke session. Nilai string tersebut adalah alamat JNDI dari dua koneksi database yang diperlukan.

Berikut adalah ilustrasi mengenai code yang diperlukan di prosedur yang akan menggunakan transaksi antar database.

@Name("transferAction")
public class TransferAction implements Serializable {
  @In EntityManager entityManager;
  @In EntityManager entityManager2;

  public void transferAntarBank(Long idAkunBank1,Long idAkunBank2, BigDecimal jumlah) {
    Transaction.instance().enlist(entityManager);
    Transaction.instance().enlist(entityManager2);
    Akun akunBank1 = entityManager.find(Akun.class, idAkunBank1);
    Akun akunBank2 = entityManager2.find(Akun.class, idAkunBank2);
    akunBank1.debet(jumlah);
    akunBank2.kredit(jumlah);

    entityManager.flush();
    entityManager2.flush();
  }

}

Di line 7 dan 8, kita mencantolkan kedua entityManager kepada Transaction yang sedang berjalan saat itu. Jika kita menggunakan managed persistence context (seperti contoh yang saya buat ini), SEAM secara otomatis akan memulai sebuah transaksi di fase JSF apply request dan dicommit (atau rollback) setelah invokeAction, dan satu transaksi lagi pada saat render response. Untuk keterangan tentang SEAM managed persistence context lihat disini. Untuk keterangan tentang fase-fase request di aplikasi JSF lihat disini.
Selanjutnya… selesai (akhirnya…!!!). Secara prinsip, itu saja yang perlu diketahui. Selamat mencoba. :)

Tags: , ,

Rudi Adianto on August 24th, 2009

Hampir semua langkah sudah dituliskan dengan benar disini. Hanya ada beberapa hal yang perlu ditambahkan agar bisa jalan dengan benar di JBoss AS 4.2.x. Selengkapnya adalah sbb.

  1. Kita akan menggunakan ‘self signed certificate’, atau bahasa mudahnya ‘sertifikat yang diterbitkan sendiri’. Jadi jalankan perintah berikut ini:
    keytool -genkey -alias tomcat -keyalg RSA

    Keytool adalah command line tool yang ada di folder instalasi JDK. Perintah diatas pada dasarnya melakukan 2 hal:

    • membuat ‘key’ baru
    • membuat ‘keystore’ baru, untuk menyimpan key yang baru saja dibuat

    Akan ada beberapa input yang harus diberikan pada keytool, isikan sesuai konteks.

    create_ssl_key_and_keystore

    Akan ada dua permintaan input untuk password. Yang pertama adalah untuk ‘key’, dan yang kedua adalah untuk ‘keystore’.

  2. File ‘keystore’ yang telah dibuat akan ditempatkan berada di path C:/Document and Settings/rudi/.keystore. Pindahkan ini ke tempat lain yang path-nya tidak mengandung spasi. Di PC lokal saya memindahkan ke D:/JBoss-4.2.1/server/default/conf/ , dan nama file diubah menjadi ‘ess.keystore’.
  3. Buka file D:/JBoss-4.2.1/server/default/deploy/jboss-web.deployer/server.xml, cari line yang mirip dengan code xml berikut dan gantikan:
    <Connector port="8443" address="${jboss.bind.address}"
     maxThreads="100" strategy="ms" maxHttpHeaderSize="8192"
     emptySessionPath="true"
     scheme="https" secure="true" clientAuth="false"
     keystoreFile="${jboss.server.home.dir}/conf/ess.keystore"
     keystorePass="eazyway" sslProtocol="TLS"  protocol="HTTP/1.1" SSLEnabled="true"/>

    Pastikan bahwa code diatas tidak berada dalam blok html comment (<!–   –>).

  4. Nyalakan server. Coba test ke http://localhost:8443/. Jika page default JBoss AS muncul, berarti konfigurasi sudah benar, dan aplikasi yang terinstall bisa diakses melalui SSL.

Tags: ,