by Shashank Sharma

How to configure multiple Camel Contexts in the Spring Boot application

This article will assume you are already familiar with Apache Camel & Spring Boot, which their documentation describe as:

Apache Camel is an open source framework for message-oriented middleware with a rule-based routing and mediation engine. It provides a Java object-based implementation of the Enterprise Integration Patterns using an application programming interface (or declarative Java domain-specific language) to configure routing and mediation rules.

The domain-specific language means that Apache Camel can support type-safe smart completion of routing rules in an integrated development environment using regular Java code without large amounts of XML configuration files. Although XML configuration inside Spring Framework is also supported.

Spring Boot is the starting point for building all Spring-based applications. Spring Boot is designed to get you up and running as quickly as possible, with minimal upfront configuration of Spring. Spring Boot makes it easy to create stand-alone, production-grade Spring-based Applications that you can run.

The problem at hand

You are designing a platform which enables end users to create multiple apps in your system. Each app can have it’s own Camel routes or can reuse predefined routes. The problem is how to ensure that one app Camel route doesn’t collide with other app routes.

One way to solve this problem is to ensure routes are always uniquely named. But this is hard to enforce and even harder if end users can define their own routes. A neat solution is to create separate Camel Contexts for each app. It’s easier to manage. There are multiple articles on how to configure Apache Camel with Spring Boot application, but none on how to configure multiple Camel Contexts at runtime.

First exclude CamelAutoConfiguration from Spring Boot Application. Instead of CamelContext created by Spring Auto Configuration, we will create CamelContext as and when required.

@SpringBootApplication@EnableAutoConfiguration(exclude = {CamelAutoConfiguration.class})public class Application {
  public static void main(String[] args) {    SpringApplication.run(Application.class, args);  }
}

To handle all Apache Camel configuration and to reuse the Spring properties, create CamelConfig Class.

@Configuration@EnableConfigurationProperties(CamelConfigurationProperties.class)@Import(TypeConversionConfiguration.class)public class CamelConfig {
  @Autowired  private ApplicationContext applicationContext;
  @Bean  @ConditionalOnMissingBean(RoutesCollector.class)  RoutesCollector routesCollector(ApplicationContext          applicationContext, CamelConfigurationProperties config) {
    Collection<CamelContextConfiguration> configurations = applicationContext.getBeansOfType(CamelContextConfiguration.class).values();
    return new RoutesCollector(applicationContext, new ArrayList<CamelContextConfiguration>(configurations), config);  }
  /**  * Camel post processor - required to support Camel annotations.  */  @Bean  CamelBeanPostProcessor camelBeanPostProcessor(ApplicationContext applicationContext) {    CamelBeanPostProcessor processor = new CamelBeanPostProcessor();    processor.setApplicationContext(applicationContext);    return processor;  }
}

To create and manage CamelContext, create class CamelContextHandler.

@Componentpublic class CamelContextHandler implements BeanFactoryAware {
  private BeanFactory beanFactory;
  @Autowired  private ApplicationContext applicationContext;
  @Autowired  private CamelConfigurationProperties camelConfigurationProperties;
  /*  * (non-Javadoc)  *  * @see  * org.springframework.beans.factory.BeanFactoryAware  * #setBeanFactory(org.springframework.beans.  * factory.BeanFactory)  */  @Override  public void setBeanFactory(BeanFactory beanFactory) {    this.beanFactory = beanFactory;  }
  public boolean camelContextExist(int id) {    String name = "camelContext" + id;    return applicationContext.containsBean(name);  }
  public CamelContext getCamelContext(int id) {    CamelContext camelContext = null;    String name = "camelContext" + id;    if (applicationContext.containsBean(name)) {           camelContext = applicationContext.getBean(name, SpringCamelContext.class);    } else {       camelContext = camelContext(name);    }    return camelContext;  }
  private CamelContext camelContext(String contextName) {    CamelContext camelContext = new SpringCamelContext(applicationContext);    SpringCamelContext.setNoStart(true);    if (!camelConfigurationProperties.isJmxEnabled()) {      camelContext.disableJMX();    }
    if (contextName != null) {      ((SpringCamelContext) camelContext).setName(contextName);    }
    if (camelConfigurationProperties.getLogDebugMaxChars() > 0) {     camelContext.getProperties().put( Exchange.LOG_DEBUG_BODY_MAX_CHARS, "" + camelConfigurationProperties.getLogDebugMaxChars());
    }
    camelContext.setStreamCaching( camelConfigurationProperties.isStreamCaching());
    camelContext.setTracing( camelConfigurationProperties.isTracing());
    camelContext.setMessageHistory( camelConfigurationProperties.isMessageHistory());
    camelContext.setHandleFault( camelConfigurationProperties.isHandleFault());
    camelContext.setAutoStartup( camelConfigurationProperties.isAutoStartup());
camelContext.setAllowUseOriginalMessage(camelConfigurationProperties.isAllowUseOriginalMessage());
    if (camelContext.getManagementStrategy().getManagementAgent() != null) {      camelContext.getManagementStrategy().getManagementAgent().setEndpointRuntimeStatisticsEnabled(camelConfigurationProperties.isEndpointRuntimeStatisticsEnabled());
      camelContext.getManagementStrategy().getManagementAgent().setStatisticsLevel(camelConfigurationProperties.getJmxManagementStatisticsLevel());
      camelContext.getManagementStrategy().getManagementAgent().setManagementNamePattern(camelConfigurationProperties.getJmxManagementNamePattern());
      camelContext.getManagementStrategy().getManagementAgent().setCreateConnector(camelConfigurationProperties.isJmxCreateConnector());
    }
    ConfigurableBeanFactory configurableBeanFactory = (ConfigurableBeanFactory) beanFactory;    configurableBeanFactory.registerSingleton(contextName, camelContext);
    try {      camelContext.start();    } catch (Exception e) {      // Log error    }    return camelContext;  }
}

Now instead of autowiring CamelContext, autowire CamelContextHandler. CamelContextHandler#getCamelContext will return CamelContext based on its ID (where ID is the unique identifier of different apps). If there isn’t existing context for that ID, CamelContextHandler#getCamelContext will create CamelContext for that ID and return.

To prevent unnecessary creation of CamelContext, we can define a helper function in CamelContextHandler which can be called before calling getCamelContext to check if context exists for that ID.

public boolean camelContextExist(int id) {  String name = "camelContext" + id;  return applicationContext.containsBean(name);}

You use the same way to load routes, and you will use CamelContextHandler#getCamelContext to get the context. Let’s assume your routes are stored in the database. And each of the routes are associated with some app ID. To load routes, we can define a method like:

public void loadRoutes(String id) {  String routestr  = fetchFromDB(id);  if (routestr != null && !routestr.isEmpty()) {    try {      RoutesDefinition routes = camelContext.loadRoutesDefinition(IOUtils.toInputStream(routestr, "UTF-8"));
      getCamelContext(id).addRouteDefinitions(routes.getRoutes());
    } catch (Exception e) {      // Log error    }  }}

And to load routes at server startup which are already present in database we can use @PostConstruct annotation from Spring.

@PostConstructpublic void afterPropertiesSet() {  List<String> appIds = getAllEnabledApps();  appIds.forEach(id -> loadRoutes(id));}

As CamelContext objects are not created through Spring, Spring will not handle the lifecycle of these CamelContext beans. To stop all context when application stops, we can define closeContext method in the CamelConfig class to close all CamelContext gracefully.

@PreDestroyvoid closeContext() {  applicationContext.getBeansOfType(CamelContext.class).values() .forEach(camelContext -> {    try {      camelContext.stop();    } catch (Exception e) {      //Log error    }  });}

The above setup can help you run multiple Camel Contexts in a Spring Boot Application. If you have any questions or suggestions, feel free to write. Cheers.

Other articles by Shashank Sh.