Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Available as of Camel 2.15.0

 

Info

Camel-scr bundle is not included in Apache Camel versions prior 2.15.0, but the artifact itself can be used with any Camel version since 2.12.0.

 

org.apache.camel/camel-scr bundle provides a base class, AbstractCamelRunner, which manages a Camel context for you and a helper class, ScrHelper, for using your SCR properties in unit tests. Camel-scr feature for Apache Karaf defines all features and bundles required for running Camel in SCR bundles.

...

Code Block
languagejava
titleLoad routesImplement getRouteBuilders()
	@Override
    protected List<RoutesBuilder> getRouteBuilders() {
        List<RoutesBuilder> routesBuilders = new ArrayList<>();
        routesBuilders.add(new YourRouteBuilderHere(registry));
        routesBuilders.add(new AnotherRouteBuilderHere(registry));
        return routesBuilders;
    }

...

Code Block
languagejava
titleDefine Default configuration with in annotations
@Properties({
   @Property(name = "camelContextId", value = "my-test"),
   @Property(name = "active", value = "true"),
   @Property(name = "...", value = "..."),
   ...
})

...

AbstractCamelRunner will make these properties available to your RouteBuilders with help of Camel's PropertiesComponent and it will also inject these values into your Service Component's and RouteBuilder's fields when their names match. The fields can be declared with any visibility level, and many types are supported (String, int, boolean, URL, ...).

AbstractCamelRunner's lifecycle in SCR

  1. When component's configuration policy and mandatory references are satisfied SCR calls activate(). This creates and sets up a CamelContext through the following call chain: activate() → prepare() → createCamelContext() → setupPropertiesComponent() → configure() → setupCamelContext(). Finally, the context is scheduled to start after a delay defined in AbstractCamelRunner.START_DELAY with runWithDelay().
  2. When Camel components (ComponentResolver services, to be exact) are registered in OSGi, SCR calls gotCamelComponent() which reschedules/delays the CamelContext start further by the same AbstractCamelRunner.START_DELAY. This in effect makes CamelContext wait until all Camel components are loaded or there is a sufficient gap between them. The same logic will tell a failed-to-start CamelContext to try again whenever we add more Camel components.
  3. When Camel components are unregistered SCR calls lostCamelComponent(). This call does nothing.
  4. When one of the requirements that caused the call to activate() is lost SCR will call deactivate(). This will shutdown the CamelContext.

In (non-OSGi) unit tests you should use prepare() → run() → stop() instead of activate() → deactivate() for more fine-grained control. Also, this allows us to avoid possible SCR specific operations in tests.

Using camel-archetype-scr

The easiest way to create an Camel SCR bundle project is to use camel-archetype-scr and Maven.

You can generate a project with the following steps:

 

Code Block
languagetext
titleGenerating a project
$ mvn archetype:generate -Dfilter=org.apache.camel.archetypes:camel-archetype-scr
 
Choose archetype:
1: local -> org.apache.camel.archetypes:camel-archetype-scr (Creates a new Camel SCR bundle project for Karaf)
Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): : 1
Define value for property 'groupId': : example
[INFO] Using property: groupId = example
Define value for property 'artifactId': : camel-scr-example
Define value for property 'version': 1.0-SNAPSHOT: :
Define value for property 'package': example: :
[INFO] Using property: archetypeArtifactId = camel-archetype-scr
[INFO] Using property: archetypeGroupId = org.apache.camel.archetypes
[INFO] Using property: archetypeVersion = 2.15-SNAPSHOT
Define value for property 'className': : CamelScrExample
Confirm properties configuration:
groupId: example
artifactId: camel-scr-example
version: 1.0-SNAPSHOT
package: example
archetypeArtifactId: camel-archetype-scr
archetypeGroupId: org.apache.camel.archetypes
archetypeVersion: 2.15-SNAPSHOT
className: CamelScrExample
Y: :

 

All done! See ReadMe.txt in the generated project folder for the next steps:

 

Code Block
languagetext
titleReadMe.txt
Camel SCR bundle project
========================

To build this project run

    mvn install

To deploy this project in Apache Karaf (2.4.0)

    On Karaf command line:

    # Add Camel feature repository
    features:chooseurl camel 2.15-SNAPSHOT

    # Install camel-scr feature
    features:install camel-scr

    # Install commons-lang, used in the example route to validate parameters
    osgi:install mvn:commons-lang/commons-lang/2.6

    # Install and start your bundle
    osgi:install -s mvn:example/camel-scr-example/1.0-SNAPSHOT

    # See how it's running
    log:tail -n 10

    Press ctrl-c to stop watching the log.

For more help see the Apache Camel documentation

    http://camel.apache.org/

Unit testing Camel routes

Service Component is a POJO and has no special requirements for (non-OSGi) unit testing. There are however some techniques that are specific to Camel SCR or just make testing easier.

Below is an example unit test, generated by camel-archetype-scr:

Below is an example of a RouteBuilder class generated by camel-archetype-scr:

 

Code Block
languagejava
titleCamelScrExampleRoute.java
// This file was generated from org.apache.camel.archetypes/camel-archetype-scr/2.15-SNAPSHOT
package example.internal;

import org.apache.camel.LoggingLevel;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.impl.SimpleRegistry;
import org.apache.commons.lang.Validate;

public class CamelScrExampleRoute extends RouteBuilder {

    SimpleRegistry registry;

    // Configured fields
    private String camelRouteId;
    private Integer maximumRedeliveries;
    private Long redeliveryDelay;
    private Double backOffMultiplier;
    private Long maximumRedeliveryDelay;

    public CamelScrExampleRoute(final SimpleRegistry registry) {
        this.registry = registry;
    }

    @Override
	public void configure() throws Exception {
        checkProperties();

        // Add a bean to Camel context registry
        registry.put("test", "bean");

        errorHandler(defaultErrorHandler()
            .retryAttemptedLogLevel(LoggingLevel.WARN)
            .maximumRedeliveries(maximumRedeliveries)
            .redeliveryDelay(redeliveryDelay)
            .backOffMultiplier(backOffMultiplier)
            .maximumRedeliveryDelay(maximumRedeliveryDelay));

        from("{{from}}")
            .startupOrder(2)
            .routeId(camelRouteId)
            .onCompletion()
                .to("direct:processCompletion")
            .end()
            .removeHeaders("CamelHttp*")
            .to("{{to}}");


		from("direct:processCompletion")
            .startupOrder(1)
            .routeId(camelRouteId + ".completion")
            .choice()
                .when(simple("${exception} == null"))
                    .log("{{messageOk}}")
                .otherwise()
                    .log(LoggingLevel.ERROR, "{{messageError}}")
            .end();
		}
	}

    public void checkProperties() {
        Validate.notNull(camelRouteId, "camelRouteId property is not set");
        Validate.notNull(maximumRedeliveries, "maximumRedeliveries property is not set");
        Validate.notNull(redeliveryDelay, "redeliveryDelay property is not set");
        Validate.notNull(backOffMultiplier, "backOffMultiplier property is not set");
        Validate.notNull(maximumRedeliveryDelay, "maximumRedeliveryDelay property is not set");
    }
}

 

Let's take a look at CamelScrExampleRoute in more detail.

 

Code Block
    // Configured fields
    private String camelRouteId;
    private Integer maximumRedeliveries;
    private Long redeliveryDelay;
    private Double backOffMultiplier;
    private Long maximumRedeliveryDelay;

The values of these fields are set with values from properties by matching their names.

 

Code Block
        // Add a bean to Camel context registry
        registry.put("test", "bean");

If you need to add some beans to CamelContext's registry for your routes, you can do it like this.

 

Code Block
    public void checkProperties() {
        Validate.notNull(camelRouteId, "camelRouteId property is not set");
        Validate.notNull(maximumRedeliveries, "maximumRedeliveries property is not set");
        Validate.notNull(redeliveryDelay, "redeliveryDelay property is not set");
        Validate.notNull(backOffMultiplier, "backOffMultiplier property is not set");
        Validate.notNull(maximumRedeliveryDelay, "maximumRedeliveryDelay property is not set");
    }

It is a good idea to check that required parameters are set and they have meaningful values before allowing the routes to start.

 

Code Block
        from("{{from}}")
            .startupOrder(2)
            .routeId(camelRouteId)
            .onCompletion()
                .to("direct:processCompletion")
            .end()
            .removeHeaders("CamelHttp*")
            .to("{{to}}");


		from("direct:processCompletion")
            .startupOrder(1)
            .routeId(camelRouteId + ".completion")
            .choice()
                .when(simple("${exception} == null"))
                    .log("{{messageOk}}")
                .otherwise()
                    .log(LoggingLevel.ERROR, "{{messageError}}")
            .end();

Note that pretty much everything in the route is configured with properties. This essentially makes your RouteBuilder a template. SCR allows you to create more instances of your routes just by providing alternative configurations. More on this in section Using Camel SCR bundle as a template.

AbstractCamelRunner's lifecycle in SCR

  1. When component's configuration policy and mandatory references are satisfied SCR calls activate(). This creates and sets up a CamelContext through the following call chain: activate() → prepare() → createCamelContext() → setupPropertiesComponent() → configure() → setupCamelContext(). Finally, the context is scheduled to start after a delay defined in AbstractCamelRunner.START_DELAY with runWithDelay().
  2. When Camel components (ComponentResolver services, to be exact) are registered in OSGi, SCR calls gotCamelComponent() which reschedules/delays the CamelContext start further by the same AbstractCamelRunner.START_DELAY. This in effect makes CamelContext wait until all Camel components are loaded or there is a sufficient gap between them. The same logic will tell a failed-to-start CamelContext to try again whenever we add more Camel components.
  3. When Camel components are unregistered SCR calls lostCamelComponent(). This call does nothing.
  4. When one of the requirements that caused the call to activate() is lost SCR will call deactivate(). This will shutdown the CamelContext.

In (non-OSGi) unit tests you should use prepare() → run() → stop() instead of activate() → deactivate() for more fine-grained control. Also, this allows us to avoid possible SCR specific operations in tests.

Using camel-archetype-scr

The easiest way to create an Camel SCR bundle project is to use camel-archetype-scr and Maven.

You can generate a project with the following steps:

Code Block
languagetext
titleGenerating a project
$ mvn archetype:generate -Dfilter=org.apache.camel.archetypes:camel-archetype-scr
 
Choose archetype:
1: local -> org.apache.camel.archetypes:camel-archetype-scr (Creates a new Camel SCR bundle project for Karaf)
Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): : 1
Define value for property 'groupId': : example
[INFO] Using property: groupId = example
Define value for property 'artifactId': : camel-scr-example
Define value for property 'version': 1.0-SNAPSHOT: :
Define value for property 'package': example: :
[INFO] Using property: archetypeArtifactId = camel-archetype-scr
[INFO] Using property: archetypeGroupId = org.apache.camel.archetypes
[INFO] Using property: archetypeVersion = 2.15-SNAPSHOT
Define value for property 'className': : CamelScrExample
Confirm properties configuration:
groupId: example
artifactId: camel-scr-example
version: 1.0-SNAPSHOT
package: example
archetypeArtifactId: camel-archetype-scr
archetypeGroupId: org.apache.camel.archetypes
archetypeVersion: 2.15-SNAPSHOT
className: CamelScrExample
Y: :

Done!

Now run:

Code Block
mvn install

and the bundle is ready to be deployed.

Unit testing Camel routes

Service Component is a POJO and has no special requirements for (non-OSGi) unit testing. There are however some techniques that are specific to Camel SCR or just make testing easier.

Below is an example unit test, generated by camel-archetype-scr:


Code Block
languagejava
titleCamelScrExampleTest.java
// This file was generated from org.apache.camel.archetypes/camel-archetype-scr/2.15-SNAPSHOT
package example;

import java.util.List;

import org.apache.camel.scr.internal.ScrHelper;
import org.apache.camel.builder.AdviceWithRouteBuilder;
import org.apache.camel.component.mock.MockComponent;
import org.apache.camel.component.mock.MockEndpoint;
import org.apache.camel.model.ModelCamelContext;
import org.apache.camel.model.RouteDefinition;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

@RunWith(JUnit4.class)
public class CamelScrExampleTest {

    Logger log = LoggerFactory.getLogger(getClass());

    @Rule
    public TestName testName = new TestName();

    CamelScrExample integration;
    ModelCamelContext context;

    @Before
    public void setUp() throws Exception {
        log.info("*******************************************************************");
        log.info("Test: " + testName.getMethodName());
        log.info("*******************************************************************");

        // Set property prefix for unit testing
        System.setProperty(CamelScrExample.PROPERTY_PREFIX, "unit");

        // Prepare the integration
        integration = new CamelScrExample();
        integration.prepare(null, ScrHelper.getScrProperties(integration.getClass().getName()));
        context = integration.getContext();

        // Disable JMX for test
        context.disableJMX();

        // Fake a component for test
        context.addComponent("amq", new MockComponent());
    }

    @After
    public void tearDown() throws Exception {
        integration.stop();
    }

	@Test
	public void testRoutes() throws Exception {
        // Adjust routes
        List<RouteDefinition> routes = context.getRouteDefinitions(
Code Block
languagejava
titleCamelScrExampleTest.java
// This file was generated from org.apache.camel.archetypes/camel-archetype-scr/2.15-SNAPSHOT
package example;

import java.util.List;

import org.apache.camel.scr.internal.ScrHelper;
import org.apache.camel.builder.AdviceWithRouteBuilder;
import org.apache.camel.component.mock.MockComponent;
import org.apache.camel.component.mock.MockEndpoint;
import org.apache.camel.model.ModelCamelContext;
import org.apache.camel.model.RouteDefinition;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

@RunWith(JUnit4.class)
public class CamelScrExampleTest {

    Logger log = LoggerFactory.getLogger(getClass());

    @Rule
    public TestName testName = new TestName();

    CamelScrExample integration;
    ModelCamelContext context;

    @Before
    public void setUp() throws Exception {
        log.info("*******************************************************************");
        log.info("Test: " + testName.getMethodName());
        log.info("*******************************************************************");

        // Set property prefix for unit testingroutes.get(0).adviceWith(context, new AdviceWithRouteBuilder() {
            @Override
            public void configure() throws Exception {
        System.setProperty(CamelScrExample.PROPERTY_PREFIX, "unit");

        // PrepareReplace the integration
  "from" endpoint with direct:start
      integration = new CamelScrExample();
        integration.prepare(null, ScrHelper.getScrProperties(integration.getClass().getName())replaceFromWith("direct:start");
        context = integration.getContext();

        // DisableMock and JMXskip forresult testendpoint
          context.disableJMX(      mockEndpoints("log:*");

            // Fake a component for test}
        });

        MockEndpoint resultEndpoint = context.addComponentgetEndpoint("amqmock:log:foo", new MockComponent())MockEndpoint.class);
    }

    @After
    public void tearDown() throws Exception {
        integration.stop();// resultEndpoint.expectedMessageCount(1); // If you want to just check the number of messages
    }

	@Test
	public void testRoutes() throws Exception {
        // Adjust routes    resultEndpoint.expectedBodiesReceived("hello"); // If you want to check the contents

        List<RouteDefinition>// routesStart = context.getRouteDefinitions();

the integration
        routesintegration.getrun(0).adviceWith(context, new AdviceWithRouteBuilder() {);

        // Send the test @Overridemessage
            public void configure() throws Exception {context.createProducerTemplate().sendBody("direct:start", "hello");

        resultEndpoint.assertIsSatisfied();
	}
}

 

Now, let's take a look at the interesting bits one by one.

 

Code Block
titleUsing property prefixing
        // Replace "from" endpoint with direct:start
        Set property prefix for unit testing
         replaceFromWith("direct:start");
                // Mock and skip result endpoint
                mockEndpoints("log:*");
System.setProperty(CamelScrExample.PROPERTY_PREFIX, "unit");

This allows you to override parts of the configuration by prefixing properties with "unit.". For example, unit.from overrides from for the unit test.

Prefixes can be used to handle the differences between the runtime environments where your routes might run. Moving the unchanged bundle through development, testing and production environments is a typical use case.

 

Code Block
languagejava
titleGetting test configuration from annotations
        integration.prepare(null, ScrHelper.getScrProperties(integration.getClass().getName()));

Here we configure the Service Component in test with the same properties that would be used in OSGi environment.

 

Code Block
languagejava
titleMocking components for test
     }
   // Fake a component for });test

        MockEndpoint resultEndpoint = context.getEndpointaddComponent("mock:log:fooamq", new MockComponent());

Components that are not available in test can be mocked like this to allow the route to start.

 

Code Block
languagejava
titleAdjusting routes for test
        // Adjust routes
   MockEndpoint.class);
        // resultEndpoint.expectedMessageCount(1); // If you want to just check the number of messages
     List<RouteDefinition> routes = resultEndpointcontext.expectedBodiesReceivedgetRouteDefinitions("hello"); // If you want to check the contents

        // Start the integration
        integration.run();

routes.get(0).adviceWith(context, new AdviceWithRouteBuilder() {
        // Send the test message@Override
        context.createProducerTemplate().sendBody("direct:start", "hello");

   public void configure()   resultEndpoint.assertIsSatisfied();
	}
}

 

Now, let's take a look at the interesting bits one by one.

 

Code Block
titleUsing property prefixing
throws Exception {
                // Set property prefix for unit testing
Replace "from" endpoint with direct:start
               System.setProperty(CamelScrExample.PROPERTY_PREFIX, "unit");

This allows you to override parts of the configuration by prefixing properties with "unit.". For example, unit.from overrides from for the unit test.

Prefixes, as a whole, can be used to cover the differences between the runtime environments where your routes might run. Moving the unchanged bundle through development, testing and production environments is a typical use case.

 

Code Block
languagejava
titleGetting test configuration from annotations
 replaceFromWith("direct:start");
               integration.prepare(null, ScrHelper.getScrProperties(integration.getClass().getName()));

Here we configure the Service Component in test with the same properties that would be used in OSGi environment.

 

Code Block
languagejava
titleMocking components for test
        // Fake a component for test
 // Mock and skip result endpoint
                context.addComponentmockEndpoints("amq", new MockComponent());log:*");
            }
        });

Camel's AdviceWith feature allows routes to be modified for testComponents that are not available in test can be mocked like this to allow the route to start.

 

Code Block
languagejava
titleAdjusting Starting the routes for test
        // AdjustStart the routesintegration
        List<RouteDefinition> routes = context.getRouteDefinitions();

        routes.get(0).adviceWith(context, new AdviceWithRouteBuilder() integration.run();

Here we start the Service Component and along with it the routes.

 

Code Block
languagejava
titleSending a test message
 {
       // Send the test  @Overridemessage
            public void configure() throws Exception {
                // Replace "from" endpoint with direct:start
                replaceFromWith("direct:start");
                // Mock and skip result endpoint
                mockEndpoints("log:*");
            }
        });

Camel's AdviceWith feature allows routes to be modified for test.

 

Code Block
languagejava
titleStarting the routes
        // Start the integration
        integration.run();

Here we start the Service Component and along with it the routes.

 

Code Block
languagejava
titleSending a test message
        // Send the test message
        context.createProducerTemplate().sendBody("direct:start", "hello");

...

context.createProducerTemplate().sendBody("direct:start", "hello");

Here we send a message to a route in test.

Running the bundle in Apache Karaf

Once the bundle has been built with mvn install it's ready to be deployed. To deploy the bundle on Apache Karaf perform the following steps on Karaf command line:

Code Block
languagetext
titleDeploying the bundle in Apache Karaf
# Add Camel feature repository
karaf@root> features:chooseurl camel 2.15-SNAPSHOT
 
# Install camel-scr feature
karaf@root> features:install camel-scr
 
# Install commons-lang, used in the example route to validate parameters
karaf@root> osgi:install mvn:commons-lang/commons-lang/2.6
 
# Install and start your bundle
karaf@root> osgi:install -s mvn:example/camel-scr-example/1.0-SNAPSHOT
 
# See how it's running
karaf@root> log:tail -n 10
 
Press ctrl-c to stop watching the log.

Overriding the default configuration

By default, Service Component's configuration PID equals the fully qualified name of its class. You can change the example bundle's properties with Karaf's config:* commands:

Code Block
languagetext
titleOverride a property
# Override 'messageOk' property
karaf@root> config:propset -p example.CamelScrExample messageOk "This is better logging"

Or you can change the configuration by editing property files in Karaf's etc folder.

Using Camel SCR bundle as a template

Let's say you have a Camel SCR bundle that implements an integration pattern that you use frequently, say, from → to, with success/failure logging and redelivery which also happens to be the pattern our example route implements. You probably don't want to create a separate bundle for every instance. No worries, SCR has you covered.

Create a configuration PID for your Service Component, but add a tail with a dash and SCR will use that configuration to create a new instance of your component.

Code Block
languagetext
titleCreating a new Service Component instance
# Create a PID with a tail
karaf@root> config:edit example.CamelScrExample-anotherone
 
# Override some properties
karaf@root> config:propset camelContextId my-other-context
karaf@root> config:propset to "file://removeme?fileName=removemetoo.txt"
 
# Save the PID
karaf@root> config:update

This will start a new CamelContext with your overridden properties. How convenient.

Tip

When designing a Service Component to be a template you typically don't want it to start without a "tailed" configuration i.e. with the default configuration.

To prevent your Service Component from starting with the default configuration add policy = ConfigurationPolicy.REQUIRE to the class level @Component annotation.