All Downloads are FREE. Search and download functionalities are using the official Maven repository.

aQute.launchpad.Launchpad Maven / Gradle / Ivy

The newest version!
package aQute.launchpad;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.eclipse.jdt.annotation.Nullable;
import org.osgi.annotation.versioning.ProviderType;
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.FrameworkEvent;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceEvent;
import org.osgi.framework.ServiceException;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
import org.osgi.framework.hooks.service.EventListenerHook;
import org.osgi.framework.hooks.service.FindHook;
import org.osgi.framework.hooks.service.ListenerHook.ListenerInfo;
import org.osgi.framework.launch.Framework;
import org.osgi.framework.namespace.PackageNamespace;
import org.osgi.framework.wiring.BundleCapability;
import org.osgi.framework.wiring.BundleWiring;
import org.osgi.framework.wiring.FrameworkWiring;
//import org.osgi.service.component.runtime.ServiceComponentRuntime;
//import org.osgi.service.component.runtime.dto.ComponentDescriptionDTO;
import org.osgi.util.tracker.ServiceTracker;

import aQute.bnd.exceptions.Exceptions;
import aQute.bnd.service.remoteworkspace.RemoteWorkspace;
import aQute.bnd.service.specifications.RunSpecification;
import aQute.launchpad.internal.ProbeImpl;
import aQute.lib.converter.Converter;
import aQute.lib.inject.Injector;
import aQute.lib.io.IO;
import aQute.lib.startlevel.StartLevelRuntimeHandler;
import aQute.lib.strings.Strings;
import aQute.libg.glob.Glob;
import aQute.libg.parameters.ParameterMap;

/**
 * This class provides an OSGi framework that is configured with the current bnd
 * workspace. A project directory is used to find the workspace. This makes all
 * repositories in the workspace available to the framework. To be able to test
 * JUnit code against/in this framework it is necessary that all packages on the
 * buildpath and testpath are actually exported in the framework. This class
 * will ensure that. Once the framework is up and running it will be possible to
 * add bundles to it. There are a number of ways that this can be achieved:
 * 
    *
  • Build a bundle – A bnd Builder is provided to create a bundle and install * it. This makes it possible to add classes from the src or test directories or * resources. See {@link #bundle()}. Convenience methods are added to get * services, see {@link #getService(Class)} et. al. Notice that this framework * starts in the same process as that the JUnit code runs. This is normally a * separately started VM. */ @ProviderType public class Launchpad implements AutoCloseable { public static final String BUNDLE_PRIORITY = "Bundle-Priority"; private static final long SERVICE_DEFAULT_TIMEOUT = 60000L; final AtomicInteger counter = new AtomicInteger(); final File projectDir; final Framework framework; final List> trackers = new ArrayList<>(); final List frameworkEvents = new CopyOnWriteArrayList<>(); final Injector injector; final Map, ServiceTracker> injectedDoNotClose = new ConcurrentHashMap<>(); final Set frameworkExports; final List errors = new ArrayList<>(); final String name; final String className; final RunSpecification runspec; final boolean hasTestBundle; final StartLevelRuntimeHandler startlevels; final RemoteWorkspace workspace; Bundle testbundle; boolean debug; final boolean byReference; PrintStream out = System.err; ServiceTracker hooks; private long closeTimeout; private Bundle proxyBundle; private Probe probe = new ProbeImpl(); Launchpad(RemoteWorkspace workspace, Framework framework, String name, String className, RunSpecification runspec, long closeTimeout, boolean debug, boolean hasTestBundle, boolean byReference) { this.workspace = workspace; this.runspec = runspec; this.closeTimeout = closeTimeout; this.hasTestBundle = hasTestBundle; this.byReference = byReference; this.proxyBundle = framework; this.startlevels = StartLevelRuntimeHandler.create(this::report, this.runspec.properties); try { this.className = className; this.name = name; this.projectDir = IO.work; this.debug = debug; this.framework = framework; this.framework.init(); this.injector = new Injector<>(makeConverter(), this::getService, Service.class); this.frameworkExports = getExports(framework).keySet(); report("Initialized framework %s", this.framework); report("Classpath %s", System.getProperty("java.class.path") .replace(File.pathSeparatorChar, '\n')); framework.getBundleContext() .addFrameworkListener(frameworkEvents::add); hooks = new ServiceTracker<>(framework.getBundleContext(), FindHook.class, null); hooks.open(); startlevels.beforeStart(framework); } catch (Exception e) { throw Exceptions.duck(e); } } public void report(String format, Object... args) { if (!debug) return; out.printf(format + "%n", args); } /** * Generate an error so that the test case can check if we found anything * wrong. This is easy to check with {@link #check(String...)} * * @param format the format string used in * {@link String#format(String, Object...)} * @param args the arguments to be formatted */ public void error(String format, Object... args) { report(format, args); String msg = String.format(format, args); errors.add(msg); } /** * Check the errors found, filtering out any unwanted with globbing patters. * Each error is filtered against all the patterns. This method return true * if there are no unfiltered errors, otherwise false. * * @param patterns globbing patterns * @return true if no errors after filtering,otherwise false */ public boolean check(String... patterns) { Glob[] globs = Stream.of(patterns) .map(Glob::new) .toArray(Glob[]::new); boolean[] used = new boolean[globs.length]; String[] unmatched = errors.stream() .filter(msg -> { for (int i = 0; i < globs.length; i++) { if (globs[i].finds(msg) >= 0) { used[i] = true; return false; } } return true; }) .toArray(String[]::new); if (unmatched.length == 0) { List report = new ArrayList<>(); for (int i = 0; i < used.length; i++) { if (!used[i]) { report.add(globs[i]); } } if (report.isEmpty()) return true; out.println("Missing patterns"); out.println(Strings.join("\n", globs)); return false; } out.println("Errors"); out.println(Strings.join("\n", unmatched)); return false; } /** * Add a file as a bundle to the framework. This bundle will not be started. * * @param f the file to install * @return the bundle object */ public Bundle bundle(File f) { try { report("Installing %s", f); return framework.getBundleContext() .installBundle(toInstallURI(f)); } catch (Exception e) { report("Failed to installing %s : %s", f, e); throw Exceptions.duck(e); } } /** * Set the debug flag */ public Launchpad debug() { this.debug = true; return this; } /** * Install a number of bundles based on their bundle specification. A bundle * specification is the format used in for example -runbundles. * * @param specification the bundle specifications * @return a list of bundles */ public List bundles(String specification) { try { return workspace.getLatestBundles(projectDir.getAbsolutePath(), specification) .stream() .map(File::new) .map(this::bundle) .collect(Collectors.toList()); } catch (Exception e) { throw Exceptions.duck(e); } } /** * Install a number of bundles * * @param runbundles the list of bundles * @return a list of bundle objects */ public List bundles(File... runbundles) { if (runbundles == null || runbundles.length == 0) return Collections.emptyList(); try { List bundles = new ArrayList<>(); for (File f : runbundles) { Bundle b = bundle(f); bundles.add(b); } return bundles; } catch (Exception e) { throw Exceptions.duck(e); } } /** * Start a bundle * * @param b the bundle object */ public void start(Bundle b) { try { if (!isFragment(b)) { report("Starting %s", b); b.start(); Set exports = getExports(b).keySet(); Set imports = getImports(b).keySet(); exports.removeAll(imports); exports.retainAll(frameworkExports); if (!exports.isEmpty()) { error( "bundle %s is exporting but NOT importing package(s) %s that are/is also exported by the framework.\n" + "This means that the test code and the bundle cannot share classes of these package.", b, exports); } } else { report("Not starting fragment %s", b); } } catch (Exception e) { report("Failed to start %s : %s", b, e); throw Exceptions.duck(e); } } /** * Start all bundles * * @param bs a collection of bundles */ public void start(Collection bs) { bs.forEach(this::start); } /** * Close this framework */ @Override public void close() throws Exception { startlevels.close(); report("Stop the framework"); framework.stop(); report("Stopped the framework"); framework.waitForStop(closeTimeout); report("Framework fully stopped"); } /** * Get the Bundle Context. If a test bundle was installed then this is the * context of the test bundle otherwise it is the context of the framework. * To be able to proxy services it is necessary to have a test bundle * installed. * * @return the bundle context of the test bundle or the framework */ public BundleContext getBundleContext() { if (testbundle != null) return testbundle.getBundleContext(); return framework.getBundleContext(); } /** * Get a service registered under class. If multiple services are registered * it will return the first * * @param serviceInterface the name of the service * @return a service */ public Optional getService(Class serviceInterface) { return getService(serviceInterface, null); } public Optional getService(Class serviceInterface, @Nullable String target) { return getServices(serviceInterface, target, 0, 0, false).stream() .map(this::getService) .findFirst(); } /** * Get a list of services of a given name * * @param serviceClass the service name * @return a list of services */ public List getServices(Class serviceClass) { return getServices(serviceClass, null); } /** * Get a list of services in the current registry * * @param serviceClass the type of the service * @param target the target, may be null * @return a list of found services currently in the registry */ public List getServices(Class serviceClass, @Nullable String target) { return getServices(serviceClass, target, 0, 0, false).stream() .map(this::getService) .collect(Collectors.toList()); } /** * Get a service from a reference. If the service is null, then throw an * exception. * * @param ref the reference * @return the service, never null */ public T getService(ServiceReference ref) { try { T service = getBundleContext().getService(ref); if (service == null) { if (ref.getBundle() == null) { throw new ServiceException( "getService(" + ref + ") returns null, the service is no longer registered"); } throw new ServiceException("getService(" + ref + ") returns null, this probbaly means the \n" + "component failed to activate. The cause can \n" + "generally be found in the log.\n" + ""); } return service; } catch (Exception e) { throw Exceptions.duck(e); } } /** * Add the standard Gogo bundles */ public Launchpad gogo() { try { bundles("org.apache.felix.gogo.runtime,org.apache.felix.gogo.command,org.apache.felix.gogo.shell"); return this; } catch (Exception e) { throw Exceptions.duck(e); } } /** * Add the standard Gogo bundles */ public Launchpad snapshot() { try { bundles("biz.aQute.bnd.runtime.snapshot"); return this; } catch (Exception e) { throw Exceptions.duck(e); } } /** * Inject an object with services and other OSGi specific values. * * @param object the object to inject */ public Launchpad inject(Object object) { try { injector.inject(object); return this; } catch (Exception e) { throw Exceptions.duck(e); } } /** * Install a bundle from a file * * @param file the file to install * @return a bundle */ public Bundle install(File file) { try { if (byReference) { String installURI = toInstallURI(file); report("Installing %s", installURI); return framework.getBundleContext() .installBundle(installURI); } try (InputStream fin = IO.stream(file)) { return framework.getBundleContext() .installBundle("-> " + file, fin); } catch (FileNotFoundException e) { report("Failed to install %s because file could not be found", file); throw Exceptions.duck(e); } catch (IOException e) { report("Failed to install %s because %s", file, e.getMessage()); throw Exceptions.duck(e); } } catch (BundleException e) { report("Failed to install %s : %s", file, e); throw Exceptions.duck(e); } } /** * Create a new synthetic bundle. * * @return the bundle builder */ public BundleBuilder bundle() { return new BundleBuilder(this); } /** * Create a new object and inject it. * * @param type the type of object * @return a new object injected and all */ public T newInstance(Class type) { try { return injector.newInstance(type); } catch (Exception e) { report("Failed to create and instance for %s : %s", type, e); throw Exceptions.duck(e); } } /** * Show the information of how the framework is setup and is running */ public Launchpad report() throws InvalidSyntaxException { boolean old = debug; debug = true; reportBundles(); reportServices(); reportEvents(); debug = old; return this; } /** * Show the installed bundles */ public void reportBundles() { Stream.of(framework.getBundleContext() .getBundles()) .forEach(bb -> { report("%4s %4s %s", bundleStateToString(bb.getState()), startlevels.getBundleStartLevel(bb), bb); }); } /** * Show the registered service */ public void reportServices() throws InvalidSyntaxException { Stream.of(framework.getBundleContext() .getAllServiceReferences(null, null)) .forEach(sref -> { report("%s", sref); }); } /** * Wait for a Service Reference to be registered * * @param class1 the name of the service * @param timeoutInMs the time to wait * @return a service reference */ public Optional> waitForServiceReference(Class class1, long timeoutInMs) { return getServices(class1, null, 1, timeoutInMs, false).stream() .findFirst(); } /** * Wait for a Service Reference to be registered * * @param class1 the name of the service * @param timeoutInMs the time to wait * @return a service reference */ public Optional> waitForServiceReference(Class class1, long timeoutInMs, String target) { return getServices(class1, target, 1, timeoutInMs, false).stream() .findFirst(); } /** * Wait for service to be registered * * @param class1 name of the service * @param timeoutInMs timeout in ms * @return a service */ public Optional waitForService(Class class1, long timeoutInMs) { return this.waitForService(class1, timeoutInMs, null); } /** * Wait for service to be registered * * @param class1 name of the service * @param timeoutInMs timeout in ms * @param target filter, may be null * @return a service */ public Optional waitForService(Class class1, long timeoutInMs, String target) { try { return getServices(class1, target, 1, timeoutInMs, false).stream() .findFirst() .map(getBundleContext()::getService); } catch (Exception e) { throw Exceptions.duck(e); } } /** * Turn a service reference's properties into a Map * * @param reference the reference * @return a Map with all the properties of the reference */ public Map toMap(ServiceReference reference) { Map map = new HashMap<>(); for (String key : reference.getPropertyKeys()) { map.put(key, reference.getProperty(key)); } return map; } /** * Get a bundle by symbolic name */ public Optional getBundle(String bsn) { return Stream.of(getBundleContext().getBundles()) .filter(b -> bsn.equals(b.getSymbolicName())) .findFirst(); } /** * Broadcast a message to many services at once */ @SuppressWarnings("unchecked") public int broadcast(Class type, Consumer consumer) { ServiceTracker tracker = new ServiceTracker<>(getBundleContext(), type, null); tracker.open(); int n = 0; try { for (T instance : (T[]) tracker.getServices()) { consumer.accept(instance); n++; } } finally { tracker.close(); } return n; } /** * Hide a service by registering a hook. This should in general be done * before you let others look. In general, the Launchpad should be started * in {@link LaunchpadBuilder#nostart()} mode. This initializes the OSGi * framework making it possible to register a service before */ public Closeable hide(Class type) { return hide(type, "hide"); } /** * Hide a service. This will register a FindHook and an EventHook for the * type. This will remove the visibility of all services with that type for * all bundles _except_ the testbundle. Notice that bundles that already * obtained a references are not affected. If you use this facility it is * best to not start the framework before you hide a service. You can * indicate this to the build with {@link LaunchpadBuilder#nostart()}. The * framework can be started after creation with {@link #start()}. Notice * that services through the testbundle remain visible for this hide. * * @param type the type to hide * @param reason the reason why it is hidden * @return a Closeable, when closed it will remove the hooks */ public Closeable hide(Class type, String reason) { ServiceRegistration eventReg = framework.getBundleContext() .registerService(EventListenerHook.class, new EventListenerHook() { @Override public void event(ServiceEvent event, Map> listeners) { ServiceReference ref = event.getServiceReference(); if (selectForHiding(type, ref)) listeners.clear(); } @Override public String toString() { return "Launchpad[" + reason + "]"; } }, null); ServiceRegistration findReg = framework.getBundleContext() .registerService(FindHook.class, new FindHook() { @Override public void find(BundleContext context, String name, String filter, boolean allServices, Collection> references) { if (name == null || name.equals(type.getName())) { references.removeIf(ref -> selectForHiding(type, ref)); } } @Override public String toString() { return "Launchpad[" + reason + "]"; } }, null); return () -> { eventReg.unregister(); findReg.unregister(); }; } /** * Check of a service reference has one of the given types in its object * class * * @param serviceReference the service reference to check * @param types the set of types * @return true if one of the types name is in the service reference's * objectClass property */ public boolean isOneOfType(ServiceReference serviceReference, Class... types) { String[] objectClasses = (String[]) serviceReference.getProperty(Constants.OBJECTCLASS); for (Class type : types) { String name = type.getName(); for (String objectClass : objectClasses) { if (objectClass.equals(name)) return true; } } return false; } private boolean selectForHiding(Class type, ServiceReference ref) { // // We never hide services registered by the testbundle // if (ref.getBundle() == testbundle) return false; // only hide references when one of their // service interfaces is of the hidden type return isOneOfType(ref, type); } /** * Start the framework if not yet started */ public void start() { try { framework.start(); List toBeStarted = new ArrayList<>(); for (String path : runspec.runbundles) { File file = new File(path); if (!file.isFile()) throw new IllegalArgumentException("-runbundle " + file + " does not exist or is not a file"); Bundle b = install(file); if (!isFragment(b)) { toBeStarted.add(b); } } FrameworkWiring fw = framework.adapt(FrameworkWiring.class); fw.resolveBundles(toBeStarted); Collections.sort(toBeStarted, this::startorder); if (hasTestBundle) testbundle(); toBeStarted.forEach(this::start); startlevels.afterStart(); } catch (BundleException e) { throw Exceptions.duck(e); } } // reverse ordering. I.e. highest priority is first int startorder(Bundle a, Bundle b) { return Integer.compare(getPriority(b), getPriority(a)); } private int getPriority(Bundle b) { try { String h = b.getHeaders() .get(BUNDLE_PRIORITY); if (h != null) return Integer.parseInt(h); } catch (Exception e) { // ignore } return 0; } /** * Stop the framework if not yet stopped */ public void stop() { try { report("Stopping the framework"); framework.stop(); } catch (BundleException e) { report("Could not stop the framework : %s", e); throw Exceptions.duck(e); } } /** * Set the test bundle */ public void testbundle() { if (testbundle != null) { throw new IllegalArgumentException("Test bundle already exists"); } try { ByteArrayOutputStream bout = new ByteArrayOutputStream(); Manifest man = new Manifest(); man.getMainAttributes() .putValue("Manifest-Version", "1"); String name = projectDir.getName() .toUpperCase(); report("Creating test bundle %s", name); man.getMainAttributes() .putValue(Constants.BUNDLE_SYMBOLICNAME, name); man.getMainAttributes() .putValue(Constants.BUNDLE_MANIFESTVERSION, "2"); JarOutputStream jout = new JarOutputStream(bout, man); jout.close(); ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray()); this.testbundle = framework.getBundleContext() .installBundle(name, bin); this.testbundle.start(); } catch (Exception e) { report("Failed to create test bundle"); throw Exceptions.duck(e); } } /** * Register a service. You can specify the type and the instance as well as * the properties. The properties are specified as varargs. That means you * can define a property by specifying the key (which must be a String) and * the value consecutively. The value can be any of the types allowed by the * service properties. * *
    	 * fw.register(Foo.class, instance, "foo", 10, "bar", new long[] {
    	 * 	1, 2, 3
    	 * });
    	 * 
    * * @param type the service type * @param instance the service object * @param props the service properties specified as a seq of "key", value * @return the service registration */ public ServiceRegistration register(Class type, T instance, Object... props) { report("Registering service %s %s", type, instance, Arrays.toString(props)); Hashtable ht = new Hashtable<>(); for (int i = 0; i < props.length; i += 2) { String key = (String) props[i]; Object value = null; if (i + 1 < props.length) { value = props[i + 1]; } ht.put(key, value); } return getBundleContext().registerService(type, instance, ht); } /** * Return the framework object * * @return the framework object */ public Framework getFramework() { return framework; } /** * Add a component class. This creates a little bundle that holds the * component class so that bnd adds the DS XML. However, it also imports the * package of the component class so that in runtime DS will load it from * the classpath. */ public Bundle component(Class type) { return bundle().addResource(type) .start(); } /** * Runs the given code within the context of a synthetic bundle. Creates a * synthetic bundle and adds the supplied class to it using * {@link BundleBuilder#addResourceWithCopy}. It then loads the class using * the synthetic bundle's class loader and instantiates it using the public, * no-parameter constructor. * * @param clazz the class to instantiate within the context of the * framework. * @return The instantiated object. * @see BundleBuilder#addResourceWithCopy(Class) */ public T instantiateInFramework(Class clazz) { try { clazz.getConstructor(); } catch (NoSuchMethodException e) { throw Exceptions.duck(e); } Bundle b = bundle().addResourceWithCopy(clazz) .start(); try { @SuppressWarnings("unchecked") Class insideClass = (Class) b.loadClass(clazz.getName()); return insideClass.getConstructor() .newInstance(); } catch (Exception e) { throw Exceptions.duck(e); } } /** * Check if a bundle is a fragement * * @param b the bundle to check */ public boolean isFragment(Bundle b) { return b.getHeaders() .get(Constants.FRAGMENT_HOST) != null; } /** * Check if a bundle is in the ACTIVE state * * @param b the bundle to check */ public boolean isActive(Bundle b) { return b.getState() == Bundle.ACTIVE; } /** * Check if a bundle is in the RESOLVED state * * @param b the bundle to check */ public boolean isResolved(Bundle b) { return b.getState() == Bundle.RESOLVED; } /** * Check if a bundle is in the INSTALLED state * * @param b the bundle to check */ public boolean isInstalled(Bundle b) { return b.getState() == Bundle.INSTALLED; } /** * Check if a bundle is in the UNINSTALLED state * * @param b the bundle to check */ public boolean isUninstalled(Bundle b) { return b.getState() == Bundle.UNINSTALLED; } /** * Check if a bundle is in the STARTING state * * @param b the bundle to check */ public boolean isStarting(Bundle b) { return b.getState() == Bundle.STARTING; } /** * Check if a bundle is in the STOPPING state * * @param b the bundle to check */ public boolean isStopping(Bundle b) { return b.getState() == Bundle.STOPPING; } /** * Check if a bundle is in the ACTIVE or STARTING state * * @param b the bundle to check */ public boolean isRunning(Bundle b) { return isActive(b) || isStarting(b); } /** * Check if a bundle is in the RESOLVED or ACTIVE or STARTING state * * @param b the bundle to check */ public boolean isReady(Bundle b) { return isResolved(b) || isActive(b) || isStarting(b); } private ParameterMap getExports(Bundle b) { return new ParameterMap(b.getHeaders() .get(Constants.EXPORT_PACKAGE)); } private ParameterMap getImports(Bundle b) { return new ParameterMap(b.getHeaders() .get(Constants.IMPORT_PACKAGE)); } private String toInstallURI(File c) { if (byReference) return "reference:" + c.toURI(); return c.toURI() .toString(); } Object getService(Injector.Target param) { try { if (param.type == Launchpad.class) { return this; } if (param.type == BundleContext.class) { return getBundleContext(); } if (param.type == Bundle.class) return testbundle; if (param.type == Framework.class) { return framework; } if (param.type == Bundle[].class) { return framework.getBundleContext() .getBundles(); } Service service = param.annotation; String target = service.target() .isEmpty() ? null : service.target(); Class serviceClass = service.service(); if (serviceClass == Object.class) serviceClass = getServiceType(param.type); if (serviceClass == null) serviceClass = getServiceType(param.primaryType); if (serviceClass == null) throw new IllegalArgumentException("Cannot define service class for " + param); long timeout = service.timeout(); if (timeout <= 0) timeout = SERVICE_DEFAULT_TIMEOUT; boolean multiple = isMultiple(param.type); int cardinality = multiple ? service.minimum() : 1; List> matchedReferences = getServices(serviceClass, target, cardinality, timeout, true); if (multiple) return matchedReferences; else return matchedReferences.get(0); } catch (Exception e) { throw Exceptions.duck(e); } } @SuppressWarnings({ "rawtypes", "unchecked" }) public List> getServices(Class serviceClass, @Nullable String target, int cardinality, long timeout, boolean exception) { try { String className = serviceClass.getName(); ServiceTracker tracker = injectedDoNotClose.computeIfAbsent(serviceClass, c -> { ServiceTracker t = new ServiceTracker<>(framework.getBundleContext(), className, null); t.open(true); return t; }); final long startNanos = System.nanoTime(); timeout = TimeUnit.MILLISECONDS.toNanos(timeout); while (true) { // we get the ALL services regardless of class space or hidden // by hooks or filters. @SuppressWarnings("unchecked") List> allReferences = (List>) getReferences(tracker, serviceClass); List> visibleReferences = allReferences.stream() .filter(ref -> ref.isAssignableTo(proxyBundle, className)) .collect(Collectors.toList()); List> unhiddenReferences = new ArrayList<>(visibleReferences); Map, FindHook> hookMap = new HashMap<>(); for (FindHook hook : this.hooks.getServices(new FindHook[0])) { List> original = new ArrayList<>(unhiddenReferences); hook.find(getBundleContext(), className, target, true, (Collection) unhiddenReferences); original.removeAll(unhiddenReferences); for (ServiceReference ref : original) { hookMap.put(ref, hook); } } List> matchedReferences; if (target == null) { matchedReferences = new ArrayList<>(unhiddenReferences); } else { Filter filter = framework.getBundleContext() .createFilter(target); matchedReferences = visibleReferences.stream() .filter(filter::match) .collect(Collectors.toList()); } if (cardinality <= matchedReferences.size()) { return matchedReferences; } long elapsed = System.nanoTime() - startNanos; if (elapsed > timeout) { String error = "Injection of service " + className; if (target != null) error += " with target " + target; error += " failed."; if (allReferences.size() > visibleReferences.size()) { List> invisibleReferences = new ArrayList<>(allReferences); invisibleReferences.removeAll(visibleReferences); for (ServiceReference r : invisibleReferences) { error += "\nInvisible reference " + r + "[" + r.getProperty(Constants.SERVICE_ID) + "] from bundle " + r.getBundle(); String[] objectClass = (String[]) r.getProperty(Constants.OBJECTCLASS); for (String clazz : objectClass) { error += "\n " + clazz + "\n registrar: " + getSource(clazz, r.getBundle()).orElse("null") + "\n proxybundle: " + getSource(clazz, proxyBundle).orElse("null"); } } } if (visibleReferences.size() > unhiddenReferences.size()) { List> hiddenReferences = new ArrayList<>(visibleReferences); hiddenReferences.removeAll(unhiddenReferences); for (ServiceReference r : hiddenReferences) { error += "\nHidden (FindHook) Reference " + r + " from bundle " + r.getBundle() + " hook " + hookMap.get(r); } } if (unhiddenReferences.size() > matchedReferences.size()) { List> untargetReferences = new ArrayList<>(unhiddenReferences); untargetReferences.removeAll(matchedReferences); error += "\nReference not matched by the target filter " + target; for (ServiceReference ref : untargetReferences) { error += "\n " + ref + " : " + getProperties(ref); } } if (exception && timeout > 1L) throw new TimeoutException(error); return Collections.emptyList(); } Thread.sleep(100); } } catch (Exception e) { throw Exceptions.duck(e); } } private Map getProperties(ServiceReference ref) { Map map = new HashMap<>(); for (String k : ref.getPropertyKeys()) { Object property = ref.getProperty(k); String s; if (property != null && property.getClass() .isArray()) { s = Arrays.deepToString((Object[]) property); } else s = property + ""; map.put(k, s); } return map; } private Optional getSource(String className, Bundle from) { try { Class loadClass = from.loadClass(className); Bundle bundle = FrameworkUtil.getBundle(loadClass); if (bundle == null) return Optional.of("from class path"); else { BundleWiring wiring = bundle.adapt(BundleWiring.class); String exported = "PRIVATE! "; List capabilities = wiring.getCapabilities(PackageNamespace.PACKAGE_NAMESPACE); String packageName = loadClass.getPackage() .getName(); for (BundleCapability c : capabilities) { if (packageName.equals(c.getAttributes() .get(PackageNamespace.PACKAGE_NAMESPACE))) { exported = "Exported from "; } } return Optional.of(exported + " " + bundle.toString()); } } catch (Exception e) { return Optional.empty(); } } void reportEvents() { frameworkEvents.forEach(fe -> { report("%s", fe); }); } private String bundleStateToString(int state) { switch (state) { case Bundle.UNINSTALLED : return "UNIN"; case Bundle.INSTALLED : return "INST"; case Bundle.RESOLVED : return "RSLV"; case Bundle.STARTING : return "STAR"; case Bundle.ACTIVE : return "ACTV"; case Bundle.STOPPING : return "STOP"; default : return "UNKN"; } } private List> getReferences(ServiceTracker tracker, Class serviceClass) { ServiceReference[] references = tracker.getServiceReferences(); if (references == null) { return Collections.emptyList(); } Arrays.sort(references); return Arrays.asList(references); } private Class getServiceType(Type type) { if (type instanceof Class) return (Class) type; if (type instanceof ParameterizedType) { Type rawType = ((ParameterizedType) type).getRawType(); if (rawType instanceof Class) { Class rawClass = (Class) rawType; if (Iterable.class.isAssignableFrom(rawClass)) { return getServiceType(((ParameterizedType) type).getActualTypeArguments()[0]); } if (Optional.class.isAssignableFrom(rawClass)) { return getServiceType(((ParameterizedType) type).getActualTypeArguments()[0]); } if (ServiceReference.class.isAssignableFrom(rawClass)) { return getServiceType(((ParameterizedType) type).getActualTypeArguments()[0]); } } } return null; } private boolean isMultiple(Type type) { if (type instanceof Class) { return ((Class) type).isArray(); } if (type instanceof ParameterizedType) { Type rawType = ((ParameterizedType) type).getRawType(); if (rawType instanceof Class) { Class clazz = (Class) rawType; if (Iterable.class.isAssignableFrom(clazz)) return true; } } return false; } private boolean isParameterizedType(Type to, Class clazz) { if (to instanceof ParameterizedType) { if (((ParameterizedType) to).getRawType() == clazz) return true; } return false; } private Converter makeConverter() { Converter converter = new Converter(); converter.hook(null, (to, from) -> { try { if (!(from instanceof ServiceReference)) return null; ServiceReference reference = (ServiceReference) from; if (isParameterizedType(to, ServiceReference.class)) return reference; if (isParameterizedType(to, Map.class)) return converter.convert(to, toMap(reference)); Object service = getService(reference); if (isParameterizedType(to, Optional.class)) return Optional.ofNullable(service); return service; } catch (Exception e) { throw e; } }); return converter; } public String getName() { return name; } public String getClassName() { return className; } public Closeable enable(Class componentClass) { return probe.enable(componentClass); } public void setProxyBundle(Bundle tb) { this.proxyBundle = tb; } public Launchpad setProbe(Probe probe) { try { inject(probe); } catch (NoClassDefFoundError e) { // ignore return this; } catch (Exception e) { // ignore return this; } this.probe = probe; return this; } public void sync() { startlevels.sync(); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy