Google App Engine 上で Apache Camel を使用
id:fits:20080922 で作成した Apache Camel のサンプルを簡易化して Google App Engine for Java 上で動作させてみた。
使用した環境は以下の通り。ただし、特に 2.0 の機能は使っていないはずなので 1.x でも動作すると思う。
- Apache Camel 2.0M1
- Google App Engine for Java SDK 1.2.0
詳細は後述するが、今回(前回=id:fits:20090425)も Google App Engine 上ですんなりと動作しなかった。(Thread・JNDI・JMX の使用を回避する必要があった)
ルーティングルール作成
まず、ルーティングルールを作成。とりあえず、id:fits:20080922 で作成したものを簡易化するだけにした。なお、今回は Servlet から直接実行するので jetty コンポーネントの設定は不要。
SampleRoute.java
package fits.gae.camel; import javax.servlet.http.HttpServletRequest; import org.apache.camel.Exchange; import org.apache.camel.Processor; import org.apache.camel.builder.RouteBuilder; public class SampleRoute extends RouteBuilder { @Override public void configure() throws Exception { //ルーティングの定義 from("direct:start").process(new Processor() { public void process(Exchange ex) { HttpServletRequest req = ex.getIn().getBody(HttpServletRequest.class); String id = req.getParameter("id"); ex.getOut().setBody("<html><body><h1>id=" + id + "</h1></body></html>"); } }); } }
Camel 実行用 Servlet作成
次に、ルーティングルールを実行する Servlet を作成する。
通常、Camel を Java Web アプリケーション内で使用するには org.springframework.web.context.ContextLoaderListener を使って /WEB-INF/applicationContext.xml 内にルーティングルールを記載する方法を取るようだが、Google App Engine 上だと色々トラブルが発生しそうな気がしたので、今回は止めた。
そこで、org.springframework.web.context.ContextLoaderListener の代わりに、ServletContextListener を自作し、Camel の準備処理を実装する事にした。(特に ServletContextListener を使わなくても Servlet 内に実装してもよい)
ここで、DefaultCamelContext(Camel の実行に使用)はデフォルトで Thread・JNDI・JMX を使うため、以下の方法で回避。(今のところ、Google App Engine 上で Thread・JNDI・JMX は使えない)
- JndiRegistry(JNDI 使用)の代わりに空実装の Registry を設定
- DeadLetterChannelBuilder(DeadLetterChannel で Thread 使用)の代わりに NoErrorHandlerBuilder を設定
- InstrumentationLifecycleStrategy(JMX 使用)の代わりに DefaultLifecycleStrategy を設定
ServletContextListener の実装は以下の通り。Camel の処理内容は Google App Engine 用の回避策を除き、ローカルアプリケーション実行と基本的に同じ。
CamelSampleContextListener.java
package fits.gae.camel; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import org.apache.camel.CamelContext; import org.apache.camel.ProducerTemplate; import org.apache.camel.builder.NoErrorHandlerBuilder; import org.apache.camel.impl.DefaultCamelContext; import org.apache.camel.impl.DefaultLifecycleStrategy; import org.apache.camel.impl.DefaultProducerTemplate; import org.apache.camel.spi.Registry; public class CamelSampleContextListener implements ServletContextListener { private final static String CAMEL_CONTEXT_NAME = "camelContext"; private final static String CAMEL_PRODUCER_NAME = "camelProducer"; @Override public void contextDestroyed(ServletContextEvent event) { CamelContext ctx = (CamelContext)event.getServletContext().getAttribute(CAMEL_CONTEXT_NAME); if (ctx != null) { try { ctx.stop(); } catch (Exception e) { } } } @Override public void contextInitialized(ServletContextEvent event) { //JNDI の利用を回避 DefaultCamelContext ctx = new DefaultCamelContext(new Registry(){ @Override public Object lookup(String arg0) { return null; } @Override public <T> T lookup(String arg0, Class<T> arg1) { return null; } }); try { //Thread の利用を回避 ctx.setErrorHandlerBuilder(new NoErrorHandlerBuilder()); //JMX の利用を回避 ctx.setLifecycleStrategy(new DefaultLifecycleStrategy()); ctx.addRoutes(new SampleRoute()); ProducerTemplate ptemp = ctx.createProducerTemplate(); if (ptemp instanceof DefaultProducerTemplate) { //デフォルトのエンドポイント設定 //(sendBody 時にエンドポイントの指定が無かった場合等に使用) ((DefaultProducerTemplate)ptemp).setDefaultEndpointUri("direct:start"); } ctx.start(); event.getServletContext().setAttribute(CAMEL_CONTEXT_NAME, ctx); event.getServletContext().setAttribute(CAMEL_PRODUCER_NAME, ptemp); } catch (Exception ex) { ex.printStackTrace(); } } }
Servlet の実装は以下の通り。CamelSampleContextListener で作成した ProducerTemplate に HttpServletRequest を送って結果を出力するだけの簡単な実装。
CamelSampleServlet.java
package fits.gae.camel; import java.io.IOException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.camel.ProducerTemplate; @SuppressWarnings("serial") public class CamelSampleServlet extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { ProducerTemplate temp = (ProducerTemplate)getServletContext().getAttribute("camelProducer"); if (temp != null) { //デフォルトエンドポイントに設定した direct:start に //HttpServletRequest オブジェクトを送る Object result = temp.sendBody(req); //結果を出力 resp.getWriter().write(result.toString()); } } }
設定ファイル作成
Servlet のデプロイメントディスクリプタに、自作 Servlet・ServletContextListener の設定を記述。
/WEB-INF/web.xml
<web-app xmlns="http://java.sun.com/xml/ns/javaee" version="2.5"> <listener> <listener-class>fits.gae.camel.CamelSampleContextListener</listener-class> </listener> <servlet> <servlet-name>camelsample1</servlet-name> <servlet-class>fits.gae.camel.CamelSampleServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>camelsample1</servlet-name> <url-pattern>/camelsample1</url-pattern> </servlet-mapping> </web-app>
基本的な /WEB-INF/appengine-web.xml ファイルも用意する。
動作確認
/WEB-INF/classes にコンパイルした Java クラスファイルを、/WEB-INF/lib に apache-camel-2.0-M1 のアーカイブに含まれる全ての JAR ファイル(不要なものが多数含まれるはずだが選別が面倒だったので)を配置して、Google App Engine 上にアップロード。
以下のような URL にアクセスして動作確認。
http://[Application Identifier].appspot.com/camelsample1?id=a1