Sunday, October 18, 2009

First steps with Apache Camel on Google App Engine

This post describes how to get a simple Camel 2 application running on Google App Engine (GAE). I'll focus on the workarounds and fixes that were necessary to succeed. Please note that the following descriptions are by no means best-practices or recommendations. They only describe my first steps for which better solutions will likely exist in the future. I plan to work on improvements to
  • make Camel deployments on GAE easier and to
  • allow Camel applications access GAE services via Camel components
For my experiments, I was using a Camel 2.1 development snapshot, the App Engine SDK 1.2.6 and the Google Plugin for Eclipse which makes local testing and remote deployment very easy. The Camel components I used are:
  • camel-core
  • camel-spring
  • camel-servlet
  • camel-http
The following snippet shows the route definition of the sample application. It uses the camel-servlet component to receive input via HTTP, converts the HTTP request body to a String, prepends a "Hello " to the body and returns the result.

package example;

import org.apache.camel.builder.RouteBuilder;

public class ExampleRoute extends RouteBuilder {

@Override
public void configure() throws Exception {
from("servlet:/test")
.convertBodyTo(String.class)
.transform(constant("Hello ").append(body()));
}
}

The route doesn't make use of any GAE services (URL fetch, tasks queues, storage, mail ...) Also, message processing is synchronous because GAE doesn't allow applications to create their own threads. For example, using SEDA or JMS queues will not work.

For processing HTTP requests, I created my own servlet class and extended the CamelHttpTransportServlet from the camel-servlet component.

package example;

import org.apache.camel.component.servlet.CamelHttpTransportServlet;
import org.apache.camel.management.JmxSystemPropertyKeys;

public class ExampleServlet extends CamelHttpTransportServlet {

static {
System.setProperty(JmxSystemPropertyKeys.DISABLED, "true");
}

}

The only thing this servlet does is to disable all JMX-related functionality because the GAE JRE doesn't support JMX. All request processing and dispatching is done by the CamelHttpTransportServlet. Configuring the servlet in the web.xml was done as follows.

<servlet>
<servlet-name>CamelServlet</servlet-name>
<servlet-class>example.ExampleServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>context.xml</param-value>
</init-param>
</servlet>

<servlet-mapping>
<servlet-name>CamelServlet</servlet-name>
<url-pattern>/camel/*</url-pattern>
</servlet-mapping>

The servlet init-param points to the Spring application context that configures the route builder and the Camel context:

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

<bean id="camelContext"
class="org.apache.camel.spring.CamelContextFactoryBean">
<property name="builderRefs">
<list>
<ref bean="routeBuilderRef"/>
</list>
</property>
</bean>

<bean id="routeBuilderRef"
class="org.apache.camel.model.RouteBuilderDefinition">
<constructor-arg value="routeBuilder" />
</bean>

<bean id="routeBuilder"
class="example.ExampleRoute">
</bean>

</beans>

A severe limitation is that one cannot use the Camel-specific configuration XML schema from the http://camel.apache.org/schema/spring namespace for configuring the Camel context. The problem is that the CamelNamespaceHandler uses JAXB to parse bean definitions which isn't supported by GAE either. One has to fallback to plain old <bean> definitions (POBD?) to configure the Camel context in Spring. Using Spring JavaConfig or something similar would make more sense here but I didn't try it.

Another JAXB-releated problem arises with Camel's Spring DSL. It is also processed with JAXB and therefore cannot be used on GAE.

Going completely without Spring leads to another problem. In this case the CamelContext uses a JndiRegistry by default that depends on javax.naming.InitialContext. This class isn't on the JRE whitelist either. Writing a simple Map-based implementation of org.apache.camel.impl.Registry and configuring the CamelContext with it does the trick.

The last obstacle to get the sample application running was to replace the Camel's UuidGenerator with another one that uses java.util.UUID from the JRE. Camel's original UuidGenerator also uses a class that is not on the JRE whitelist. Since replacement by configuration was not possible, changes to the Camel code base were necessary (patch already submitted).

After deploying the application to GAE and POSTing a request containing "Martin" to http://<appname>.appspot.com/camel/test I was able to send myself greetings. In the URL, <appname> must of course be replaced with the name of an existing application.

3 comments:

  1. Fantastic findings and post.

    I have btw committed the UUID patch. Maybe we should provide the simple registry out of the box as well in Camel. Feel free to create a JIRA and patch for that also.

    On CamelContext itself there is a disableJMX() method which should allows you to much easier disable JMX.

    ReplyDelete
  2. Thanks for the hint Claus, I knew that there's a better way to disable JMX :)

    I'll contribute my simple Registry implementation to Camel soon.

    ReplyDelete
  3. You really did a great job on that! useful content for people.steps to going public

    ReplyDelete