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 は制限が多く、フレームワークを使った場合などに苦労するのが残念。
今回使用した環境は以下の通り。
- Java SE 6u13
- Google App Engine for Java SDK 1.2.0
- RESTEasy 1.1 RC2
- AspectJ 1.6.4
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 は案外当てにならないかも。