Thursday, June 18, 2015

Maven Jetty Run : menggunakan JNDI

Saya ingin memberikan sedikit tips tentang running webapp menggunakan jetty plugin di maven.
Berawal pada saat ingin running aplikasi web spring menggunakan mvn jetty:run.
Awalnya baik baik saja. kemudian muncul masalah ketika source code di repository jdbc connection ke MySQL diganti menggunakan JNDI yg dipasang di jetty server.

 <jee:jndi-lookup id="dataSource"  
            jndi-name="jdbc/SampleDB"  
            expected-type="javax.sql.DataSource" />  
  <bean class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" id="entityManagerFactory">  
     <property name="persistenceUnitName" value="testPersistenceUnit"/>  
     <property name="dataSource" ref="dataSource"/>  
 </bean>  

Ketika di run lewat mvn jetty terjadi error, karena JNDI name tidak temukan.

 2015-06-19 11:07:04.474:WARN:oejw.WebAppContext:main: Failed startup of context o.e.j.m.p.JettyWebAppContext@7270fd39{/organika,file:/Data/Data-Project/Nostra/Organika/sources/nostratech-farming-server/nostra-web/src/main/webapp/,STARTING}{file:/Data/Data-Project/Nostra/Organika/sources/nostratech-farming-server/nostra-web/src/main/webapp/}  
 org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in file [/Data/Data-Project/Nostra/Organika/sources/nostratech-farming-server/nostra-web/target/classes/spring/applicationContext.xml]: Cannot resolve reference to bean 'dataSource' while setting bean property 'dataSource'; nested exception is org.springframework.beans.factory.BeanCreationException:  
 Error creating bean with name 'dataSource': Invocation of init method failed; nested exception is javax.naming.NameNotFoundException; remaining name 'jdbc/SampleDB'  
 .....  
 Caused by:  
 org.springframework.beans.factory.BeanCreationException:   
 Error creating bean with name 'dataSource': Invocation of init method failed; nested exception is javax.naming.NameNotFoundException; remaining name 'jdbc/SampleDB'  

Salah satu solusinya adalah saya harus menginstall standalone jetty dan menambahkan JNDI config di jetty nya. Karena hal itu cukup rumit maka saya tidak melakukannya.
Cara lain yg saya lakukan adalah memasang JNDI di maven jetty plugin.
Di folder resource kita buat file jetty-env.xml.



Di dalamnya kita definisikan JNDI yg kita pakai name nya kita samakan saja dengan yg dipakai di server.
 <?xml version="1.0" encoding="UTF-8"?>  
 <!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">  
 <Configure id ="sampledb" class="org.eclipse.jetty.webapp.WebAppContext">  
   <New id="SampleDB" class="org.eclipse.jetty.plus.jndi.Resource">  
     <Arg>jdbc/sampleDB</Arg>  
         <Arg>  
     <New class="com.mysql.jdbc.jdbc2.optional.MysqlConnectionPoolDataSource">  
       <Set name="Url">jdbc:mysql://localhost:3306/test_db</Set>  
       <Set name="User">root</Set>  
       <Set name="Password"></Set>  
     </New>  
     </Arg>  
   </New>  
 </Configure>  

Setelah itu kita panggil jetty-env.xml itu di config jetty plugin (pom.xml)
di configuration :
<jettyXml>src/main/resources/jetty-env.xml</jettyXml>
       <plugin>  
         <groupId>org.eclipse.jetty</groupId>  
         <artifactId>jetty-maven-plugin</artifactId>  
         <version>9.1.2.v20140210</version>  
                     <configuration>  
           <jettyXml>src/main/resources/jetty-env.xml</jettyXml>  
                          <webAppConfig>  
                               <contextPath>/${war.name}</contextPath>  
                          </webAppConfig>  
           <stopKey/>  
           <stopPort/>    
         <dependencies>  
           <dependency>  
             <groupId>mysql</groupId>  
             <artifactId>mysql-connector-java</artifactId>  
             <version>5.1.30</version>  
             <type>jar</type>  
           </dependency>  
         </dependencies>  
                </plugin>  

Kita running lagi mvn jetty:run.


Yes akhirnya bisa juga.

Referensi : https://wiki.eclipse.org/Jetty/Howto/Configure_JNDI_Datasource
                  https://wiki.eclipse.org/Jetty/Feature/JNDI

Selamat mencoba.



FullText Search Query : menggunakan Hibernate-Search


Jumpa lagi kita dengan team nostratech. Kali ini kita akan belajar tentang seraching data di database menggunakan Full Text Search. Apa itu full text search?.

Seperti saat kita search di google. Kita ketik keyword apa, maka akan muncul list data yang sesuai (match atau sama persis dengan keyword yang kita ketik) dan juga semua data ada hubungannya dengan keyword yang kita ketik. Misal kita ketik 'hibernate book', maka akan muncul semua list yang ada kata mirip denhan 'hibernate' dan 'book'.



Seperti itulah sederhananya pengertian full text search.

Kita ambil contoh yang sederhana saja.
Misalnya kita punya data list nama contact di tabel seperti ini :


Disini kita asumsikan kita menggunakan ORM hibernate.

Bagaimana caranya search data “Paolo Coelho Jr” ? Kalo menggunakan keyword 'paolo' tentunya kita memakai query :

 select a from Contact where a.name like 'paolo%'  

jika keyword dari usernya 'coelho' :

 select a from Contact where a.name like '%coelho%'  

Nah bagaimana jika keyword dari usernya seperti ini : 'coelho Jr', 'coelho jr paolo', 'famous writer paolo coelho', 'the name of Coelho NM paolo ' dan lain lain.
Tentu kita akan bingung dan akan kesulitan tentunya kita.
Search query classic : name like '%keyword%' tentu tidak bisa menghandle lagi jika sudah menggunakan query yang aneh aneh seperti diatas.
Selain tidak mendapatkan hasil yang diharapkan, tentunya query %% tentu sangat buruk untuk masalah performence kalau datanya sudah terlampau banyak (walaupun field name di tabel Contact sudah di Index).

Solusinya kita bisa menggunakan library hibernate-search untuk menangani search query untuk problem diatas.
Hibernate search didalamnya menggunakan engine apache lucene untuk indexing datanya. Jadi nantinya data Name dari tabel Contact akan disimpan di index lucene.
Hibernate tidak akan langsung mencari data di tabel contact tetapi di cari terlebih dulu di index luncene.

Proses yang terjadi kurang lebih seperti ini :

Hibernate --> search index lucene --> search index name dan get id nya --> search ke tabel contact berdasarkan id --> tampilkan data.

Tambahkan dependency maven di pom.xml

 <dependency>  
   <groupId>org.hibernate</groupId>  
   <artifactId>hibernate-core</artifactId>  
   <version>4.3.9.Final</version>  
 </dependency>  
 <dependency>  
   <groupId>org.hibernate</groupId>  
   <artifactId>hibernate-search-orm</artifactId>  
   <version>5.2.0.Final</version>  
 </dependency>  

Mari kita mulai ke contohnya.

Kita buat dulu model Contact.

Set property mana yang akan diindex menggunakan @Field

 import javax.persistence.Entity;  
 import javax.persistence.Id;  
 import javax.persistence.Table;  
 import org.apache.lucene.analysis.core.LowerCaseFilterFactory;  
 import org.apache.lucene.analysis.snowball.SnowballPorterFilterFactory;  
 import org.apache.lucene.analysis.standard.StandardTokenizerFactory;  
 import org.hibernate.search.annotations.*;  
 /**  
  * The persistent class for the contact database table.  
  */  
 @Entity  
 @Indexed  
 @AnalyzerDef(name = "customanalyzer",  
     tokenizer = @TokenizerDef(factory = StandardTokenizerFactory.class),  
     filters = {  
         @TokenFilterDef(factory = LowerCaseFilterFactory.class)  
         , @TokenFilterDef(factory = SnowballPorterFilterFactory.class  
     )  
     })  
 @Table(name = "contact")  
 public class Contact {  
   @Id  
   @DocumentId  
   private Integer id;  
   @Field(index = Index.YES, store = Store.NO)  
   @Analyzer(definition = "customanalyzer")  
   private String name;  
   private String email;  
      /// setter + getter  
 }  

Hibernate config xml
 <?xml version="1.0" encoding="UTF-8"?>  
 <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN"   
 "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">  
 <hibernate-configuration>  
   <session-factory>  
    <property name="show_sql">false</property>  
    <property name="format_sql">true</property>  
    <property name="dialect">org.hibernate.dialect.MySQLDialect</property>  
    <property name="connection.driver_class">com.mysql.jdbc.Driver</property>  
    <property name="connection.url">jdbc:mysql://localhost:3306/test</property>  
    <property name="connection.username">root</property>  
    <property name="connection.password"></property>  
    <property name="hibernate.hbm2ddl.auto">update</property>  
    <property name="hibernate.show_sql">true</property>  
    <property name="hibernate.search.default.directory_provider">filesystem</property>  
    <property name="hibernate.search.default.indexBase">/tmp/lucene/indexes</property>  
    <mapping class="com.srccodes.example.hibernate.Contact"/>  
   </session-factory>  
 </hibernate-configuration>  

untuk menentukan dimana kita menyimpan file index nya.

 <property name="hibernate.search.default.directory_provider">filesystem</property>  
 <property name="hibernate.search.default.indexBase">/tmp/lucene/indexes</property>  


Hibernate session until (kalau kita mengunakan JPA diatas hibernate kita bisa menggunakan entity manager)

 public class HibernateUtil {  
   private static SessionFactory sessionFactory = null;   
   private static ServiceRegistry serviceRegistry = null;   
   private static SessionFactory configureSessionFactory() throws HibernateException {   
     Configuration configuration = new Configuration();   
     configuration.configure();   
     Properties properties = configuration.getProperties();  
    serviceRegistry = new ServiceRegistryBuilder().applySettings(properties).buildServiceRegistry();       
     sessionFactory = configuration.buildSessionFactory(serviceRegistry);   
     return sessionFactory;   
   }  
   static {  
    configureSessionFactory();  
   }  
   private HibernateUtil() {}  
   public static Session getSession() {  
    return sessionFactory.openSession();  
   }  
 }  

Create indexing pada setiap insert, update, delete data agar index di lucene terupdate mengikuti data di tabel.

 private void doIndex() throws InterruptedException {  
   Session session = HibernateUtil.getSession();  
   FullTextSession fullTextSession = Search.getFullTextSession(session);  
   fullTextSession.createIndexer().startAndWait();  
   fullTextSession.close();  
 }  

Create fulltext session dan lucene query yang nantinya akan diterjemahkan ke JPA/hibernate query. Jadi kita tidak perlu secara detail tahu tentang lucene query karen sudah dihandle oleh si hibernate.

 private List<Contact> search(String queryString) {  
   Session session = HibernateUtil.getSession();  
   FullTextSession fullTextSession = Search.getFullTextSession(session);  
   QueryBuilder queryBuilder = fullTextSession.getSearchFactory().buildQueryBuilder().forEntity(Contact.class).get();  
   org.apache.lucene.search.Query luceneQuery = queryBuilder  
   .keyword().onFields("name").matching(queryString).createQuery();  
   org.hibernate.Query fullTextQuery = fullTextSession.createFullTextQuery(luceneQuery, Contact.class);  
   List<Contact> contactList = fullTextQuery.list();  
   fullTextSession.close();  
   return contactList;  
 }  

jika dilihat di sq log maka query ditabel contact hanya mengunakan where id (yg didapat dari lucene index) tidak ada pencarian berasarkan 'name'. Karena name sudah dihandle oleh hibernate-search.

 Hibernate:   
   select  
     this_.id as id1_0_0_,  
     this_.email as email2_0_0_,  
     this_.name as name3_0_0_   
   from  
     contact this_   
   where  
     (  
       this_.id in (  
         ?  
       )  
     )  


Berikut contoh pencarian data dengan beberapa keyword :
 >>>>>>Record found for 'paolo coelho' count : 1  
 Id: 2 | Name:Paolo Coelho | Email:paolo@gmail.com  
 ===============================================  
 >>>>>>Record found for 'mr coelho paolo is his name' count : 1  
 Id: 2 | Name:Paolo Coelho | Email:paolo@gmail.com  
 ===============================================  
 >>>>>>Record found for 'who is mister coelho paolo ?' count : 1  
 Id: 2 | Name:Paolo Coelho | Email:paolo@gmail.com  
 ===============================================  
 >>>>>>Record found for 'famous writer paolo coelho' count : 1  
 Id: 2 | Name:Paolo Coelho | Email:paolo@gmail.com  
 ===============================================  

Sekedar tambahan file index lucene bisa dibuka menggunakan software Luke.


Setiap kata di property Contact -> name di index oleh lucene (dan disimpan Id nya). Dari sinilah hibernate mencari index beradasarkan keyword yang diinginkan





Happy coding & selamat menjalankan ibadah puasa ramadhan bagi yang menjalankan.




Tuesday, June 16, 2015

Enabling Cross-Origin Resource Sharing (CORS) with HAProxy


Beberapa waktu yang lalu, teman saya sempat meminta tolong kepada saya ketika menemui isu CORS pada aplikasi AngularJS yang akan dia deploy ke cloud. Aplikasi AngularJS tersebut meng-consume REST services yang berada di belakang HAProxy. Setelah saya googling ke beberapa tempat, saya menemukan cara yang sesuai dengan kasus yang saya temui. 

Pada dasarnya ketika sebuah aplikasi Javascript me-request REST service untuk pertama kalinya, aplikasi tersebut akan mengirimkan request dengan method OPTION terlebih dahulu. Request OPTION tadi bertujuan untuk meminta otoritas mengakses resource REST yang akan diakses setelahnya. OPTION request mengharapkan response yang menyatakan origin, headers, dan HTTP method apa saja yang diijinkan oleh aplikasi backend untuk diakses secara cross-origin. Setelah mendapatkan response tersebut dan diijinkan oleh backend, barulah aplikasi Javascript memanggil REST request yang asli.

Terinspirasi ide dari salah satu pertanyaan di StackOverflow [1], yang saya lakukan adalah sebagai berikut:
  1. Filter HTTP request yang masuk ke HAProxy.
  2. Jika ada HTTP request dengan OPTION method, arahkan ke CORS handler.
  3. HTTP request selain itu arahkan ke default backend.
  4. Buat sebuah dummy CORS handler yang akan mengembalikan error file yang berisi HTTP OK 200. 

Berikut adalah potongan konfigurasi HAProxy yang diperlukan:
...
frontend web-frontend
    bind app.nostratech.com:80
    mode http
    option httplog
    use_backend cors-handlers if METH_OPTIONS
    default_backend nginx-servers

backend cors-handlers
    errorfile 503 /etc/haproxy/errors/custom/cors.http
    server cors app.nostratech.com:9999 maxconn 50 disabled

backend nginx-servers
    mode http
    option httplog
    option httpclose
    option forwardfor
    option httpchk HEAD / HTTP/1.1\r\nHost:\ app.nostratech.com
    option redispatch
    retries 5
    http-request set-header X-Forwarded-Port %[dst_port]
    http-request add-header X-Forwarded-Proto https if { ssl_fc }
    balance roundrobin
    server app1 app1.nostratech.com:80 cookie app1
...

Pada potongan konfigurasi di atas, HAProxy memiliki 1 frontend yang akan mengarahkan request dengan method OPTIONS ke CORS handlers. Selain request OPTIONS tersebut, request akan diarahkan secara normal ke backend Nginx servers. Perlu diperhatikan bahwa sebenarnya CORS handlers ini hanyalah sebuah dummy backend yang tidak memiliki backend servers sama sekali. Server app.nostratech.com:9999 tidak pernah ada, dan bahkan statusnya disabled sehingga akan memicu HAProxy untuk mengembalikan response code 503 error ke client. Namun, CORS handlers meng-override dokumen 503 default HAProxy menjadi seperti ini:
HTTP/1.0 200 OK
Cache-Control: no-cache
Connection: close
Content-Type: text/html
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Authorization, Content-Type
Content-Length: 0


Alih-alih dokumen 503 standar, HAProxy akan mengembalikan response HTTP 200 OK dan menambahkan HTTP headers yang diperlukan untuk menjawab method OPTIONS yang pertama yaitu Access-Control-Allow-Origin, Access-Control-Allow-Methods, dan Access-Control-Allow-Headers. Setelah melakukan perubahan tersebut, saya restart  proses HAProxy server dan isu yang ditemui teman saya sudah berhasil diselesaikan.

Silahkan mencoba :).

[1] StackOverflow: How to Send a Response with HAProxy without Passing the Request to Web Servers?

Monday, June 15, 2015

XSD Enumeration

Apabila pada kesempatan sebelumnya kita berhasil memvalidasi XSD menggunakan mediator. Maka pada kesempatan kali ini kita akan mencoba membuat XSD menggunakan enum atau membatasi data yang dapat masuk pada XSD.

Pada dasarnya XSD memiliki banyak atribut yang dapat digunakan untuk membantu menyeleksi data. Seperti menggunakan tipe data yang telah disediakan seperti string, decimal, integer, boolean, date,time dll.

Selain tipe data tersebut di atas kita juga dapat mendeklarasikan tipe data sendiri yang sebenarrnya merupakan element yang memiliki element sederhana. Pada kesempatan kali ini kita akan mencoba membuat sebuah type data yang memiliki batasan-batasan data pada XSD.

Seperti halnya pada kebanyakan bahasa pemprograman, tentunya kita sering menemui enumeration. Enumeration adalah sebuah tipe data yang sudah ditentukan terlebih dahulu nilai-nilainya. Ketika kita membuat sebuah service ada kalanya kita sangat memerlukan penggunaan enum untuk membantu dalam pembersihan data dari pihak-pihak yang sengaja maupun tidak sengaja mengisi data yang tidak sesuai.

Baiklah, langsung saja kita memulai membuat sebuah xsd dengan membuat enum sebagai satu type elemennya. Berikut adalah source xsd yang kita buat : 


<xs:schema attributeformdefault="unqualified" elementformdefault="qualified" targetnamespace="http://xmlns.oracle.com/Test/Test2/bpelValidator" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns="http://xmlns.oracle.com/Test/Test2/bpelValidator">
<xs:element name="process">
<xs:complextype>
<xs:sequence>
<xs:element name="String" type="xs:string"/>
                                <xs:element name="Integer" type="xs:integer"/>
                                <xs:element name="Date" type="xs:date"/>
                                <xs:element name="Boolean" type="xs:boolean"/>
                                <xs:element name="StringUseEnum" type="enumData"/>
</xs:sequence>
</xs:complextype>
</xs:element>
<xs:element name="processResponse">
<xs:complextype>
<xs:sequence>
<xs:element name="result" type="xs:string"/>
</xs:sequence>
</xs:complextype>
        </xs:element>
        <xs:simpletype name="enumData">
          <xs:restriction base="xs:string">
            <xs:enumeration value="ENUM1"/>
            <xs:enumeration value="ENUM2"/>
            <xs:enumeration value="ENUM3"/>
         </xs:restriction>
        </xs:simpletype>
</xs:schema>



Apabila kita mencari cara membuat enum, pasti anda akan menjumpai xsd yang tiba-tiba memiliki "xs" atau "xsd" di depan setiap tag. Mengapa demikian?  "xs" atau "xsd" pada tag tersebut membantu kita dalam menentukan asal element tersebut berasal. Kita akan membuat sebuah type data baru yg akan kita sebut enumData dan bukan type data asli yang sudah terdefinisi dari "http://www.w3.org/2001/XMLSchema" sehingga kita harus mendefinisikannya pada xsd kita dari mana element tersebut berasal. 

Apabila kita lht kembali di atas maka akan terlihat 2 macam deklarasi yang ada : 
xmlns:xs="http://www.w3.org/2001/XMLSchema"  
=> memiliki nama alias "xs" yang nantinya setiap element yang terdefinisi harus di beri "xs" di depan setiap element yang akan digunakan.
xmlns="http://xmlns.oracle.com/Test/Test2/bpelValidator" 
=> membuat element yang terdefinisi pada file yang memiliki namespace tersebut dapat digunakan pada xsd tanpa seperti penggunaan element normal.

Dari keterangan tersebut sebenarnya kita bsa memberi alias apa saja pada saat mendeklarasi element. Setelah mengetahui cara kerja dari pendeklarasian pada xsd. Kita bisa menentukan bagaimana cara membuat type data baru dengan lebih mudah. Seperti misalkan kita balik kegiatan pendeklarasian element yang akan digunakan agar lebih sedikit perubahan yang dibutuhkan pada xsd yang telah dibuat sebelumnya. Seperti contoh di bawah : 

Cara Lain Penulisan XSD dengan Tambahan Element Baru
Demikian sekilas mengenai pembuatan enumeration dan hal- hal yang diperlukan serta pengertiannya. Semoga membantu. 



Saturday, June 13, 2015

Validasi XSD pada BPEL Menggunakan Mediator


Pada kesempatan kali ini saya ingin berbagi mengenai memvalidasi inputan pada bpel. Pada saat kita membuat xsd pada bpel sering kali kita sudah menentukan data-data yang diinginkan, semisal dengan menentukan sebuah element bertipe data date, integer , ataupun menentukan element tersebut mandatory atau tidak. Pada saat kita mengetes melalui Oracle Enterprise Manager(OEM atau EM) , request kita akan tervaldiasi sesuai dengan apa yang kita setting pada xsd, tetapi bila kita menggunakan soap ui element" tersebut dapat kita isi apa saja dan akan tetap masuk ke dalam proses bpel yang ada. Seperti terlihat pada gambar di bawah, sekalipun memasukkan a pada element yang merupakan integer bpel tetap dapat menerima dan memprosesnya.
a sebagai integer tetap dapat masuk kedalam bpel

OEM akan memvalidasi inputan dari user
Hal tersebut sangat merepotkan karena tentunya dapat menyebabkan kesalahan data dalam pemprosesan maupun penyimpanan. Apabila harus mengecek setiap data dalam xsd tersebut, tentunya sangat tidak memungkinkan apabila terdapat banyak data d dalamnya. Setelah mencari-cari, akhirnya saya menemukan fitur yang dimiliki oleh mediator yang berfungsi untuk memvalidasi xsd sebelum nantinya akan di gunakan dalam proses bpel.

Cara menggunakan mediator pada bpel anda sangat mudah. Pertama-tama perlu dibuat component mediator terlebih dahulu. 

membuat mediator


Kemudian menghapus koneksi antara bpel dengan xsd yang di ekspose sebagai service. Tidak perlu ragu, apabila ditanyakan kepastian untuk mendelete, segera konfirmasi dengan yakin.
Delete Koneksi


confirmasi dengan yes

Setelah selesai mendelete sambungan antara xsd dengan bpel, Saatnya membuat sambungan baru, sambungan yang akan kita buat adalah xsd - mediator - bpel . Karena itu pertama tarik sambungan dari xsd ke arah sebelah kiri mediator, kemudian tarik sambungan dari sebelah kanan mediator ke sebelah kiri bpel.
xsd ke mediator
mediator ke bpel

Setelah selesai membuat sambungan, double klik pada mediator. Akan terlihat tampilan seperti di bawah. centang bagian validate Syntax(XSD) yang berada di bawah Routing Rules.
Centang Validate Syntax(XSD)
Yap,sudah selesai. Mediator sudah terpasang pada bpel anda tanpa merusak bpel yang sudah ada :D
Dan apabila di coba menggunakan soapUI. Respose yang didapat akan seperti di bawah ini, kita akan mendapatkan informasi data yang salah dan berada di element yang mana sehingga akan lebih informatif.

Invalid XSD divalidate oleh Mediator

Sekian dulu mengenai validasi xsd pada kesempatan kali ini. Semoga membantu.