You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by ac...@apache.org on 2020/03/24 15:42:01 UTC

[camel-karaf] 01/02: Added camel-osgi-activator component

This is an automated email from the ASF dual-hosted git repository.

acosentino pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/camel-karaf.git

commit b4e74c1b122d75d9555772bd6b2ef78be2187ff0
Author: Andrea Cosentino <an...@gmail.com>
AuthorDate: Tue Mar 24 16:25:17 2020 +0100

    Added camel-osgi-activator component
---
 components/camel-osgi-activator/pom.xml            | 195 ++++++++++++++++++++
 .../src/assembly/test-bundles.xml                  |  48 +++++
 .../services/org/apache/camel/other.properties     |   7 +
 .../src/generated/resources/osgi-activator.json    |  13 ++
 .../src/main/docs/osgi-activator.adoc              |  73 ++++++++
 .../camel/osgi/activator/CamelRoutesActivator.java | 181 +++++++++++++++++++
 .../activator/CamelRoutesActivatorConstants.java   |  26 +++
 .../osgi/activator/CamelOsgiActivatorIT.java       | 198 +++++++++++++++++++++
 .../component/osgi/activator/PaxExamOptions.java   | 103 +++++++++++
 components/pom.xml                                 |   1 +
 10 files changed, 845 insertions(+)

diff --git a/components/camel-osgi-activator/pom.xml b/components/camel-osgi-activator/pom.xml
new file mode 100644
index 0000000..393205a
--- /dev/null
+++ b/components/camel-osgi-activator/pom.xml
@@ -0,0 +1,195 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.camel.karaf</groupId>
+        <artifactId>components</artifactId>
+        <version>3.2.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>camel-osgi-activator</artifactId>
+    <packaging>jar</packaging>
+
+    <name>Camel :: OSGi Activator</name>
+    <description>Camel OSGi Activator for running Camel routes from other bundles</description>
+
+    <properties>
+        <camel.osgi.activator>org.apache.camel.osgi.activator.CamelRoutesActivator</camel.osgi.activator>
+        <camel.osgi.dynamic>*</camel.osgi.dynamic>
+        <firstVersion>3.1.0</firstVersion>
+    </properties>
+
+    <dependencies>
+        <!-- osgi -->
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>osgi.cmpn</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>osgi.core</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-core-osgi</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-core-languages</artifactId>
+        </dependency>
+        <!-- test -->
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-timer</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-log</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <!-- PAX Exam -->
+        <dependency>
+            <groupId>org.ops4j.pax.exam</groupId>
+            <artifactId>pax-exam</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.ops4j.pax.exam</groupId>
+            <artifactId>pax-exam-spi</artifactId>
+            <version>${pax-exam-version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.ops4j.pax.exam</groupId>
+            <artifactId>pax-exam-junit4</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.ops4j.pax.exam</groupId>
+            <artifactId>pax-exam-container-karaf</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.ops4j.pax.url</groupId>
+            <artifactId>pax-url-aether</artifactId>
+            <version>2.4.5</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.ops4j.pax.tinybundles</groupId>
+            <artifactId>tinybundles</artifactId>
+            <version>2.1.1</version>
+            <scope>test</scope>
+        </dependency>
+
+        <!-- Karaf & Command Shell -->
+        <dependency>
+            <groupId>org.apache.karaf</groupId>
+            <artifactId>apache-karaf</artifactId>
+            <version>${karaf4-version}</version>
+            <type>zip</type>
+            <scope>test</scope>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.apache.karaf.features</groupId>
+                    <artifactId>framework</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <!-- Karaf Features -->
+        <dependency>
+            <groupId>org.apache.camel.karaf</groupId>
+            <artifactId>apache-camel</artifactId>
+            <version>${project.version}</version>
+            <classifier>features</classifier>
+            <type>xml</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>javax.inject</groupId>
+            <artifactId>javax.inject</artifactId>
+            <version>1</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.servicemix.tooling</groupId>
+                <artifactId>depends-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>generate-depends-file</id>
+                        <goals>
+                            <goal>generate-depends-file</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <artifactId>maven-assembly-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <phase>pre-integration-test</phase>
+                        <goals>
+                            <goal>single</goal>
+                        </goals>
+                        <configuration>
+                            <descriptors>
+                                <descriptor>src/assembly/test-bundles.xml</descriptor>
+                            </descriptors>
+                            <finalName>test</finalName>
+                            <attach>false</attach>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <!-- Execute in the integration-test phase so that the packaged 
+                JAR can be used -->
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <phase>integration-test</phase>
+                        <goals>
+                            <goal>test</goal>
+                        </goals>
+                        <configuration>
+                            <includes>
+                                <include>**/*IT.java</include>
+                            </includes>
+                            <forkedProcessTimeoutInSeconds>300</forkedProcessTimeoutInSeconds>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/components/camel-osgi-activator/src/assembly/test-bundles.xml b/components/camel-osgi-activator/src/assembly/test-bundles.xml
new file mode 100644
index 0000000..a7798e9
--- /dev/null
+++ b/components/camel-osgi-activator/src/assembly/test-bundles.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2"
+          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+          xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
+  <id>bundles</id>
+  <formats>
+    <format>dir</format>
+  </formats>
+  <includeBaseDirectory>false</includeBaseDirectory>
+  <dependencySets>
+    <dependencySet>
+      <outputDirectory/>
+      <outputFileNameMapping>${artifact.artifactId}.jar</outputFileNameMapping>
+      <includes>
+        <include>org.apache.camel:camel-api</include>
+        <include>org.apache.camel:camel-base</include>
+        <include>org.apache.camel:camel-core-engine</include>
+        <include>org.apache.camel:camel-core-osgi</include>
+        <include>org.apache.camel:camel-core-languages</include>
+        <include>org.apache.camel:camel-management-api</include>
+        <include>org.apache.camel:camel-osgi-activator</include>
+        <include>org.apache.camel:camel-support</include>
+        <include>org.apache.camel:camel-util</include>
+        <include>org.apache.camel:camel-timer</include>
+        <include>org.apache.camel:camel-log</include>
+      </includes>
+      <scope>test</scope>
+    </dependencySet>
+  </dependencySets>
+</assembly>
\ No newline at end of file
diff --git a/components/camel-osgi-activator/src/generated/resources/META-INF/services/org/apache/camel/other.properties b/components/camel-osgi-activator/src/generated/resources/META-INF/services/org/apache/camel/other.properties
new file mode 100644
index 0000000..989d3e7
--- /dev/null
+++ b/components/camel-osgi-activator/src/generated/resources/META-INF/services/org/apache/camel/other.properties
@@ -0,0 +1,7 @@
+# Generated by camel build tools - do NOT edit this file!
+name=osgi-activator
+groupId=org.apache.camel
+artifactId=camel-osgi-activator
+version=3.2.0-SNAPSHOT
+projectName=Camel :: OSGi Activator
+projectDescription=Camel OSGi Activator for running Camel routes from other bundles
diff --git a/components/camel-osgi-activator/src/generated/resources/osgi-activator.json b/components/camel-osgi-activator/src/generated/resources/osgi-activator.json
new file mode 100644
index 0000000..26df4ea
--- /dev/null
+++ b/components/camel-osgi-activator/src/generated/resources/osgi-activator.json
@@ -0,0 +1,13 @@
+{
+  "other": {
+    "kind": "other",
+    "name": "osgi-activator",
+    "title": "Osgi Activator",
+    "description": "Camel OSGi Activator for running Camel routes from other bundles",
+    "deprecated": false,
+    "firstVersion": "3.1.0",
+    "groupId": "org.apache.camel",
+    "artifactId": "camel-osgi-activator",
+    "version": "3.2.0-SNAPSHOT"
+  }
+}
diff --git a/components/camel-osgi-activator/src/main/docs/osgi-activator.adoc b/components/camel-osgi-activator/src/main/docs/osgi-activator.adoc
new file mode 100644
index 0000000..407e4b6
--- /dev/null
+++ b/components/camel-osgi-activator/src/main/docs/osgi-activator.adoc
@@ -0,0 +1,73 @@
+[[OsgiActivator]]
+= OSGi Camel Routes Activator
+
+*Since Camel 3.1*
+
+A small OSGi activator for starting an OSGi Apache Camel Project.
+
+The bundle starts a shared CamelContext and registers any RouteBuilder instances
+(discovered via the OSGi Service Registry), from any other bundles that gets installed.
+And when the bundles gets uninstalled then the routes are stopped and removed from the shared CamelContext.
+
+== Important
+
+This OSGi activator is a based prototype for quickly starting up an OSGi container with a single shared
+CamelContext and then being able to use OSGi dynamism to deploy and undeploy bundlers with Camel routes.
+
+Beware that this OSGi activator is a basic implementation and has limited support for dealing with errors
+when new routes are added which may clash with existing routes (route ids).
+
+Configuration of the CamelContext is also very limited.
+
+Therefore only use this for prototyping and experiments. This is NOT recommended for production usage.
+
+== Install bundle
+
+Register an Apache Camel RouteBuilder as an OSGi service.
+
+Using OSGi annotations:
+
+[source,java]
+----
+@Component(service = RouteBuilder.class)
+public class MyRouteBuilder extends RouteBuilder {
+    @Override
+    public void configure() throws Exception {
+        from("timer:test?fixedRate=true&period=1000")
+            .log("Hello");
+    }
+}
+----
+
+Or Manually:
+
+[source,java]
+----
+public void start(BundleContext context) throws Exception {
+  context.registerService(RouteBuilder.class, new MyRouteBuilder(), null);
+}
+----
+
+And it's automatically added or removed to the context from any bundle!
+
+[source,text]
+----
+Route: route1 started and consuming from: timer://test?fixedRate=true&period=1000
+----
+
+For routes that need to be started before the CamelContext the "camel.osgi.activator.pre-startup" service property may be added.  
+
+[source,java]
+----
+@Component(service = RouteBuilder.class, property = {CamelRoutesActivatorConstants.PRE_START_UP_PROP_NAME + "=true"})
+public class MyStartupRouteBuilder extends RouteBuilder {
+    @Override
+    public void configure() throws Exception {
+        getContext().setStreamCaching(true);
+
+        restConfiguration().component("netty-http").port(8080);
+    }
+}
+----
+
+If this RouteBuilder is added after other non pre startup RouteBuilders then CamelContext will automatically restart.  This allows pre start up RouteBuilder to run their configure methods before other RouteBuilders.
diff --git a/components/camel-osgi-activator/src/main/java/org/apache/camel/osgi/activator/CamelRoutesActivator.java b/components/camel-osgi-activator/src/main/java/org/apache/camel/osgi/activator/CamelRoutesActivator.java
new file mode 100644
index 0000000..2b2160a
--- /dev/null
+++ b/components/camel-osgi-activator/src/main/java/org/apache/camel/osgi/activator/CamelRoutesActivator.java
@@ -0,0 +1,181 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.osgi.activator;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.core.osgi.OsgiDefaultCamelContext;
+import org.apache.camel.model.ModelCamelContext;
+import org.apache.camel.model.RouteDefinition;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.util.tracker.ServiceTracker;
+import org.osgi.util.tracker.ServiceTrackerCustomizer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class CamelRoutesActivator implements BundleActivator, ServiceTrackerCustomizer<RouteBuilder, RouteBuilder> {
+
+    private static final Logger LOG = LoggerFactory.getLogger(CamelRoutesActivator.class);
+    private ServiceRegistration<CamelContext> camelContextRef;
+    private ModelCamelContext camelContext;
+    private BundleContext bundleContext;
+    private ServiceTracker<RouteBuilder, RouteBuilder> routeServiceTracker;
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public void start(BundleContext context) throws Exception {
+        this.bundleContext = context;
+        
+        this.camelContext = new OsgiDefaultCamelContext(this.bundleContext);
+        
+        camelContextRef = this.bundleContext.registerService(CamelContext.class, camelContext, null);
+        
+        camelContext.start();
+
+        this.routeServiceTracker = new ServiceTracker<RouteBuilder, RouteBuilder>(context, RouteBuilder.class, this);
+        
+        this.routeServiceTracker.open();
+
+        LOG.info("Camel OSGi Activator RouteBuilder ServiceTracker Tracker Open");
+    }
+
+    @Override
+    public RouteBuilder addingService(ServiceReference<RouteBuilder> reference) {
+        RouteBuilder builder = this.bundleContext.getService(reference);
+        if (isPreStartRouteBuilder(reference)) {
+            reloadTrackedServices(reference);
+        } else {
+            addRoute(builder);
+        }
+        return builder;
+    }
+    
+    @Override
+    public void stop(BundleContext context) throws Exception {
+        this.routeServiceTracker.close();
+        stopAndClearCamelRoutes();
+        this.bundleContext.ungetService(camelContextRef.getReference());
+    }
+    
+    @Override
+    public void modifiedService(ServiceReference<RouteBuilder> reference, RouteBuilder service) {
+        removedService(reference, service);
+        addingService(reference);
+    }
+
+    @Override
+    public void removedService(ServiceReference<RouteBuilder> reference, RouteBuilder service) {
+        if (isPreStartRouteBuilder(reference)) {
+            reloadTrackedServices();
+        } else {
+            removeRoute(service);
+        }
+    }
+    
+    private boolean isPreStartRouteBuilder(ServiceReference<RouteBuilder> reference) {
+        
+        boolean result = false;
+        
+        Object preStartProperty = reference.getProperty(CamelRoutesActivatorConstants.PRE_START_UP_PROP_NAME);
+        
+        if (preStartProperty instanceof Boolean) {
+            result = (Boolean)preStartProperty;
+        } else if (preStartProperty instanceof String) {
+            result = Boolean.parseBoolean((String) preStartProperty);
+        }
+                
+        return result;
+    }
+    
+    private void loadAndRestartCamelContext(List<ServiceReference<RouteBuilder>> existingRouteBuildersReferences) {
+        if (existingRouteBuildersReferences != null) {
+            List<RouteBuilder> postStartUpRoutes = new ArrayList<>();
+            for (ServiceReference<RouteBuilder> currentRouteBuilderReference : existingRouteBuildersReferences) {
+                RouteBuilder builder = this.bundleContext.getService(currentRouteBuilderReference);
+                if (isPreStartRouteBuilder(currentRouteBuilderReference)) {
+                    addRoute(builder);
+                } else {
+                    postStartUpRoutes.add(builder);
+                }
+            }
+            camelContext.start();
+            postStartUpRoutes.forEach(this::addRoute);
+        }
+    }
+    
+    private void reloadTrackedServices(ServiceReference<RouteBuilder> reference) {
+        LOG.info("Reload Camel Context Routes Triggered");
+        try {
+            synchronized (camelContext) {
+                stopAndClearCamelRoutes();
+                List<ServiceReference<RouteBuilder>> routeServiceReferenceArrayList = new ArrayList<>();
+                if (reference != null) {
+                    routeServiceReferenceArrayList.add(reference);
+                }
+                ServiceReference<RouteBuilder>[] existingTrackedRoutes = this.routeServiceTracker.getServiceReferences();
+                if (existingTrackedRoutes != null) {
+                    routeServiceReferenceArrayList.addAll(Arrays.asList(existingTrackedRoutes));
+                }
+                loadAndRestartCamelContext(routeServiceReferenceArrayList);
+            }
+        } catch (Exception e) {
+            LOG.error("Error Reloading Camel Context Routes", e);
+        }
+    }
+    
+    private void reloadTrackedServices() {
+        reloadTrackedServices(null);
+    }
+
+    private void addRoute(RouteBuilder builder) {
+        try {
+            // need to synchronize here since adding routes is not synchronized
+            synchronized (camelContext) {
+                this.camelContext.addRoutes(builder);
+                LOG.debug("Camel Routes from RouteBuilder Class {} Added to Camel OSGi Activator Context", builder.getClass().getName());
+            }
+        } catch (Exception e) {
+            LOG.error("Error Adding Camel RouteBuilder", e);
+        }
+    }
+
+    private void stopAndClearCamelRoutes() throws Exception {
+        camelContext.stop();
+        camelContext.removeRouteDefinitions(new ArrayList<RouteDefinition>(this.camelContext.getRouteDefinitions()));
+    }
+
+    private void removeRoute(RouteBuilder service) {
+        List<RouteDefinition> routesToBeRemoved = service.getRouteCollection().getRoutes();
+        try {
+            synchronized (camelContext) {
+                camelContext.removeRouteDefinitions(routesToBeRemoved);
+                LOG.debug("Camel Routes from RouteBuilder Class {} Removed from Camel OSGi Activator Context",
+                        service.getClass().getName());
+            }
+        } catch (Exception e) {
+            LOG.error("Error Removing Camel Route Builder", e);
+        }
+    }
+
+}
diff --git a/components/camel-osgi-activator/src/main/java/org/apache/camel/osgi/activator/CamelRoutesActivatorConstants.java b/components/camel-osgi-activator/src/main/java/org/apache/camel/osgi/activator/CamelRoutesActivatorConstants.java
new file mode 100644
index 0000000..dc01581
--- /dev/null
+++ b/components/camel-osgi-activator/src/main/java/org/apache/camel/osgi/activator/CamelRoutesActivatorConstants.java
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.osgi.activator;
+
+public final class CamelRoutesActivatorConstants {
+
+    public static final String PRE_START_UP_PROP_NAME = "camel.osgi.activator.pre-startup";
+    
+    private CamelRoutesActivatorConstants() {
+        
+    }
+}
diff --git a/components/camel-osgi-activator/src/test/java/org/apache/camel/component/osgi/activator/CamelOsgiActivatorIT.java b/components/camel-osgi-activator/src/test/java/org/apache/camel/component/osgi/activator/CamelOsgiActivatorIT.java
new file mode 100644
index 0000000..d08df32
--- /dev/null
+++ b/components/camel-osgi-activator/src/test/java/org/apache/camel/component/osgi/activator/CamelOsgiActivatorIT.java
@@ -0,0 +1,198 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.osgi.activator;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Date;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import javax.inject.Inject;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.osgi.activator.CamelRoutesActivatorConstants;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.Configuration;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.junit.PaxExam;
+import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;
+import org.ops4j.pax.exam.spi.reactors.PerClass;
+import org.ops4j.pax.tinybundles.core.TinyBundles;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.ops4j.pax.exam.CoreOptions.junitBundles;
+import static org.ops4j.pax.exam.CoreOptions.options;
+import static org.ops4j.pax.exam.CoreOptions.streamBundle;
+
+@RunWith(PaxExam.class)
+@ExamReactorStrategy(PerClass.class)
+public class CamelOsgiActivatorIT {
+    @Inject
+    private BundleContext bc;
+
+    @Configuration
+    public Option[] configuration() throws IOException, URISyntaxException, ClassNotFoundException {
+        return options(
+                PaxExamOptions.KARAF.option(),
+                PaxExamOptions.CAMEL_CORE_OSGI.option(),
+                streamBundle(
+                        TinyBundles.bundle()
+                            .read(
+                                Files.newInputStream(
+                                    Paths.get("target/test-bundles")
+                                        .resolve("camel-osgi-activator.jar")))
+                            .build()),
+                junitBundles());
+    }
+    
+    @Test
+    public void testBundleLoaded() throws Exception {
+        boolean hasOsgi = false;
+        boolean hasCamelCoreOsgiActivator = false;
+        for (Bundle b : bc.getBundles()) {
+            if ("org.apache.camel.camel-core-osgi".equals(b.getSymbolicName())) {
+                hasOsgi = true;
+                assertEquals("Camel Core OSGi not activated", Bundle.ACTIVE, b.getState());
+            }
+            
+            if ("org.apache.camel.camel-osgi-activator".equals(b.getSymbolicName())) {
+                hasCamelCoreOsgiActivator = true;
+                assertEquals("Camel OSGi Activator not activated", Bundle.ACTIVE, b.getState());
+            }
+        }
+        assertTrue("Camel Core OSGi bundle not found", hasOsgi);
+        assertTrue("Camel OSGi Activator bundle not found", hasCamelCoreOsgiActivator);
+    }
+
+    @Test
+    public void testRouteLoadAndRemoved() throws Exception {
+        CountDownLatch latch = new CountDownLatch(1);
+        ServiceRegistration<RouteBuilder> testServiceRegistration = bc.registerService(RouteBuilder.class,
+                new RouteBuilder() {
+
+                    @Override
+                    public void configure() throws Exception {
+                        from("timer:test?fixedRate=true&period=300").process(exchange -> {
+                            latch.countDown();
+                        });
+                    }
+                }, null);
+
+        latch.await(10, TimeUnit.SECONDS);
+
+        CamelContext camelContext = bc.getService(bc.getServiceReference(CamelContext.class));
+
+        assertEquals("There should be one route in the context.", 1, camelContext.getRoutes().size());
+
+        testServiceRegistration.unregister();
+
+        assertEquals("There should be no routes in the context.", 0, camelContext.getRoutes().size());
+
+    }
+    
+    @Test
+    public void testPreStartupLoadAndRemoved() throws Exception {
+        CountDownLatch preStartLatch = new CountDownLatch(1);
+        
+        CountDownLatch postStartLatch = new CountDownLatch(1);
+        
+        CamelContext camelContext = bc.getService(bc.getServiceReference(CamelContext.class));
+        
+        Date originalCamelStartTime = camelContext.getStartDate(); 
+        
+        ServiceRegistration<RouteBuilder> testRegularServiceRegistration = bc.registerService(RouteBuilder.class,
+                new RouteBuilder() {
+
+                    @Override
+                    public void configure() throws Exception {
+                        from("timer:test1?fixedRate=true&period=300")
+                            .description("PostStartRoute")
+                            .process(exchange -> {
+                                postStartLatch.countDown();
+                            });
+                    }
+                }, null);
+        
+        postStartLatch.await(10, TimeUnit.SECONDS);
+        
+        Date regularRouteAddCamelContextStartTime = camelContext.getStartDate();
+        
+        assertEquals("Camel Context Should NOT be restarted when removing regular RouteBuilder", originalCamelStartTime, regularRouteAddCamelContextStartTime);
+        
+        assertEquals("There should be one route in the context.", 1, camelContext.getRoutes().size());
+        
+        assertEquals("The PostStartRoute should be first.", "PostStartRoute", camelContext.getRoutes().get(0).getDescription());
+
+        
+        Dictionary<String, String> preStartUpProperties = new Hashtable<>();
+        preStartUpProperties.put(CamelRoutesActivatorConstants.PRE_START_UP_PROP_NAME, "true");
+        ServiceRegistration<RouteBuilder> testPreStartupServiceRegistration = bc.registerService(RouteBuilder.class, 
+                new RouteBuilder() {
+                    
+                    @Override
+                    public void configure() throws Exception {
+                        getContext().setStreamCaching(true);
+                        
+                        from("timer:test2?fixedRate=true&period=300")
+                            .description("PreStartRoute")
+                            .process(exchange -> {
+                                preStartLatch.countDown();
+                            });
+                        
+                    }
+                }, preStartUpProperties);
+
+        preStartLatch.await(10, TimeUnit.SECONDS);
+
+        Date preStartCamelContextStartTime = camelContext.getStartDate();
+        
+        assertTrue("Camel Context Should be restarted when adding startup RouteBuilder", preStartCamelContextStartTime.after(originalCamelStartTime));
+
+        assertEquals("There should be two route in the context.", 2, camelContext.getRoutes().size());
+        
+        assertEquals("The PreStartRoute should be first.", "PreStartRoute", camelContext.getRoutes().get(0).getDescription());
+
+        testPreStartupServiceRegistration.unregister();
+
+        Date preStartRemovedCamelContextStartTime = camelContext.getStartDate();
+        
+        assertEquals("There should be one routes in the context.", 1, camelContext.getRoutes().size());
+        
+        assertTrue("Camel Context Should be restarted when removing startup RouteBuilder", preStartRemovedCamelContextStartTime.after(preStartCamelContextStartTime));
+        
+        testRegularServiceRegistration.unregister();
+        
+        Date regularRouteRemovedCamelContextStartTime = camelContext.getStartDate();
+        
+        assertEquals("Camel Context Should NOT be restarted when removing regular RouteBuilder", preStartRemovedCamelContextStartTime, regularRouteRemovedCamelContextStartTime);
+
+        assertEquals("There should be no routes in the context.", 0, camelContext.getRoutes().size());
+
+    }
+
+}
diff --git a/components/camel-osgi-activator/src/test/java/org/apache/camel/component/osgi/activator/PaxExamOptions.java b/components/camel-osgi-activator/src/test/java/org/apache/camel/component/osgi/activator/PaxExamOptions.java
new file mode 100644
index 0000000..38f3a8e
--- /dev/null
+++ b/components/camel-osgi-activator/src/test/java/org/apache/camel/component/osgi/activator/PaxExamOptions.java
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.osgi.activator;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.karaf.container.internal.JavaVersionUtil;
+import org.ops4j.pax.exam.karaf.options.LogLevelOption;
+import org.ops4j.pax.exam.options.DefaultCompositeOption;
+import org.ops4j.pax.exam.options.extra.VMOption;
+import org.ops4j.pax.tinybundles.core.TinyBundles;
+
+import static org.ops4j.pax.exam.CoreOptions.maven;
+import static org.ops4j.pax.exam.CoreOptions.streamBundle;
+import static org.ops4j.pax.exam.CoreOptions.when;
+import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.configureConsole;
+import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.karafDistributionConfiguration;
+import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.keepRuntimeFolder;
+import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.logLevel;
+
+public enum PaxExamOptions {
+    
+    KARAF(
+            karafDistributionConfiguration()
+                .frameworkUrl(
+                    maven()
+                        .groupId("org.apache.karaf")
+                        .artifactId("apache-karaf")
+                        .versionAsInProject()
+                        .type("zip"))
+                .name("Apache Karaf")
+                .useDeployFolder(false)
+                .unpackDirectory(new File("target/paxexam/unpack/")),
+            keepRuntimeFolder(),
+            // Don't bother with local console output as it just ends up cluttering the logs
+            configureConsole().ignoreLocalConsole(),
+            // Force the log level to INFO so we have more details during the test. It defaults to WARN.
+            logLevel(LogLevelOption.LogLevel.INFO),
+            when(JavaVersionUtil.getMajorVersion() >= 9)
+                .useOptions(
+                        new VMOption("-classpath"),
+                        new VMOption("lib/jdk9plus/*" + File.pathSeparator + "lib/boot/*")
+                        )
+        ),
+        CAMEL_CORE_OSGI(
+                createStreamBundleOption("camel-core-engine.jar"),
+                createStreamBundleOption("camel-core-languages.jar"),
+                createStreamBundleOption("camel-api.jar"),
+                createStreamBundleOption("camel-base.jar"),
+                createStreamBundleOption("camel-management-api.jar"),
+                createStreamBundleOption("camel-support.jar"),
+                createStreamBundleOption("camel-util.jar"),
+                createStreamBundleOption("camel-timer.jar"),
+                createStreamBundleOption("camel-log.jar"),
+                createStreamBundleOption("camel-core-osgi.jar")
+        );
+
+    private final Option[] options;
+
+    PaxExamOptions(Option... options) {
+        this.options = options;
+    }
+
+    public Option option() {
+        return new DefaultCompositeOption(options);
+    }
+    
+    public static Option createStreamBundleOption(String fileName) {
+        InputStream bundleInputStream = null;
+        try {
+            bundleInputStream = Files.newInputStream(
+                    Paths.get("target/test-bundles")
+                        .resolve(fileName));
+            
+        } catch (IOException e) {
+            throw new RuntimeException("Error resolving Bundle", e);
+        }
+        
+        return streamBundle(
+                    TinyBundles.bundle()
+                        .read(bundleInputStream)
+                        .build());
+    }
+}
diff --git a/components/pom.xml b/components/pom.xml
index 3ccab0a..901ad02 100644
--- a/components/pom.xml
+++ b/components/pom.xml
@@ -37,6 +37,7 @@
         <module>camel-cxf-blueprint</module>
         <module>camel-cxf-transport-blueprint</module>
         <module>camel-kura</module>
+        <module>camel-osgi-activator</module>
         <module>camel-paxlogging</module>
     </modules>