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/25 10:22:24 UTC
[camel-karaf] 03/05: Added camel-test-karaf
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 c4471396fb5c85449945313568f96ed865a84bbd
Author: Andrea Cosentino <an...@gmail.com>
AuthorDate: Wed Mar 25 10:20:10 2020 +0100
Added camel-test-karaf
---
components/camel-test-karaf/pom.xml | 216 ++++++++++
.../services/org/apache/camel/other.properties | 7 +
.../src/generated/resources/test-karaf.json | 14 +
.../camel-test-karaf/src/main/docs/test-karaf.adoc | 10 +
.../camel/test/karaf/AbstractFeatureTest.java | 479 +++++++++++++++++++++
.../camel/test/karaf/CamelKarafTestSupport.java | 390 +++++++++++++++++
components/pom.xml | 1 +
7 files changed, 1117 insertions(+)
diff --git a/components/camel-test-karaf/pom.xml b/components/camel-test-karaf/pom.xml
new file mode 100644
index 0000000..fae9c3a
--- /dev/null
+++ b/components/camel-test-karaf/pom.xml
@@ -0,0 +1,216 @@
+<?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-test-karaf</artifactId>
+ <packaging>jar</packaging>
+
+ <name>Camel Karaf :: Test :: Karaf</name>
+ <description>Camel integration testing with Apache Karaf</description>
+
+ <properties>
+ <firstVersion>2.18.0</firstVersion>
+ <label>testing,java,osgi</label>
+
+ <karf-test-version>${karaf4-version}</karf-test-version>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.ops4j.pax.exam</groupId>
+ <artifactId>pax-exam-junit4</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.ops4j.pax.exam</groupId>
+ <artifactId>pax-exam</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.ops4j.pax.exam</groupId>
+ <artifactId>pax-exam-container-karaf</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.ops4j.pax.exam</groupId>
+ <artifactId>pax-exam-link-mvn</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>javax.inject</groupId>
+ <artifactId>javax.inject</artifactId>
+ <version>1</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.karaf.features</groupId>
+ <artifactId>org.apache.karaf.features.core</artifactId>
+ <version>${karaf4-version}</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.karaf</groupId>
+ <artifactId>apache-karaf</artifactId>
+ <version>${karf-test-version}</version>
+ <type>tar.gz</type>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.configadmin</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.gogo.runtime</artifactId>
+ <version>1.1.2</version>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>osgi.core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>osgi.cmpn</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-support</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-test</artifactId>
+ </dependency>
+
+ <!-- test -->
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <pluginManagement>
+ <plugins>
+ <!-- Eclipse m2e Lifecycle Management -->
+ <plugin>
+ <groupId>org.eclipse.m2e</groupId>
+ <artifactId>lifecycle-mapping</artifactId>
+ <version>${lifecycle-mapping-version}</version>
+ <configuration>
+ <lifecycleMappingMetadata>
+ <pluginExecutions>
+ <pluginExecution>
+ <pluginExecutionFilter>
+ <groupId>org.apache.servicemix.tooling</groupId>
+ <artifactId>depends-maven-plugin</artifactId>
+ <versionRange>${depends-maven-plugin-version}</versionRange>
+ <goals>
+ <goal>generate-depends-file</goal>
+ </goals>
+ </pluginExecutionFilter>
+ <action>
+ <ignore />
+ </action>
+ </pluginExecution>
+ </pluginExecutions>
+ </lifecycleMappingMetadata>
+ </configuration>
+ </plugin>
+ </plugins>
+ </pluginManagement>
+ <plugins>
+ <!-- generate dependencies versions -->
+ <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-surefire-plugin</artifactId>
+ <configuration>
+ <!-- do not re-run these tests -->
+ <rerunFailingTestsCount>0</rerunFailingTestsCount>
+ <systemPropertyVariables>
+ <karafVersion>${karaf4-version}</karafVersion>
+ </systemPropertyVariables>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-bundle-plugin</artifactId>
+ <configuration>
+ <instructions>
+ <Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
+ <!-- Export-Package>org.apache.camel.test.karaf</Export-Package -->
+ <DynamicImport-Package>*</DynamicImport-Package>
+ <Import-Package />
+ <_removeheaders>Import-Package, Private-Package, Include-Resource, Karaf-Info,
+ Require-Capability
+ </_removeheaders>
+ </instructions>
+ </configuration>
+ </plugin>
+
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>ci-build-profile</id>
+ <activation>
+ <property>
+ <name>maven.repo.local</name>
+ </property>
+ </activation>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <forkedProcessTimeoutInSeconds>300</forkedProcessTimeoutInSeconds>
+ <!--
+ when the local repo location has been specified, we need to pass
+ on this information to PAX mvn url
+ -->
+ <argLine>-Dorg.ops4j.pax.url.mvn.localRepository=${maven.repo.local}</argLine>
+ <systemPropertyVariables>
+ <karafVersion>${karaf4-version}</karafVersion>
+ </systemPropertyVariables>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+
+ </profiles>
+</project>
diff --git a/components/camel-test-karaf/src/generated/resources/META-INF/services/org/apache/camel/other.properties b/components/camel-test-karaf/src/generated/resources/META-INF/services/org/apache/camel/other.properties
new file mode 100644
index 0000000..f3258d5
--- /dev/null
+++ b/components/camel-test-karaf/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=test-karaf
+groupId=org.apache.camel.karaf
+artifactId=camel-test-karaf
+version=3.2.0-SNAPSHOT
+projectName=Camel Karaf :: Test :: Karaf
+projectDescription=Camel integration testing with Apache Karaf
diff --git a/components/camel-test-karaf/src/generated/resources/test-karaf.json b/components/camel-test-karaf/src/generated/resources/test-karaf.json
new file mode 100644
index 0000000..8fee4cc
--- /dev/null
+++ b/components/camel-test-karaf/src/generated/resources/test-karaf.json
@@ -0,0 +1,14 @@
+{
+ "other": {
+ "kind": "other",
+ "name": "test-karaf",
+ "title": "Test Karaf",
+ "description": "Camel integration testing with Apache Karaf",
+ "deprecated": false,
+ "firstVersion": "2.18.0",
+ "label": "testing,java,osgi",
+ "groupId": "org.apache.camel.karaf",
+ "artifactId": "camel-test-karaf",
+ "version": "3.2.0-SNAPSHOT"
+ }
+}
diff --git a/components/camel-test-karaf/src/main/docs/test-karaf.adoc b/components/camel-test-karaf/src/main/docs/test-karaf.adoc
new file mode 100644
index 0000000..5668522
--- /dev/null
+++ b/components/camel-test-karaf/src/main/docs/test-karaf.adoc
@@ -0,0 +1,10 @@
+= Test Karaf
+
+*Available since Camel 2.18*
+
+Camel testing using Apache Karaf with Pax-Exam.
+
+This component allows to perform integration testing by running Karaf containers using Pax-Exam.
+
+The component is in development and needs some more polish to be ready.
+In addition there is also need for documentation.
diff --git a/components/camel-test-karaf/src/main/java/org/apache/camel/test/karaf/AbstractFeatureTest.java b/components/camel-test-karaf/src/main/java/org/apache/camel/test/karaf/AbstractFeatureTest.java
new file mode 100644
index 0000000..4297bdb
--- /dev/null
+++ b/components/camel-test-karaf/src/main/java/org/apache/camel/test/karaf/AbstractFeatureTest.java
@@ -0,0 +1,479 @@
+/*
+ * 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.test.karaf;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Field;
+import java.net.URL;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Dictionary;
+import java.util.EnumSet;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Locale;
+import java.util.Properties;
+import java.util.function.Consumer;
+
+import javax.inject.Inject;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Component;
+import org.apache.camel.spi.DataFormat;
+import org.apache.camel.spi.Language;
+import org.apache.karaf.features.FeaturesService;
+import org.junit.After;
+import org.junit.Before;
+import org.ops4j.pax.exam.CoreOptions;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.ProbeBuilder;
+import org.ops4j.pax.exam.TestProbeBuilder;
+import org.ops4j.pax.exam.karaf.container.internal.JavaVersionUtil;
+import org.ops4j.pax.exam.karaf.options.KarafDistributionOption;
+import org.ops4j.pax.exam.karaf.options.LogLevelOption;
+import org.ops4j.pax.exam.options.UrlReference;
+import org.ops4j.pax.exam.options.extra.VMOption;
+import org.ops4j.pax.tinybundles.core.TinyBundle;
+import org.ops4j.pax.tinybundles.core.TinyBundles;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.Constants;
+import org.osgi.framework.Filter;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.blueprint.container.BlueprintContainer;
+import org.osgi.service.cm.Configuration;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.util.tracker.ServiceTracker;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.junit.Assert.assertNotNull;
+import static org.ops4j.pax.exam.CoreOptions.maven;
+import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
+import static org.ops4j.pax.exam.CoreOptions.systemProperty;
+import static org.ops4j.pax.exam.CoreOptions.vmOption;
+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.editConfigurationFilePut;
+import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.features;
+import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.keepRuntimeFolder;
+import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.logLevel;
+
+public abstract class AbstractFeatureTest {
+
+ public static final Long SERVICE_TIMEOUT = 30000L;
+ protected static final Logger LOG = LoggerFactory.getLogger(AbstractFeatureTest.class);
+
+ @Inject
+ protected BundleContext bundleContext;
+
+ @Inject
+ protected BlueprintContainer blueprintContainer;
+
+ @Inject
+ protected FeaturesService featuresService;
+
+ @ProbeBuilder
+ public TestProbeBuilder probeConfiguration(TestProbeBuilder probe) {
+ // makes sure the generated Test-Bundle contains this import!
+ probe.setHeader(Constants.DYNAMICIMPORT_PACKAGE, "*");
+ return probe;
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ LOG.info("setUp() using BundleContext: {}", bundleContext);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ LOG.info("tearDown()");
+ }
+
+ protected Bundle installBlueprintAsBundle(String name, URL url, boolean start) throws BundleException {
+ return installBlueprintAsBundle(name, url, start, bundle -> { });
+ }
+
+ protected Bundle installBlueprintAsBundle(String name, URL url, boolean start, Consumer<Object> consumer) throws BundleException {
+ // TODO Type Consumer<TinyBundle> cannot be used for this method signature to avoid bundle dependency to pax tinybundles
+ TinyBundle bundle = TinyBundles.bundle();
+ bundle.add("OSGI-INF/blueprint/blueprint-" + name.toLowerCase(Locale.ENGLISH) + ".xml", url);
+ bundle.set("Manifest-Version", "2")
+ .set("Bundle-ManifestVersion", "2")
+ .set("Bundle-SymbolicName", name)
+ .set("Bundle-Version", "1.0.0")
+ .set(Constants.DYNAMICIMPORT_PACKAGE, "*");
+ consumer.accept(bundle);
+ Bundle answer = bundleContext.installBundle(name, bundle.build());
+
+ if (start) {
+ answer.start();
+ }
+ return answer;
+ }
+
+ protected Bundle installSpringAsBundle(String name, URL url, boolean start) throws BundleException {
+ return installSpringAsBundle(name, url, start, bundle -> { });
+ }
+
+ protected Bundle installSpringAsBundle(String name, URL url, boolean start, Consumer<Object> consumer) throws BundleException {
+ // TODO Type Consumer<TinyBundle> cannot be used for this method signature to avoid bundle dependency to pax tinybundles
+ TinyBundle bundle = TinyBundles.bundle();
+ bundle.add("META-INF/spring/spring-" + name.toLowerCase(Locale.ENGLISH) + ".xml", url);
+ bundle.set("Manifest-Version", "2")
+ .set("Bundle-ManifestVersion", "2")
+ .set("Bundle-SymbolicName", name)
+ .set("Bundle-Version", "1.0.0");
+ consumer.accept(bundle);
+ Bundle answer = bundleContext.installBundle(name, bundle.build());
+
+ if (start) {
+ answer.start();
+ }
+ return answer;
+ }
+
+ protected void installCamelFeature(String mainFeature) throws Exception {
+ if (!mainFeature.startsWith("camel-")) {
+ mainFeature = "camel-" + mainFeature;
+ }
+ LOG.info("Install main feature: {}", mainFeature);
+ // do not refresh bundles causing out bundle context to be invalid
+ // TODO: see if we can find a way maybe to install camel.xml as bundle/feature instead of part of unit test (see src/test/resources/OSGI-INF/blueprint)
+ featuresService.installFeature(mainFeature, EnumSet.of(FeaturesService.Option.NoAutoRefreshBundles));
+ }
+
+ protected void overridePropertiesWithConfigAdmin(String pid, Properties props) throws IOException {
+ ConfigurationAdmin configAdmin = getOsgiService(bundleContext, ConfigurationAdmin.class);
+ // passing null as second argument ties the configuration to correct bundle.
+ Configuration config = configAdmin.getConfiguration(pid, null);
+ if (config == null) {
+ throw new IllegalArgumentException("Cannot find configuration with pid " + pid + " in OSGi ConfigurationAdmin service.");
+ }
+
+ // let's merge configurations
+ Dictionary<String, Object> currentProperties = config.getProperties();
+ Dictionary newProps = new Properties();
+ if (currentProperties == null) {
+ currentProperties = newProps;
+ }
+ for (Enumeration<String> ek = currentProperties.keys(); ek.hasMoreElements();) {
+ String k = ek.nextElement();
+ newProps.put(k, currentProperties.get(k));
+ }
+ for (String p : props.stringPropertyNames()) {
+ newProps.put(p, props.getProperty(p));
+ }
+
+ LOG.info("Updating ConfigAdmin {} by overriding properties {}", config, newProps);
+ config.update(newProps);
+ }
+
+ protected void testComponent(String component) throws Exception {
+ testComponent("camel-" + component, component);
+ }
+
+ protected void testComponent(String mainFeature, String component) throws Exception {
+ LOG.info("Looking up CamelContext(myCamel) in OSGi Service Registry");
+
+ installCamelFeature(mainFeature);
+
+ CamelContext camelContext = getOsgiService(bundleContext, CamelContext.class, "(camel.context.name=myCamel)", SERVICE_TIMEOUT);
+ assertNotNull("Cannot find CamelContext with name myCamel", camelContext);
+
+ LOG.info("Getting Camel component: {}", component);
+ // do not auto start the component as it may not have been configured properly and fail in its start method
+ Component comp = camelContext.getComponent(component, true, false);
+ assertNotNull("Cannot get component with name: " + component, comp);
+
+ LOG.info("Found Camel component: {} instance: {} with className: {}", component, comp, comp.getClass());
+ }
+
+ protected void testDataFormat(String dataFormat) throws Exception {
+ testDataFormat("camel-" + dataFormat, dataFormat);
+ }
+
+ protected void testDataFormat(String mainFeature, String dataFormat) throws Exception {
+ LOG.info("Looking up CamelContext(myCamel) in OSGi Service Registry");
+
+ installCamelFeature(mainFeature);
+
+ CamelContext camelContext = getOsgiService(bundleContext, CamelContext.class, "(camel.context.name=myCamel)", SERVICE_TIMEOUT);
+ assertNotNull("Cannot find CamelContext with name myCamel", camelContext);
+
+ LOG.info("Getting Camel dataformat: {}", dataFormat);
+ DataFormat df = camelContext.resolveDataFormat(dataFormat);
+ assertNotNull("Cannot get dataformat with name: " + dataFormat, df);
+
+ LOG.info("Found Camel dataformat: {} instance: {} with className: {}", dataFormat, df, df.getClass());
+ }
+
+ protected void testLanguage(String language) throws Exception {
+ testLanguage("camel-" + language, language);
+ }
+
+ protected void testLanguage(String mainFeature, String language) throws Exception {
+ LOG.info("Looking up CamelContext(myCamel) in OSGi Service Registry");
+
+ installCamelFeature(mainFeature);
+
+ CamelContext camelContext = getOsgiService(bundleContext, CamelContext.class, "(camel.context.name=myCamel)", 20000);
+ assertNotNull("Cannot find CamelContext with name myCamel", camelContext);
+
+ LOG.info("Getting Camel language: {}", language);
+ Language lan = camelContext.resolveLanguage(language);
+ assertNotNull("Cannot get language with name: " + language, lan);
+
+ LOG.info("Found Camel language: {} instance: {} with className: {}", language, lan, lan.getClass());
+ }
+
+ public static String extractName(Class<?> clazz) {
+ String name = clazz.getName();
+ int id0 = name.indexOf("Camel") + "Camel".length();
+ int id1 = name.indexOf("Test");
+ StringBuilder sb = new StringBuilder();
+ for (int i = id0; i < id1; i++) {
+ char c = name.charAt(i);
+ if (Character.isUpperCase(c) && sb.length() > 0) {
+ sb.append("-");
+ }
+ sb.append(Character.toLowerCase(c));
+ }
+ return sb.toString();
+ }
+
+ public static UrlReference getCamelKarafFeatureUrl() {
+ return mavenBundle().
+ groupId("org.apache.camel.karaf").
+ artifactId("apache-camel").
+ version(getCamelKarafFeatureVersion()).
+ type("xml/features");
+ }
+
+ private static String getCamelKarafFeatureVersion() {
+ String camelKarafFeatureVersion = System.getProperty("camelKarafFeatureVersion");
+ if (camelKarafFeatureVersion == null) {
+ throw new RuntimeException("Please specify the maven artifact version to use for org.apache.camel.karaf/apache-camel through the camelKarafFeatureVersion System property");
+ }
+ return camelKarafFeatureVersion;
+ }
+
+ private static void switchPlatformEncodingToUTF8() {
+ try {
+ System.setProperty("file.encoding", "UTF-8");
+ Field charset = Charset.class.getDeclaredField("defaultCharset");
+ charset.setAccessible(true);
+ charset.set(null, null);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static String getKarafVersion() {
+ InputStream ins = AbstractFeatureTest.class.getResourceAsStream("/META-INF/maven/dependencies.properties");
+ Properties p = new Properties();
+ try {
+ p.load(ins);
+ } catch (Throwable t) {
+ // ignore
+ }
+ String karafVersion = p.getProperty("org.apache.karaf/apache-karaf/version");
+ if (karafVersion == null) {
+ karafVersion = System.getProperty("karafVersion");
+ }
+ if (karafVersion == null) {
+ // setup the default version of it
+ karafVersion = "4.1.0";
+ }
+ return karafVersion;
+ }
+
+ public static Option[] configure(String... extra) {
+
+ List<String> camel = new ArrayList<>();
+ camel.add("camel");
+ if (extra != null && extra.length > 0) {
+ for (String e : extra) {
+ camel.add(e);
+ }
+ }
+ final String[] camelFeatures = camel.toArray(new String[camel.size()]);
+
+ switchPlatformEncodingToUTF8();
+ String karafVersion = getKarafVersion();
+ LOG.info("*** Apache Karaf version is " + karafVersion + " ***");
+
+ Option[] options = new Option[]{
+ // for remote debugging
+// new VMOption("-Xdebug"),
+// new VMOption("-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5008"),
+
+ KarafDistributionOption.karafDistributionConfiguration()
+ .frameworkUrl(maven().groupId("org.apache.karaf").artifactId("apache-karaf").type("tar.gz").versionAsInProject())
+ .karafVersion(karafVersion)
+ .name("Apache Karaf")
+ .useDeployFolder(false).unpackDirectory(new File("target/paxexam/unpack/")),
+ logLevel(LogLevelOption.LogLevel.INFO),
+
+ // keep the folder so we can look inside when something fails
+ keepRuntimeFolder(),
+
+ // Disable the SSH port
+ configureConsole().ignoreRemoteShell(),
+
+ // need to modify the jre.properties to export some com.sun packages that some features rely on
+// KarafDistributionOption.replaceConfigurationFile("etc/jre.properties", new File("src/test/resources/jre.properties")),
+
+ vmOption("-Dfile.encoding=UTF-8"),
+
+ // Disable the Karaf shutdown port
+ editConfigurationFilePut("etc/custom.properties", "karaf.shutdown.port", "-1"),
+
+ // log config
+ editConfigurationFilePut("etc/custom.properties", "karaf.log", "${karaf.data}/log"),
+
+ // Assign unique ports for Karaf
+// editConfigurationFilePut("etc/org.ops4j.pax.web.cfg", "org.osgi.service.http.port", Integer.toString(AvailablePortFinder.getNextAvailable())),
+// editConfigurationFilePut("etc/org.apache.karaf.management.cfg", "rmiRegistryPort", Integer.toString(AvailablePortFinder.getNextAvailable())),
+// editConfigurationFilePut("etc/org.apache.karaf.management.cfg", "rmiServerPort", Integer.toString(AvailablePortFinder.getNextAvailable())),
+
+ // install junit
+ CoreOptions.junitBundles(),
+
+ // install camel
+ features(getCamelKarafFeatureUrl(), camelFeatures),
+
+ // install camel-test-karaf as bundle (not feature as the feature causes a bundle refresh that invalidates the @Inject bundleContext)
+ mavenBundle().groupId("org.apache.camel").artifactId("camel-test-karaf").versionAsInProject(),
+ when(JavaVersionUtil.getMajorVersion() >= 9)
+ .useOptions(
+ systemProperty("pax.exam.osgi.`unresolved.fail").value("true"),
+ systemProperty("java.awt.headless").value("true"),
+ new VMOption("--add-reads=java.xml=java.logging"),
+ new VMOption("--add-exports=java.base/org.apache.karaf.specs.locator=java.xml,ALL-UNNAMED"),
+ new VMOption("--patch-module"),
+ new VMOption("java.base=lib/endorsed/org.apache.karaf.specs.locator-"
+ + System.getProperty("karafVersion", "4.2.4") + ".jar"),
+ new VMOption("--patch-module"),
+ new VMOption("java.xml=lib/endorsed/org.apache.karaf.specs.java.xml-"
+ + System.getProperty("karafVersion", "4.2.4") + ".jar"),
+ new VMOption("--add-opens"),
+ new VMOption("java.base/java.security=ALL-UNNAMED"),
+ new VMOption("--add-opens"),
+ new VMOption("java.base/java.net=ALL-UNNAMED"),
+ new VMOption("--add-opens"),
+ new VMOption("java.base/java.lang=ALL-UNNAMED"),
+ new VMOption("--add-opens"),
+ new VMOption("java.base/java.util=ALL-UNNAMED"),
+ new VMOption("--add-opens"),
+ new VMOption("java.naming/javax.naming.spi=ALL-UNNAMED"),
+ new VMOption("--add-opens"),
+ new VMOption("java.rmi/sun.rmi.transport.tcp=ALL-UNNAMED"),
+ new VMOption("--add-exports=java.base/sun.net.www.protocol.http=ALL-UNNAMED"),
+ new VMOption("--add-exports=java.base/sun.net.www.protocol.https=ALL-UNNAMED"),
+ new VMOption("--add-exports=java.base/sun.net.www.protocol.jar=ALL-UNNAMED"),
+ new VMOption("--add-exports=jdk.naming.rmi/com.sun.jndi.url.rmi=ALL-UNNAMED"),
+ new VMOption("-classpath"),
+ new VMOption("lib/jdk9plus/*" + File.pathSeparator + "lib/boot/*")
+ )
+ };
+
+ return options;
+ }
+
+ protected <T> T getOsgiService(BundleContext bundleContext, Class<T> type) {
+ return getOsgiService(bundleContext, type, null, SERVICE_TIMEOUT);
+ }
+
+ protected <T> T getOsgiService(BundleContext bundleContext, Class<T> type, long timeout) {
+ return getOsgiService(bundleContext, type, null, timeout);
+ }
+
+ @SuppressWarnings("unchecked")
+ public static <T> T getOsgiService(BundleContext bundleContext, Class<T> type, String filter, long timeout) {
+ ServiceTracker tracker;
+ try {
+ String flt;
+ if (filter != null) {
+ if (filter.startsWith("(")) {
+ flt = "(&(" + Constants.OBJECTCLASS + "=" + type.getName() + ")" + filter + ")";
+ } else {
+ flt = "(&(" + Constants.OBJECTCLASS + "=" + type.getName() + ")(" + filter + "))";
+ }
+ } else {
+ flt = "(" + Constants.OBJECTCLASS + "=" + type.getName() + ")";
+ }
+ Filter osgiFilter = FrameworkUtil.createFilter(flt);
+ tracker = new ServiceTracker(bundleContext, osgiFilter, null);
+ tracker.open(true);
+ // Note that the tracker is not closed to keep the reference
+ // This is buggy, as the service reference may change i think
+ Object svc = tracker.waitForService(timeout);
+
+ if (svc == null) {
+ Dictionary<?, ?> dic = bundleContext.getBundle().getHeaders();
+ LOG.warn("Test bundle headers: " + explode(dic));
+
+ for (ServiceReference ref : asCollection(bundleContext.getAllServiceReferences(null, null))) {
+ LOG.warn("ServiceReference: " + ref + ", bundle: " + ref.getBundle() + ", symbolicName: " + ref.getBundle().getSymbolicName());
+ }
+
+ for (ServiceReference ref : asCollection(bundleContext.getAllServiceReferences(null, flt))) {
+ LOG.warn("Filtered ServiceReference: " + ref + ", bundle: " + ref.getBundle() + ", symbolicName: " + ref.getBundle().getSymbolicName());
+ }
+
+ throw new RuntimeException("Gave up waiting for service " + flt);
+ }
+ return type.cast(svc);
+ } catch (InvalidSyntaxException e) {
+ throw new IllegalArgumentException("Invalid filter", e);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Explode the dictionary into a <code>,</code> delimited list of <code>key=value</code> pairs.
+ */
+ private static String explode(Dictionary<?, ?> dictionary) {
+ Enumeration<?> keys = dictionary.keys();
+ StringBuilder result = new StringBuilder();
+ while (keys.hasMoreElements()) {
+ Object key = keys.nextElement();
+ result.append(String.format("%s=%s", key, dictionary.get(key)));
+ if (keys.hasMoreElements()) {
+ result.append(", ");
+ }
+ }
+ return result.toString();
+ }
+
+ /**
+ * Provides an iterable collection of references, even if the original array is <code>null</code>.
+ */
+ private static Collection<ServiceReference> asCollection(ServiceReference[] references) {
+ return references == null ? new ArrayList<>(0) : Arrays.asList(references);
+ }
+
+}
diff --git a/components/camel-test-karaf/src/main/java/org/apache/camel/test/karaf/CamelKarafTestSupport.java b/components/camel-test-karaf/src/main/java/org/apache/camel/test/karaf/CamelKarafTestSupport.java
new file mode 100644
index 0000000..992af1e
--- /dev/null
+++ b/components/camel-test-karaf/src/main/java/org/apache/camel/test/karaf/CamelKarafTestSupport.java
@@ -0,0 +1,390 @@
+/*
+ * 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.test.karaf;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.PrintStream;
+import java.net.URL;
+import java.security.Principal;
+import java.security.PrivilegedExceptionAction;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeUnit;
+
+import javax.inject.Inject;
+import javax.management.remote.JMXConnector;
+import javax.management.remote.JMXConnectorFactory;
+import javax.management.remote.JMXServiceURL;
+import javax.security.auth.Subject;
+
+import org.apache.camel.RuntimeCamelException;
+import org.apache.camel.test.junit4.CamelTestSupport;
+import org.apache.felix.service.command.CommandProcessor;
+import org.apache.felix.service.command.CommandSession;
+import org.apache.karaf.features.Feature;
+import org.apache.karaf.features.FeaturesService;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.ProbeBuilder;
+import org.ops4j.pax.exam.TestProbeBuilder;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.Filter;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.util.tracker.ServiceTracker;
+
+public class CamelKarafTestSupport extends CamelTestSupport {
+
+ static final Long COMMAND_TIMEOUT = 30000L;
+ static final Long SERVICE_TIMEOUT = 30000L;
+
+ protected ExecutorService executor = Executors.newCachedThreadPool();
+
+ @Inject
+ protected BundleContext bundleContext;
+
+ @Inject
+ protected FeaturesService featuresService;
+
+ @ProbeBuilder
+ public TestProbeBuilder probeConfiguration(TestProbeBuilder probe) {
+ probe.setHeader(Constants.DYNAMICIMPORT_PACKAGE, "*,org.apache.felix.service.*;status=provisional");
+ return probe;
+ }
+
+ public File getConfigFile(String path) {
+ URL res = this.getClass().getResource(path);
+ if (res == null) {
+ throw new RuntimeException("Config resource " + path + " not found");
+ }
+ return new File(res.getFile());
+ }
+
+ public static Option[] configure(String... extra) {
+ return AbstractFeatureTest.configure(extra);
+ }
+
+ /**
+ * Executes a shell command and returns output as a String.
+ * Commands have a default timeout of 10 seconds.
+ *
+ * @param command The command to execute
+ * @param principals The principals (e.g. RolePrincipal objects) to run the command under
+ */
+ protected String executeCommand(final String command, Principal... principals) {
+ return executeCommand(command, COMMAND_TIMEOUT, false, principals);
+ }
+
+ /**
+ * Executes a shell command and returns output as a String.
+ * Commands have a default timeout of 10 seconds.
+ *
+ * @param command The command to execute.
+ * @param timeout The amount of time in millis to wait for the command to execute.
+ * @param silent Specifies if the command should be displayed in the screen.
+ * @param principals The principals (e.g. RolePrincipal objects) to run the command under
+ */
+ protected String executeCommand(final String command, final Long timeout, final Boolean silent, final Principal... principals) {
+
+ waitForCommandService(command);
+ String response;
+ final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ final PrintStream printStream = new PrintStream(byteArrayOutputStream);
+ final Callable<String> commandCallable = new Callable<String>() {
+ @Override
+ public String call() throws Exception {
+ try {
+ if (!silent) {
+ System.err.println(command);
+ }
+ final CommandProcessor commandProcessor = getOsgiService(CommandProcessor.class);
+ final CommandSession commandSession = commandProcessor.createSession(System.in, printStream, System.err);
+ commandSession.execute(command);
+ } catch (Exception e) {
+ throw new RuntimeException(e.getMessage(), e);
+ }
+ printStream.flush();
+ return byteArrayOutputStream.toString();
+ }
+ };
+
+ FutureTask<String> commandFuture;
+ if (principals.length == 0) {
+ commandFuture = new FutureTask<>(commandCallable);
+ } else {
+ // If principals are defined, run the command callable via Subject.doAs()
+ commandFuture = new FutureTask<>(new Callable<String>() {
+ @Override
+ public String call() throws Exception {
+ Subject subject = new Subject();
+ subject.getPrincipals().addAll(Arrays.asList(principals));
+ return Subject.doAs(subject, new PrivilegedExceptionAction<String>() {
+ @Override
+ public String run() throws Exception {
+ return commandCallable.call();
+ }
+ });
+ }
+ });
+ }
+
+
+ try {
+ executor.submit(commandFuture);
+ response = commandFuture.get(timeout, TimeUnit.MILLISECONDS);
+ } catch (Exception e) {
+ e.printStackTrace(System.err);
+ response = "SHELL COMMAND TIMED OUT: ";
+ }
+
+ return response;
+ }
+
+ private void waitForCommandService(String command) {
+ // the commands are represented by services. Due to the asynchronous nature of services they may not be
+ // immediately available. This code waits the services to be available, in their secured form. It
+ // means that the code waits for the command service to appear with the roles defined.
+
+ if (command == null || command.length() == 0) {
+ return;
+ }
+
+ int spaceIdx = command.indexOf(' ');
+ if (spaceIdx > 0) {
+ command = command.substring(0, spaceIdx);
+ }
+ int colonIndx = command.indexOf(':');
+
+ try {
+ if (colonIndx > 0) {
+ String scope = command.substring(0, colonIndx);
+ String function = command.substring(colonIndx + 1);
+ waitForService("(&(osgi.command.scope=" + scope + ")(osgi.command.function=" + function + "))", SERVICE_TIMEOUT);
+ } else {
+ waitForService("(osgi.command.function=" + command + ")", SERVICE_TIMEOUT);
+ }
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private void waitForService(String filter, long timeout) throws InvalidSyntaxException,
+ InterruptedException {
+
+ ServiceTracker st = new ServiceTracker(bundleContext,
+ bundleContext.createFilter(filter),
+ null);
+ try {
+ st.open();
+ st.waitForService(timeout);
+ } finally {
+ st.close();
+ }
+ }
+
+ protected <T> T getOsgiService(Class<T> type, long timeout) {
+ return getOsgiService(type, null, timeout);
+ }
+
+ protected <T> T getOsgiService(Class<T> type) {
+ return getOsgiService(type, null, SERVICE_TIMEOUT);
+ }
+
+ @SuppressWarnings("unchecked")
+ protected <T> T getOsgiService(Class<T> type, String filter, long timeout) {
+ ServiceTracker tracker = null;
+ try {
+ String flt;
+ if (filter != null) {
+ if (filter.startsWith("(")) {
+ flt = "(&(" + Constants.OBJECTCLASS + "=" + type.getName() + ")" + filter + ")";
+ } else {
+ flt = "(&(" + Constants.OBJECTCLASS + "=" + type.getName() + ")(" + filter + "))";
+ }
+ } else {
+ flt = "(" + Constants.OBJECTCLASS + "=" + type.getName() + ")";
+ }
+ Filter osgiFilter = FrameworkUtil.createFilter(flt);
+ tracker = new ServiceTracker(bundleContext, osgiFilter, null);
+ tracker.open(true);
+ // Note that the tracker is not closed to keep the reference
+ // This is buggy, as the service reference may change i think
+ Object svc = type.cast(tracker.waitForService(timeout));
+ if (svc == null) {
+ Dictionary dic = bundleContext.getBundle().getHeaders();
+ System.err.println("Test bundle headers: " + explode(dic));
+
+ for (ServiceReference ref : asCollection(bundleContext.getAllServiceReferences(null, null))) {
+ System.err.println("ServiceReference: " + ref);
+ }
+
+ for (ServiceReference ref : asCollection(bundleContext.getAllServiceReferences(null, flt))) {
+ System.err.println("Filtered ServiceReference: " + ref);
+ }
+
+ throw new RuntimeException("Gave up waiting for service " + flt);
+ }
+ return type.cast(svc);
+ } catch (InvalidSyntaxException e) {
+ throw new IllegalArgumentException("Invalid filter", e);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /*
+ * Explode the dictionary into a ,-delimited list of key=value pairs
+ */
+ private static String explode(Dictionary dictionary) {
+ Enumeration keys = dictionary.keys();
+ StringBuilder sb = new StringBuilder();
+ while (keys.hasMoreElements()) {
+ Object key = keys.nextElement();
+ sb.append(String.format("%s=%s", key, dictionary.get(key)));
+ if (keys.hasMoreElements()) {
+ sb.append(", ");
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Provides an iterable collection of references, even if the original array is null
+ */
+ private static Collection<ServiceReference> asCollection(ServiceReference[] references) {
+ return references != null ? Arrays.asList(references) : Collections.<ServiceReference>emptyList();
+ }
+
+ public JMXConnector getJMXConnector() throws Exception {
+ return getJMXConnector("karaf", "karaf");
+ }
+
+ public JMXConnector getJMXConnector(String userName, String passWord) throws Exception {
+ JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:1099/karaf-root");
+ Hashtable<String, Object> env = new Hashtable<>();
+ String[] credentials = new String[]{userName, passWord};
+ env.put("jmx.remote.credentials", credentials);
+ JMXConnector connector = JMXConnectorFactory.connect(url, env);
+ return connector;
+ }
+
+ public void assertFeatureInstalled(String featureName) {
+ try {
+ Feature[] features = featuresService.listInstalledFeatures();
+ for (Feature feature : features) {
+ if (featureName.equals(feature.getName())) {
+ return;
+ }
+ }
+ fail("Feature " + featureName + " should be installed but is not");
+ } catch (Exception e) {
+ throw RuntimeCamelException.wrapRuntimeCamelException(e);
+ }
+ }
+
+ public void assertFeatureInstalled(String featureName, String featureVersion) {
+ try {
+ Feature[] features = featuresService.listInstalledFeatures();
+ for (Feature feature : features) {
+ if (featureName.equals(feature.getName()) && featureVersion.equals(feature.getVersion())) {
+ return;
+ }
+ }
+ fail("Feature " + featureName + "/" + featureVersion + " should be installed but is not");
+ } catch (Exception e) {
+ throw RuntimeCamelException.wrapRuntimeCamelException(e);
+ }
+ }
+
+ protected void installAndAssertFeature(String feature) throws Exception {
+ featuresService.installFeature(feature);
+ assertFeatureInstalled(feature);
+ }
+
+ protected void installAndAssertFeature(String feature, String version) throws Exception {
+ featuresService.installFeature(feature, version);
+ assertFeatureInstalled(feature, version);
+ }
+
+ protected void installAssertAndUninstallFeature(String feature) throws Exception {
+ Set<Feature> featuresBefore = new HashSet<>(Arrays.asList(featuresService.listInstalledFeatures()));
+ try {
+ featuresService.installFeature(feature);
+ assertFeatureInstalled(feature);
+ } finally {
+ uninstallNewFeatures(featuresBefore);
+ }
+ }
+
+ protected void installAssertAndUninstallFeature(String feature, String version) throws Exception {
+ Set<Feature> featuresBefore = new HashSet<>(Arrays.asList(featuresService.listInstalledFeatures()));
+ try {
+ featuresService.installFeature(feature, version);
+ assertFeatureInstalled(feature, version);
+ } finally {
+ uninstallNewFeatures(featuresBefore);
+ }
+ }
+
+ protected void installAssertAndUninstallFeatures(String... feature) throws Exception {
+ Set<Feature> featuresBefore = new HashSet<>(Arrays.asList(featuresService.listInstalledFeatures()));
+ try {
+ for (String curFeature : feature) {
+ featuresService.installFeature(curFeature);
+ assertFeatureInstalled(curFeature);
+ }
+ } finally {
+ uninstallNewFeatures(featuresBefore);
+ }
+ }
+
+ /**
+ * The feature service does not uninstall feature dependencies when uninstalling a single feature.
+ * So we need to make sure we uninstall all features that were newly installed.
+ */
+ protected void uninstallNewFeatures(Set<Feature> featuresBefore) {
+ try {
+ Feature[] features = featuresService.listInstalledFeatures();
+ for (Feature curFeature : features) {
+ if (!featuresBefore.contains(curFeature)) {
+ try {
+ System.out.println("Uninstalling " + curFeature.getName());
+ featuresService.uninstallFeature(curFeature.getName(), curFeature.getVersion());
+ } catch (Exception e) {
+ // ignore
+ }
+ }
+ }
+ } catch (Exception e) {
+ throw RuntimeCamelException.wrapRuntimeCamelException(e);
+ }
+ }
+
+}
diff --git a/components/pom.xml b/components/pom.xml
index a02240d..c68fc35 100644
--- a/components/pom.xml
+++ b/components/pom.xml
@@ -40,6 +40,7 @@
<module>camel-kura</module>
<module>camel-osgi-activator</module>
<module>camel-paxlogging</module>
+ <module>camel-test-karaf</module>
</modules>
<properties>