...
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 | ||||
---|---|---|---|---|
| ||||
@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:
Code Block | ||||
---|---|---|---|---|
| ||||
@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:
Code Block | ||||
---|---|---|---|---|
| ||||
@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 | ||||
---|---|---|---|---|
| ||||
// 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.
...
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:
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/ |
...
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(); // 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 | ||
---|---|---|
| ||
// Set property prefix for unit testing System.setProperty(CamelScrExample.PROPERTY_PREFIX, "unit"); |
...
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 | ||||
---|---|---|---|---|
| ||||
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 | ||||
---|---|---|---|---|
| ||||
// 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 | ||||
---|---|---|---|---|
| ||||
// 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"); |
...