FELIX-4085 : [Core R5] Implement updates to the Bundle Hook Specification. Apply patch from David Bosschaert
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1549745 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/framework/src/main/java/org/apache/felix/framework/BundleImpl.java b/framework/src/main/java/org/apache/felix/framework/BundleImpl.java
index be8447d..4a2ecfe 100644
--- a/framework/src/main/java/org/apache/felix/framework/BundleImpl.java
+++ b/framework/src/main/java/org/apache/felix/framework/BundleImpl.java
@@ -26,8 +26,8 @@
import java.util.*;
import org.apache.felix.framework.cache.BundleArchive;
-import org.apache.felix.framework.ext.SecurityProvider;
import org.apache.felix.framework.util.SecurityManagerEx;
+import org.apache.felix.framework.util.ShrinkableCollection;
import org.apache.felix.framework.util.StringMap;
import org.apache.felix.framework.util.Util;
import org.osgi.framework.AdaptPermission;
@@ -40,6 +40,7 @@
import org.osgi.framework.ServicePermission;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.Version;
+import org.osgi.framework.hooks.bundle.CollisionHook;
import org.osgi.framework.startlevel.BundleStartLevel;
import org.osgi.framework.wiring.BundleRevision;
import org.osgi.framework.wiring.BundleRevisions;
@@ -93,7 +94,7 @@
m_activator = null;
m_context = null;
- BundleRevision revision = createRevision();
+ BundleRevision revision = createRevision(false);
addRevision(revision);
}
@@ -1138,7 +1139,7 @@
m_archive.revise(location, is);
try
{
- BundleRevision revision = createRevision();
+ BundleRevision revision = createRevision(true);
addRevision(revision);
}
catch (Exception ex)
@@ -1189,7 +1190,7 @@
}
}
- private BundleRevision createRevision() throws Exception
+ private BundleRevision createRevision(boolean isUpdate) throws Exception
{
// Get and parse the manifest from the most recent revision and
// create an associated revision object for it.
@@ -1208,7 +1209,7 @@
String allowMultiple =
(String) getFramework().getConfig().get(Constants.FRAMEWORK_BSNVERSION);
allowMultiple = (allowMultiple == null)
- ? Constants.FRAMEWORK_BSNVERSION_SINGLE
+ ? Constants.FRAMEWORK_BSNVERSION_MANAGED
: allowMultiple;
if (revision.getManifestVersion().equals("2")
&& !allowMultiple.equals(Constants.FRAMEWORK_BSNVERSION_MULTIPLE))
@@ -1217,25 +1218,44 @@
bundleVersion = (bundleVersion == null) ? Version.emptyVersion : bundleVersion;
String symName = revision.getSymbolicName();
+ List<Bundle> collisionCanditates = new ArrayList<Bundle>();
Bundle[] bundles = getFramework().getBundles();
for (int i = 0; (bundles != null) && (i < bundles.length); i++)
{
long id = ((BundleImpl) bundles[i]).getBundleId();
if (id != getBundleId())
{
- String sym = bundles[i].getSymbolicName();
- Version ver = bundles[i].getVersion();
- if ((symName != null)
- && (sym != null)
- && symName.equals(sym)
- && bundleVersion.equals(ver))
+ if (symName.equals(bundles[i].getSymbolicName()) &&
+ bundleVersion.equals(bundles[i].getVersion()))
+ {
+ collisionCanditates.add(bundles[i]);
+ }
+ }
+ }
+ if (!collisionCanditates.isEmpty() && allowMultiple.equals(Constants.FRAMEWORK_BSNVERSION_MANAGED))
+ {
+ Set<ServiceReference<CollisionHook>> hooks = getFramework().getHooks(CollisionHook.class);
+ if (!hooks.isEmpty())
+ {
+ Collection<Bundle> shrinkableCollisionCandidates = new ShrinkableCollection<Bundle>(collisionCanditates);
+ for (ServiceReference<CollisionHook> hook : hooks)
{
- throw new BundleException(
- "Bundle symbolic name and version are not unique: "
- + sym + ':' + ver, BundleException.DUPLICATE_BUNDLE_ERROR);
+ CollisionHook ch = getFramework().getService(getFramework(), hook);
+ if (ch != null)
+ {
+ Felix.m_secureAction.invokeBundleCollisionHook(ch,
+ isUpdate ? CollisionHook.UPDATING : CollisionHook.INSTALLING,
+ this, shrinkableCollisionCandidates);
+ }
}
}
}
+ if (!collisionCanditates.isEmpty())
+ {
+ throw new BundleException(
+ "Bundle symbolic name and version are not unique: "
+ + symName + ':' + bundleVersion, BundleException.DUPLICATE_BUNDLE_ERROR);
+ }
}
return revision;
diff --git a/framework/src/main/java/org/apache/felix/framework/ServiceRegistry.java b/framework/src/main/java/org/apache/felix/framework/ServiceRegistry.java
index 83bdcff..32494e2 100644
--- a/framework/src/main/java/org/apache/felix/framework/ServiceRegistry.java
+++ b/framework/src/main/java/org/apache/felix/framework/ServiceRegistry.java
@@ -19,6 +19,7 @@
package org.apache.felix.framework;
import java.util.*;
+
import org.apache.felix.framework.capabilityset.CapabilitySet;
import org.apache.felix.framework.capabilityset.SimpleFilter;
import org.apache.felix.framework.wiring.BundleCapabilityImpl;
@@ -54,6 +55,7 @@
new WeakHashMap<ServiceReference, ServiceReference>();
private final static Class<?>[] m_hookClasses = {
+ org.osgi.framework.hooks.bundle.CollisionHook.class,
org.osgi.framework.hooks.bundle.FindHook.class,
org.osgi.framework.hooks.bundle.EventHook.class,
org.osgi.framework.hooks.service.EventHook.class,
@@ -805,4 +807,4 @@
{
void serviceChanged(ServiceEvent event, Dictionary oldProps);
}
-}
\ No newline at end of file
+}
diff --git a/framework/src/main/java/org/apache/felix/framework/util/SecureAction.java b/framework/src/main/java/org/apache/felix/framework/util/SecureAction.java
index 853c9ef..a6a2584 100644
--- a/framework/src/main/java/org/apache/felix/framework/util/SecureAction.java
+++ b/framework/src/main/java/org/apache/felix/framework/util/SecureAction.java
@@ -27,8 +27,8 @@
import java.util.Hashtable;
import java.util.Map;
import java.util.zip.ZipFile;
-import org.osgi.framework.Bundle;
+import org.osgi.framework.Bundle;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
@@ -1056,6 +1056,30 @@
}
}
+ public void invokeBundleCollisionHook(
+ org.osgi.framework.hooks.bundle.CollisionHook ch, int operationType,
+ Bundle targetBundle, Collection<Bundle> collisionCandidates)
+ throws Exception
+ {
+ if (System.getSecurityManager() != null)
+ {
+ Actions actions = (Actions) m_actions.get();
+ actions.set(Actions.INVOKE_BUNDLE_COLLISION_HOOK, ch, operationType, targetBundle, collisionCandidates);
+ try
+ {
+ AccessController.doPrivileged(actions, m_acc);
+ }
+ catch (PrivilegedActionException e)
+ {
+ throw e.getException();
+ }
+ }
+ else
+ {
+ ch.filterCollisions(operationType, targetBundle, collisionCandidates);
+ }
+ }
+
public void invokeBundleFindHook(
org.osgi.framework.hooks.bundle.FindHook fh,
BundleContext bc, Collection<Bundle> bundles)
@@ -1428,6 +1452,7 @@
public static final int INVOKE_RESOLVER_HOOK_SINGLETON = 50;
public static final int INVOKE_RESOLVER_HOOK_MATCHES = 51;
public static final int INVOKE_RESOLVER_HOOK_END = 52;
+ public static final int INVOKE_BUNDLE_COLLISION_HOOK = 53;
private int m_action = -1;
private Object m_arg1 = null;
@@ -1673,9 +1698,13 @@
case INVOKE_RESOLVER_HOOK_END:
((org.osgi.framework.hooks.resolver.ResolverHook) arg1).end();
return null;
+ case INVOKE_BUNDLE_COLLISION_HOOK:
+ ((org.osgi.framework.hooks.bundle.CollisionHook) arg1).filterCollisions((Integer) arg2,
+ (Bundle) arg3, (Collection<Bundle>) arg4);
+ return null;
}
return null;
}
}
-}
\ No newline at end of file
+}
diff --git a/framework/src/test/java/org/apache/felix/framework/CollisionHookTest.java b/framework/src/test/java/org/apache/felix/framework/CollisionHookTest.java
new file mode 100644
index 0000000..3ecbfdb
--- /dev/null
+++ b/framework/src/test/java/org/apache/felix/framework/CollisionHookTest.java
@@ -0,0 +1,256 @@
+/*
+ * 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.felix.framework;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import junit.framework.TestCase;
+
+import org.apache.felix.framework.cache.BundleArchive;
+import org.apache.felix.framework.cache.BundleArchiveRevision;
+import org.mockito.Mockito;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.Version;
+import org.osgi.framework.hooks.bundle.CollisionHook;
+
+public class CollisionHookTest extends TestCase {
+ public void testCollisionHookInstall() throws Exception {
+ BundleImpl identicalBundle = mockBundleImpl(1L, "foo", "1.2.1.a");
+ BundleImpl differentBundle = mockBundleImpl(2L, "bar", "1.2.1.a");
+
+ CollisionHook testCollisionHook = new CollisionHook() {
+ public void filterCollisions(int operationType, Bundle target, Collection<Bundle> collisionCandidates) {
+ if ((target.getBundleId() == 3L) && (operationType == CollisionHook.INSTALLING)) {
+ collisionCandidates.clear();
+ }
+ }
+ };
+
+ @SuppressWarnings("unchecked")
+ ServiceReference<CollisionHook> chRef = Mockito.mock(ServiceReference.class);
+
+ // Mock the framework
+ StatefulResolver mockResolver = Mockito.mock(StatefulResolver.class);
+ Felix felixMock = Mockito.mock(Felix.class);
+ Mockito.when(felixMock.getHooks(CollisionHook.class)).thenReturn(Collections.singleton(chRef));
+ Mockito.when(felixMock.getResolver()).thenReturn(mockResolver);
+ Mockito.when(felixMock.getBundles()).thenReturn(new Bundle[] {differentBundle, identicalBundle});
+ Mockito.when(felixMock.getService(felixMock, chRef)).thenReturn(testCollisionHook);
+
+ // Mock the archive of the bundle being installed
+ Map<String,String> headerMap = new HashMap<String, String>();
+ headerMap.put(Constants.BUNDLE_SYMBOLICNAME, "foo");
+ headerMap.put(Constants.BUNDLE_VERSION, "1.2.1.a");
+ headerMap.put(Constants.BUNDLE_MANIFESTVERSION, "2");
+
+ BundleArchiveRevision archiveRevision = Mockito.mock(BundleArchiveRevision.class);
+ Mockito.when(archiveRevision.getManifestHeader()).thenReturn(headerMap);
+
+ BundleArchive archive = Mockito.mock(BundleArchive.class);
+ Mockito.when(archive.getCurrentRevision()).thenReturn(archiveRevision);
+ Mockito.when(archive.getId()).thenReturn(3L);
+
+ BundleImpl bi = new BundleImpl(felixMock, archive);
+ assertEquals(3L, bi.getBundleId());
+
+ // Do the revise operation.
+ try {
+ bi.revise(null, null);
+ fail("Should have thrown a BundleException because the installed bundle is not unique");
+ } catch (BundleException be) {
+ // good
+ assertTrue(be.getMessage().contains("not unique"));
+ }
+ }
+
+ public void testCollisionHookUpdate() throws Exception {
+ BundleImpl identicalBundle = mockBundleImpl(1L, "foo", "1.2.1.a");
+ BundleImpl differentBundle = mockBundleImpl(2L, "foo", "1.2.1");
+
+ CollisionHook testCollisionHook = new CollisionHook() {
+ public void filterCollisions(int operationType, Bundle target, Collection<Bundle> collisionCandidates) {
+ if ((target.getBundleId() == 3L) && (operationType == CollisionHook.UPDATING)) {
+ collisionCandidates.clear();
+ }
+ }
+ };
+
+ @SuppressWarnings("unchecked")
+ ServiceReference<CollisionHook> chRef = Mockito.mock(ServiceReference.class);
+
+ Map<String, String> config = new HashMap<String, String>();
+ config.put(Constants.FRAMEWORK_BSNVERSION, Constants.FRAMEWORK_BSNVERSION_MANAGED);
+
+ // Mock the framework
+ StatefulResolver mockResolver = Mockito.mock(StatefulResolver.class);
+ Felix felixMock = Mockito.mock(Felix.class);
+ Mockito.when(felixMock.getConfig()).thenReturn(config);
+ Mockito.when(felixMock.getHooks(CollisionHook.class)).thenReturn(Collections.singleton(chRef));
+ Mockito.when(felixMock.getResolver()).thenReturn(mockResolver);
+ Mockito.when(felixMock.getBundles()).thenReturn(new Bundle[] {differentBundle, identicalBundle});
+ Mockito.when(felixMock.getService(felixMock, chRef)).thenReturn(testCollisionHook);
+
+ // Mock the archive of the bundle being installed
+ Map<String,String> headerMap = new HashMap<String, String>();
+ headerMap.put(Constants.BUNDLE_SYMBOLICNAME, "zar");
+ headerMap.put(Constants.BUNDLE_VERSION, "1.2.1.a");
+ headerMap.put(Constants.BUNDLE_MANIFESTVERSION, "2");
+
+ BundleArchiveRevision archiveRevision = Mockito.mock(BundleArchiveRevision.class);
+ Mockito.when(archiveRevision.getManifestHeader()).thenReturn(headerMap);
+
+ BundleArchive archive = Mockito.mock(BundleArchive.class);
+ Mockito.when(archive.getCurrentRevision()).thenReturn(archiveRevision);
+ Mockito.when(archive.getId()).thenReturn(3L);
+
+ BundleImpl bi = new BundleImpl(felixMock, archive);
+ assertEquals("zar", bi.getSymbolicName());
+
+ // Do the revise operation, change the bsn to foo
+ headerMap.put(Constants.BUNDLE_SYMBOLICNAME, "foo");
+ bi.revise(null, null);
+ assertEquals("foo", bi.getSymbolicName());
+ }
+
+ public void testCollisionNotEnabled() throws Exception {
+ BundleImpl identicalBundle = mockBundleImpl(1L, "foo", "1.2.1.a");
+ BundleImpl differentBundle = mockBundleImpl(2L, "bar", "1.2.1.a");
+
+ CollisionHook testCollisionHook = new CollisionHook() {
+ public void filterCollisions(int operationType, Bundle target, Collection<Bundle> collisionCandidates) {
+ if ((target.getBundleId() == 3L) && (operationType == CollisionHook.INSTALLING)) {
+ collisionCandidates.clear();
+ }
+ }
+ };
+
+ @SuppressWarnings("unchecked")
+ ServiceReference<CollisionHook> chRef = Mockito.mock(ServiceReference.class);
+
+ Map<String, String> config = new HashMap<String, String>();
+ config.put(Constants.FRAMEWORK_BSNVERSION, Constants.FRAMEWORK_BSNVERSION_SINGLE);
+
+ // Mock the framework
+ StatefulResolver mockResolver = Mockito.mock(StatefulResolver.class);
+ Felix felixMock = Mockito.mock(Felix.class);
+ Mockito.when(felixMock.getConfig()).thenReturn(config);
+ Mockito.when(felixMock.getHooks(CollisionHook.class)).thenReturn(Collections.singleton(chRef));
+ Mockito.when(felixMock.getResolver()).thenReturn(mockResolver);
+ Mockito.when(felixMock.getBundles()).thenReturn(new Bundle[] {differentBundle, identicalBundle});
+ Mockito.when(felixMock.getService(felixMock, chRef)).thenReturn(testCollisionHook);
+
+ // Mock the archive of the bundle being installed
+ Map<String,String> headerMap = new HashMap<String, String>();
+ headerMap.put(Constants.BUNDLE_SYMBOLICNAME, "foo");
+ headerMap.put(Constants.BUNDLE_VERSION, "1.2.1.a");
+ headerMap.put(Constants.BUNDLE_MANIFESTVERSION, "2");
+
+ BundleArchiveRevision archiveRevision = Mockito.mock(BundleArchiveRevision.class);
+ Mockito.when(archiveRevision.getManifestHeader()).thenReturn(headerMap);
+
+ BundleArchive archive = Mockito.mock(BundleArchive.class);
+ Mockito.when(archive.getCurrentRevision()).thenReturn(archiveRevision);
+ Mockito.when(archive.getId()).thenReturn(3L);
+
+ try {
+ new BundleImpl(felixMock, archive);
+ fail("Should have thrown a BundleException because the collision hook is not enabled");
+ } catch (BundleException be) {
+ // good
+ assertTrue(be.getMessage().contains("not unique"));
+ }
+ }
+
+ public void testAllowMultiple() throws Exception {
+ BundleImpl identicalBundle = mockBundleImpl(1L, "foo", "1.2.1.a");
+ BundleImpl differentBundle = mockBundleImpl(2L, "bar", "1.2.1.a");
+
+ Map<String, String> config = new HashMap<String, String>();
+ config.put(Constants.FRAMEWORK_BSNVERSION, Constants.FRAMEWORK_BSNVERSION_MULTIPLE);
+
+ // Mock the framework
+ StatefulResolver mockResolver = Mockito.mock(StatefulResolver.class);
+ Felix felixMock = Mockito.mock(Felix.class);
+ Mockito.when(felixMock.getConfig()).thenReturn(config);
+ Mockito.when(felixMock.getResolver()).thenReturn(mockResolver);
+ Mockito.when(felixMock.getBundles()).thenReturn(new Bundle[] {differentBundle, identicalBundle});
+
+ // Mock the archive of the bundle being installed
+ Map<String,String> headerMap = new HashMap<String, String>();
+ headerMap.put(Constants.BUNDLE_SYMBOLICNAME, "foo");
+ headerMap.put(Constants.BUNDLE_VERSION, "1.2.1.a");
+ headerMap.put(Constants.BUNDLE_MANIFESTVERSION, "2");
+
+ BundleArchiveRevision archiveRevision = Mockito.mock(BundleArchiveRevision.class);
+ Mockito.when(archiveRevision.getManifestHeader()).thenReturn(headerMap);
+
+ BundleArchive archive = Mockito.mock(BundleArchive.class);
+ Mockito.when(archive.getCurrentRevision()).thenReturn(archiveRevision);
+ Mockito.when(archive.getId()).thenReturn(3L);
+
+ BundleImpl bi = new BundleImpl(felixMock, archive);
+ assertEquals(3L, bi.getBundleId());
+ }
+
+ public void testNoCollisionHook() throws Exception {
+ BundleImpl identicalBundle = mockBundleImpl(1L, "foo", "1.2.1.a");
+ BundleImpl differentBundle = mockBundleImpl(2L, "bar", "1.2.1.a");
+
+ // Mock the framework
+ StatefulResolver mockResolver = Mockito.mock(StatefulResolver.class);
+ Felix felixMock = Mockito.mock(Felix.class);
+ Mockito.when(felixMock.getResolver()).thenReturn(mockResolver);
+ Mockito.when(felixMock.getBundles()).thenReturn(new Bundle[] {differentBundle, identicalBundle});
+
+ // Mock the archive of the bundle being installed
+ Map<String,String> headerMap = new HashMap<String, String>();
+ headerMap.put(Constants.BUNDLE_SYMBOLICNAME, "foo");
+ headerMap.put(Constants.BUNDLE_VERSION, "1.2.1.a");
+ headerMap.put(Constants.BUNDLE_MANIFESTVERSION, "2");
+
+ BundleArchiveRevision archiveRevision = Mockito.mock(BundleArchiveRevision.class);
+ Mockito.when(archiveRevision.getManifestHeader()).thenReturn(headerMap);
+
+ BundleArchive archive = Mockito.mock(BundleArchive.class);
+ Mockito.when(archive.getCurrentRevision()).thenReturn(archiveRevision);
+ Mockito.when(archive.getId()).thenReturn(3L);
+
+ try {
+ new BundleImpl(felixMock, archive);
+ fail("Should have thrown a BundleException because the installed bundle is not unique");
+ } catch (BundleException be) {
+ // good
+ assertTrue(be.getMessage().contains("not unique"));
+ }
+ }
+
+ private BundleImpl mockBundleImpl(long id, String bsn, String version) {
+ BundleImpl identicalBundle = Mockito.mock(BundleImpl.class);
+ Mockito.when(identicalBundle.getSymbolicName()).thenReturn(bsn);
+ Mockito.when(identicalBundle.getVersion()).thenReturn(Version.parseVersion(version));
+ Mockito.when(identicalBundle.getBundleId()).thenReturn(id);
+ return identicalBundle;
+ }
+}