Apache Camel is a powerful routing and conversion engine use in many projects. In this article we present some best practices when integrating Camel into the JBoss application server aka WildFly/EAP7. Most of this is straight-forward, yet we also faced some problems with the thread pool management.
Step 1: Add Camel components
First you will have to add all the Camel components you need, such as camel-core, camel-cdi, camel-http4, etc. to your Maven POM:
<dependency> <groupId>org.apache.camel</groupId> <artifactId>camel-core</artifactId> <version>${org.apache.camel.version}</version> <exclusions> <exclusion> <artifactId>slf4j-api</artifactId> <groupId>org.slf4j</groupId> </exclusion> <exclusion> <artifactId>jaxb-core</artifactId> <groupId>com.sun.xml.bind</groupId> </exclusion> <exclusion> <artifactId>jaxb-impl</artifactId> <groupId>com.sun.xml.bind</groupId> </exclusion> </exclusions> </dependency>
Note the exclusions which will prevent adding JARs to the resulting artifact that are already contained within the application server. Instead, you may have to add a jboss-deployment-structure.xml to your deployment referencing all dependent modules:
<jboss-deployment-structure> <deployment> <dependencies> <module name="org.apache.commons.io"/> <module name="com.sun.xml.bind"/> ... </dependencies> </deployment> </jboss-deployment-structure>
The main purpose of this is to avoid version clashes in used libraries/modules and to downsize deployments.
Step 2: Startup Camel
Add a @Startup class that will initialize Camel at application startup. Camel registers itself with CDI so that you may inject all Camel objects into your EJBs automatically.
@Singleton @Startup public class StartupCamel { @Inject private CamelContext context; @Inject private Logger logger; @PostConstruct public void startup() { logger.info("Starting camel routes using {}", context); try { configureThreadPool(); addComponents(); // Your custom components, if any addRoutes(); addTypeConverters(); context.start(); logger.info("Camel routes started"); } catch (final Exception e) { logger.error("Error starting up camel", e); }}}
Step 3: Thread pooling
We experienced quite some problems with the thread pooling. The problem is that Camel manages its threads itself (and uses them a lot). This may interfere with the JEE environment where the creation of your threads is strictly prohibited by the JEE specification:
The enterprise bean must not attempt to manage threads. The enterprise bean must not attempt to start, stop, suspend, or resume a thread, or to change a thread’s priority or name. The enterprise bean must not attempt to manage thread groups. [EJB 3.1 spec, page 599]
In our case this resulted in unpredictable and confusing OutOfMemoryErrors:
java.lang.OutOfMemoryError: unable to create new native thread
Luckily, since JEE 7 there is an elegant solution to this problem: the (managed) ExecutorService. This feature allows us to implement some simple wrapper classes to be used as the Camel thread pool manager that integrate seamlessly with the application server’s thread pooling:
@Inject private ImexThreadPoolProvider provider; ... private void configureThreadPool() { context.getExecutorServiceManager().setThreadPoolFactory(provider); }
public class ImexThreadPoolProvider implements ThreadPoolFactory { @Inject private ImexExecutorService executorService; @Override public ExecutorService newCachedThreadPool(final ThreadFactory tf) { return executorService; } ... }
public class ImexExecutorService implements ScheduledExecutorService { @Resource private ManagedScheduledExecutorService managedExecutorService; ... @Override public <T> Future<T> submit(final Callable<T> task) { return managedExecutorService.submit(task); } ... }