diff options
| author | Thomas Watson | 2011-08-08 19:59:53 +0000 |
|---|---|---|
| committer | Thomas Watson | 2011-08-08 19:59:53 +0000 |
| commit | fe711b408d3523f680c4e356221369581057e6ce (patch) | |
| tree | 124f2ba6848b21717ee17e593201d53d7712d8fc | |
| parent | a2cf4324f88e434de94e8904835dfb53fff6916b (diff) | |
| download | rt.equinox.framework-fe711b408d3523f680c4e356221369581057e6ce.tar.gz rt.equinox.framework-fe711b408d3523f680c4e356221369581057e6ce.tar.xz rt.equinox.framework-fe711b408d3523f680c4e356221369581057e6ce.zip | |
Bug 348967 - Handle new option "managed"v20110808-1537
for org.osgi.framework.bsnversion configuration property
6 files changed, 228 insertions, 44 deletions
diff --git a/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/bundles/BundleInstallUpdateTests.java b/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/bundles/BundleInstallUpdateTests.java index cd21c1712..43b645e37 100644 --- a/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/bundles/BundleInstallUpdateTests.java +++ b/bundles/org.eclipse.osgi.tests/src/org/eclipse/osgi/tests/bundles/BundleInstallUpdateTests.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009 IBM Corporation and others. + * Copyright (c) 2009, 2011 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -11,13 +11,16 @@ package org.eclipse.osgi.tests.bundles; import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; import java.net.URL; +import java.util.Collection; import junit.framework.Test; import junit.framework.TestSuite; import org.eclipse.osgi.internal.baseadaptor.AdaptorUtil; import org.eclipse.osgi.tests.OSGiTestsActivator; -import org.osgi.framework.Bundle; -import org.osgi.framework.BundleException; +import org.osgi.framework.*; +import org.osgi.framework.hooks.bundle.CollisionHook; public class BundleInstallUpdateTests extends AbstractBundleTests { public static Test suite() { @@ -195,4 +198,51 @@ public class BundleInstallUpdateTests extends AbstractBundleTests { } } } + + public void testCollisionHook() throws BundleException, MalformedURLException, IOException { + Bundle test1 = installer.installBundle("test"); + installer.installBundle("test2"); + try { + test1.update(new URL(installer.getBundleLocation("test2")).openStream()); + fail("Expected to fail to update to another bsn/version that causes collision"); + } catch (BundleException e) { + // expected; + } + Bundle junk = null; + try { + junk = OSGiTestsActivator.getContext().installBundle("junk", new URL(installer.getBundleLocation("test2")).openStream()); + fail("Expected to fail to install duplication bsn/version that causes collision"); + } catch (BundleException e) { + // expected; + } finally { + if (junk != null) + junk.uninstall(); + junk = null; + } + + CollisionHook hook = new CollisionHook() { + public void filterCollisions(int operationType, Bundle target, Collection collisionCandidates) { + collisionCandidates.clear(); + } + }; + ServiceRegistration reg = OSGiTestsActivator.getContext().registerService(CollisionHook.class, hook, null); + try { + try { + test1.update(new URL(installer.getBundleLocation("test2")).openStream()); + } catch (BundleException e) { + fail("Expected to succeed in updating to a duplicate bsn/version", e); + } + try { + junk = OSGiTestsActivator.getContext().installBundle("junk", new URL(installer.getBundleLocation("test2")).openStream()); + } catch (BundleException e) { + fail("Expected to succeed to install duplication bsn/version that causes collision", e); + } finally { + if (junk != null) + junk.uninstall(); + junk = null; + } + } finally { + reg.unregister(); + } + } } diff --git a/bundles/org.eclipse.osgi/.settings/.api_filters b/bundles/org.eclipse.osgi/.settings/.api_filters index a95ba2ca5..57d8c9271 100644 --- a/bundles/org.eclipse.osgi/.settings/.api_filters +++ b/bundles/org.eclipse.osgi/.settings/.api_filters @@ -890,6 +890,13 @@ </message_arguments> </filter> </resource> +<resource path="osgi/src/org/osgi/framework/hooks/bundle/CollisionHook.java" type="org.osgi.framework.hooks.bundle.CollisionHook"> +<filter comment="Ignore OSGi API" id="1110441988"> +<message_arguments> +<message_argument value="org.osgi.framework.hooks.bundle.CollisionHook"/> +</message_arguments> +</filter> +</resource> <resource path="osgi/src/org/osgi/framework/hooks/bundle/EventHook.java" type="org.osgi.framework.hooks.bundle.EventHook"> <filter comment="Ingore OSGi API." id="1110441988"> <message_arguments> diff --git a/bundles/org.eclipse.osgi/core/framework/org/eclipse/osgi/framework/internal/core/AbstractBundle.java b/bundles/org.eclipse.osgi/core/framework/org/eclipse/osgi/framework/internal/core/AbstractBundle.java index 36d52ab9a..8acd9a9a2 100644 --- a/bundles/org.eclipse.osgi/core/framework/org/eclipse/osgi/framework/internal/core/AbstractBundle.java +++ b/bundles/org.eclipse.osgi/core/framework/org/eclipse/osgi/framework/internal/core/AbstractBundle.java @@ -29,6 +29,7 @@ import org.eclipse.osgi.service.resolver.ResolverError; import org.eclipse.osgi.signedcontent.*; import org.eclipse.osgi.util.NLS; import org.osgi.framework.*; +import org.osgi.framework.hooks.bundle.CollisionHook; import org.osgi.framework.startlevel.BundleStartLevel; import org.osgi.framework.wiring.*; @@ -660,7 +661,7 @@ public abstract class AbstractBundle implements Bundle, Comparable<Bundle>, Keye try { BundleData newBundleData = storage.begin(); // Must call framework createBundle to check execution environment. - final AbstractBundle newBundle = framework.createAndVerifyBundle(newBundleData, false); + final AbstractBundle newBundle = framework.createAndVerifyBundle(CollisionHook.UPDATING, this, newBundleData, false); boolean exporting; int st = getState(); synchronized (bundles) { diff --git a/bundles/org.eclipse.osgi/core/framework/org/eclipse/osgi/framework/internal/core/BundleRepository.java b/bundles/org.eclipse.osgi/core/framework/org/eclipse/osgi/framework/internal/core/BundleRepository.java index b5dae7fd1..f10edf828 100644 --- a/bundles/org.eclipse.osgi/core/framework/org/eclipse/osgi/framework/internal/core/BundleRepository.java +++ b/bundles/org.eclipse.osgi/core/framework/org/eclipse/osgi/framework/internal/core/BundleRepository.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2003, 2010 IBM Corporation and others. + * Copyright (c) 2003, 2011 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -68,18 +68,22 @@ public final class BundleRepository { return bundlesBySymbolicName.get(symbolicName); } - public synchronized AbstractBundle getBundle(String symbolicName, Version version) { + @SuppressWarnings("unchecked") + public synchronized List<AbstractBundle> getBundles(String symbolicName, Version version) { AbstractBundle[] bundles = getBundles(symbolicName); + List<AbstractBundle> result = null; if (bundles != null) { if (bundles.length > 0) { for (int i = 0; i < bundles.length; i++) { if (bundles[i].getVersion().equals(version)) { - return bundles[i]; + if (result == null) + result = new ArrayList<AbstractBundle>(); + result.add(bundles[i]); } } } } - return null; + return result == null ? Collections.EMPTY_LIST : result; } public synchronized void add(AbstractBundle bundle) { diff --git a/bundles/org.eclipse.osgi/core/framework/org/eclipse/osgi/framework/internal/core/Framework.java b/bundles/org.eclipse.osgi/core/framework/org/eclipse/osgi/framework/internal/core/Framework.java index 297b457a4..050a1954a 100644 --- a/bundles/org.eclipse.osgi/core/framework/org/eclipse/osgi/framework/internal/core/Framework.java +++ b/bundles/org.eclipse.osgi/core/framework/org/eclipse/osgi/framework/internal/core/Framework.java @@ -33,8 +33,7 @@ import org.eclipse.osgi.signedcontent.SignedContentFactory; import org.eclipse.osgi.util.ManifestElement; import org.eclipse.osgi.util.NLS; import org.osgi.framework.*; -import org.osgi.framework.hooks.bundle.EventHook; -import org.osgi.framework.hooks.bundle.FindHook; +import org.osgi.framework.hooks.bundle.*; import org.osgi.util.tracker.ServiceTracker; /** @@ -77,6 +76,10 @@ public class Framework implements EventPublisher, Runnable { protected StartLevelManager startLevelManager; /** The ServiceRegistry */ private ServiceRegistry serviceRegistry; + private final int BSN_VERSION; + private static final int BSN_VERSION_SINGLE = 1; + private static final int BSN_VERSION_MULTIPLE = 2; + private static final int BSN_VERSION_MANAGED = 3; /* * The following maps objects keep track of event listeners @@ -99,6 +102,7 @@ public class Framework implements EventPublisher, Runnable { protected static final int BATCHEVENT_END = Integer.MIN_VALUE; static final String eventHookName = EventHook.class.getName(); static final String findHookName = FindHook.class.getName(); + static final String collisionHookName = CollisionHook.class.getName(); /** EventManager for event delivery. */ protected EventManager eventManager; /* Reservation object for install synchronization */ @@ -110,7 +114,6 @@ public class Framework implements EventPublisher, Runnable { private boolean bootDelegateAll = false; public final boolean contextBootDelegation = "true".equals(FrameworkProperties.getProperty("osgi.context.bootdelegation", "true")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ public final boolean compatibiltyBootDelegation = "true".equals(FrameworkProperties.getProperty(Constants.OSGI_COMPATIBILITY_BOOTDELEGATION, "true")); //$NON-NLS-1$ //$NON-NLS-2$ - private final boolean allowDuplicateBSNVersion = Constants.FRAMEWORK_BSNVERSION_MULTIPLE.equals(FrameworkProperties.getProperty(Constants.FRAMEWORK_BSNVERSION)); private final boolean allowRefreshDuplicateBSN = Boolean.TRUE.toString().equals(FrameworkProperties.getProperty(Constants.REFRESH_DUPLICATE_BSN, "true")); //$NON-NLS-1$ ClassLoaderDelegateHook[] delegateHooks; private volatile boolean forcedRestart = false; @@ -159,20 +162,19 @@ public class Framework implements EventPublisher, Runnable { * */ public Framework(FrameworkAdaptor adaptor) { - initialize(adaptor); - } - - /** - * Initialize the framework to an unlaunched state. This method is called - * by the Framework constructor. - * - */ - protected void initialize(FrameworkAdaptor initAdaptor) { if (Profile.PROFILE && Profile.STARTUP) Profile.logEnter("Framework.initialze()", null); //$NON-NLS-1$ + String bsnVersion = FrameworkProperties.getProperty(Constants.FRAMEWORK_BSNVERSION); + if (Constants.FRAMEWORK_BSNVERSION_SINGLE.equals(bsnVersion)) { + BSN_VERSION = BSN_VERSION_SINGLE; + } else if (Constants.FRAMEWORK_BSNVERSION_MULTIPLE.equals(bsnVersion)) { + BSN_VERSION = BSN_VERSION_MULTIPLE; + } else { + BSN_VERSION = BSN_VERSION_MANAGED; + } long start = System.currentTimeMillis(); - this.adaptor = initAdaptor; - delegateHooks = initAdaptor instanceof BaseAdaptor ? ((BaseAdaptor) initAdaptor).getHookRegistry().getClassLoaderDelegateHooks() : null; + this.adaptor = adaptor; + delegateHooks = adaptor instanceof BaseAdaptor ? ((BaseAdaptor) adaptor).getHookRegistry().getClassLoaderDelegateHooks() : null; active = false; installSecurityManager(); if (Debug.DEBUG_SECURITY) { @@ -183,11 +185,11 @@ public class Framework implements EventPublisher, Runnable { // initialize ContextFinder initializeContextFinder(); /* initialize the adaptor */ - initAdaptor.initialize(this); + adaptor.initialize(this); if (Profile.PROFILE && Profile.STARTUP) Profile.logTime("Framework.initialze()", "adapter initialized"); //$NON-NLS-1$//$NON-NLS-2$ try { - initAdaptor.initializeStorage(); + adaptor.initializeStorage(); } catch (IOException e) /* fatal error */{ throw new RuntimeException(e.getMessage(), e); } @@ -197,12 +199,12 @@ public class Framework implements EventPublisher, Runnable { * This must be done before calling any of the framework getProperty * methods. */ - initializeProperties(initAdaptor.getProperties()); + initializeProperties(adaptor.getProperties()); /* initialize admin objects */ packageAdmin = new PackageAdminImpl(this); try { // always create security admin even with security off - securityAdmin = new SecurityAdmin(null, this, initAdaptor.getPermissionStorage()); + securityAdmin = new SecurityAdmin(null, this, adaptor.getPermissionStorage()); } catch (IOException e) /* fatal error */{ e.printStackTrace(); throw new RuntimeException(e.getMessage(), e); @@ -227,13 +229,13 @@ public class Framework implements EventPublisher, Runnable { if (Profile.PROFILE && Profile.STARTUP) Profile.logTime("Framework.initialze()", "done createSystemBundle"); //$NON-NLS-1$ //$NON-NLS-2$ /* install URLStreamHandlerFactory */ - installURLStreamHandlerFactory(systemBundle.context, initAdaptor); + installURLStreamHandlerFactory(systemBundle.context, adaptor); /* install ContentHandlerFactory for OSGi URLStreamHandler support */ - installContentHandlerFactory(systemBundle.context, initAdaptor); + installContentHandlerFactory(systemBundle.context, adaptor); if (Profile.PROFILE && Profile.STARTUP) Profile.logTime("Framework.initialze()", "done new URLStream/Content HandlerFactory"); //$NON-NLS-1$//$NON-NLS-2$ /* create bundle objects for all installed bundles. */ - BundleData[] bundleDatas = initAdaptor.getInstalledBundles(); + BundleData[] bundleDatas = adaptor.getInstalledBundles(); bundles = new BundleRepository(bundleDatas == null ? 10 : bundleDatas.length + 1); /* add the system bundle to the Bundle Repository */ bundles.add(systemBundle); @@ -703,14 +705,20 @@ public class Framework implements EventPublisher, Runnable { /** * Create a new Bundle object. - * * @param bundledata the BundleData of the Bundle to create */ - AbstractBundle createAndVerifyBundle(BundleData bundledata, boolean setBundle) throws BundleException { + AbstractBundle createAndVerifyBundle(int operationType, Bundle target, BundleData bundledata, boolean setBundle) throws BundleException { // Check for a bundle already installed with the same symbolic name and version. - if (!allowDuplicateBSNVersion && bundledata.getSymbolicName() != null) { - AbstractBundle installedBundle = getBundleBySymbolicName(bundledata.getSymbolicName(), bundledata.getVersion()); - if (installedBundle != null && installedBundle.getBundleId() != bundledata.getBundleID()) { + if (BSN_VERSION != BSN_VERSION_MULTIPLE && bundledata.getSymbolicName() != null) { + List<AbstractBundle> installedBundles = getBundleBySymbolicName(bundledata.getSymbolicName(), bundledata.getVersion()); + if (operationType == CollisionHook.UPDATING) { + installedBundles.remove(target); + } + if (BSN_VERSION == BSN_VERSION_MANAGED && !installedBundles.isEmpty()) { + notifyCollisionHooks(operationType, target, installedBundles); + } + if (!installedBundles.isEmpty()) { + Bundle installedBundle = installedBundles.iterator().next(); String msg = NLS.bind(Msg.BUNDLE_INSTALL_SAME_UNIQUEID, new Object[] {installedBundle.getSymbolicName(), installedBundle.getVersion().toString(), installedBundle.getLocation()}); throw new DuplicateBundleException(msg, installedBundle); } @@ -825,7 +833,7 @@ public class Framework implements EventPublisher, Runnable { * then the location is used to get the bundle content. * @return The Bundle of the installed bundle. */ - AbstractBundle installBundle(final String location, final InputStream in, BundleContext origin) throws BundleException { + AbstractBundle installBundle(final String location, final InputStream in, final BundleContextImpl origin) throws BundleException { if (Debug.DEBUG_GENERAL) { Debug.println("install from inputstream: " + location + ", " + in); //$NON-NLS-1$ //$NON-NLS-2$ } @@ -835,7 +843,7 @@ public class Framework implements EventPublisher, Runnable { /* Map the InputStream or location to a URLConnection */ URLConnection source = in != null ? new BundleSource(in) : adaptor.mapLocationToURLConnection(location); /* call the worker to install the bundle */ - return installWorkerPrivileged(location, source, callerContext); + return installWorkerPrivileged(location, source, callerContext, origin); } }, origin); } @@ -919,16 +927,20 @@ public class Framework implements EventPublisher, Runnable { * The location identifier of the bundle to install. * @param source * The URLConnection from which the bundle will be read. + * @param callerContext + * The caller access control context + * @param origin + * The origin bundle context that is installing the the bundle * @return The {@link AbstractBundle}of the installed bundle. * @exception BundleException * If the provided stream cannot be read. */ - protected AbstractBundle installWorkerPrivileged(String location, URLConnection source, AccessControlContext callerContext) throws BundleException { + protected AbstractBundle installWorkerPrivileged(String location, URLConnection source, AccessControlContext callerContext, BundleContextImpl origin) throws BundleException { BundleOperation storage = adaptor.installBundle(location, source); final AbstractBundle bundle; try { BundleData bundledata = storage.begin(); - bundle = createAndVerifyBundle(bundledata, true); + bundle = createAndVerifyBundle(CollisionHook.INSTALLING, origin.getBundle(), bundledata, true); BundleWatcher bundleStats = adaptor.getBundleWatcher(); if (bundleStats != null) @@ -1022,17 +1034,16 @@ public class Framework implements EventPublisher, Runnable { } /** - * Retrieve the bundle that has the given symbolic name and version. + * Retrieve the bundles that has the given symbolic name and version. * * @param symbolicName * The symbolic name of the bundle to retrieve * @param version The version of the bundle to retrieve - * @return A {@link AbstractBundle}object, or <code>null</code> if the - * identifier doesn't match any installed bundle. + * @return A collection of {@link AbstractBundle} that match the symbolic name and version */ - public AbstractBundle getBundleBySymbolicName(String symbolicName, Version version) { + public List<AbstractBundle> getBundleBySymbolicName(String symbolicName, Version version) { synchronized (bundles) { - return bundles.getBundle(symbolicName, version); + return bundles.getBundles(symbolicName, version); } } @@ -1113,6 +1124,41 @@ public class Framework implements EventPublisher, Runnable { }); } + private void notifyCollisionHooks(final int operationType, final Bundle target, List<AbstractBundle> collisionCandidates) { + final Collection<Bundle> shrinkable = new ShrinkableCollection<Bundle>(collisionCandidates); + if (System.getSecurityManager() == null) { + notifyCollisionHooksPriviledged(operationType, target, shrinkable); + } else { + AccessController.doPrivileged(new PrivilegedAction<Object>() { + public Object run() { + notifyCollisionHooksPriviledged(operationType, target, shrinkable); + return null; + } + }); + } + } + + void notifyCollisionHooksPriviledged(final int operationType, final Bundle target, final Collection<Bundle> collisionCandidates) { + if (Debug.DEBUG_HOOKS) { + Debug.println("notifyCollisionHooks(" + operationType + ", " + target + ", " + collisionCandidates + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ + } + getServiceRegistry().notifyHooksPrivileged(new HookContext() { + public void call(Object hook, ServiceRegistration<?> hookRegistration) throws Exception { + if (hook instanceof CollisionHook) { + ((CollisionHook) hook).filterCollisions(operationType, target, collisionCandidates); + } + } + + public String getHookClassName() { + return collisionHookName; + } + + public String getHookMethodName() { + return "filterCollisions"; //$NON-NLS-1$ + } + }); + } + /** * Resume a bundle. * @@ -1209,7 +1255,7 @@ public class Framework implements EventPublisher, Runnable { * @param symbolicName * The symbolic name for the bundle * @return Bundle object for bundle with the specified Unique or null if no - * bundle is installed with the specified location. + * bundle is installed with the specified symbolicName. */ protected AbstractBundle[] getBundleBySymbolicName(String symbolicName) { synchronized (bundles) { diff --git a/bundles/org.eclipse.osgi/osgi/src/org/osgi/framework/hooks/bundle/CollisionHook.java b/bundles/org.eclipse.osgi/osgi/src/org/osgi/framework/hooks/bundle/CollisionHook.java new file mode 100644 index 000000000..94dabb724 --- /dev/null +++ b/bundles/org.eclipse.osgi/osgi/src/org/osgi/framework/hooks/bundle/CollisionHook.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) OSGi Alliance (2011). All Rights Reserved. + * + * Licensed 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.osgi.framework.hooks.bundle; + +import java.util.Collection; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; + +/** + * OSGi Framework Bundle Collision Hook Service. + * + * <p> + * Bundles registering this service will be called during framework bundle + * install and update operations to determine if an install or update + * operation will result in a bundle symbolic name and version collision. + * + * @ThreadSafe + * @version $Id: 64a98074fa8331b0cd21989f9c8583b99b2370cf $ + */ +public interface CollisionHook { + + /** + * Specifies a bundle install operation is being performed. + */ + public static final int INSTALLING = 0x01; + /** + * Specifies a bundle update operation is being performed. + */ + public static final int UPDATING = 0x02; + + /** + * Filter bundle collisions hook method. This method is called during the + * install or update operation. The operation type will be + * {@link #INSTALLING installing} or + * {@link #UPDATING updating}. Depending on the operation + * type the target bundle and the collision candidate collection are the following: + * <ul> + * <li> {@link #INSTALLING installing} - The target is the bundle associated with the + * {@link BundleContext} used to call one of the + * {@link BundleContext#installBundle(String) install} + * methods. The collision candidate collection contains the existing bundles + * installed which have the same symbolic name and version as the bundle being installed. + * <li> {@link #UPDATING updating} - The target is the bundle used to call one of the + * {@link Bundle#update() update} methods. The collision candidate collection + * contains the existing bundles installed which have the same symbolic name and version + * as the content the target bundle is being updated to. + * </ul> + * This method can filter the list of collision candidates by removing + * potential collisions. Removing a collision candidate will allow the + * specified operation to succeed as if the collision candidate is not installed. + * + * @param operationType the operation type. Must be the value of + * {@link #INSTALLING installing} or {@link #UPDATING updating}. + * @param target the target bundle used to determine what collision candidates to filter. + * @param collisionCandidates a collection of collision candidates. + * The collection supports all the optional {@code Collection} + * operations except {@code add} and {@code addAll}. Attempting + * to add to the collection will result in an + * {@code UnsupportedOperationException}. The collection is not + * synchronized. + */ + void filterCollisions(int operationType, Bundle target, Collection<Bundle> collisionCandidates); +} |
