RailsのController実装をJavaで作る

前回でViewにJSPを使えるようになったので今回はController。
とりあえず以下のようなJavaのActionクラスをRailsのControllerにマッピングすることを考える。

package sample;

public class SampleAction {
	public Integer result;
	public String v1;
	public String v2;
	
	public String index(){
		return "test.jsp";
	}
	public String add(){
		result = Integer.parseInt(v1) + Integer.parseInt(v2);
		return "test.jsp";
	}
}

publicメソッドがRailsのControllerで定義したのと同じような感じで使えるようになればいい。
publicフィールドは入出力に使うこととする。

Rails Controller

JavaActionというJRubyの自作Moduleをjava_action_module.rbファイルとして作成しておいて、RailsのControllerではそれを利用してJavaのクラスで宣言されたメソッドを自動的にマッピングすることを考える。

(このコード自体をJavaで作ることも可能かもしれないが、めんどいのでやめておく)

require 'java_action_module'

class PollsController < ApplicationController
  include JavaAction
  def initialize
    add_methods("sample.SampleAction")  
  end
end

module JavaAction

さてここからが本題。
Javaクラスのメソッド情報を取得して、Rubyでメソッドを動的に定義できればよい。
また、JavaクラスのフィールドをViewとの入出力で使えるようにする必要がある。


まずは、後でpublicメソッド・フィールドを取得するときのためにMemberにpublic?メソッドを定義しておく。(Javaの既存クラスにメソッドを追加定義するにはJavaUtilities.extend_proxyを使う)

module JavaAction
  JavaUtilities.extend_proxy "java.lang.reflect.Member" do
    def public?
      java.lang.reflect.Modifier.isPublic(self.getModifiers)
    end
  end

Method,FieldはMemberインターフェースを実装しているので、Method,Fieldでもpublic?メソッドが使えるようになった。
Javaのインターフェースにもメソッドを追加定義できるのがJRubyの面白いところ。



次は、ViewにJSPを使うためのコード(JSP実行結果の取得)。前回のとほぼ同じ

  def jsp(path)
    writer = java.io.StringWriter.new
    req = request.env['java.servlet_request']
    resp = javax.servlet.http.HttpServletResponse.impl do |method|
      case method
      when :getWriter
        java.io.PrintWriter.new(writer)
      else
        logger.warn "method:#{method} called"
      end
    end

    $servlet_context.getRequestDispatcher(path).include(req, resp)
    writer.toString
  end

そして、メソッドを動的追加するためのメソッド。
まずは、Javaクラス内で宣言されたpublicメソッド・フィールドの配列を取得する
(.select &:public? の部分でpublicなものだけ取るようにしている)

  def add_methods(java_class)
    clazz = java.lang.Class.forName(java_class)
    methods = clazz.declared_methods.select &:public?
    fields = clazz.declared_fields.select &:public?


次に呼び出し元のファイルパスからViewファイルが入ったディレクトリのパスを取ってくる。
(RequestDispatcherに渡すときのパスに使うので/WEB-INF/から始まる文字列になる)

    path = caller[0].sub(%r!.*(/WEB-INF/.*/app/).*/(.*)_controller.rb.*!, '\1views/\2/')


Javaクラスのメソッドごとにdefine_methodで動的にメソッドを作成する。
define_methodはModuleのコンテキストで呼ぶ必要があるのでmodule_eval内で呼ぶこととなる。

    methods.each do |m|
      JavaAction.module_eval do
        define_method(m.name) do


define_methodの中では、まずHttpServletRequestの取得とJavaクラスのインスタンス化をして、

インスタンスのフィールドにリクエストのパラメータを入れて、
インスタンスのメソッド実行をする

          req = request.env['java.servlet_request']
          instance = clazz.newInstance

          fields.each do |f|
            instance.send("#{f.name}=", params[f.name]) unless params[f.name].nil?
          end
          page = instance.send(m.name)

最後にリクエストのAttributeにフィールドの名前と値を入れて、JSP呼び出しを行って完了。

          fields.each{|f| req.setAttribute(f.name, f.get(instance))}

          render :inline => jsp(path + page), :layout => true
        end
      end
    end
  end
end


これでとりあえずJavaでのController実装ができることがわかった。
ただし、まだいろいろ課題がある。
バリデーションとかリダイレクトの対応も必要だし、JSPでカスタムタグ使いたいよね


何かフレームワークとか使えるのあるだろうか・・・