| /* |
| * 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.karaf").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); |
| } |
| |
| } |