Repository: camel Updated Branches: refs/heads/master 79fbb6a3b -> 72e3b27e5
Added camel-scr docs to Gitbook Project: http://git-wip-us.apache.org/repos/asf/camel/repo Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/72e3b27e Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/72e3b27e Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/72e3b27e Branch: refs/heads/master Commit: 72e3b27e504cabb9fe76c5623fd7c0c530b1f5b8 Parents: 79fbb6a Author: Andrea Cosentino <anco...@gmail.com> Authored: Tue May 24 08:45:25 2016 +0200 Committer: Andrea Cosentino <anco...@gmail.com> Committed: Tue May 24 08:45:25 2016 +0200 ---------------------------------------------------------------------- .../camel-scr/src/main/docs/camel-and-scr.adoc | 677 +++++++++++++++++++ docs/user-manual/en/SUMMARY.md | 1 + 2 files changed, 678 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/camel/blob/72e3b27e/components/camel-scr/src/main/docs/camel-and-scr.adoc ---------------------------------------------------------------------- diff --git a/components/camel-scr/src/main/docs/camel-and-scr.adoc b/components/camel-scr/src/main/docs/camel-and-scr.adoc new file mode 100644 index 0000000..577fd93 --- /dev/null +++ b/components/camel-scr/src/main/docs/camel-and-scr.adoc @@ -0,0 +1,677 @@ +[[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. + +==== http://git-wip-us.apache.org/repos/asf/camel/blob/72e3b27e/docs/user-manual/en/SUMMARY.md ---------------------------------------------------------------------- diff --git a/docs/user-manual/en/SUMMARY.md b/docs/user-manual/en/SUMMARY.md index 78f08b2..d34ba40 100644 --- a/docs/user-manual/en/SUMMARY.md +++ b/docs/user-manual/en/SUMMARY.md @@ -230,6 +230,7 @@ * [Saxon](xquery.adoc) * [Scp](scp.adoc) * [Schematron](schematron.adoc) + * [SCR](camel-and-scr.adoc) * [SJMS](sjms.adoc) * [SJMS Batch](sjms-batch.adoc) * [Telegram](telegram.adoc)