読者です 読者をやめる 読者になる 読者になる

jrubyでmakeSyncCallができると聞いて

http://twitter.com/urekat/status/7785353904

そんな噂がつぶやかれていたので、これは試してみなければならないと思いちょっとやってみました。rubyはほとんどやったことがないので、環境設定から。

jrubyのインストール

macなら、

sudo port install jruby

とします。

irbからためしてみる

jirb

すると、インタラクティブモードで環境が動きます。

とりあえず、jrubyjavaのクラスが動くかどうか確認したいので、jribのなかで、

require 'appengine-api-1.0-sdk-1.3.0.jar'
apiproxy = Java::com.google.apphosting.api.ApiProxy
apiproxy.makeSyncCall('datastore_v3','Get',nil)

などとやってみます。すると、

NativeException: com.google.apphosting.api.ApiProxy$CallNotFoundException: The API package 'datastore_v3' or call 'Get()' was not found.
from com/google/apphosting/api/ApiProxy.java:81:in `makeSyncCall'

とおこられるはずです。試したことのあるかたはわかると思いますが、apiのstubにclasspathが通っていないとこんなおこられ方がjavaでもされるので、classpathを通せばよいことがわかります。classpathを通すというよりは、requireするとjrubyのスクリプトから見えるようになるようです。

jrubyスクリプトからためしてみる

jirbでいろいろrequireしていくのを毎回やるのはめんどくさいので、スクリプトを書こうと思います。

その前に、このひとたちをrequireしないといけないので、sdkのディレクトリからかき集めます。

appengine-api-1.0-sdk-1.3.0.jar
appengine-api-labs-1.3.0.jar
appengine-api-stubs.jar
appengine-local-runtime.jar

javaでは、ローカルでmakeSyncCallするときには、例えばこんな感じのコードを書いてapiproxyを動作させます。

ApiProxyLocal apiProxyLocal = new ApiProxyLocalImpl(new File("") {};
ApiProxy.setEnvironmentForCurrentThread(environment);
ApiProxy.setDelegate(apiProxyLocal);

jrubyから素直に(というか僕がちょっと調べた範囲で)javaインスタンスを生成するには、例えば、

getresponse = Java::com.google.apphosting.api.DatastorePb.GetResponse
getpb = getresponse.new

というような書き方をするようです。使用するパッケージを指定しないといけなくてちょっとめんどくさいのと、javaでいうところの匿名クラスでインスタンスを生成するjrubyでのやり方がわからなかったので、妥協してjavaで書いてそれをロードします。

config = Java::balmysundaycandy.core.test.EnvironmentConfiguration.new('',false,false)
puts config
envutils = Java::balmysundaycandy.core.test.TestEnvironmentUtils
envutils.setupEnvironment(config)

単体テスト向けにtestパッケージに作っちゃったものを使ってて名前が微妙だけど、まぁそこはご愛嬌ということで。


で、どうやらjavaのコードで上書きしているthreadに対するenvironmentは有効になっているようなので、これでmakeSyncCallすれば通るはずです。やってみましょう。

apiproxy = Java::com.google.apphosting.api.ApiProxy
byte = "this is sample byte".unpack("C*")
result = apiproxy.makeSyncCall('datastore_v3','Get', byte.to_java(:byte))
puts result

クラスをロードしてbyte配列を生成し、getの操作を呼び出しています。javaのbyte配列に変換するには、上記のような感じでよいようです。

[B@4268cc6

などの結果がかえるので、ローカル環境でのサービス呼び出しが通っていることがわかります。

まだできてないこと

さすがに文字列引数とbyte配列の組み合わせでがんばるのは厳しいということで、自分で作ってる例のあれを使います。

datastore = Java::balmysundaycandy.more.low.level.operations.datastore.DatastoreOperations
getrequest = Java::com.google.apphosting.api.DatastorePb.GetRequest
getresponse = datastore.GET.call(getrequest.new)
puts getresponse

おそらく、

Class.java:-2:in `getDeclaredFields0': java.lang.NoClassDefFoundError: com/google/storage/onestore/v3/OnestoreAction$Action

というようなことを言われると思います(この問題がまだ解決していない。。)。どうやら、javaでDatastorePbがimportしているOnestoreActionというクラスが存在しないようです。sdkの中身を見てもないようで、どうしたものかと考え中。

jrubyの挙動は昨日初めてつかったのでよくつかめていませんが、java上でクラスがimportしているクラスのクラス定義を芋づる式にrubyのオブジェクト?的なものとして生成しているように見えますが、その途中でno class def foundとなってしまうようです。

とはいえ、sdkのコードでOnestoreActionがなければコンパイルが通らない気もしていて。ちょっとよくわかんないという感じです。

いちおう、ここまでのソースはここに置いてあります。