[[CamelandSCR-WorkingwithCamelandSCR]]
Working with Camel and SCR
~~~~~~~~~~~~~~~~~~~~~~~~~~

SCR stands for Service Component Runtime and is an implementation of
OSGi Declarative Services specification. SCR enables any plain old Java
object to expose and use OSGi services with no boilerplate code.

OSGi framework knows your object by looking at SCR descriptor files in
its bundle which are typically generated from Java annotations by a
plugin such as `org.apache.felix:maven-scr-plugin`.

Running Camel in an SCR bundle is a great alternative for Spring DM and
Blueprint based solutions having significantly fewer lines of code
between you and the OSGi framework. Using SCR your bundle can remain
completely in Java world; there is no need to edit XML or properties
files. This offers you full control over everything and means your IDE
of choice knows exactly what is going on in your project.

[[CamelandSCR-CamelSCRsupport]]
Camel SCR support
^^^^^^^^^^^^^^^^^

INFO: *Available as of Camel 2.15.0*. 
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.

`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:

*Add required annotations*

[source,java]
---------------------------------------------------------------------------------------------------------------
@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:

 

*Implement getRouteBuilders()*

[source,java]
------------------------------------------------------------------
    @Override
    protected List<RoutesBuilder> getRouteBuilders() {
        List<RoutesBuilder> routesBuilders = new ArrayList<>();
        routesBuilders.add(new YourRouteBuilderHere(registry));
        routesBuilders.add(new AnotherRouteBuilderHere(registry));
        return routesBuilders;
    }
------------------------------------------------------------------

And finally provide the default configuration with:

*Default configuration in annotations*

[source,java]
---------------------------------------------------------
@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:`

*CamelScrExample.java*

[source,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 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, ...).

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

 

*CamelScrExampleRoute.java*

[source,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.

 

[source,java]
----------------------------------------
    // 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.

 

[source,java]
-----------------------------------------------
        // 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.

 

[source,java]
-----------------------------------------------------------------------------------------------
    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.

 

[source,java]
----------------------------------------------------------------
        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_.

[[CamelandSCR-AbstractCamelRunnerlifecycleinSCR]]
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.

[[CamelandSCR-Usingcamel-archetype-scr]]
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:

*Generating a project*

[source,text]
--------------------------------------------------------------------------------------------------------------
$ 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:

[source,java]
-----------
mvn install
-----------

and the bundle is ready to be deployed.

[[CamelandSCR-UnittestingCamelroutes]]
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`:

[source,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.

*Using property prefixing*

[source,java]
--------------------------------------------------------------------
        // Set property prefix for unit testing
        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.

 

*Getting test configuration from annotations*

[source,java]
------------------------------------------------------------------------------------------------
        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.

 

*Mocking components for test*

[source,java]
---------------------------------------------------------
        // Fake a component for test
        context.addComponent("amq", new MockComponent());
---------------------------------------------------------

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

 

*Adjusting routes for test*

[source,java]
------------------------------------------------------------------------
        // 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:*");
            }
        });
------------------------------------------------------------------------

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

 

*Starting the routes*

[source,java]
--------------------------------
        // Start the integration
        integration.run();
--------------------------------

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

 

*Sending a test message*

[source,java]
---------------------------------------------------------------------------
        // Send the test message
        context.createProducerTemplate().sendBody("direct:start", "hello");
---------------------------------------------------------------------------

Here we send a message to a route in test.

[[CamelandSCR-RunningthebundleinApacheKaraf]]
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:

*Deploying the bundle in Apache Karaf*

[source,text]
------------------------------------------------------------------------
# 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.
------------------------------------------------------------------------

[[CamelandSCR-Overridingthedefaultconfiguration]]
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:

*Override a property*

[source,text]
----------------------------------------------------------------------------------------
# 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.

[[CamelandSCR-UsingCamelSCRbundleasatemplate]]
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.

*Creating a new Service Component instance*

[source,text]
------------------------------------------------------------------------
# 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.

====
