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.