Camel Cache

This document demonstrates how the Apache Camel's cache component implements caching capabilities for a web service. For this purpose we are using a service as following:

public class SayHelloService {

	public String sayHello(String name){
		return "Hello " + name;
	}
}

Each time a request is received, Camel checks first the cache, if found, the result is returned, otherwise a call to the service is done and the result is cached.

Case 1: Data not found in cache

The sequence diagrams below explains the cases where the data is not found in the cache.

Message transformation

Figure: Data not found in cache

Case 2: Data found in cache

The sequence diagrams below explains the cases where the data is found in the cache.

Message transformation

Figure: Data found in cache

The source code for this example can be found here.CachingServerWithCamelAndCXF.zip

Cache Configuration

URI format

	cache://cacheName[?option=value&option=#beanRef&...]

Example

This is a cache configuration example with different options. More details can be found here.

	from("cache://ServerCacheTest" +
	          "?maxElementsInMemory=1000" +
	          "&memoryStoreEvictionPolicy = MemoryStoreEvictionPolicy.LFU" +
	          "&overflowToDisk=true" +
	          "&eternal=true" +
	          "&timeToLiveSeconds=300" +
	          "&timeToIdleSeconds=true" +
	          "&diskPersistent=true" +
	          "&diskExpiryThreadIntervalSeconds=300")

The Camel cache component allows caching operations like GET, CHECK, ADD, UPDATE, DELETE and DELETEALL.

POM.XML

We need to add the following dependencies to our pom.xml

	<dependencies>
		<dependency>
			<groupId>org.apache.camel</groupId>
			<artifactId>camel-core</artifactId>
			<version>${camel-version}</version>
		</dependency>
		<dependency>
			<groupId>org.apache.camel</groupId>
			<artifactId>camel-spring</artifactId>
			<version>${camel-version}</version>
		</dependency>
		<dependency>
			<groupId>org.apache.camel</groupId>
			<artifactId>camel-cache</artifactId>
			<version>${camel-version}</version>
		</dependency>
		<dependency>
			<groupId>org.apache.camel</groupId>
			<artifactId>camel-test</artifactId>
			<version>${camel-version}</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>log4j</groupId>
			<artifactId>log4j</artifactId>
			<version>${log4j-version}</version>
		</dependency>
		<dependency>
			<groupId>org.apache.activemq</groupId>
			<artifactId>activemq-all</artifactId>
			<version>5.3.0</version>
		</dependency>
		<dependency>
			<groupId>org.apache.camel</groupId>
			<artifactId>camel-jms</artifactId>
			<version>${camel-version}</version>
			<scope>compile</scope>
		</dependency>
		<dependency>
			<groupId>org.apache.camel</groupId>
			<artifactId>camel-cxf</artifactId>
			<version>${camel-version}</version>
		</dependency>
		<!-- using Jetty with CXF -->
		<dependency>
			<groupId>org.apache.cxf</groupId>
			<artifactId>cxf-rt-transports-http-jetty</artifactId>
			<version>${cxf-version}</version>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
			<version>1.6.1</version>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-log4j12</artifactId>
			<version>1.6.1</version>
		</dependency>
	</dependencies>

Spring Files

The Spring file allows us to specify the package where the Camel route is configured.

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:camel="http://camel.apache.org/schema/spring"
	xmlns:broker="http://activemq.apache.org/schema/core"
	xsi:schemaLocation="
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd
       http://activemq.apache.org/schema/core http://activemq.apache.org/schema/core/activemq-core.xsd">

	<camel:camelContext id="camelContext">
		<camel:package>com.infinity.integration.cache</camel:package>
	</camel:camelContext>
</beans>

We will add a second Spring file to configure a CXF endpoint to specify a signature to a simple web service implemented with Axis2.

	<?xml version="1.0" encoding="UTF-8"?>

	<beans xmlns="http://www.springframework.org/schema/beans"
	       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	       xmlns:cxf="http://camel.apache.org/schema/cxf"
	       xsi:schemaLocation="
	         http://www.springframework.org/schema/beans 
	         http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
	         http://camel.apache.org/schema/cxf 
	         http://camel.apache.org/schema/cxf/camel-cxf.xsd">
	
	  <import resource="classpath:META-INF/cxf/cxf.xml"/>
	  <import resource="classpath:META-INF/cxf/cxf-extension-soap.xml"/>
	  <import resource="classpath:META-INF/cxf/cxf-extension-http-jetty.xml"/>
	  
	  <cxf:cxfEndpoint id="SayHelloService"
	                   address="http://localhost:8080/axis2/services/SayHelloService/"
	                   serviceClass="com.infinity.integration.cache.SayHelloService"
	                   />
	</beans>

Implementation

Java Classes

We implemented two Java classes. The first one is to define a simple web service and the second one is to configure our Camel route.

We just use the following POJO class to implement web service using Axis2.

package com.infinity.integration.cache;

public class SayHelloService {

	public String sayHello(String name){
		return "Hello " + name;
	}
}

The class below is where we configure our Camel route.

package com.infinity.integration.cache;

import org.apache.camel.Exchange;
import org.apache.camel.Message;
import org.apache.camel.Processor;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.cache.CacheConstants;
import org.apache.camel.spring.Main;
import org.apache.log4j.Logger;


public class ServerRouteBuilder extends RouteBuilder {
	private static final Logger logger = Logger.getLogger(ServerRouteBuilder.class
			.getName());

	/**
	 * Lets configure the Camel routing rules using Java code...
	 */
	public void configure() {
	
		from("cache://ServerCacheTest" +
		          "?maxElementsInMemory=5" +
		          "&timeToLiveSeconds=10" +
		          "&diskPersistent=true")
		    .log("*** Value added to the cache ****").end();

		from("activemq:queue:HelloService.queue").process(
				new Processor() {
					public void process(Exchange exchange) throws Exception {
						String message = (String) exchange.getIn().getBody();
						Message in = exchange.getIn();
						in.setHeader(CacheConstants.CACHE_OPERATION, CacheConstants.CACHE_OPERATION_CHECK);
						in.setHeader(CacheConstants.CACHE_KEY, message);
					}
				})
			.to("cache://ServerCacheTest")
			.choice()
				.when(header(CacheConstants.CACHE_ELEMENT_WAS_FOUND).isNull())
					.process(new Processor() {
							public void process(Exchange exchange) throws Exception {
								String message = (String) exchange.getIn().getBody();
								Message in = exchange.getIn();
								in.setHeader(CacheConstants.CACHE_OPERATION, CacheConstants.CACHE_OPERATION_ADD);
								in.setHeader(CacheConstants.CACHE_KEY, message);
							}
						})
					.to("cxf:bean:SayHelloService")
					.convertBodyTo(String.class)
		        	.to("direct:ShowData")
		        	.to("cache://ServerCacheTest") 
		        	.to("activemq:queue:HelloServiceResponse.queue")
				.otherwise()
		        	.process(new Processor() {
							public void process(Exchange exchange) throws Exception {
								String message = (String) exchange.getIn().getBody();
								Message in = exchange.getIn();
								in.setHeader(CacheConstants.CACHE_OPERATION, CacheConstants.CACHE_OPERATION_GET);
								in.setHeader(CacheConstants.CACHE_KEY, message);
							}
						})
		          	.to("cache://ServerCacheTest")
		          	.to("direct:ShowData")
	        		.to("activemq:queue:HelloServiceResponse.queue")
	        	.end();
		
		from("direct:ShowData").process(new Processor() {
			public void process(Exchange exchange) throws Exception {
				String operation = (String) exchange.getIn().getHeader(
						CacheConstants.CACHE_OPERATION);
				String key = (String) exchange.getIn().getHeader(
						CacheConstants.CACHE_KEY);
				Object body = exchange.getIn().getBody();
				String data = exchange.getContext().getTypeConverter()
					.convertTo(String.class, body);
				if (operation.equals("ADD")){
					logger.info("------- Cache element was not found, Add the element to the cache ---------");
					
				}else {
					logger.info("------- Element found in the cache ---------");
				}
				logger.info("Show Data from: ServerCacheTest");
				logger.info("Operation = " + operation);
				logger.info("Key = " + key);
				logger.info("Value = " + data);
				logger.info("------ End  ------");
			}
		});
	}
}

To configure our route, we have to override the RouteBuilder method. First of all, we start consuming messages from the JMS queue.
Then, we check if this value exist in the cache by adding "CHECK" operation and the "KEY" value to the message header as below:

		from("activemq:queue:HelloService.queue").process(
				new Processor() {
					public void process(Exchange exchange) throws Exception {
						String message = (String) exchange.getIn().getBody();
						Message in = exchange.getIn();
						in.setHeader(CacheConstants.CACHE_OPERATION, CacheConstants.CACHE_OPERATION_CHECK);
						in.setHeader(CacheConstants.CACHE_KEY, message);
					}
				})
			.to("cache://ServerCacheTest")

Then, if we don't find this message in the cache, Camel calls the web service and put the result in the cache using the "CACHE_OPERATION_ADD":

			.choice()
				.when(header(CacheConstants.CACHE_ELEMENT_WAS_FOUND).isNull())
					.process(new Processor() {
							public void process(Exchange exchange) throws Exception {
								String message = (String) exchange.getIn().getBody();
								Message in = exchange.getIn();
								in.setHeader(CacheConstants.CACHE_OPERATION, CacheConstants.CACHE_OPERATION_ADD);
								in.setHeader(CacheConstants.CACHE_KEY, message);
							}
						})
					.to("cxf:bean:SayHelloService")
					.convertBodyTo(String.class)
		        	.to("direct:ShowData")
		        	.to("cache://ServerCacheTest") 
		        	.to("activemq:queue:HelloServiceResponse.queue")

Finally, if the message was cached, the otherwise section is executed and the cache value is returned. The "CACHE_OPERATION_GET" is used in this case.

				.otherwise()
		        	.process(new Processor() {
							public void process(Exchange exchange) throws Exception {
								String message = (String) exchange.getIn().getBody();
								Message in = exchange.getIn();
								in.setHeader(CacheConstants.CACHE_OPERATION, CacheConstants.CACHE_OPERATION_GET);
								in.setHeader(CacheConstants.CACHE_KEY, message);
							}
						})
		          	.to("cache://ServerCacheTest")
		          	.to("direct:ShowData")
	        		.to("activemq:queue:HelloServiceResponse.queue")
	        	.end();

Result

Here is the result we get after running the example.

[2011-09-08 14:57:28,303] INFO : com.infinity.integration.cache.ServerRouteBuilder$4.process(ServerRouteBuilder.java:94) - ------- Cache element was not found, Add the element to the cache ---------
[2011-09-08 14:57:28,303] INFO : com.infinity.integration.cache.ServerRouteBuilder$4.process(ServerRouteBuilder.java:99) - Show Data from: ServerCacheTest
[2011-09-08 14:57:28,303] INFO : com.infinity.integration.cache.ServerRouteBuilder$4.process(ServerRouteBuilder.java:100) - Operation = ADD
[2011-09-08 14:57:28,303] INFO : com.infinity.integration.cache.ServerRouteBuilder$4.process(ServerRouteBuilder.java:101) - Key = Gerald
[2011-09-08 14:57:28,304] INFO : com.infinity.integration.cache.ServerRouteBuilder$4.process(ServerRouteBuilder.java:102) - Value = Hello Gerald
[2011-09-08 14:57:28,304] INFO : com.infinity.integration.cache.ServerRouteBuilder$4.process(ServerRouteBuilder.java:103) - ------ End  ------
[2011-09-08 14:57:28,307] INFO : org.apache.camel.processor.Logger.log(Logger.java:212) - *** Value added to the cache ****
[2011-09-08 14:57:28,354] INFO : com.infinity.integration.cache.ServerRouteBuilder$4.process(ServerRouteBuilder.java:97) - ------- Element found in the cache ---------
[2011-09-08 14:57:28,354] INFO : com.infinity.integration.cache.ServerRouteBuilder$4.process(ServerRouteBuilder.java:99) - Show Data from: ServerCacheTest
[2011-09-08 14:57:28,355] INFO : com.infinity.integration.cache.ServerRouteBuilder$4.process(ServerRouteBuilder.java:100) - Operation = GET
[2011-09-08 14:57:28,355] INFO : com.infinity.integration.cache.ServerRouteBuilder$4.process(ServerRouteBuilder.java:101) - Key = Gerald
[2011-09-08 14:57:28,355] INFO : com.infinity.integration.cache.ServerRouteBuilder$4.process(ServerRouteBuilder.java:102) - Value = Hello Gerald
[2011-09-08 14:57:28,355] INFO : com.infinity.integration.cache.ServerRouteBuilder$4.process(ServerRouteBuilder.java:103) - ------ End  ------
[2011-09-08 14:57:28,385] INFO : com.infinity.integration.cache.ServerRouteBuilder$4.process(ServerRouteBuilder.java:97) - ------- Element found in the cache ---------
[2011-09-08 14:57:28,385] INFO : com.infinity.integration.cache.ServerRouteBuilder$4.process(ServerRouteBuilder.java:99) - Show Data from: ServerCacheTest
[2011-09-08 14:57:28,385] INFO : com.infinity.integration.cache.ServerRouteBuilder$4.process(ServerRouteBuilder.java:100) - Operation = GET
[2011-09-08 14:57:28,388] INFO : com.infinity.integration.cache.ServerRouteBuilder$4.process(ServerRouteBuilder.java:101) - Key = Gerald
[2011-09-08 14:57:28,388] INFO : com.infinity.integration.cache.ServerRouteBuilder$4.process(ServerRouteBuilder.java:102) - Value = Hello Gerald
[2011-09-08 14:57:28,388] INFO : com.infinity.integration.cache.ServerRouteBuilder$4.process(ServerRouteBuilder.java:103) - ------ End  ------
[2011-09-08 14:57:28,417] INFO : com.infinity.integration.cache.ServerRouteBuilder$4.process(ServerRouteBuilder.java:97) - ------- Element found in the cache ---------
[2011-09-08 14:57:28,417] INFO : com.infinity.integration.cache.ServerRouteBuilder$4.process(ServerRouteBuilder.java:99) - Show Data from: ServerCacheTest
[2011-09-08 14:57:28,417] INFO : com.infinity.integration.cache.ServerRouteBuilder$4.process(ServerRouteBuilder.java:100) - Operation = GET
[2011-09-08 14:57:28,417] INFO : com.infinity.integration.cache.ServerRouteBuilder$4.process(ServerRouteBuilder.java:101) - Key = Gerald
[2011-09-08 14:57:28,417] INFO : com.infinity.integration.cache.ServerRouteBuilder$4.process(ServerRouteBuilder.java:102) - Value = Hello Gerald
[2011-09-08 14:57:28,417] INFO : com.infinity.integration.cache.ServerRouteBuilder$4.process(ServerRouteBuilder.java:103) - ------ End  ------
[2011-09-08 14:57:28,442] INFO : com.infinity.integration.cache.ServerRouteBuilder$4.process(ServerRouteBuilder.java:97) - ------- Element found in the cache ---------
[2011-09-08 14:57:28,442] INFO : com.infinity.integration.cache.ServerRouteBuilder$4.process(ServerRouteBuilder.java:99) - Show Data from: ServerCacheTest
[2011-09-08 14:57:28,442] INFO : com.infinity.integration.cache.ServerRouteBuilder$4.process(ServerRouteBuilder.java:100) - Operation = GET
[2011-09-08 14:57:28,443] INFO : com.infinity.integration.cache.ServerRouteBuilder$4.process(ServerRouteBuilder.java:101) - Key = Gerald
[2011-09-08 14:57:28,443] INFO : com.infinity.integration.cache.ServerRouteBuilder$4.process(ServerRouteBuilder.java:102) - Value = Hello Gerald
[2011-09-08 14:57:28,443] INFO : com.infinity.integration.cache.ServerRouteBuilder$4.process(ServerRouteBuilder.java:103) - ------ End  ------

In the first part of log, we don't find the new message in cache, so Camel proceeds by calling the web service and adding it to the cache.

In the second part, all messages with "Gerald" as key exist in the cache, so Camel returns the result of the "GET" operation.