Versions Compared

Key

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

...

AbstractCamelRunner class ties CamelContext's lifecycle to Service Component's lifecycle and handles configuration with help of Camel's PropertiesComponent. All you have to do to make a Service Component out of your java class is to extend it from AbstractCamelRunner and add the following org.apache.felix.scr.annotations on class level:

Code Block
languagejava
titleRequired annotations
@Component
@References({
    @Reference(name = "camelComponent",referenceInterface = ComponentResolver.class,
        cardinality = ReferenceCardinality.MANDATORY_MULTIPLE, policy = ReferencePolicy.DYNAMIC,
        policyOption = ReferencePolicyOption.GREEDY, bind = "gotCamelComponent", unbind = "lostCamelComponent")
})

Then implement getRouteBuilders() method which returns the Camel routes you want to run. And finally provide the default configuration with:

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

That's all. And if you used camel-archetype-scr to generate a project all this is already taken care of.

Below is an example of a complete Service Component class, generated by camel-archetype-scr:

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

import java.util.ArrayList;
import java.util.List;

import org.apache.camel.scr.AbstractCamelRunner;
import example.internal.CamelScrExampleRoute;
import org.apache.camel.RoutesBuilder;
import org.apache.camel.spi.ComponentResolver;
import org.apache.felix.scr.annotations.*;

@Component(label = CamelScrExample.COMPONENT_LABEL, description = CamelScrExample.COMPONENT_DESCRIPTION, immediate = true, metatype = true)
@Properties({
    @Property(name = "camelContextId", value = "camel-scr-example"),
    @Property(name = "camelRouteId", value = "foo/timer-log"),
    @Property(name = "active", value = "true"),
    @Property(name = "from", value = "timer:foo?period=5000"),
    @Property(name = "to", value = "log:foo?showHeaders=true"),
    @Property(name = "messageOk", value = "Success: {{from}} -> {{to}}"),
    @Property(name = "messageError", value = "Failure: {{from}} -> {{to}}"),
    @Property(name = "maximumRedeliveries", value = "0"),
    @Property(name = "redeliveryDelay", value = "5000"),
    @Property(name = "backOffMultiplier", value = "2"),
    @Property(name = "maximumRedeliveryDelay", value = "60000")
})
@References({
    @Reference(name = "camelComponent",referenceInterface = ComponentResolver.class,
        cardinality = ReferenceCardinality.MANDATORY_MULTIPLE, policy = ReferencePolicy.DYNAMIC,
        policyOption = ReferencePolicyOption.GREEDY, bind = "gotCamelComponent", unbind = "lostCamelComponent")
})
public class CamelScrExample extends AbstractCamelRunner {

    public static final String COMPONENT_LABEL = "example.CamelScrExample";
    public static final String COMPONENT_DESCRIPTION = "This is the description for camel-scr-example.";

    @Override
    protected List<RoutesBuilder> getRouteBuilders() {
        List<RoutesBuilder> routesBuilders = new ArrayList<>();
        routesBuilders.add(new CamelScrExampleRoute(registry));
        return routesBuilders;
    }
}

CamelContextId and active properties control the CamelContext's name (defaults to "camel-runner-default") and whether it will be started or not (defaults to "false"), respectively. In addition to these you can add and use as many properties as you like. Camel's PropertiesComponent handles recursive properties and prefixing with fallback without problem.

AbstractCamelRunner will make these properties available to your RouteBuilders through 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, ...).

...

  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 archetype.mvn .

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! Check Let's check 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

...

On Karaf command line:

...

 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 and 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();

        routes.get(0).adviceWith(context, new AdviceWithRouteBuilder() {
            @Override
            public void configure() throws Exception {
                // Replace "from" endpoint with direct:start
                replaceFromWith("direct:start");
                // Mock and skip result endpoint
                mockEndpoints("log:*");
            }
        });

        MockEndpoint resultEndpoint = context.getEndpoint("mock:log:foo", MockEndpoint.class);
        // resultEndpoint.expectedMessageCount(1); // If you want to just check the number of messages
        resultEndpoint.expectedBodiesReceived("hello"); // If you want to check the contents

        // Start the integration
        integration.run();

        // Send the test message
        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
        // Set property prefix for unit testing
        System.setProperty(CamelScrExample.PROPERTY_PREFIX, "unit");
Code Block
languagejava
titleGetting test configuration from annotations
        integration.prepare(null, ScrHelper.getScrProperties(integration.getClass().getName()));
Code Block
languagejava
titleMock components that are not available for test
        // Fake a component for test
        // context.addComponent("amq", new MockComponent());
Code Block
languagejava
titleAdjusting routes for test
        // Adjust routes
        List<RouteDefinition> routes = context.getRouteDefinitions();

        routes.get(0).adviceWith(context, new AdviceWithRouteBuilder() {
            @Override
            public void configure() throws Exception {
                // Replace "from" endpoint with direct:start
                replaceFromWith("direct:start");
                // Mock and skip result endpoint
                mockEndpoints("log:*");
            }
        });
Code Block
languagejava
titleStarting the routes
        // Start the integration
        integration.run();
Code Block
languagejava
titleSending a test message
        // Send the test message
        context.createProducerTemplate().sendBody("direct:start", "hello");