Google App Engine 上で Apache Camel を使用

id:fits:20080922 で作成した Apache Camel のサンプルを簡易化して Google App Engine for Java 上で動作させてみた。

使用した環境は以下の通り。ただし、特に 2.0 の機能は使っていないはずなので 1.x でも動作すると思う。

詳細は後述するが、今回(前回=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