Google App Engine for Java 上で単純な JAX-RS の利用 - RESTEasy を使って

Google App Engine 上で単純な JAX-RS アプリケーションを実行する方法を調査してみた。

まず、Jersey 1.0.3 では com.sun.jersey.spi.container.servlet.WebComponent.configure() メソッド内で java.lang.reflect.Proxy.newProxyInstance() メソッド実行でエラーが発生し、正常に実行できなかった。

次に、RESTEasy 1.1RC2 でも org.jboss.resteasy.plugins.providers.jaxb.JAXBXmlSeeAlsoProvider クラスを org.jboss.resteasy.core.PropertyInjectorImpl.populateMap() 内で処理する際に java.lang.SecurityException が発生し、正常に実行できなかった。(原因はリフレクションの機能を呼び出す際に失敗している模様)


いろいろと試してみた結果、RESTEasy を Google App Engine 上で動作させる事に一応成功したが(ただし、JAXB は使えず)、現状の Google App Engine for Java は制限が多く、フレームワークを使った場合などに苦労するのが残念。


今回使用した環境は以下の通り。

Google App Engine 用 RESTEasy コアライブラリ作成

まず、そもそもの問題は JAXBXmlSeeAlsoProvider を org.jboss.resteasy.plugins.providers.RegisterBuiltin.optionalProvider() メソッド内で読み込んでいる箇所だったので、optionalProvider メソッドで JAXB 関連のクラスを処理しないようにした。(ついでに optionalContextResolver も)

つまり、今回はとりあえず JAXB の機能を捨てる事にした。

やり方としては、ソースコードを変更してビルドする手もありだが、そんな面倒なことはやってられないので、AspectJ を使って JAR ファイルを変更する事にした。

具体的には、以下のアスペクトを resteasy-jaxrs-1.1-RC2.jar ファイルに適用し、resteasy-jaxrs-fits-custom.jar ファイルを生成した。

RegisterBuiltinAspect.aj
package fits.tools.gae.resteasy;

import org.jboss.resteasy.plugins.providers.RegisterBuiltin;

public aspect RegisterBuiltinAspect {
    void around(): call(void RegisterBuiltin.optionalProvider(..)) || call(void RegisterBuiltin.optionalContextResolver(..)) {
        String dependency = thisJoinPoint.getArgs()[0].toString();

        //jaxb という名称を含まないもののみ通常処理を実施
        if (!dependency.contains("jaxb")) {
            proceed();
        }
    }
}

Google App Engine 用 RESTEasy のディレクトリ・ファイル構成を作成

RESTEasy の resteasy-jaxrs.war\WEB-INF ディレクトリをベースに以下のような変更を加え、Google App Engine 用のディレクトリ構成を用意。

  • appengine-web.xml を配置
  • web.xml を編集
  • classes 内に JAX-RS アプリケーション用クラスファイル配置
  • lib から resteasy-jaxrs-1.1-RC2.jar を削除し、resteasy-jaxrs-fits-custom.jar ファイルを代わりに配置
  • lib に AspectJ のランタイムライブラリ aspectjrt.jar を配置

構成は以下のようになる。(JAXB は使わないので jaxb-api-2.1.jar や jaxb-impl-2.1.8.jar も削除した。ただし、resteasy-jaxb-provider-1.1-RC2.jar は要るので注意)

ディレクトリ構成例
  • WEB-INF
    • appengine-web.xml
    • web.xml
    • classes
      • sample/SampleApplication.class
      • sample/resources/SimpleResource.class
    • lib
      • resteasy-jaxrs-fits-custom.jar
      • activation-1.1.jar
      • aspectjrt.jar
      • commons-codec-1.2.jar
      • commons-logging-1.0.4.jar
      • FastInfoset-1.2.2.jar
      • javassist-3.6.0.GA.jar
      • jaxrs-api-1.1-RC2.jar
      • jettison-1.0.1.jar
      • jsr250-api-1.0.jar
      • jyaml-1.3.jar
      • mail-1.4.jar
      • resteasy-atom-provider-1.1-RC2.jar
      • resteasy-jaxb-provider-1.1-RC2.jar
      • resteasy-multipart-provider-1.1-RC2.jar
      • resteasy-yaml-provider-1.1-RC2.jar
      • scannotation-1.0.2.jar
      • sjsxp-1.0.1.jar
      • slf4j-api-1.5.2.jar
      • slf4j-simple-1.5.2.jar
      • stax-api-1.0.jar

作成/変更したファイルの内容は以下の通り。

sample/SampleApplication.java
package sample;

import javax.ws.rs.core.Application;
import java.util.HashSet;
import java.util.Set;

import sample.resources.SimpleResource;

public class SampleApplication extends Application {
   private Set<Object> singletons = new HashSet<Object>();
   private Set<Class<?>> empty = new HashSet<Class<?>>();

   public SampleApplication() {
      singletons.add(new SimpleResource());
   }

   @Override
   public Set<Class<?>> getClasses() {
      return empty;
   }

   @Override
   public Set<Object> getSingletons() {
      return singletons;
   }
}
sample/resources/SimpleResource.java
package sample.resources;

import javax.ws.rs.GET;
import javax.ws.rs.Produces;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;

@Path("message")
public class SimpleResource {
    @GET
    @Path("{id}")
    @Produces("text/plain")
    public String find(@PathParam("id") String id) {
        return "test" + id;
    }
}
appengine-web.xml
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
    <application>[ここには Google App Engine に登録した Application Identifier を設定]</application>
    <version>1</version>
</appengine-web-app>
web.xml
<web-app xmlns="http://java.sun.com/xml/ns/javaee" version="2.5">
    <context-param>
        <param-name>javax.ws.rs.Application</param-name>
        <param-value>sample.SampleApplication</param-value>
    </context-param>
    <listener>
        <listener-class>org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap</listener-class>
    </listener>
    <servlet>
        <servlet-name>Resteasy</servlet-name>
        <servlet-class>org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>Resteasy</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
</web-app>

Google App Engine へのアップロードと動作確認

WEB-INF ディレクトリを配置しているディレクトリをカレントとして、以下のコマンドを実行して Google App Engine へのアップロードを実施。

>appcfg update .

Web ブラウザで Google App Engine のアプリケーションサイト(http://[Application Identifier].appspot.com/)の /message/1 にアクセスして一応動作している事を確認できた。


上記では、いきなり Google App Engine にアップロードしたが、通常は dev_appserver を使って、まずローカルで動作確認する事になる。ただし、dev_appserver では動作しても実際にアップロードすると動作しないケースが多い(逆のケースもある)ようで、dev_appserver は案外当てにならないかも。