[github]Github Pagesを作りました

Octopressとgithub Pagesを使ったブログ - 0xff.toBlog()


この記事を読んで、Github Pagesでblogを書いてみたくなり、作ってみました。


http://masayuki038.github.com/


以前からOctopressが気になっていたので、ちょうど良いタイミングでした。
ということで、Github Pagesの方に引っ越します。

mvn assembly:assemblyとjava.lang.IncompatibleClassChangeError

mvn assembly:assemblyで作成したファイルをjava -jarで実行すると以下の例外が出る、という事象に悩まされてました。

Exception in thread "main" java.lang.IncompatibleClassChangeError: Implementing
class
        at java.lang.ClassLoader.defineClass1(Native Method)
        at java.lang.ClassLoader.defineClass(Unknown Source)
        at java.security.SecureClassLoader.defineClass(Unknown Source)
        at java.net.URLClassLoader.defineClass(Unknown Source)
        at java.net.URLClassLoader.access$000(Unknown Source)
        at java.net.URLClassLoader$1.run(Unknown Source)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.net.URLClassLoader.findClass(Unknown Source)
        at java.lang.ClassLoader.loadClass(Unknown Source)
        at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
        at java.lang.ClassLoader.loadClass(Unknown Source)
        at java.lang.ClassLoader.loadClassInternal(Unknown Source)
        at net.wrap_trap.utils.EntityService.<init>(EntityService.java:47)
        at net.wrap_trap.utils.BsonStore.initialize(BsonStore.java:276)
        at net.wrap_trap.utils.BsonStore.<init>(BsonStore.java:33)
        at net.wrap_trap.utils.FileStoredMap.<init>(FileStoredMap.java:26)
        at net.wrap_trap.collections.fsm.bench.Benchmark1.main(Benchmark1.java:9
)

IncompatibleClassChangeError?これまで遭遇したことがない例外だったので調べてみました。
IncompatibleClassChangeError (Java Platform SE 6)

Thrown when an incompatible class change has occurred to some class definition. The definition of some class, on which the currently executing method depends, has since changed.

クラス定義ファイル間の不整合が発生しているようですが、そんな変なクラスの変更したかな…。一旦cleanしてからリトライしましたが、事象が変わらず。

コンパイルからちょっと離れて、クラス間の定義で不整合が出るという点に着目してみたところ、2つ思い当たる事がありました。今回のモジュールはorg.mongodb.bsonに直接依存しており、mongo-java-driverに間接依存していました。mongo-java-driverのjarファイルの中にorg.bsonのクラスファイルが含まれており、それが直接依存しているorg.mongodb.bsonのクラスファイルとバージョンが合わない為、この例外が出ているのではないかと。

そしてもう1つ、mvn assembly:assemblyする際に、依存モジュールをunpackするようにしていました。

<assembly>
  <id>distribution</id>
  <formats>
    <format>zip</format>
  </formats>
  <includeBaseDirectory>false</includeBaseDirectory>
  <dependencySets>
    <dependencySet>
      <unpack>true</unpack>
      <scope>runtime</scope>
      <outputDirectory>/</outputDirectory>
    </dependencySet>
  </dependencySets>
</assembly>

unpackを指定すると、assembly時に依存モジュールのjarを展開してクラスファイルとしてモジュールに取り込みます。
今回の問題は、2つのモジュールにバージョンの異なるorg.bsonのクラスファイルがそれぞれ入っていており、展開される際にどちらかが上書きされてしまう為、不整合が起きていました。

依存モジュールでmongo-java-driverを使っているので、org.bsonを直接参照するのをやめ、代わりにmongo-java-driverを参照するようにしたところ、この例外は起こらなくなりました。dependencySetのunpackをtrueにする場合、この点に気をつける必要があります。

JDK6をソースコードからビルド

jvmgcまわりの挙動を確認する為、CentOS上でJDK6のソースコードを取得してビルドしてみました。基本的な流れは以下のページを参考にしています。

「Compile Hotspot JDK from Source Code」
http://konpairu.net/blog/archives/139

ビルドするのに色々躓きましたが、その度ググって何とかなりました。躓いた箇所と対応を、備忘録として記載しておきます。

依存パッケージのインストール

# yum install alsa-lib-devel -y
# yum install cups-devel -y

Missing ./../src/share/lib/fonts/LucidaTypewriterRegular.ttf

ERROR: Missing ./../src/share/lib/fonts/LucidaTypewriterRegular.ttf. Verify you have downloaded and overlayed on the source area all the binary files.

フォントのコピーが必要。以下のページを参考にしました。

Java SE 6(Mustang)をソースからビルドする」
http://d.hatena.ne.jp/torutk/20050825/1124985836

# cp -pir /opt/jdk1.6.0_16/jre/lib/fonts/* ../../j2se/src/share/lib/fonts/

No rule to make target `/usr/X11R6/lib/X11/config/Imake.tmpl'

make[3]: *** No rule to make target `/usr/X11R6/lib/X11/config/Imake.tmpl', needed by `xmkmf'. Stop.

X11のフォルダ構成の問題らしい。別の所にあるファイルだったのでsymlinkを貼りました。

# cd /usr/X11R6/lib/
# ls -l X11
(何も無かった)
# rm -r X11
# ln -s /usr/share/X11 X11

error: static declaration of 'sigignore' follows non-static declaration

../../../../src/solaris/hpi/native_threads/src/interrupt_md.c:115: error: static declaration of 'sigignore' follows non-static declaration
/usr/include/signal.h:377: error: previous declaration of 'sigignore' was here

ヘッダではnon-staticで宣言されているものの、実装ではstaticになっていることが原因。
以下のページを参考にしました。

「jdk1.6のコンパイル
http://www.melange.co.jp/blog/?p=779

# pushd ../../j2se/src/solaris/hpi/native_threads/src/
# cp -pi interrupt_md.c interrupt_md.org
# vi interrupt_md.c
# diff -u interrupt_md.org interrupt_md.c
--- interrupt_md.org    2012-02-27 18:26:56.000000000 +0900
+++ interrupt_md.c      2012-03-03 20:09:06.000000000 +0900
@@ -110,7 +110,7 @@
 }
 
 #ifndef HAVE_SIGIGNORE
-static int                                                     
+int                                                    
 sigignore(int sig)             
 {                              
     struct sigaction action;
# popd

No rule to make target `/usr/src/jvms/jdk16u16/control/build/linux-amd64/classes/com/sun/java/swing/plaf/windows/icons/Computer.gif'

make[5]: *** No rule to make target `/usr/src/jvms/jdk16u16/control/build/linux-amd64/classes/com/sun/java/swing/plaf/windows/icons/Computer.gif', needed by `other_files'. Stop.

Computer.gif等の画像ファイルをコピーして取り込もうとしているようです。この画像を探してみたのですが、bootstrap jdkに含まれてなかった為、コピーするタスク自体を消しました。

# pushd ../../j2se/make/javax/swing/plaf
# cp -pi FILES.gmk FILES.org
# vi FILES.gmk 
# popd

No rule to make target `/usr/src/jvms/jdk16u16/control/build/linux-amd64/lib/audio/soundbank.gm'

make[4]: *** No rule to make target `/usr/src/jvms/jdk16u16/control/build/linux-amd64/lib/audio/soundbank.gm', needed by `copy-files'. Stop.
soundbank.gmはbootstrap jdkjre/libの下にあったので、それをコピーして先に進めました。

# cp -pi /home/masayuki/install/soundbank.gm ../build/linux-amd64/lib/audio/

(諸事情によりコピー元のパスがjre/libではありませんが)

../../../src/solaris/native/sun/awt/img_util_md.h:14: error: expected specifier-qualifier-list before 'XID'

../../../src/solaris/native/sun/awt/img_util_md.h:14: error: expected specifier-qualifier-list before 'XID'

以下のページを参考にして、libXt-develをインストール。

「Building OpenJDK on Ubuntu
http://d.hatena.ne.jp/dolduke/20080420

yum install libXt-devel -y

/usr/bin/ld: cannot find -lXext

/usr/bin/ld: cannot find -lXext

libXext-develをインストール。

# yum install libXext-devel -y

/usr/bin/ld: cannot find -lXtst

/usr/bin/ld: cannot find -lXtst

libXtst-develをインストール。

# yum install libXtst-devel -y

../../../src/solaris/native/sun/awt/awt_dnd.c:172: error: static declaration of 'xerror_code' follows non-static declaration

../../../src/solaris/native/sun/awt/awt_dnd.c:172: error: static declaration of 'xerror_code' follows non-static declaration

awt_dnd.cのxerror_codeのstaticを外しました。

# pushd ../../j2se/src/solaris/native/sun/awt/
# cp -pi awt_dnd.c awt_dnd.org
# vi awt_dnd.c
# diff -u awt_dnd.org awt_dnd.c
--- awt_dnd.org 2012-02-27 18:26:56.000000000 +0900
+++ awt_dnd.c   2012-03-03 21:24:11.000000000 +0900
@@ -169,7 +169,7 @@
     return awt_root_window;
 }
 
-static unsigned char xerror_code = Success;
+unsigned char xerror_code = Success;
 
 static int 
 xerror_handler(Display *dpy, XErrorEvent *err) {
# popd

No rule to make target `../../../src/share/lib/cmm/sRGB.pf', needed by `/usr/src/jvms/jdk16u16/control/build/linux-amd64/lib/cmm/sRGB.pf'. Stop.

make[4]: *** No rule to make target `../../../src/share/lib/cmm/sRGB.pf', needed by `/usr/src/jvms/jdk16u16/control/build/linux-amd64/lib/cmm/sRGB.pf'. Stop.

これもjre/libの下にあったのでコピーしています。

# mkdir ../../j2se/src/share/lib/cmm
# cp -pi /home/masayuki/install/cmm/* ../../j2se/src/share/lib/cmm/

relocation R_X86_64_32 against `a local symbol' can not be used when making a shared object; recompile with -fPIC

make[4]: Entering directory `/usr/src/jvms/jdk16u16/j2se/make/sun/jdbc'
/usr/bin/gcc -shared -o /usr/src/jvms/jdk16u16/control/build/linux-amd64/tmp/sun/sun.jdbc.odbc/JdbcOdbc/libodbcinst.so dummyodbc.c
/usr/bin/ld: /tmp/cccOEmCj.o: relocation R_X86_64_32 against `a local symbol' can not be used when making a shared object; recompile with -fPIC

jdbcのところで上記エラーが出ました。何故ここだけ…。CFLAGSを見てないようなのでMakefileに「-fPIC」をハードコード。

# diff -u ../../j2se/make/sun/jdbc/Makefile.org ../../j2se/make/sun/jdbc/Makefile
--- ../../j2se/make/sun/jdbc/Makefile.org       2012-02-27 18:26:50.000000000 +0900
+++ ../../j2se/make/sun/jdbc/Makefile   2012-03-03 22:11:06.000000000 +0900
@@ -85,10 +85,10 @@
 make_libs: $(TEMPDIR)/libodbcinst.so $(TEMPDIR)/libodbc.so
 
 $(TEMPDIR)/libodbcinst.so: dummyodbc.c  $(TEMPDIR)
-       $(CC) -shared -o $@ $<
+       $(CC) -fPIC -shared -o $@ $<
 
 $(TEMPDIR)/libodbc.so: dummyodbc.c $(TEMPDIR) 
-       $(CC) -shared -o $@ $<
+       $(CC) -fPIC -shared -o $@ $<
 
 clean::
        $(RM) -f $(TEMPDIR)/libodbcinst.so $(TEMPDIR)/libodbc.so

No rule to make target `/usr/src/jvms/jdk16u16/control/build/linux-amd64-fastdebug/lib/audio/soundbank.gm'

make[6]: *** No rule to make target `/usr/src/jvms/jdk16u16/control/build/linux-amd64-fastdebug/lib/audio/soundbank.gm', needed by `copy-files'. Stop.

またsoundbank.gmが。fastbugというモジュール?も作られていて、そちら側でエラーとなっています。前回と同様にコピー。

# mkdir ../build/linux-amd64-fastdebug/lib/audio
# cp -ip /home/masayuki/install/soundbank.gm ../build/linux-amd64-fastdebug/lib/audio

../../../src/solaris/native/sun/awt/awt_TopLevel.c:25:31: error: X11/Xmu/Editres.h: No such file or directory

../../../src/solaris/native/sun/awt/awt_TopLevel.c:25:31: error: X11/Xmu/Editres.h: No such file or directory

libXmu-develをインストール。

# yum install libXmu-devel -y

Mockito+PowerMockでStringのmockを作る

文字列のハッシュ衝突時のテストケースを書くことになりましたが、いつも使っているMockitoはfinal classのmockを作ることができません。

調べてみたところ、PowerMockというモジュールを使うことによって、MockitoAPIを用いてfinal classのmockを作ることができることが分かり、試してみました。

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertThat;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

import static org.powermock.api.mockito.PowerMockito.*; 

@RunWith(PowerMockRunner.class)
@PrepareForTest(String.class)
public class HashCollisionTest {

    @Test
    public void testHashCodeEquivalent(){
        String foo = "foo";
        String bar = "bar";

        assertThat(foo.hashCode(), is(not(bar.hashCode())));
        
        bar = spy(bar);
        when(bar.hashCode()).thenReturn(foo.hashCode());
        
        assertThat(bar, is("bar"));
        assertThat(foo.hashCode(), is(bar.hashCode()));
    }
}

Mockitoのみを用いた場合と異なるのは以下の3点です。

  • static importの変更
    • 「org.mockito.Mockito.*」から「org.powermock.api.mockito.PowerMockito.*」に
  • @RunWith(PowerMockRunner.class)を追加
  • @PrepareForTest([mockの対象となるclass])を追加

PowerMockは既存のmockモジュールを拡張するモジュールで、EasyMockMockitoに対応しています。final classのmockを作成する為に別のmockモジュールを導入しても良いですが、既にEasyMockMockitoを使ってテストコードを書いているのであれば、PowerMockを導入してみることをお勧めします。

[java]new Semaphore(0)

とあるコードの中に、以下のような記述がありました。

import java.util.concurrent.Semaphore;
...
    Semaphore permit = new Semaphore(0);

これまで、ミューテックスは1つの、セマフォは任意の数のクリティカルセクションへの進入を許可するもの、と覚えていました。ですので、初期値に「0」が指定されたセマフォがどういった意味を持つのか理解できず調べてみました。その中で、意外にセマフォ自体を理解できていないことが分かり、認識の弱かった部分を纏めてみました。

所有(アカウント)という概念がない

ミューテックスの場合は獲得したタスク(スレッド)がそのミューテックスを所有しますが、セマフォには所有されるという概念がありません。以下のコードを実行し、スレッドダンプを取ってみました。

import java.util.concurrent.Semaphore;

import org.junit.Test;

public class SemaphoreTest {

    @Test
    public void trySemaphore() throws InterruptedException{
        final Semaphore permit = new Semaphore(2);
        Runnable task = new Runnable(){
            public void run() {
                try {
                    permit.acquire();
                    Thread.sleep(Long.MAX_VALUE);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally{
                    permit.release();
                }
            }
        };
        
        Thread t1 = new Thread(task, "t1");
        Thread t2 = new Thread(task, "t2");
        Thread t3 = new Thread(task, "t3");
        
        t1.start();
        t2.start();
        t3.start();

        t1.join();
        t2.join();
        t3.join();
    }
}

上記コードの実行中にスレッドダンプを取ってみると、以下のようになります。

Full thread dump Java HotSpot(TM) Client VM (21.0-b17 mixed mode, sharing):

"t3" prio=6 tid=0x046cfc00 nid=0xb00 waiting on condition [0x04c9f000]
   java.lang.Thread.State: WAITING (parking)
	at sun.misc.Unsafe.park(Native Method)
	- parking to wait for  <0x24b57978> (a java.util.concurrent.Semaphore$NonfairSync)
	at java.util.concurrent.locks.LockSupport.park(Unknown Source)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(Unknown Source)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireSharedInterruptibly(Unknown Source)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(Unknown Source)
	at java.util.concurrent.Semaphore.acquire(Unknown Source)
	at net.wrap_trap.test.concurrent.SemaphoreTest$1.run(SemaphoreTest.java:15)
	at java.lang.Thread.run(Unknown Source)

   Locked ownable synchronizers:
	- None

"t2" prio=6 tid=0x046cf400 nid=0x308 waiting on condition [0x0489f000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
	at java.lang.Thread.sleep(Native Method)
	at net.wrap_trap.test.concurrent.SemaphoreTest$1.run(SemaphoreTest.java:16)
	at java.lang.Thread.run(Unknown Source)

   Locked ownable synchronizers:
	- None

"t1" prio=6 tid=0x046bf000 nid=0x860 waiting on condition [0x04c1f000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
	at java.lang.Thread.sleep(Native Method)
	at net.wrap_trap.test.concurrent.SemaphoreTest$1.run(SemaphoreTest.java:16)
	at java.lang.Thread.run(Unknown Source)

   Locked ownable synchronizers:
	- None
...

スレッドt1,t2はセマフォを獲得し、t3はセマフォを待っている状態ですが、「Locked ownable synchronizers:」は何れもNoneとなっています。
同じことをReentrantLockで試してみます。

Full thread dump Java HotSpot(TM) Client VM (21.0-b17 mixed mode, sharing):

"t3" prio=6 tid=0x047e6800 nid=0xc2c waiting on condition [0x04aef000]
   java.lang.Thread.State: WAITING (parking)
	at sun.misc.Unsafe.park(Native Method)
	- parking to wait for  <0x24b57f50> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
	at java.util.concurrent.locks.LockSupport.park(Unknown Source)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(Unknown Source)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(Unknown Source)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(Unknown Source)
	at java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(Unknown Source)
	at java.util.concurrent.locks.ReentrantLock.lock(Unknown Source)
	at net.wrap_trap.test.concurrent.ReentrantLockTest$1.run(ReentrantLockTest.java:15)
	at java.lang.Thread.run(Unknown Source)

   Locked ownable synchronizers:
	- None

"t2" prio=6 tid=0x047e3400 nid=0x1204 waiting on condition [0x0466f000]
   java.lang.Thread.State: WAITING (parking)
	at sun.misc.Unsafe.park(Native Method)
	- parking to wait for  <0x24b57f50> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
	at java.util.concurrent.locks.LockSupport.park(Unknown Source)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(Unknown Source)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(Unknown Source)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(Unknown Source)
	at java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(Unknown Source)
	at java.util.concurrent.locks.ReentrantLock.lock(Unknown Source)
	at net.wrap_trap.test.concurrent.ReentrantLockTest$1.run(ReentrantLockTest.java:15)
	at java.lang.Thread.run(Unknown Source)

   Locked ownable synchronizers:
	- None

"t1" prio=6 tid=0x0478ec00 nid=0x5d4 waiting on condition [0x04c0f000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
	at java.lang.Thread.sleep(Native Method)
	at net.wrap_trap.test.concurrent.ReentrantLockTest$1.run(ReentrantLockTest.java:16)
	at java.lang.Thread.run(Unknown Source)

   Locked ownable synchronizers:
	- <0x24b57f50> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
...

スレッドt1はロックを獲得しており、「Locked ownable synchronizers:」には獲得しているロックの情報が表示されます。セマフォは獲得したスレッドには関連せず、獲得できる数を管理しているだけです。

どのスレッドからも解放できる

セマフォは特定のスレッドと関連しない為、どのスレッドからでもセマフォを解放することができます。例えば以下のようなコード。

import java.util.Random;
import java.util.concurrent.Semaphore;

import org.junit.Test;

public class ConsumerProducer {

    static class Value {
        public Object value;
    }
    
    @Test
    public void tryProvide() throws InterruptedException {
        final Semaphore s = new Semaphore(0);
        final Value shared = new Value();
        final Random rand = new Random();
        
        class Consumer implements Runnable {
            public void run() {
                while(true) {
                    try {
                        s.acquire();
                        System.out.println("get: " + shared.value);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }            
        }
        
        class Producer implements Runnable {
            public void run() {
                while(true) {
                    shared.value = rand.nextInt();
                    System.out.println("put: " + shared.value);
                    s.release();
                    try {
                        Thread.sleep(3000L);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }                        
        }
    
        Thread t1 = new Thread(new Consumer());
        t1.start();
        
        Thread t2 = new Thread(new Producer());
        t2.start();
        
        t1.join();
        t2.join();
    }
}

Customerのスレッドはセマフォを獲得するだけ、Producerのスレッドはセマフォを解放するだけです。つまり、セマフォを獲得したスレッドとは異なるスレッドでセマフォを解放しています。上記コードでは「new Semaphore(0);」としていて、最初にCustomerのスレッドがセマフォを獲得しようとするのですが、Producerが値を準備してセマフォを解放するまでセマフォを獲得することができません。

もちろんObject#wait、Object#notifyを使って同じようなことはできますが、synchronizedブロックを必要としないセマフォの方が書き方としてはスマートだと思います。但し、前記のとおりセマフォには所有という考え方が無い為、例えばセマフォの獲得で長い時間待たされた場合、どのスレッドがセマフォを解放できていないのかスレッドダンプで確認しようとしても、セマフォを獲得しているスレッドを特定することはできません。この点は注意が必要です。

[java][bson]POJOをBSON形式でシリアライズする

JavaのオブジェクトをBSON形式でファイルに保存する為、いくつかの実装を試してみました。

BSONのバイナリに変換できるライブラリ

BSONのページのImplementationJava実装が列挙されている為、今回はこれらを試してみました。

  1. mongo-java-driver
  2. bson4jasckson
  3. ebson

mongo-java-driver

名前にあるとおりMongoDBのドライバですが、BasicBSONEncoderを使って、Javaのオブジェクト(BSONObjct)を直接BSONのバイナリに変換することもできます。バイナリからBSONObjctに戻すには、BasicBSONDecoderを使います。

bson4jasckson

JSONを扱うJacksonのFactoryを拡張してBSONを扱えるようにしたのがbson4jascksonです。Jacksonと同様、ObjectMapperを使うとPOJOを直接BSONのバイナリに変換することができます。

    @Test
    public void testSerialize() throws JsonGenerationException, JsonMappingException, IOException{
        Employee emp = new Employee();
        emp.setA(76);
        emp.setName("babb");
        emp.setSal(Integer.MAX_VALUE-2);

        //serialize
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectMapper mapper = new ObjectMapper(new BsonFactory());
        mapper.writeValue(baos, emp);
        byte[] buffer = baos.toByteArray();

        // deserialize
        ByteArrayInputStream bais = new ByteArrayInputStream(buffer);
        Employee dst = mapper.readValue(bais, Employee.class);
        assertThat(dst.getA(), is(76));
        assertThat(dst.getName(), is("babb"));
        assertThat(dst.getSal(), is(Integer.MAX_VALUE-2));
    }

また、Jacksonは複数のシリアライズ方法を提供しており、ObjectMapperに任せると都合が悪い場合はmongo-java-driverのBSONObjectのような汎用型を扱うTreeModel、さらに細かく調整する場合にはStreamingAPIを使うこともできます。詳細は以下のURLに記載されています。
http://wiki.fasterxml.com/JacksonFAQ#Processing_Models

ebson

これはスクラッチで作成されたBSON実装です。サマリには「Extensible BSON encoder/decoder library written in Java with pluggable Java-to-BSON type mappings.」とあり、独自のマッピングを記述しやすいのが特徴です。
残念なのは、APIのインターフェースにByteBufferが使用されていること。ByteBufferは途中でcapacityを変更できない為、ちょっと使いづらかったというのが正直な感想です。
代わりにコードはかなり整理されており、参考になる点が多数ありました。

まとめ

POJOを直接BSONバイナリに変更したい場合はbson4jascksonのObjectMapperが便利ですが、特殊なマッピングを行うのであればebsonも面白いと思います。MongoDB絡みで連携可能な既存資産があれば、mongo-java-driverかな。

hbaserb

アプリケーションのログをparseしてHBaseに格納する為に、hbaserbというライブラリを使ってみました。hbaserbからHBaseを操作するのはThrift経由となる為、先にThriftの受け口を起動しておきます。

hbase-daemon.sh start thrift

tableもhbaserbで作成することはできますが、今回は予めhbase shellでtableを作っておき、データを保存してみました。

hbase(main):004:0> create 'hbaserb_test', 'data'

HBaseを操作するRubyのコードは以下です。

require 'rubygems'
require 'hbaserb'

client = HBaseRb::Client.new '127.0.1.1'
table = client.get_table 'hbaserb_test'

table.mutate_row 'row1', {'data:key1' => 'value1', 'data:key2' => 'value2'}
table.mutate_row 'row2', {'data:key1' => 'hoge', 'data:key2' => 'foobar'}

table.create_scanner('row1') do |row|
  row.columns.keys.each do |key|
    puts "#{key} => #{row.columns[key].value}"
  end
end

上記コードを実行すると、以下のように表示されます。

masayuki@ubuntu-vm:~/work/ruby/hbaserb$ ruby test.rb
data:key1 => value1
data:key2 => value2
data:key1 => hoge
data:key2 => foobar

hbase shellからも保存したデータを確認することができます。

hbase(main):005:0> scan 'hbaserb_test'          
ROW                           COLUMN+CELL                                                                         
 row1                         column=data:key1, timestamp=1312999209128, value=value1                             
 row1                         column=data:key2, timestamp=1312999209128, value=value2                             
 row2                         column=data:key1, timestamp=1312999209130, value=hoge                               
 row2                         column=data:key2, timestamp=1312999209130, value=foobar                             
2 row(s) in 0.2240 seconds