...
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 | ||||
---|---|---|---|---|
| ||||
@Override protected List<RoutesBuilder> getRouteBuilders() { List<RoutesBuilder> routesBuilders = new ArrayList<>(); routesBuilders.add(new YourRouteBuilderHere(registry)); routesBuilders.add(new AnotherRouteBuilderHere(registry)); return routesBuilders; } |
...
Code Block | ||||
---|---|---|---|---|
| ||||
@Properties({ @Property(name = "camelContextId", value = "my-test"), @Property(name = "active", value = "true"), @Property(name = "...", value = "..."), ... }) |
...
Code Block | ||||
---|---|---|---|---|
| ||||
// 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; |
...
You can generate a project with the following steps:
Code Block | ||||
---|---|---|---|---|
| ||||
$ 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:
Done!
Now run:
Code Block | ||||
---|---|---|---|---|
| ||||
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
:
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 | ||||
---|---|---|---|---|
| ||||
// 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 | ||||
Code Block | ||||
| ||||
// 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 Disable JMX for test public void setUpcontext.disableJMX(); throws Exception { // Fake log.info("*******************************************************************");a component for test logcontext.infoaddComponent("Test: amq", +new testName.getMethodNameMockComponent()); } log.info("*******************************************************************"); @After public void tearDown() throws Exception { integration.stop(); } @Test public void testRoutes() throws Exception { // Set property prefix for unit testing Adjust routes List<RouteDefinition> routes = Systemcontext.setProperty(CamelScrExample.PROPERTY_PREFIX, "unit"getRouteDefinitions(); // Prepare the integrationroutes.get(0).adviceWith(context, new AdviceWithRouteBuilder() { integration = new CamelScrExample(); @Override integration.prepare(null, ScrHelper.getScrProperties(integration.getClass().getName())); public void configure() throws Exception { context = integration.getContext(); // Replace "from" endpoint with // Disable JMX for test direct:start context.disableJMX(replaceFromWith("direct:start"); // Fake a component for test // Mock and skip context.addComponent("amq", new MockComponent()); result endpoint } @After public void tearDownmockEndpoints() throws Exception {"log:*"); integration.stop(); } @Test public void testRoutes() throws Exception { // Adjust routes}); List<RouteDefinition>MockEndpoint routesresultEndpoint = context.getRouteDefinitions(getEndpoint("mock:log:foo", MockEndpoint.class); routes.get(0).adviceWith(context, new AdviceWithRouteBuilder() { // resultEndpoint.expectedMessageCount(1); // If you want to just check the number of messages @Override resultEndpoint.expectedBodiesReceived("hello"); // If you want to check the public void configure() throws Exception {contents // Start the integration integration.run(); // ReplaceSend "from"the endpointtest with direct:start message replaceFromWithcontext.createProducerTemplate().sendBody("direct:start", "hello"); resultEndpoint.assertIsSatisfied(); } } |
Now, let's take a look at the interesting bits one by one.
Code Block | ||
---|---|---|
| ||
// MockSet property andprefix skipfor resultunit endpointtesting 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 | ||||
---|---|---|---|---|
| ||||
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 | ||||
---|---|---|---|---|
| ||||
mockEndpoints("log:*"); // 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.
Code Block | ||||
---|---|---|---|---|
| ||||
}); MockEndpoint// resultEndpointAdjust = context.getEndpoint("mock:log:foo", MockEndpoint.class); routes List<RouteDefinition> routes //= resultEndpointcontext.expectedMessageCountgetRouteDefinitions(1); // If you want to just check the number of messages routes.get(0).adviceWith(context, new AdviceWithRouteBuilder() { resultEndpoint.expectedBodiesReceived("hello"); // If you want@Override to check the contents //public Startvoid the integration configure() throws Exception { integration.run(); // Send the test messageReplace "from" endpoint with direct:start context.createProducerTemplate().sendBody replaceFromWith("direct:start", "hello"); resultEndpoint.assertIsSatisfied(); } } |
Now, let's take a look at the interesting bits one by one.
Code Block | ||
---|---|---|
| ||
// Set property prefix for unit testing Mock and skip result endpoint 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.
mockEndpoints("log:*");
}
}); |
Camel's AdviceWith feature allows routes to be modified for test.
Code Block | |||||
---|---|---|---|---|---|
| |||||
// Start the integration integration.prepare(null, ScrHelper.getScrProperties(integration.getClass().getNamerun())); |
Here we configure start the Service Component in test and along with the same properties that would be used in OSGi environmentit the routes.
Code Block | ||||
---|---|---|---|---|
| ||||
// FakeSend athe componenttest for testmessage context.createProducerTemplate().addComponentsendBody("amqdirect:start", new MockComponent()"hello"); |
Components that are not available in test can be mocked like this to allow the route to start.
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 | ||||
---|---|---|---|---|
| ||||
// 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.
Code Block | ||||
---|---|---|---|---|
| ||||
// Start the integration
integration.run(); |
Here we start the Service Component and along with it the routes.
Code Block | ||||
---|---|---|---|---|
| ||||
// Send the test message
context.createProducerTemplate().sendBody("direct:start", "hello"); |
Here we send a message to a route in test.
Using Camel SCR bundle as a template
...
| |
# 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 | ||||
---|---|---|---|---|
| ||||
# 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 | ||||
---|---|---|---|---|
| ||||
# 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 |