Install this theme
Creating custom Apache Camel components

Some time ago I wrote an article about Apache Camel and how to send iOS push notifications using it. Now it’s time to demonstrate how one can create Apache Camel custom components.

Before we start, some words about Apache Camel itself. What exactly is Apache Camel? Wikipedia says:

Apache Camel is a rule-based routing and mediation engine which provides a Java object-based implementation
of the Enterprise Integration Patterns using an API (or declarative Java Domain Specific Language) to configure routing and mediation rules.

But it seems that many people still don’t understand the purpose of this framework. For those I would recommend to read the StackOverflow
discussion regarding this: http://stackoverflow.com/questions/8845186/what-exactly-is-apache-camel

One of the fundamental core elements of Apache Camel are components. Camel provides out of the box a rich set of pre-built components for nearly all common tasks enterprise developer may need nowadays while implementing Enterprise Integration Patterns (EIP). However, in rare cases it can happen that you have to develop a custom component, either to implement not yet covered task/usecase or just to encapsulate a complex route definition.

In this blog post i’ll demonstrate, how to create a simple custom component that can repeatedly generate random character sequences.

The source code of the project discussed in this article can be found on GitHub: https://github.com/bigpuritz/javaforge-blog/blob/master/camel-rnd

First of all, let’s define the URI scheme our component will provide:

rnd:someName?options

Notice here:

  • rnd: - an unique URI prefix, that identifies our component
  • someName - any string to uniquely identify the endpoint
  • options - query options in the following format, ?option=value&option=value&... In this way you can customize the component behavior.

Now let’s start.

1. Using Maven and camel-archetype-component create a new project

mvn archetype:generate \
    -DarchetypeGroupId=org.apache.camel.archetypes \
    -DarchetypeArtifactId=camel-archetype-component \
    -DarchetypeVersion=2.12.1 \
    -DgroupId=net.javaforge.blog.camel \
    -DartifactId=camel-rnd \
    -Dname=Rnd \
    -Dscheme=rnd 

This call will generate a new maven project containing following files:

  • META-INF/services/org/apache/camel/component/rnd - file to suppport auto-discovery of your component, where rnd is the URI scheme for your component and any related endpoints created on the fly. This file contains an one-line definition of the component class, that looks like this:
    class=net.javaforge.blog.camel.rnd.RndComponent
  • RndComponent.java - the component class responsible for creaing an appropriate “rnd”-endpoint
  • RndEndpoint.java - is the Endpoint implementation, that will be referred in the DSL via its URI (“rnd:” in our case). Endpoints are responsible for creating Producer and Consumer instances, associated with it.
  • RndProducer.java - the Producer implementation for sending message exchanges to the endpoint.
  • RndConsumer.java - the Consumer implementation for consuming message exchanges from the endpoint.

2. Before we start with the Apache Camel specific implementation, let’s first create the RndGenerator class, that can generate random character sequences. In this example I’ll just use the pre-implemented random string generation functionality from commons-lang3 library and provide a random generator implemented as an enumeration supporting 5 generation types: RANDOM, ALPHABETIC, ALPHANUMERIC, NUMERIC and ASCII.

Add commons-lang3 dependency to the pom.xml:

<dependency>
	<groupId>org.apache.commons</groupId>
	<artifactId>commons-lang3</artifactId>
	<version>3.1</version>
</dependency>    

and create the RndGenerator class:

public enum RndGenerator {

    RANDOM {
        @Override
        public String generate() {
            if (chars != null)
                return RandomStringUtils.random(length, chars);
            else
                return RandomStringUtils.random(length, start, end, letters,
                        numbers);
        }
    },
    ALPHABETIC {
        @Override
        public String generate() {
            return RandomStringUtils.randomAlphabetic(length);
        }
    },
    ALPHANUMERIC {
        @Override
        public String generate() {
            return RandomStringUtils.randomAlphanumeric(length);
        }
    },
    NUMERIC {
        @Override
        public String generate() {
            return RandomStringUtils.randomNumeric(length);
        }
    },
    ASCII {
        @Override
        public String generate() {
            return RandomStringUtils.randomAscii(length);
        }
    };

    int length;

    String chars;

    boolean letters = false;

    boolean numbers = false;

    int start = 0;

    int end = 0;

    private RndGenerator() {
        this(10);
    }

    private RndGenerator(int length) {
        this.length = length;
    }

    public RndGenerator chars(String chars) {
        this.chars = chars;
        return this;
    }

    public RndGenerator letters(boolean letters) {
        this.letters = letters;
        return this;
    }

    public RndGenerator numbers(boolean numbers) {
        this.numbers = numbers;
        return this;
    }

    public RndGenerator length(int length) {
        this.length = length;
        return this;
    }

    public RndGenerator start(int start) {
        this.start = start;
        return this;
    }

    public RndGenerator end(int end) {
        this.end = end;
        return this;
    }

    /**
     * Generates random string sequence...
     *
     * @return random string
     */
    public abstract String generate();

}

3. Now it is time to implement the RndEnpoint. Since we want our endpoint repeatedly generates random strings, it is easier to inherit from Camel’s ScheduledPollEndpoint. It automatically provides support for endpoints which create polling consumers and can recognize “polling”-specific options like pollStrategy, initialDelay, delay and many others (for further details see http://camel.apache.org/polling-consumer.html).

Furthermore this class defines all component specific properties (generator, length,…), that we want to offer as URI options.
Last but not least, an endpoint implementation shouled provide a producer and consumer, associated with it. In our special case, there is no need in producer implementation, since we only consume polling events from the endpoint and on each event send a random character sequence to the next processor in the route.

Find below the brief endpoint implementation. For further details please consult the GitHub repository:

@UriEndpoint(scheme = "rnd")
public class RndEndpoint extends ScheduledPollEndpoint {

	@UriParam
	private RndGenerator generator = RndGenerator.RANDOM;

	@UriParam
	private int length = 10;

	@UriParam
	private String chars = null;

	@UriParam
	private boolean letters = false;

	@UriParam
	private boolean numbers = false;

	@UriParam
	private int start = 0;

	@UriParam
	private int end = 0;

	public RndEndpoint() {
	}

	public RndEndpoint(String uri, RndComponent component) {
		super(uri, component);
	}

	public Producer createProducer() throws Exception {
		throw new RuntimeCamelException("Cannot produce to a RndEndpoint: "
				+ getEndpointUri());
	}

	public Consumer createConsumer(Processor processor) throws Exception {
		RndConsumer consumer = new RndConsumer(this, processor);
		configureConsumer(consumer);
		return consumer;
	}

	public boolean isSingleton() {
		return true;
	}

	// getter/setter skipped

}    

4. Remaining step is the consumer implementation, that is very straight forward in our case:

public class RndConsumer extends ScheduledPollConsumer {

    private final RndEndpoint endpoint;

    public RndConsumer(RndEndpoint endpoint, Processor processor) {
        super(endpoint, processor);
        this.endpoint = endpoint;
    }

    @Override
    protected int poll() throws Exception {

        Exchange exchange = endpoint.createExchange();

        // create a message body
        exchange.getIn().setBody(generateRandomSequence());

        try {
            // send message to next processor in the route
            getProcessor().process(exchange);
            return 1; // number of messages polled
        } finally {
            // log exception if an exception occurred and was not handled
            if (exchange.getException() != null) {
                getExceptionHandler().handleException(
                        "Error processing exchange", exchange,
                        exchange.getException());
            }
        }
    }

    private String generateRandomSequence() {
        RndGenerator generator = endpoint.getGenerator();
        ObjectHelper.notNull(generator, "generator");
        return generator.length(endpoint.getLength())
                .chars(endpoint.getChars()).letters(endpoint.isLetters())
                .numbers(endpoint.isNumbers()).start(endpoint.getStart())
				.end(endpoint.getEnd()).generate();
    }

}    

That’s it.. Now we are able to use our new custom component in the following fashion:

Repeatedly generate random alphabetic character sequences each 5 characters long and send them to the console output stream:

from("rnd:foo?generator=alphabetic&length=30").to("stream:out");

image

Every 50ms generate random alphanumeric character sequences each 10 characters long, aggregate them to the lists of 5 elements and send each list as a new message to the subsequent log component:

from("rnd:foo?generator=alphanumeric&initialDelay=0&delay=50&length=10")
        .setHeader("foo", constant("bar"))
        .aggregate(header("foo"), new ListAggregationStrategy())
        .completionSize(5)
        .to("log:net.javaforge.blog.camel?level=INFO"); 
        
private static class ListAggregationStrategy implements AggregationStrategy {

    @SuppressWarnings("unchecked")
    @Override
    public Exchange aggregate(Exchange oldExchange, Exchange newExchange) {
        Object newBody = newExchange.getIn().getBody();
        List<Object> list;
        if (oldExchange == null) {
            list = new ArrayList<Object>();
            list.add(newBody);
            newExchange.getIn().setBody(list);
            return newExchange;
        } else {
            list = oldExchange.getIn().getBody(List.class);
            list.add(newBody);
            return oldExchange;
        }
    }
}        

image

Repeatedly (every 10ms) generate random alphanumeric character sequences each 50 characters long and append them to the existing file out.txt:

from("rnd:foo?generator=alphanumeric&delay=10&length=50)
    .process(new Processor() {
            @Override
            public void process(Exchange exchange) throws Exception {
                exchange.getIn().setBody(exchange.getIn().getBody() + "\n");
            }
        })
    .to("file:target?fileName=out.txt&fileExist=Append")    

image

Generate random string sequences of length 30 containing only characters a,b and c:

from("rnd:foo?initialDelay=0&generator=random&chars=abc&length=30").to("stream:out");    

image

The project source code is hosted on GitHub under https://github.com/bigpuritz/javaforge-blog/blob/master/camel-rnd

 
Blog comments powered by Disqus