diff options
author | gjoranv <gv@verizonmedia.com> | 2019-10-10 15:01:40 +0200 |
---|---|---|
committer | gjoranv <gv@verizonmedia.com> | 2019-10-16 12:13:48 +0200 |
commit | 951eeeb47ea79072cb1e77791d7c542cfed3785b (patch) | |
tree | 8a70857bfbd4520556a39e39c5628621ceb0959a | |
parent | af771506b97cf996dd7c25053c07e1cc8f9eba99 (diff) |
Implement FindHook for a consistent view of the installed bundles.
- Add FelixFramework.getBundles that takes a bundle context to
retrive bundles for.
3 files changed, 135 insertions, 12 deletions
diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/core/BundleCollisionHook.java b/jdisc_core/src/main/java/com/yahoo/jdisc/core/BundleCollisionHook.java index 6fc7c368ad1..58ad5df9b0d 100644 --- a/jdisc_core/src/main/java/com/yahoo/jdisc/core/BundleCollisionHook.java +++ b/jdisc_core/src/main/java/com/yahoo/jdisc/core/BundleCollisionHook.java @@ -7,6 +7,7 @@ import org.osgi.framework.ServiceRegistration; import org.osgi.framework.Version; import org.osgi.framework.hooks.bundle.CollisionHook; import org.osgi.framework.hooks.bundle.EventHook; +import org.osgi.framework.hooks.bundle.FindHook; import java.util.Collection; import java.util.HashMap; @@ -24,7 +25,7 @@ import java.util.Set; * * @author gjoranv */ -public class BundleCollisionHook implements CollisionHook, EventHook { +public class BundleCollisionHook implements CollisionHook, EventHook, FindHook { private ServiceRegistration<?> registration; private Map<Bundle, BsnVersion> allowedDuplicates = new HashMap<>(5); @@ -33,8 +34,8 @@ public class BundleCollisionHook implements CollisionHook, EventHook { if (registration != null) { throw new IllegalStateException(); } - registration = context.registerService(new String[]{CollisionHook.class.getName(), EventHook.class.getName()}, - this, null); + String[] serviceClasses = {CollisionHook.class.getName(), EventHook.class.getName(), FindHook.class.getName()}; + registration = context.registerService(serviceClasses, this, null); } public void stop() { @@ -47,7 +48,7 @@ public class BundleCollisionHook implements CollisionHook, EventHook { */ synchronized void allowDuplicateBundles(Collection<Bundle> bundles) { for (var bundle : bundles) { - allowedDuplicates.put(bundle, new BsnVersion(bundle.getSymbolicName(), bundle.getVersion())); + allowedDuplicates.put(bundle, new BsnVersion(bundle)); } } @@ -63,29 +64,63 @@ public class BundleCollisionHook implements CollisionHook, EventHook { } } + /** + * Removes duplicates of the allowed duplicate bundles from the given collision candidates. + */ @Override public synchronized void filterCollisions(int operationType, Bundle target, Collection<Bundle> collisionCandidates) { Set<Bundle> whitelistedCandidates = new HashSet<>(); for (var bundle : collisionCandidates) { - var bsnVersion = new BsnVersion(bundle.getSymbolicName(), bundle.getVersion()); - // This is O(n), but n should be small here, plus this is only called when bundles collide. - if (allowedDuplicates.containsValue(bsnVersion)) { + if (allowedDuplicates.containsValue(new BsnVersion(bundle))) { whitelistedCandidates.add(bundle); } } collisionCandidates.removeAll(whitelistedCandidates); } + /** + * Filters out the set of bundles that should not be visible to the bundle associated with the given context. + * If the given context represents one of the allowed duplicates, this method filters out all bundles + * that are duplicates of the allowed duplicates. Otherwise this method filters out the allowed duplicates, + * so they are not visible to other bundles. + * + * NOTE: This hook method is added for a consistent view of the installed bundles, but is not actively + * used by jdisc. The OSGi framework does not use FindHooks when calculating bundle wiring. + */ + @Override + public synchronized void find(BundleContext context, Collection<Bundle> bundles) { + Set<Bundle> bundlesToHide = new HashSet<>(); + if (allowedDuplicates.containsKey(context.getBundle())) { + for (var bundle : bundles) { + // isDuplicate... is O(n), but n should be small here, plus this is only run for duplicate bundles. + if (isDuplicateOfAllowedDuplicates(bundle)) { + bundlesToHide.add(bundle); + } + } + } else { + for (var bundle : bundles) { + if (allowedDuplicates.containsKey(bundle)) { + bundlesToHide.add(bundle); + } + } + } + bundles.removeAll(bundlesToHide); + } + + private boolean isDuplicateOfAllowedDuplicates(Bundle bundle) { + return ! allowedDuplicates.containsKey(bundle) && allowedDuplicates.containsValue(new BsnVersion(bundle)); + } + static class BsnVersion { private final String symbolicName; private final Version version; - BsnVersion(String symbolicName, Version version) { - this.symbolicName = symbolicName; - this.version = version; + BsnVersion(Bundle bundle) { + this.symbolicName = bundle.getSymbolicName(); + this.version = bundle.getVersion(); } @Override diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/core/FelixFramework.java b/jdisc_core/src/main/java/com/yahoo/jdisc/core/FelixFramework.java index 6c382153944..19a1707e97c 100644 --- a/jdisc_core/src/main/java/com/yahoo/jdisc/core/FelixFramework.java +++ b/jdisc_core/src/main/java/com/yahoo/jdisc/core/FelixFramework.java @@ -153,6 +153,10 @@ public class FelixFramework implements OsgiFramework { return Arrays.asList(felix.getBundleContext().getBundles()); } + public List<Bundle> getBundles(Bundle requestingBundle) { + return Arrays.asList(requestingBundle.getBundleContext().getBundles()); + } + public void allowDuplicateBundles(Collection<Bundle> bundles) { collisionHook.allowDuplicateBundles(bundles); } diff --git a/jdisc_core_test/integration_test/src/test/java/com/yahoo/jdisc/core/BundleCollisionHookIntegrationTest.java b/jdisc_core_test/integration_test/src/test/java/com/yahoo/jdisc/core/BundleCollisionHookIntegrationTest.java index 58c6c1ba8d6..80de2a28322 100644 --- a/jdisc_core_test/integration_test/src/test/java/com/yahoo/jdisc/core/BundleCollisionHookIntegrationTest.java +++ b/jdisc_core_test/integration_test/src/test/java/com/yahoo/jdisc/core/BundleCollisionHookIntegrationTest.java @@ -6,16 +6,22 @@ import org.junit.Before; import org.junit.Test; import org.osgi.framework.Bundle; import org.osgi.framework.BundleException; +import org.osgi.framework.launch.Framework; import java.util.Collections; +import java.util.List; +import java.util.Set; import static com.yahoo.jdisc.core.FelixFrameworkIntegrationTest.startBundle; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; /** - * Note that the '-dup' bundles are necessary, because felix ignores duplicate bundles with the same location. + * Note that the '-dup' bundles are necessary, because installing a bundle with the same location as an already + * installed bundle is a NOP. From the OSGi spec: "Every bundle is uniquely identified by its location string." * * @author gjoranv */ @@ -71,7 +77,6 @@ public class BundleCollisionHookIntegrationTest { } catch (BundleException e) { assertTrue(e.getMessage().contains("Bundle symbolic name and version are not unique")); } - } @Test @@ -85,4 +90,83 @@ public class BundleCollisionHookIntegrationTest { startBundle(felix, "cert-ml-dup.jar"); } + @Test + public void allowed_duplicates_are_visible_to_the_framework_and_vice_versa() throws Exception { + Bundle bundleA = startBundle(felix, "cert-a.jar"); + Bundle bundleB = startBundle(felix, "cert-b.jar"); + + // Makes A and B visible to each other + felix.allowDuplicateBundles(Set.of(bundleA)); + felix.allowDuplicateBundles(Set.of(bundleB)); + + List<Bundle> visibleBundles = felix.getBundles(bundleA); + + // The framework bundle should always be visible (the FindHook does not remove it) + Bundle frameworkBundle = visibleBundles.get(0); + assertTrue(isFrameworkBundle(frameworkBundle)); + + // All bundles are always visible to the framework (this is according to the OSGi spec and handled by Felix) + List<Bundle> visibleToFramework = felix.getBundles(frameworkBundle); + assertEquals(3, visibleToFramework.size()); + } + + @Test + public void allowed_duplicates_are_visible_to_its_own_members() throws Exception { + Bundle bundleA = startBundle(felix, "cert-a.jar"); + Bundle bundleB = startBundle(felix, "cert-b.jar"); + + // Makes A and B visible to each other + felix.allowDuplicateBundles(Set.of(bundleA)); + felix.allowDuplicateBundles(Set.of(bundleB)); + + List<Bundle> visibleBundles = felix.getBundles(bundleA); + assertEquals(3, visibleBundles.size()); + assertSame(bundleA, visibleBundles.get(1)); + assertSame(bundleB, visibleBundles.get(2)); + } + + @Test + public void allowed_duplicates_are_invisible_to_unrelated_bundles() throws Exception { + Bundle bundleL = startBundle(felix, "cert-l1.jar"); + Bundle bundleA = startBundle(felix, "cert-a.jar"); + + // Makes L invisible to bundles outside the set of allowed duplicates + felix.allowDuplicateBundles(Set.of(bundleL)); + + List<Bundle> visibleBundles = felix.getBundles(bundleA); + assertEquals(2, visibleBundles.size()); + assertTrue(isFrameworkBundle(visibleBundles.get(0))); + assertSame(bundleA, visibleBundles.get(1)); + } + + @Test + public void set_of_allowed_duplicates_are_invisible_to_all_bundles_of_which_they_are_duplicates() throws Exception { + Bundle bundleL = startBundle(felix, "cert-l1.jar"); + + // Makes L invisible to bundles outside the set of allowed duplicates + felix.allowDuplicateBundles(Set.of(bundleL)); + Bundle bundleL2 = startBundle(felix, "cert-l1-dup.jar"); + + List<Bundle> visibleBundles = felix.getBundles(bundleL2); + assertEquals(2, visibleBundles.size()); + assertSame(bundleL2, visibleBundles.get(1)); + } + + @Test + public void allowed_duplicates_cannot_see_any_of_the_bundles_of_which_they_are_duplicates() throws Exception { + Bundle bundleL = startBundle(felix, "cert-l1.jar"); + + // Makes L invisible to bundles outside the set of allowed duplicates + felix.allowDuplicateBundles(Set.of(bundleL)); + Bundle invisibleToAllowedDuplicates = startBundle(felix, "cert-l1-dup.jar"); + + List<Bundle> visibleToAllowedDuplicates = felix.getBundles(bundleL); + assertEquals(2, visibleToAllowedDuplicates.size()); + assertSame(bundleL, visibleToAllowedDuplicates.get(1)); + } + + private boolean isFrameworkBundle(Bundle bundle) { + return (bundle == felix.bundleContext().getBundle() && (bundle instanceof Framework)); + } + } |