Citrus 3 provides enhanced support for Apache Camel with new features and improvements. This enables great opportunities and leverages the full enterprise integration power of Apache Camel in Citrus.

Before this post explains the enhancements in Citrus 3 regarding Camel let us have a brief look at what has always been in the box.

Recap the Camel support in Citrus

Camel has been integrated with the Citrus framework for years. You can read about it in the official Citrus user guide.

With Camel included in the box Citrus users are able to use the 300+ Camel components in order to connect with various message transports and technologies.

Citrus is able to interact with Camel in following areas

Usually Camel routes live in a Camel context defined in the Citrus project.

@Bean
public CamelContext camelContext() {
    CamelContext context = new DefaultCamelContext();

    context.addRoutes(new RouteBuilder() {
        @Override
        public void configure() throws Exception {
            from("direct:hello")
                .routeId("helloRoute")
                .to("log:com.consol.citrus.camel?level=INFO")
                .to("seda:greetings");
        }
    });

    return context;
}

The context defines a Camel route listening on the endpoint uri direct:hello. Incoming messages will be logged to the console using a log Camel component. After that the message is forwarded to a seda Camel component which is a simple queue in memory.

The Citrus endpoint can interact with this sample route definition sending messages to the direct:hello endpoint.

send("camel:direct:hello")
    .message()
    .body("Hello from Citrus!");

The Camel endpoint uri can refer to any Camel endpoint component. See the list of available Camel components in order to connect your route with message transports and technologies.

As an alternative to sending messages directly to a Camel endpoint uri you can create a Citrus endpoint that interacts with a Camel route. The endpoint can be shared in multiple tests and defines several properties such as the endpoint uri that will be called.

@Bean
public CamelEndpoint helloCamelEndpoint() {
    return new CamelEndpoint()
            .endpointUri("direct:hello")
            .build();
}
send(helloCamelEndpoint)
    .message()
    .body("Hello from Citrus!");

Of course, you can also consume messages from a Camel route using the endpoint pattern.

receive("camel:seda:greetings")
    .message()
    .type(MessageType.PLAINTEXT)
    .body("Hello from Citrus!");

The Camel support in Citrus has been enhanced with Citrus 3. Read the following sections to find out about awesome new features and improvements.

What’s new in Citrus 3?

Endpoint DSL support

Since Camel 3 you can use a Java fluent API to deal with Camel endpoint URIs. You can use the Camel endpoint DSL in the Citrus send/receive actions now, too.

send(camel().endpoint(direct("hello")::getUri))
    .message()
    .body("Hello from Citrus!");

receive(camel().endpoint(seda("greetings")::getUri)")
    .message()
    .type(MessageType.PLAINTEXT)
    .body("Hello from Citrus!");

The send and receive actions above use the Camel endpoint DSL to construct the proper endpoint URI. The camel().endpoint(seda("test")::getUri) builds the endpoint uri seda:test. The endpoint DSL provides all settings and properties that you can set for a Camel endpoint component.

Camel processor support

Camel implements the concept of processors as enterprise integration pattern. A processor is able to add custom logic to a Camel route. Each processor is able to access the Camel exchange that is being processed in the current route. The processor is able to change the message content (body, headers) as well as the exchange information.

The send/receive operations in Citrus also implement the processor concept. With the Citrus Camel support you can now use the very same Camel processor also in a Citrus test action.

import com.consol.citrus.camel.message.CamelMessageProcessor;

public class CamelMessageProcessorIT extends TestNGCitrusSpringSupport {

    @Autowired
    private CamelContext camelContext;

    @Test
    @CitrusTest
    public void shouldProcessMessages() {
        CamelMessageProcessor.Builder toUppercase = camel(camelContext)
                .process(exchange -> exchange
                        .getMessage()
                        .setBody(exchange.getMessage().getBody(String.class).toUpperCase()));

        $(send(camel().endpoint(seda("test")::getUri))
                .message()
                .body("Citrus rocks!")
                .process(toUppercase)
        );
    }
}

The example above uses a Camel processor to change the exchange and the message content before the message is sent to the endpoint. This way you can apply custom changes to the message as part of the test action.

public class CamelMessageProcessorIT extends TestNGCitrusSpringSupport {

    @Autowired
    private CamelContext camelContext;

    @Test
    @CitrusTest
    public void shouldProcessMessages() {
        CamelMessageProcessor.Builder toUppercase = camel(camelContext)
                .process(exchange -> exchange
                        .getMessage()
                        .setBody(exchange.getMessage().getBody(String.class).toUpperCase()));
        
        $(send(camel().endpoint(seda("test")::getUri))
                .message()
                .body("Citrus rocks!"));

        $(receive(camel().endpoint(seda("test")::getUri))
                .process(toUppercase)
                .message()
                .type(MessageType.PLAINTEXT)
                .body("CITRUS ROCKS!"));
    }
}

The Camel processors are very powerful. In particular, you can apply transformations of multiple kind.

public class CamelTransformIT extends TestNGCitrusSpringSupport {

    @Autowired
    private CamelContext camelContext;

    @Test
    @CitrusTest
    public void shouldTransformMessageReceived() {
        $(send(camel().endpoint(seda("hello")::getUri))
                .message()
                .body("{\"message\": \"Citrus rocks!\"}")
        );

        $(receive(camel().endpoint(seda("hello")::getUri))
                .transform(
                    camel()
                        .camelContext(camelContext)
                        .transform()
                        .jsonpath("$.message"))
                .message()
                .type(MessageType.PLAINTEXT)
                .body("Citrus rocks!"));
    }
}

The transform pattern is able to change the message content before a message is received/sent in Citrus. The example above applies a JsonPath expression as part of the message processing. The JsonPath expression evaluates $.message on the Json payload and saves the result as new message body content. The following message validation expects the plaintext value Citrus rocks!.

The message processor is also able to apply a complete route logic as part of the test action.

public class CamelRouteProcessorIT extends TestNGCitrusSpringSupport {

    @Autowired
    private CamelContext camelContext;

    @Test
    @CitrusTest
    public void shouldProcessRoute() {
        CamelRouteProcessor.Builder beforeReceive = camel(camelContext).route(route ->
                route.choice()
                    .when(jsonpath("$.greeting[?(@.language == 'EN')]"))
                        .setBody(constant("Hello!"))
                    .when(jsonpath("$.greeting[?(@.language == 'DE')]"))
                        .setBody(constant("Hallo!"))
                    .otherwise()
                        .setBody(constant("Hi!")));

        $(send(camel().endpoint(seda("greetings")::getUri))
                .message()
                .body("{" +
                        "\"greeting\": {" +
                            "\"language\": \"EN\"" +
                        "}" +
                      "}")
        );

        $(receive("camel:" + camel().endpoints().seda("greetings").getUri())
                .process(beforeReceive)
                .message()
                .type(MessageType.PLAINTEXT)
                .body("Hello!"));
    }
}

With the complete route logic you have the full power of Camel ready to be used in your send/receive test action. This enables many capabilities as Camel implements the enterprise integration patterns such as split, choice, enrich and many more.

Camel data format support

Camel uses the concept of data format to transform message content in form of marshal/unmarshal operations. You can use the data formats supported in Camel in Citrus, too.

public class CamelDataFormatIT extends TestNGCitrusSpringSupport {

    @Autowired
    private CamelContext camelContext;

    @Test
    @CitrusTest
    public void shouldApplyDataFormat() {
        when(send(camel().endpoint(seda("data")::getUri))
                .message()
                .body("Citrus rocks!")
                .transform(camel(camelContext)
                        .marshal()
                        .base64())
        );

        then(receive("camel:" + camel().endpoints().seda("data").getUri())
                .transform(camel(camelContext)
                        .unmarshal()
                        .base64())
                .transform(camel(camelContext)
                        .convertBodyTo(String.class))
                .message()
                .type(MessageType.PLAINTEXT)
                .body("Citrus rocks!"));
    }
}

The example above uses the base64 data format provided in Camel to marshal/unmarshal the message content to/from a base64 encoded String. Camel provides support for many data formats as you can see in the documentation on data formats.

Wrap up!

That completes the new features in Citrus 3 regarding Apache Camel.

Please give it a try, provide some feedback and tell us what you think about Camel and Citrus!