Friday, August 14, 2009

Developed a camel-cache component to store, perform lookups and listen for cache events on a camel route

I have recently submitted a Camel cache component which is slated for release in Apache Camel version 2.1 in Septermber, 09

http://issues.apache.org/activemq/browse/CAMEL-1868

The Camel Caching component has the following abilities

a> In Producer mode, the component provides the ability to direct payloads in exchanges to a stored in a pre-existing or created-on-demand Cache. The producer mode supports operations to ADD/UPDATE/DELETE/DELETEALL elements in a cache. (Examples goven below)

b> In Consumer mode, the component provides the ability to listen on a pre-existing or created-on-demand Cache using an event Listener and receive automatic notifications when any cache activity take place (i.e ADD/UPDATE/DELETE/DELETEALL). Upon such an activity taking place, an exchange containing header elements describing the operation and cachekey and a body containing the just added/updated payload is placed and sent. In case of a DELETEALL operation the body of the exchanage is not populated.

The cache itself may be created on demand or if a cache of that name already exists then it is simply utilized with its original settings. The URL itself may take the following form

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

// Note that all the attributes of the above URL are
// standard ehCache settings that may be set at
// Cache creation.

Given below are examples of how to create/set routes:

Producer Example 1: Adding keys to the cache with a body received from direct:start

RouteBuilder builder = new RouteBuilder() {
public void configure() {
from("direct:start")
.setHeader("CACHE_OPERATION", constant("ADD"))
.setHeader("CACHE_KEY", constant("Ralph_Waldo_Emerson"))
.to("cache://TestCache1")
}
};


Producer Example 2: Updating existing keys in a cache with a body received from direct:start

RouteBuilder builder = new RouteBuilder() {
public void configure() {
from("direct:start")
.setHeader("CACHE_OPERATION", constant("UPDATE"))
.setHeader("CACHE_KEY", constant("Ralph_Waldo_Emerson"))
.to("cache://TestCache1")
}
};


Producer Example 3: Deleting existing keys in a cache with a body received from direct:start

RouteBuilder builder = new RouteBuilder() {
public void configure() {
from("direct:start")
.setHeader("CACHE_OPERATION", constant("DELETE"))
.setHeader("CACHE_KEY", constant("Ralph_Waldo_Emerson"))
.to("cache://TestCache1")
}
};


Producer Example 4: Deleting all keys in a cache with a body received from direct:start

RouteBuilder builder = new RouteBuilder() {
public void configure() {
from("direct:start")
.setHeader("CACHE_OPERATION", constant("ADD"))
.setHeader("CACHE_KEY", constant("Ralph_Waldo_Emerson"))
.to("cache://TestCache1");

from("direct:start")
.setHeader("CACHE_OPERATION", constant("ADD"))
.setHeader("CACHE_KEY", constant("Ralph_Waldo_Emerson2"))
.to("cache://TestCache1");

from("direct:start")
.setHeader("CACHE_OPERATION", constant("DELETEALL"))
.to("cache://TestCache1");
}
};


Consumer Example 1: Notifying any changes registering in a Cache to Processors and other Producers

// Note: in this example the consumer is
// created first and then 3 routes
// send different message as Cache Producers

RouteBuilder builder = new RouteBuilder() {
public void configure() {
from("cache://TestCache1")
.process(new Processor() {
public void process(Exchange exchange)
throws Exception {
String operation = (String) exchange.getIn().getHeader("CACHE_OPERATION");
String key = (String) exchange.getIn().getHeader("CACHE_KEY");
Object body = exchange.getIn().getBody();
// Do something
}
})
}
};


I have added a set of nice processors to the camel-cache component to provide the ability to perform cache lookups and selectively replace payload content at the
- body
- token
- xpath level

The mechanics of doing this are as follows

Producer Example 3: Deleting existing keys in a cache with a body received from direct:start

RouteBuilder builder = new RouteBuilder() {
public void configure() {
//Message Body Replacer
from("cache://TestCache1")
.filter(header("CACHE_KEY").isEqualTo("greeting"))
.process(new CacheBasedMessageBodyReplacer("cache://TestCache1", "farewell"))
.to("direct:next");

//Message Token replacer
from("cache://TestCache1")
.filter(header("CACHE_KEY").isEqualTo("quote"))
.process(new CacheBasedTokenReplacer("cache://TestCache1", "novel", "#novel#"))
.process(new CacheBasedTokenReplacer("cache://TestCache1", "author", "#author#"))
.process(new CacheBasedTokenReplacer("cache://TestCache1", "number", "#number#"))
.to("direct:next");

from("cache://TestCache1").
.filter(header("CACHE_KEY").isEqualTo("XML_FRAGMENT"))
.process(new CacheBasedXPathReplacer("cache://TestCache1", "book1", "/books/book1"))
.process (new CacheBasedXPathReplacer("cache://TestCache1", "book2", "/books/book2"))
.to("direct:next");
}
};

5 comments:

Rich Newcomb said...

Nice work Ashwin. Out of curiosity, did you evaluate ehCache against Jakarta JCS? What features of ehCache won out?

Claus Ibsen said...

Ashwin you are on a roll again. Glad when 2.0 is finally out so we can get your components into the Camel family :)

Ashwin Karpe said...

Hi Rich,

ehCache is a more robust and enterprise ready cache implementation. It is used in JBoss and other open source implementations.

It certainly seems to be the dominant implementation for caching out there. Moreover it has an Apache 2.0 license so it works out nicely with Camel :)

- Ashwin

C. F. said...

I am trying to specify property keys dynamically for each object. All examples I find has a hard coded key.

Is there a way to a way to dynamically specify the key of the object being inserted?

kowshik_nandagudi said...

Good example! do you have a maven project you can use ?