| /** |
| * 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.blueprint; |
| |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileNotFoundException; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.net.MalformedURLException; |
| import java.net.URL; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Dictionary; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.jar.JarInputStream; |
| |
| import de.kalpatec.pojosr.framework.PojoServiceRegistryFactoryImpl; |
| import de.kalpatec.pojosr.framework.launch.BundleDescriptor; |
| import de.kalpatec.pojosr.framework.launch.ClasspathScanner; |
| import de.kalpatec.pojosr.framework.launch.PojoServiceRegistry; |
| import de.kalpatec.pojosr.framework.launch.PojoServiceRegistryFactory; |
| import org.apache.camel.impl.DefaultClassResolver; |
| import org.apache.camel.spi.ClassResolver; |
| import org.apache.camel.util.FileUtil; |
| import org.apache.camel.util.IOHelper; |
| import org.apache.camel.util.ObjectHelper; |
| import org.apache.camel.util.ResourceHelper; |
| import org.ops4j.pax.swissbox.tinybundles.core.TinyBundle; |
| import org.ops4j.pax.swissbox.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.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.apache.camel.test.junit4.TestSupport.createDirectory; |
| import static org.apache.camel.test.junit4.TestSupport.deleteDirectory; |
| |
| /** |
| * Helper for using Blueprint with Camel. |
| */ |
| public final class CamelBlueprintHelper { |
| |
| public static final long DEFAULT_TIMEOUT = 30000; |
| public static final String BUNDLE_FILTER = "(Bundle-SymbolicName=*)"; |
| public static final String BUNDLE_VERSION = "1.0.0"; |
| private static final Logger LOG = LoggerFactory.getLogger(CamelBlueprintHelper.class); |
| private static final ClassResolver RESOLVER = new DefaultClassResolver(); |
| |
| private CamelBlueprintHelper() { |
| } |
| |
| public static BundleContext createBundleContext(String name, String descriptors, boolean includeTestBundle) throws Exception { |
| return createBundleContext(name, descriptors, includeTestBundle, BUNDLE_FILTER, BUNDLE_VERSION); |
| } |
| |
| public static BundleContext createBundleContext(String name, String descriptors, boolean includeTestBundle, |
| String bundleFilter, String testBundleVersion) throws Exception { |
| return createBundleContext(name, descriptors, includeTestBundle, bundleFilter, testBundleVersion, null); |
| } |
| |
| public static BundleContext createBundleContext(String name, String descriptors, boolean includeTestBundle, |
| String bundleFilter, String testBundleVersion, String testBundleDirectives) throws Exception { |
| TinyBundle bundle = null; |
| |
| if (includeTestBundle) { |
| // add ourselves as a bundle |
| bundle = createTestBundle(testBundleDirectives == null ? name : name + ';' + testBundleDirectives, testBundleVersion, descriptors); |
| } |
| |
| return createBundleContext(name, bundleFilter, bundle); |
| } |
| |
| public static BundleContext createBundleContext(String name, String bundleFilter, TinyBundle bundle) throws Exception { |
| // ensure pojosr stores bundles in an unique target directory |
| String uid = "" + System.currentTimeMillis(); |
| String tempDir = "target/bundles/" + uid; |
| System.setProperty("org.osgi.framework.storage", tempDir); |
| createDirectory(tempDir); |
| |
| // use another directory for the jar of the bundle as it cannot be in the same directory |
| // as it has a file lock during running the tests which will cause the temp dir to not be |
| // fully deleted between tests |
| createDirectory("target/test-bundles"); |
| |
| // get the bundles |
| List<BundleDescriptor> bundles = getBundleDescriptors(bundleFilter); |
| |
| if (bundle != null) { |
| String jarName = name.toLowerCase(Locale.ENGLISH) + "-" + uid + ".jar"; |
| bundles.add(getBundleDescriptor("target/test-bundles/" + jarName, bundle)); |
| } |
| |
| if (LOG.isDebugEnabled()) { |
| for (int i = 0; i < bundles.size(); i++) { |
| BundleDescriptor desc = bundles.get(i); |
| LOG.debug("Bundle #{} -> {}", i, desc); |
| } |
| } |
| |
| // setup pojosr to use our bundles |
| Map<String, List<BundleDescriptor>> config = new HashMap<String, List<BundleDescriptor>>(); |
| config.put(PojoServiceRegistryFactory.BUNDLE_DESCRIPTORS, bundles); |
| |
| // create pojorsr osgi service registry |
| PojoServiceRegistry reg = new PojoServiceRegistryFactoryImpl().newPojoServiceRegistry(config); |
| return reg.getBundleContext(); |
| } |
| |
| public static void disposeBundleContext(BundleContext bundleContext) throws BundleException { |
| try { |
| if (bundleContext != null) { |
| List<Bundle> bundles = new ArrayList<Bundle>(); |
| bundles.addAll(Arrays.asList(bundleContext.getBundles())); |
| Collections.reverse(bundles); |
| for (Bundle bundle : bundles) { |
| LOG.debug("Stopping bundle {}", bundle); |
| bundle.stop(); |
| } |
| } |
| } catch (Exception e) { |
| IllegalStateException ise = ObjectHelper.getException(IllegalStateException.class, e); |
| if (ise != null) { |
| // we dont care about illegal state exception as that may happen from OSGi |
| LOG.debug("Error during disposing BundleContext. This exception will be ignored.", e); |
| } else { |
| LOG.warn("Error during disposing BundleContext. This exception will be ignored.", e); |
| } |
| } finally { |
| String tempDir = System.clearProperty("org.osgi.framework.storage"); |
| if (tempDir != null) { |
| LOG.info("Deleting work directory {}", tempDir); |
| deleteDirectory(tempDir); |
| } |
| } |
| } |
| |
| // pick up persistent file configuration |
| @SuppressWarnings({"unchecked", "rawtypes"}) |
| public static void setPersistentFileForConfigAdmin(BundleContext bundleContext, String pid, |
| String fileName, Dictionary props) throws IOException { |
| if (pid != null) { |
| if (fileName == null) { |
| throw new IllegalArgumentException("The persistent file should not be null"); |
| } else { |
| File load = new File(fileName); |
| LOG.debug("Loading properties from OSGi config admin file: {}", load); |
| org.apache.felix.utils.properties.Properties cfg = new org.apache.felix.utils.properties.Properties(load); |
| for (Object key : cfg.keySet()) { |
| props.put(key, cfg.get(key)); |
| } |
| |
| ConfigurationAdmin configAdmin = CamelBlueprintHelper |
| .getOsgiService(bundleContext, ConfigurationAdmin.class); |
| if (configAdmin != null) { |
| // ensure we update |
| Configuration config = configAdmin.getConfiguration(pid); |
| LOG.info("Updating ConfigAdmin {} by overriding properties {}", config, props); |
| config.update(props); |
| } |
| |
| } |
| } |
| } |
| |
| public static <T> T getOsgiService(BundleContext bundleContext, Class<T> type, long timeout) { |
| return getOsgiService(bundleContext, type, null, timeout); |
| } |
| |
| public static <T> T getOsgiService(BundleContext bundleContext, Class<T> type) { |
| return getOsgiService(bundleContext, type, null, DEFAULT_TIMEOUT); |
| } |
| |
| public static <T> T getOsgiService(BundleContext bundleContext, Class<T> type, String filter) { |
| return getOsgiService(bundleContext, type, filter, DEFAULT_TIMEOUT); |
| } |
| |
| public static <T> T getOsgiService(BundleContext bundleContext, 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 = 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); |
| } |
| } |
| |
| protected static TinyBundle createTestBundle(String name, String version, String descriptors) throws FileNotFoundException, MalformedURLException { |
| TinyBundle bundle = TinyBundles.newBundle(); |
| for (URL url : getBlueprintDescriptors(descriptors)) { |
| LOG.info("Using Blueprint XML file: " + url.getFile()); |
| bundle.add("OSGI-INF/blueprint/blueprint-" + url.getFile().replace("/", "-"), url); |
| } |
| bundle.set("Manifest-Version", "2") |
| .set("Bundle-ManifestVersion", "2") |
| .set("Bundle-SymbolicName", name) |
| .set("Bundle-Version", version); |
| return bundle; |
| } |
| |
| /** |
| * 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<ServiceReference>(0) : Arrays.asList(references); |
| } |
| |
| /** |
| * Gets list of bundle descriptors. |
| * @param bundleFilter Filter expression for OSGI bundles. |
| * |
| * @return List pointers to OSGi bundles. |
| * @throws Exception If looking up the bundles fails. |
| */ |
| private static List<BundleDescriptor> getBundleDescriptors(final String bundleFilter) throws Exception { |
| return new ClasspathScanner().scanForBundles(bundleFilter); |
| } |
| |
| /** |
| * Gets the bundle descriptors as {@link URL} resources. |
| * |
| * @param descriptors the bundle descriptors, can be separated by comma |
| * @return the bundle descriptors. |
| * @throws FileNotFoundException is thrown if a bundle descriptor cannot be found |
| */ |
| private static Collection<URL> getBlueprintDescriptors(String descriptors) throws FileNotFoundException, MalformedURLException { |
| List<URL> answer = new ArrayList<URL>(); |
| String descriptor = descriptors; |
| if (descriptor != null) { |
| // there may be more resources separated by comma |
| Iterator<Object> it = ObjectHelper.createIterator(descriptor); |
| while (it.hasNext()) { |
| String s = (String) it.next(); |
| LOG.trace("Resource descriptor: {}", s); |
| |
| // remove leading / to be able to load resource from the classpath |
| s = FileUtil.stripLeadingSeparator(s); |
| |
| // if there is wildcards for *.xml then we need to find the urls from the package |
| if (s.endsWith("*.xml")) { |
| String packageName = s.substring(0, s.length() - 5); |
| // remove trailing / to be able to load resource from the classpath |
| Enumeration<URL> urls = ObjectHelper.loadResourcesAsURL(packageName); |
| while (urls.hasMoreElements()) { |
| URL url = urls.nextElement(); |
| File dir = new File(url.getFile()); |
| if (dir.isDirectory()) { |
| File[] files = dir.listFiles(); |
| if (files != null) { |
| for (File file : files) { |
| if (file.isFile() && file.exists() && file.getName().endsWith(".xml")) { |
| String name = packageName + file.getName(); |
| LOG.debug("Resolving resource: {}", name); |
| URL xmlUrl = ObjectHelper.loadResourceAsURL(name); |
| if (xmlUrl != null) { |
| answer.add(xmlUrl); |
| } |
| } |
| } |
| } |
| } |
| } |
| } else { |
| LOG.debug("Resolving resource: {}", s); |
| URL url = ResourceHelper.resolveMandatoryResourceAsUrl(RESOLVER, s); |
| if (url == null) { |
| throw new FileNotFoundException("Resource " + s + " not found"); |
| } |
| answer.add(url); |
| } |
| } |
| } else { |
| throw new IllegalArgumentException("No bundle descriptor configured. Override getBlueprintDescriptor() or getBlueprintDescriptors() method"); |
| } |
| |
| if (answer.isEmpty()) { |
| throw new IllegalArgumentException("Cannot find any resources in classpath from descriptor " + descriptors); |
| } |
| return answer; |
| } |
| |
| private static BundleDescriptor getBundleDescriptor(String path, TinyBundle bundle) throws Exception { |
| File file = new File(path); |
| // tell the JVM its okay to delete this file on exit as its a temporary file |
| // the JVM may not successfully delete the file though |
| file.deleteOnExit(); |
| |
| FileOutputStream fos = new FileOutputStream(file, false); |
| InputStream is = bundle.build(); |
| try { |
| IOHelper.copyAndCloseInput(is, fos); |
| } finally { |
| IOHelper.close(is, fos); |
| } |
| |
| BundleDescriptor answer = null; |
| FileInputStream fis = null; |
| JarInputStream jis = null; |
| try { |
| fis = new FileInputStream(file); |
| jis = new JarInputStream(fis); |
| Map<String, String> headers = new HashMap<String, String>(); |
| for (Map.Entry<Object, Object> entry : jis.getManifest().getMainAttributes().entrySet()) { |
| headers.put(entry.getKey().toString(), entry.getValue().toString()); |
| } |
| |
| answer = new BundleDescriptor( |
| bundle.getClass().getClassLoader(), |
| new URL("jar:" + file.toURI().toString() + "!/"), |
| headers); |
| } finally { |
| IOHelper.close(jis, fis); |
| } |
| |
| return answer; |
| } |
| |
| } |