diff options
author | Thomas Watson | 2008-12-19 21:10:00 +0000 |
---|---|---|
committer | Thomas Watson | 2008-12-19 21:10:00 +0000 |
commit | c0bf408b216d2511900a0e48b4185d50fb6017e2 (patch) | |
tree | cc91e6e2a43514f5088471048125db7503fed0b8 | |
parent | f1a814066412e3ae14174179eea848452496bb34 (diff) | |
download | rt.equinox.framework-c0bf408b216d2511900a0e48b4185d50fb6017e2.tar.gz rt.equinox.framework-c0bf408b216d2511900a0e48b4185d50fb6017e2.tar.xz rt.equinox.framework-c0bf408b216d2511900a0e48b4185d50fb6017e2.zip |
Bug 254021 Implement new LinkBundleFactory service (rfc 138)
28 files changed, 1856 insertions, 65 deletions
diff --git a/bundles/org.eclipse.osgi/.classpath b/bundles/org.eclipse.osgi/.classpath index 9bffd6a97..0629761ab 100644 --- a/bundles/org.eclipse.osgi/.classpath +++ b/bundles/org.eclipse.osgi/.classpath @@ -8,6 +8,7 @@ <classpathentry kind="src" path="supplement/src"/> <classpathentry kind="src" path="core/adaptor"/> <classpathentry kind="src" path="core/framework"/> + <classpathentry kind="src" path="core/composite"/> <classpathentry kind="src" path="resolver/src"/> <classpathentry kind="src" path="defaultAdaptor/src"/> <classpathentry kind="src" path="eclipseAdaptor/src"/> diff --git a/bundles/org.eclipse.osgi/META-INF/MANIFEST.MF b/bundles/org.eclipse.osgi/META-INF/MANIFEST.MF index c43cffc6c..1d4c49726 100644 --- a/bundles/org.eclipse.osgi/META-INF/MANIFEST.MF +++ b/bundles/org.eclipse.osgi/META-INF/MANIFEST.MF @@ -21,6 +21,7 @@ Export-Package: org.eclipse.osgi.event;version="1.0", org.osgi.framework.launch; version="1.0", org.osgi.framework.hooks.service; version="1.0", org.osgi.service.condpermadmin;version="1.1", + org.osgi.service.framework; version="1.0", org.osgi.service.packageadmin;version="1.2", org.osgi.service.permissionadmin;version="1.2", org.osgi.service.startlevel;version="1.1", @@ -43,6 +44,7 @@ Export-Package: org.eclipse.osgi.event;version="1.0", org.eclipse.osgi.framework.internal.reliablefile;x-internal:=true, org.eclipse.osgi.framework.util;x-internal:=true, org.eclipse.osgi.internal.baseadaptor;x-internal:=true, + org.eclipse.osgi.internal.composite; x-internal:=true, org.eclipse.osgi.internal.loader;x-internal:=true, org.eclipse.osgi.internal.loader.buddy; x-internal:=true, org.eclipse.osgi.internal.module;x-internal:=true, @@ -53,7 +55,8 @@ Export-Package: org.eclipse.osgi.event;version="1.0", org.eclipse.osgi.internal.provisional.service.security; x-friends:="org.eclipse.equinox.security.ui";version="1.0.0", org.eclipse.osgi.internal.provisional.verifier;x-friends:="org.eclipse.update.core,org.eclipse.ui.workbench,org.eclipse.equinox.p2.artifact.repository", org.eclipse.osgi.internal.service.security;x-friends:="org.eclipse.equinox.security.ui", - org.eclipse.osgi.internal.signedcontent; x-internal:=true + org.eclipse.osgi.internal.signedcontent; x-internal:=true, + org.eclipse.osgi.service.internal.composite; x-internal:=true Export-Service: org.osgi.service.packageadmin.PackageAdmin, org.osgi.service.permissionadmin.PermissionAdmin, org.osgi.service.startlevel.StartLevel, diff --git a/bundles/org.eclipse.osgi/build.properties b/bundles/org.eclipse.osgi/build.properties index f5b1e7614..70326c98f 100644 --- a/bundles/org.eclipse.osgi/build.properties +++ b/bundles/org.eclipse.osgi/build.properties @@ -22,6 +22,7 @@ src.includes = about.html,\ source.. = osgi/src,\ core/adaptor/,\ core/framework/,\ + core/composite/,\ resolver/src/,\ defaultAdaptor/src/,\ eclipseAdaptor/src/,\ diff --git a/bundles/org.eclipse.osgi/core/adaptor/org/eclipse/osgi/framework/adaptor/BundleData.java b/bundles/org.eclipse.osgi/core/adaptor/org/eclipse/osgi/framework/adaptor/BundleData.java index 6c1d0ddac..ff33a0232 100644 --- a/bundles/org.eclipse.osgi/core/adaptor/org/eclipse/osgi/framework/adaptor/BundleData.java +++ b/bundles/org.eclipse.osgi/core/adaptor/org/eclipse/osgi/framework/adaptor/BundleData.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2003, 2007 IBM Corporation and others. + * Copyright (c) 2003, 2008 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 @@ -42,6 +42,10 @@ public interface BundleData { public static final int TYPE_SINGLETON = 0x00000008; /** The BundleData is for an extension classpath bundle */ public static final int TYPE_EXTCLASSPATH_EXTENSION = 0x00000010; + /** The BundleData is for a composite bundle */ + public static final int TYPE_COMPOSITEBUNDLE = 0x00000020; + /** The BundleData is for a composite bundle surrogate */ + public static final int TYPE_SURROGATEBUNDLE = 0x00000040; /** * Creates the ClassLoader for the BundleData. The ClassLoader created diff --git a/bundles/org.eclipse.osgi/core/composite/org/eclipse/osgi/internal/composite/CompositeBase.java b/bundles/org.eclipse.osgi/core/composite/org/eclipse/osgi/internal/composite/CompositeBase.java new file mode 100644 index 000000000..9ca68c91b --- /dev/null +++ b/bundles/org.eclipse.osgi/core/composite/org/eclipse/osgi/internal/composite/CompositeBase.java @@ -0,0 +1,111 @@ +/******************************************************************************* + * Copyright (c) 2008 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 + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.osgi.internal.composite; + +import java.io.IOException; +import java.io.InputStream; +import org.eclipse.osgi.framework.adaptor.BundleData; +import org.eclipse.osgi.framework.adaptor.ClassLoaderDelegate; +import org.eclipse.osgi.framework.internal.core.BundleHost; +import org.eclipse.osgi.framework.internal.core.FrameworkProperties; +import org.eclipse.osgi.internal.module.CompositeResolveHelper; +import org.eclipse.osgi.service.internal.composite.CompositeModule; +import org.eclipse.osgi.service.resolver.BundleDescription; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleException; +import org.osgi.framework.launch.Framework; +import org.osgi.service.framework.CompositeBundle; + +/** + * This is a base class for both composite and surrogate bundles. + */ +public abstract class CompositeBase extends BundleHost implements CompositeResolveHelper, CompositeModule { + protected static String PROP_COMPOSITE = "org.eclipse.equinox.Composite"; //$NON-NLS-1$ + protected static String PROP_PARENTFRAMEWORK = "org.eclipse.equinox.parentFramework"; //$NON-NLS-1$ + + protected final Framework companionFramework; + protected final long companionID; + protected final ThreadLocal refreshing = new ThreadLocal(); + + public CompositeBase(BundleData bundledata, org.eclipse.osgi.framework.internal.core.Framework framework) throws BundleException { + super(bundledata, framework); + this.companionFramework = findCompanionFramework(framework, bundledata); + this.companionID = isSurrogate() ? ((CompositeBundle) FrameworkProperties.getProperties().get(PROP_COMPOSITE)).getBundleId() : 1; + } + + /* + * Finds the companion framework for the composite/surrogate. + * For surrogate bundles this is the parent framework. + * For composite bundles this is the child framework. + */ + protected abstract Framework findCompanionFramework(org.eclipse.osgi.framework.internal.core.Framework thisFramework, BundleData thisData) throws BundleException; + + /* + * Gets the companion bundle for the composite/surrogate. + * For surrogate bundles this is the composite bundle. + * For composite bundles this is the surrogate bundle. + */ + Bundle getCompanionBundle() { + return companionFramework.getBundleContext().getBundle(companionID); + } + + protected boolean isSurrogate() { + return false; + } + + public BundleDescription getCompositeDescription() { + return getBundleDescription(); + } + + public ClassLoaderDelegate getDelegate() { + return getBundleLoader(); + } + + public void refreshContent(boolean synchronously) { + if (synchronously) + refreshing.set(Boolean.TRUE); + try { + framework.getPackageAdmin().refreshPackages(new Bundle[] {this}, synchronously); + } finally { + if (synchronously) + refreshing.set(null); + } + } + + public boolean resolveContent() { + return framework.getPackageAdmin().resolveBundles(new Bundle[] {this}); + } + + public void started(CompositeModule surrogate) { + // nothing + } + + public void stopped(CompositeModule surrogate) { + // nothing + } + + public void updateContent(InputStream content) throws BundleException { + super.update(content); + } + + public void update() throws BundleException { + throw new BundleException("Cannot update composite bundles", BundleException.INVALID_OPERATION); + } + + public void update(InputStream in) throws BundleException { + try { + in.close(); + } catch (IOException e) { + // ignore + } + throw new BundleException("Cannot update composite bundles", BundleException.INVALID_OPERATION); + } +} diff --git a/bundles/org.eclipse.osgi/core/composite/org/eclipse/osgi/internal/composite/CompositeClassLoader.java b/bundles/org.eclipse.osgi/core/composite/org/eclipse/osgi/internal/composite/CompositeClassLoader.java new file mode 100644 index 000000000..ab2a6d772 --- /dev/null +++ b/bundles/org.eclipse.osgi/core/composite/org/eclipse/osgi/internal/composite/CompositeClassLoader.java @@ -0,0 +1,142 @@ +/******************************************************************************* + * Copyright (c) 2008 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 + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.osgi.internal.composite; + +import java.io.IOException; +import java.net.URL; +import java.security.ProtectionDomain; +import java.util.*; +import org.eclipse.osgi.baseadaptor.BaseData; +import org.eclipse.osgi.baseadaptor.bundlefile.BundleEntry; +import org.eclipse.osgi.baseadaptor.bundlefile.BundleFile; +import org.eclipse.osgi.baseadaptor.loader.*; +import org.eclipse.osgi.framework.adaptor.BundleData; +import org.eclipse.osgi.framework.adaptor.ClassLoaderDelegate; + +public class CompositeClassLoader extends ClassLoader implements BaseClassLoader { + + private final ClassLoaderDelegate delegate; + private final ClasspathManager manager; + private final ClassLoaderDelegate companionDelegate; + //Support to cut class / resource loading cycles in the context of one thread. The contained object is a set of classname + private final ThreadLocal beingLoaded = new ThreadLocal(); + + public CompositeClassLoader(ClassLoader parent, ClassLoaderDelegate delegate, ClassLoaderDelegate companionDelegate, BaseData data) { + super(parent); + this.delegate = delegate; + this.manager = new ClasspathManager(data, new String[0], this); + this.companionDelegate = companionDelegate; + } + + public ClasspathEntry createClassPathEntry(BundleFile bundlefile, ProtectionDomain cpDomain) { + // nothing + return null; + } + + public Class defineClass(String name, byte[] classbytes, ClasspathEntry classpathEntry, BundleEntry entry) { + // nothing + return null; + } + + public ClasspathManager getClasspathManager() { + return manager; + } + + public ProtectionDomain getDomain() { + // no domain + return null; + } + + public Object publicDefinePackage(String name, String specTitle, String specVersion, String specVendor, String implTitle, String implVersion, String implVendor, URL sealBase) { + return definePackage(name, specTitle, specVersion, specVendor, implTitle, implVersion, implVendor, sealBase); + } + + public Class publicFindLoaded(String classname) { + return findLoadedClass(classname); + } + + public Object publicGetPackage(String pkgname) { + return getPackage(pkgname); + } + + public void attachFragment(BundleData bundledata, ProtectionDomain domain, String[] classpath) { + // nothing + } + + public void close() { + // nothing + } + + public Class findLocalClass(String classname) throws ClassNotFoundException { + if (!startLoading(classname)) + throw new ClassNotFoundException(classname); + try { + return companionDelegate.findClass(classname); + } finally { + stopLoading(classname); + } + } + + public URL findLocalResource(String resource) { + if (!startLoading(resource)) + return null; + try { + return companionDelegate.findResource(resource); + } finally { + stopLoading(resource); + } + } + + public Enumeration findLocalResources(String resource) { + if (!startLoading(resource)) + return null; + try { + return companionDelegate.findResources(resource); + } catch (IOException e) { + return null; + } finally { + stopLoading(resource); + } + } + + public ClassLoaderDelegate getDelegate() { + return delegate; + } + + public URL getResource(String name) { + return delegate.findResource(name); + } + + public void initialize() { + manager.initialize(); + } + + public Class loadClass(String name) throws ClassNotFoundException { + return delegate.findClass(name); + } + + private boolean startLoading(String name) { + Set classesAndResources = (Set) beingLoaded.get(); + if (classesAndResources != null && classesAndResources.contains(name)) + return false; + + if (classesAndResources == null) { + classesAndResources = new HashSet(3); + beingLoaded.set(classesAndResources); + } + classesAndResources.add(name); + return true; + } + + private void stopLoading(String name) { + ((Set) beingLoaded.get()).remove(name); + } +} diff --git a/bundles/org.eclipse.osgi/core/composite/org/eclipse/osgi/internal/composite/CompositeConfigurator.java b/bundles/org.eclipse.osgi/core/composite/org/eclipse/osgi/internal/composite/CompositeConfigurator.java new file mode 100644 index 000000000..b317ece24 --- /dev/null +++ b/bundles/org.eclipse.osgi/core/composite/org/eclipse/osgi/internal/composite/CompositeConfigurator.java @@ -0,0 +1,182 @@ +/******************************************************************************* + * Copyright (c) 2008 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 + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.osgi.internal.composite; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URLConnection; +import java.security.AllPermission; +import java.security.ProtectionDomain; +import java.util.*; +import org.eclipse.osgi.baseadaptor.*; +import org.eclipse.osgi.baseadaptor.bundlefile.BundleEntry; +import org.eclipse.osgi.baseadaptor.hooks.AdaptorHook; +import org.eclipse.osgi.baseadaptor.hooks.ClassLoadingHook; +import org.eclipse.osgi.baseadaptor.loader.*; +import org.eclipse.osgi.framework.adaptor.*; +import org.eclipse.osgi.framework.log.FrameworkLog; +import org.eclipse.osgi.internal.module.*; +import org.eclipse.osgi.service.internal.composite.CompositeModule; +import org.eclipse.osgi.service.resolver.BundleDescription; +import org.osgi.framework.*; +import org.osgi.framework.launch.Framework; +import org.osgi.service.framework.CompositeBundle; +import org.osgi.service.framework.CompositeBundleFactory; + +public class CompositeConfigurator implements HookConfigurator, AdaptorHook, ClassLoadingHook, CompositeBundleFactory, CompositeResolveHelperRegistry { + + // the base adaptor + private BaseAdaptor adaptor; + // the composite bundle factory service reference + private ServiceRegistration factoryService; + // the system bundle context + private BundleContext systemContext; + + public void addHooks(HookRegistry hookRegistry) { + // this is an adaptor hook to register the composite factory and + // to shutdown child frameworks on shutdown + hookRegistry.addAdaptorHook(this); + // this is a class loading hook in order to create special class loaders for composites + hookRegistry.addClassLoadingHook(this); + } + + public void addProperties(Properties properties) { + // nothing + } + + public FrameworkLog createFrameworkLog() { + // nothing + return null; + } + + /** + * @throws BundleException + */ + public void frameworkStart(BundleContext context) throws BundleException { + this.systemContext = context; + // this is a composite resolve helper registry; add it to the resolver + ((ResolverImpl) adaptor.getState().getResolver()).setCompositeResolveHelperRegistry(this); + // register this as the composite bundle factory + factoryService = context.registerService(new String[] {CompositeBundleFactory.class.getName()}, this, null); + } + + public void frameworkStop(BundleContext context) { + // unregister the factory + if (factoryService != null) + factoryService.unregister(); + factoryService = null; + // stop any child frameworks than may still be running. + stopFrameworks(); + } + + public void frameworkStopping(BundleContext context) { + // nothing + } + + public void handleRuntimeError(Throwable error) { + // nothing + } + + public void initialize(BaseAdaptor initAdaptor) { + this.adaptor = initAdaptor; + } + + public URLConnection mapLocationToURLConnection(String location) { + // nothing + return null; + } + + public boolean matchDNChain(String pattern, String[] dnChain) { + // nothing + return false; + } + + public CompositeBundle installCompositeBundle(Map frameworkConfig, String location, Map compositeManifest) throws BundleException { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) + // must have AllPermission to do this + sm.checkPermission(new AllPermission()); + // make a local copy of the manifest first + compositeManifest = new HashMap(compositeManifest); + // make sure the manifest is valid + CompositeHelper.validateCompositeManifest(compositeManifest); + + try { + // get an in memory input stream to jar content of the composite we want to install + InputStream content = CompositeHelper.getCompositeInput(frameworkConfig, compositeManifest); + CompositeBundle result = (CompositeBundle) systemContext.installBundle(location, content); + // set the permissions + CompositeHelper.setCompositePermissions(location, systemContext); + return result; + } catch (IOException e) { + throw new BundleException("Error creating composite bundle", e); + } + } + + private void stopFrameworks() { + Bundle[] allBundles = systemContext.getBundles(); + // stop each child framework + for (int i = 0; i < allBundles.length; i++) { + if (!(allBundles[i] instanceof CompositeBundle)) + continue; + CompositeBundle composite = (CompositeBundle) allBundles[i]; + try { + Framework child = composite.getCompositeFramework(); + child.stop(); + // need to wait for each child to stop + child.waitForStop(30000); + // TODO need to figure out a way to invalid the child + } catch (Throwable t) { + // TODO consider logging + t.printStackTrace(); + } + } + } + + public CompositeResolveHelper getCompositeResolveHelper(BundleDescription bundle) { + // EquinoxComposite bundles implement the resolver helper + Bundle composite = systemContext.getBundle(bundle.getBundleId()); + // If we found a resolver helper bundle; return it + return (CompositeResolveHelper) ((!(composite instanceof CompositeResolveHelper)) ? null : composite); + } + + public boolean addClassPathEntry(ArrayList cpEntries, String cp, ClasspathManager hostmanager, BaseData sourcedata, ProtectionDomain sourcedomain) { + // nothing + return false; + } + + public BaseClassLoader createClassLoader(ClassLoader parent, ClassLoaderDelegate delegate, BundleProtectionDomain domain, BaseData data, String[] bundleclasspath) { + if ((data.getType() & (BundleData.TYPE_COMPOSITEBUNDLE | BundleData.TYPE_SURROGATEBUNDLE)) == 0) + return null; + // only create composite class loaders for bundles that are of type composite | surrogate + ClassLoaderDelegate companionDelegate = ((CompositeModule) ((CompositeBase) data.getBundle()).getCompanionBundle()).getDelegate(); + return new CompositeClassLoader(parent, delegate, companionDelegate, data); + } + + public String findLibrary(BaseData data, String libName) { + // nothing + return null; + } + + public ClassLoader getBundleClassLoaderParent() { + // nothing + return null; + } + + public void initializedClassLoader(BaseClassLoader baseClassLoader, BaseData data) { + // nothing + } + + public byte[] processClass(String name, byte[] classbytes, ClasspathEntry classpathEntry, BundleEntry entry, ClasspathManager manager) { + // nothing + return null; + } +} diff --git a/bundles/org.eclipse.osgi/core/composite/org/eclipse/osgi/internal/composite/CompositeHelper.java b/bundles/org.eclipse.osgi/core/composite/org/eclipse/osgi/internal/composite/CompositeHelper.java new file mode 100644 index 000000000..52f38a758 --- /dev/null +++ b/bundles/org.eclipse.osgi/core/composite/org/eclipse/osgi/internal/composite/CompositeHelper.java @@ -0,0 +1,263 @@ +/******************************************************************************* + * Copyright (c) 2008 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 + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.osgi.internal.composite; + +import java.io.*; +import java.util.*; +import java.util.Map.Entry; +import java.util.jar.*; +import org.eclipse.osgi.internal.baseadaptor.BaseStorageHook; +import org.eclipse.osgi.service.resolver.*; +import org.osgi.framework.*; +import org.osgi.service.permissionadmin.PermissionAdmin; +import org.osgi.service.permissionadmin.PermissionInfo; + +public class CompositeHelper { + private static final PermissionInfo[] COMPOSITE_PERMISSIONS = new PermissionInfo[] {new PermissionInfo(PackagePermission.class.getName(), "*", PackagePermission.EXPORT), new PermissionInfo(ServicePermission.class.getName(), "*", ServicePermission.REGISTER + ',' + ServicePermission.GET)}; //$NON-NLS-1$ //$NON-NLS-2$ + private static final String COMPOSITE_POLICY = "org.eclipse.osgi.composite"; //$NON-NLS-1$ + private static String ELEMENT_SEPARATOR = "; "; //$NON-NLS-1$ + private static final Object EQUALS_QUOTE = "=\""; //$NON-NLS-1$ + private static final String[] INVALID_COMPOSITE_HEADERS = new String[] {Constants.DYNAMICIMPORT_PACKAGE, Constants.FRAGMENT_HOST, Constants.REQUIRE_BUNDLE, Constants.BUNDLE_NATIVECODE, Constants.BUNDLE_CLASSPATH, Constants.BUNDLE_ACTIVATOR, Constants.BUNDLE_LOCALIZATION, Constants.BUNDLE_ACTIVATIONPOLICY}; + + private static Manifest getCompositeManifest(Map compositeManifest) { + Manifest manifest = new Manifest(); + Attributes attributes = manifest.getMainAttributes(); + attributes.putValue("Manifest-Version", "1.0"); //$NON-NLS-1$//$NON-NLS-2$ + // get the common headers Bundle-ManifestVersion, Bundle-SymbolicName and Bundle-Version + // get the manifest version from the map + String manifestVersion = (String) compositeManifest.remove(Constants.BUNDLE_MANIFESTVERSION); + // here we assume the validation got the correct version for us + attributes.putValue(Constants.BUNDLE_MANIFESTVERSION, manifestVersion); + // Ignore the Equinox composite bundle header + compositeManifest.remove(BaseStorageHook.COMPOSITE_HEADER); + attributes.putValue(BaseStorageHook.COMPOSITE_HEADER, BaseStorageHook.COMPOSITE_BUNDLE); + for (Iterator entries = compositeManifest.entrySet().iterator(); entries.hasNext();) { + Map.Entry entry = (Entry) entries.next(); + if (entry.getKey() instanceof String && entry.getValue() instanceof String) + attributes.putValue((String) entry.getKey(), (String) entry.getValue()); + } + return manifest; + } + + private static Manifest getSurrogateManifest(Dictionary compositeManifest, BundleDescription compositeDesc, ExportPackageDescription[] matchingExports) { + Manifest manifest = new Manifest(); + Attributes attributes = manifest.getMainAttributes(); + attributes.putValue("Manifest-Version", "1.0"); //$NON-NLS-1$//$NON-NLS-2$ + // Ignore the manifest version from the map + // always use bundle manifest version 2 + attributes.putValue(Constants.BUNDLE_MANIFESTVERSION, "2"); //$NON-NLS-1$ + // Ignore the Equinox composite bundle header + attributes.putValue(BaseStorageHook.COMPOSITE_HEADER, BaseStorageHook.SURROGATE_BUNDLE); + + if (compositeDesc != null && matchingExports != null) { + // convert the exports from the composite into imports + addImports(attributes, compositeDesc, matchingExports); + + // convert the matchingExports from the composite into exports + addExports(attributes, matchingExports); + } + + // add the rest + for (Enumeration keys = compositeManifest.keys(); keys.hasMoreElements();) { + Object header = keys.nextElement(); + if (Constants.BUNDLE_MANIFESTVERSION.equals(header) || BaseStorageHook.COMPOSITE_HEADER.equals(header) || Constants.IMPORT_PACKAGE.equals(header) || Constants.EXPORT_PACKAGE.equals(header)) + continue; + if (header instanceof String && compositeManifest.get(header) instanceof String) + attributes.putValue((String) header, (String) compositeManifest.get(header)); + } + return manifest; + } + + static InputStream getCompositeInput(Map frameworkConfig, Map compositeManifest) throws IOException { + // use an in memory stream to store the content + ByteArrayOutputStream bytesOut = new ByteArrayOutputStream(); + // the composite bundles only consist of a manifest describing the packages they import and export + // and a framework config properties file + Manifest manifest = CompositeHelper.getCompositeManifest(compositeManifest); + JarOutputStream jarOut = new JarOutputStream(bytesOut, manifest); + try { + // store the framework config + Properties fwProps = new Properties(); + if (frameworkConfig != null) + fwProps.putAll(frameworkConfig); + JarEntry entry = new JarEntry(CompositeImpl.COMPOSITE_CONFIGURATION); + jarOut.putNextEntry(entry); + fwProps.store(jarOut, null); + jarOut.closeEntry(); + jarOut.flush(); + } finally { + try { + jarOut.close(); + } catch (IOException e) { + // nothing + } + } + return new ByteArrayInputStream(bytesOut.toByteArray()); + } + + static InputStream getSurrogateInput(Dictionary compositeManifest, BundleDescription compositeDesc, ExportPackageDescription[] matchingExports) throws IOException { + // use an in memory stream to store the content + ByteArrayOutputStream bytesOut = new ByteArrayOutputStream(); + Manifest manifest = CompositeHelper.getSurrogateManifest(compositeManifest, compositeDesc, matchingExports); + JarOutputStream jarOut = new JarOutputStream(bytesOut, manifest); + jarOut.flush(); + jarOut.close(); + return new ByteArrayInputStream(bytesOut.toByteArray()); + } + + private static void addImports(Attributes attrigutes, BundleDescription compositeDesc, ExportPackageDescription[] matchingExports) { + ExportPackageDescription[] exports = compositeDesc.getExportPackages(); + List systemExports = getSystemExports(matchingExports); + if (exports.length == 0 && systemExports.size() == 0) + return; + StringBuffer importStatement = new StringBuffer(); + Collection importedNames = new ArrayList(exports.length); + int i = 0; + for (; i < exports.length; i++) { + if (i != 0) + importStatement.append(','); + importedNames.add(exports[i].getName()); + getImportFrom(exports[i], importStatement); + } + for (Iterator iSystemExports = systemExports.iterator(); iSystemExports.hasNext();) { + ExportPackageDescription systemExport = (ExportPackageDescription) iSystemExports.next(); + if (!importedNames.contains(systemExport.getName())) { + if (i != 0) + importStatement.append(','); + i++; + importStatement.append(systemExport.getName()).append(ELEMENT_SEPARATOR).append(Constants.BUNDLE_SYMBOLICNAME_ATTRIBUTE).append('=').append(Constants.SYSTEM_BUNDLE_SYMBOLICNAME); + } + } + attrigutes.putValue(Constants.IMPORT_PACKAGE, importStatement.toString()); + } + + private static List getSystemExports(ExportPackageDescription[] matchingExports) { + ArrayList list = null; + for (int i = 0; i < matchingExports.length; i++) { + if (matchingExports[i].getExporter().getBundleId() != 0) + continue; + if (list == null) + list = new ArrayList(); + list.add(matchingExports[i]); + } + return list == null ? Collections.EMPTY_LIST : list; + } + + private static void getImportFrom(ExportPackageDescription export, StringBuffer importStatement) { + importStatement.append(export.getName()).append(ELEMENT_SEPARATOR); + Version version = export.getVersion(); + importStatement.append(Constants.VERSION_ATTRIBUTE).append(EQUALS_QUOTE).append('[').append(version).append(',').append(new Version(version.getMajor(), version.getMinor(), version.getMicro() + 1)).append(')').append('\"'); + addMap(importStatement, export.getAttributes(), "="); //$NON-NLS-1$ + } + + private static void addExports(Attributes attributes, ExportPackageDescription[] matchingExports) { + if (matchingExports.length == 0) + return; + StringBuffer exportStatement = new StringBuffer(); + for (int i = 0; i < matchingExports.length; i++) { + if (i != 0) + exportStatement.append(','); + getExportFrom(matchingExports[i], exportStatement); + } + attributes.putValue(Constants.EXPORT_PACKAGE, exportStatement.toString()); + } + + private static void getExportFrom(ExportPackageDescription export, StringBuffer exportStatement) { + exportStatement.append(export.getName()).append(ELEMENT_SEPARATOR); + exportStatement.append(Constants.VERSION_ATTRIBUTE).append(EQUALS_QUOTE).append(export.getVersion()).append('\"'); + addMap(exportStatement, export.getDirectives(), ":="); //$NON-NLS-1$ + addMap(exportStatement, export.getAttributes(), "="); //$NON-NLS-1$ + } + + private static void addMap(StringBuffer manifest, Map values, String assignment) { + if (values == null) + return; // nothing to add + for (Iterator iEntries = values.entrySet().iterator(); iEntries.hasNext();) { + manifest.append(ELEMENT_SEPARATOR); + Map.Entry entry = (Entry) iEntries.next(); + manifest.append(entry.getKey()).append(assignment).append('\"'); + Object value = entry.getValue(); + if (value instanceof String[]) { + String[] strings = (String[]) value; + for (int i = 0; i < strings.length; i++) { + if (i != 0) + manifest.append(','); + manifest.append(strings[i]); + } + } else { + manifest.append(value); + } + manifest.append('\"'); + } + } + + static void setCompositePermissions(String bundleLocation, BundleContext systemContext) { + ServiceReference ref = systemContext.getServiceReference(PermissionAdmin.class.getName()); + PermissionAdmin permAdmin = (PermissionAdmin) (ref == null ? null : systemContext.getService(ref)); + if (permAdmin == null) + throw new RuntimeException("No Permission Admin service is available"); + try { + permAdmin.setPermissions(bundleLocation, COMPOSITE_PERMISSIONS); + } finally { + systemContext.ungetService(ref); + } + } + + static void setDisabled(boolean disable, Bundle bundle, BundleContext systemContext) { + ServiceReference ref = systemContext.getServiceReference(PlatformAdmin.class.getName()); + PlatformAdmin pa = (PlatformAdmin) (ref == null ? null : systemContext.getService(ref)); + if (pa == null) + throw new RuntimeException("No Platform Admin service is available."); + try { + State state = pa.getState(false); + BundleDescription desc = state.getBundle(bundle.getBundleId()); + setDisabled(disable, desc); + } finally { + systemContext.ungetService(ref); + } + } + + static void setDisabled(boolean disable, BundleDescription bundle) { + State state = bundle.getContainingState(); + if (disable) { + state.addDisabledInfo(new DisabledInfo(COMPOSITE_POLICY, "Composite companion bundle is not resolved.", bundle)); + } else { + DisabledInfo toRemove = state.getDisabledInfo(bundle, COMPOSITE_POLICY); + if (toRemove != null) + state.removeDisabledInfo(toRemove); + } + } + + static void validateCompositeManifest(Map compositeManifest) throws BundleException { + if (compositeManifest == null) + throw new BundleException("The composite manifest cannot be null.", BundleException.MANIFEST_ERROR); + // check for symbolic name + if (compositeManifest.get(Constants.BUNDLE_SYMBOLICNAME) == null) + throw new BundleException("The composite manifest must contain a Bundle-SymbolicName header.", BundleException.MANIFEST_ERROR); + // check for invalid manifests headers + for (int i = 0; i < INVALID_COMPOSITE_HEADERS.length; i++) + if (compositeManifest.get(INVALID_COMPOSITE_HEADERS[i]) != null) + throw new BundleException("The composite manifest must not contain the header " + INVALID_COMPOSITE_HEADERS[i], BundleException.MANIFEST_ERROR); + // validate manifest version + String manifestVersion = (String) compositeManifest.get(Constants.BUNDLE_MANIFESTVERSION); + if (manifestVersion == null) { + compositeManifest.put(Constants.BUNDLE_MANIFESTVERSION, "2"); //$NON-NLS-1$ + } else { + try { + Integer parsed = Integer.valueOf(manifestVersion); + if (parsed.intValue() > 2 || parsed.intValue() < 2) + throw new BundleException("Invalid Bundle-ManifestVersion: " + manifestVersion); + } catch (NumberFormatException e) { + throw new BundleException("Invalid Bundle-ManifestVersion: " + manifestVersion); + } + } + } +} diff --git a/bundles/org.eclipse.osgi/core/composite/org/eclipse/osgi/internal/composite/CompositeImpl.java b/bundles/org.eclipse.osgi/core/composite/org/eclipse/osgi/internal/composite/CompositeImpl.java new file mode 100644 index 000000000..4b840716e --- /dev/null +++ b/bundles/org.eclipse.osgi/core/composite/org/eclipse/osgi/internal/composite/CompositeImpl.java @@ -0,0 +1,280 @@ +/******************************************************************************* + * Copyright (c) 2008 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 + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.osgi.internal.composite; + +import java.io.*; +import java.net.URL; +import java.util.Map; +import java.util.Properties; +import org.eclipse.osgi.framework.adaptor.BundleData; +import org.eclipse.osgi.framework.internal.core.Constants; +import org.eclipse.osgi.internal.loader.BundleLoader; +import org.eclipse.osgi.internal.loader.BundleLoaderProxy; +import org.eclipse.osgi.launch.Equinox; +import org.eclipse.osgi.service.internal.composite.CompositeModule; +import org.eclipse.osgi.service.resolver.BundleDescription; +import org.eclipse.osgi.service.resolver.ExportPackageDescription; +import org.osgi.framework.*; +import org.osgi.framework.launch.Framework; +import org.osgi.service.framework.*; + +public class CompositeImpl extends CompositeBase implements CompositeBundle { + private static String COMPOSITE_STORAGE = "store"; //$NON-NLS-1$ + public static String COMPOSITE_CONFIGURATION = "compositeConfig.properties"; //$NON-NLS-1$ + + private final ServiceTrackerManager trackerManager = new ServiceTrackerManager(); + + public CompositeImpl(BundleData bundledata, org.eclipse.osgi.framework.internal.core.Framework framework) throws BundleException { + super(bundledata, framework); + } + + protected Framework findCompanionFramework(org.eclipse.osgi.framework.internal.core.Framework thisFramework, BundleData thisData) throws BundleException { + // allocate storage area for the composite framework + File compositeStorage = thisData.getDataFile(COMPOSITE_STORAGE); + boolean firstTime = false; + if (!compositeStorage.exists()) + // the child storage area has not been allocated; this is the first time + firstTime = true; + // find the configuration properties + URL childConfig = bundledata.getEntry(COMPOSITE_CONFIGURATION); + Properties props = new Properties(); + try { + props.load(childConfig.openStream()); + } catch (IOException e) { + throw new BundleException("Could not load child configuration", e); + } + props.put(Constants.FRAMEWORK_STORAGE, compositeStorage.getAbsolutePath()); + // save the parent framework so the parent companion bundle can find it + props.put(PROP_PARENTFRAMEWORK, thisFramework.getSystemBundleContext().getBundle()); + // TODO leaks "this" out of the constructor + props.put(PROP_COMPOSITE, this); + Equinox equinox = new Equinox(props); + if (!firstTime) + // if not the first time then we are done + return equinox; + equinox.init(); + installSurrogate(equinox.getBundleContext(), thisData); + return equinox; + } + + private void installSurrogate(BundleContext companionContext, BundleData thisData) throws BundleException { + Bundle surrogate; + try { + InputStream surrogateContent = CompositeHelper.getSurrogateInput(thisData.getManifest(), null, null); + surrogate = companionContext.installBundle(thisData.getLocation(), surrogateContent); + } catch (IOException e) { + throw new BundleException("Error installing parent companion composite bundle", e); + } + // disable the surrogate initially since we know we have not resolved the composite yet. + CompositeHelper.setDisabled(true, surrogate, companionContext); + // set the permissions of the surrogate bundle + CompositeHelper.setCompositePermissions(thisData.getLocation(), companionContext); + } + + private boolean updateSurrogate(BundleData thisData, BundleDescription child, ExportPackageDescription[] matchingExports) throws BundleException { + // update the surrogate content with the matching exports provided by the composite + InputStream surrogateContent; + try { + surrogateContent = CompositeHelper.getSurrogateInput(thisData.getManifest(), child, matchingExports); + } catch (IOException e) { + throw new BundleException("Error updating surrogate bundle.", e); + } + CompositeModule surrogateComposite = (CompositeModule) getSurrogateBundle(); + surrogateComposite.updateContent(surrogateContent); + // enable/disable the surrogate composite based on if we have exports handed to us + boolean disable = matchingExports == null ? true : false; + CompositeHelper.setDisabled(disable, getSurrogateBundle(), getCompositeFramework().getBundleContext()); + // return true if we can resolve the surrogate bundle + return disable ? false : surrogateComposite.resolveContent(); + } + + private SurrogateBundle findSurrogateBundle() throws BundleException { + if ((companionFramework.getState() & (Bundle.INSTALLED | Bundle.RESOLVED)) != 0) + companionFramework.init(); + return (SurrogateBundle) getCompanionBundle(); + } + + public Framework getCompositeFramework() { + return companionFramework; + } + + public SurrogateBundle getSurrogateBundle() { + try { + return findSurrogateBundle(); + } catch (BundleException e) { + throw new RuntimeException("Error intializing child framework", e); + } + } + + public void update(Map compositeManifest) throws BundleException { + // validate the composite manifest + CompositeHelper.validateCompositeManifest(compositeManifest); + if (isResolved()) { + // force the class loader creation before updating the surrogate to cache the current state + // this is to allow for lazy updates of composite bundles + BundleLoader loader = getBundleLoader(); + if (loader != null) + loader.createClassLoader(); + } + try { + Map frameworkConfig = getFrameworkConfig(); + // first update the parent companion and disable it + updateSurrogate(getBundleData(), null, null); + // update the content with the new manifest + updateContent(CompositeHelper.getCompositeInput(frameworkConfig, compositeManifest)); + } catch (IOException e) { + throw new BundleException("Error updating composite.", e); + } + } + + private Map getFrameworkConfig() throws IOException { + Properties result = new Properties(); + URL config = getEntry(COMPOSITE_CONFIGURATION); + result.load(config.openStream()); + return result; + } + + public void uninstall() throws BundleException { + // ensure class loader is created if needed + checkClassLoader(); + // stop first before stopping the child to let the service listener clean up + stop(Bundle.STOP_TRANSIENT); + stopChildFramework(); + super.uninstall(); + } + + private void checkClassLoader() { + BundleLoaderProxy proxy = getLoaderProxy(); + if (proxy != null && proxy.inUse() && proxy.getBundleLoader() != null) + proxy.getBundleLoader().createClassLoader(); + } + + protected void startHook() throws BundleException { + // always start the child framework + companionFramework.start(); + trackerManager.startedComposite(); + } + + protected void stopHook() throws BundleException { + trackerManager.stoppedComposite(); + // do not stop the framework unless we are persistently stopped + if ((bundledata.getStatus() & Constants.BUNDLE_STARTED) == 0) + stopChildFramework(); + } + + public void started(CompositeModule surrogate) { + if (surrogate == getSurrogateBundle()) + trackerManager.startedSurrogate(); + } + + public void stopped(CompositeModule surrogate) { + if (surrogate == getSurrogateBundle()) + trackerManager.stoppedSurrogate(); + } + + private void stopChildFramework() throws BundleException { + companionFramework.stop(); + try { + FrameworkEvent stopped = companionFramework.waitForStop(30000); + switch (stopped.getType()) { + case FrameworkEvent.ERROR : + throw new BundleException("Error stopping the child framework.", stopped.getThrowable()); + case FrameworkEvent.INFO : + throw new BundleException("Timed out waiting for the child framework to stop."); + case FrameworkEvent.STOPPED : + // normal stop, just return + return; + default : + throw new BundleException("Unexpected code returned when stopping the child framework:" + stopped.getType()); + } + } catch (InterruptedException e) { + throw new BundleException("Error stopping child framework", e); + } + } + + public boolean giveExports(ExportPackageDescription[] matchingExports) { + if (matchingExports == null) { + SurrogateBundle surrogate = getSurrogateBundle(); + // disable the surrogate + CompositeHelper.setDisabled(true, getSurrogateBundle(), getCompositeFramework().getBundleContext()); + // refresh the parent composite synchronously + ((CompositeModule) surrogate).refreshContent(true); + return true; + } + try { + return updateSurrogate(getBundleData(), getBundleDescription(), matchingExports); + } catch (BundleException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return false; + } + } + + /* + * Listens to source and target bundles and source and target framework changes + */ + class ServiceTrackerManager { + static final int COMPOSITE_ACTIVE = 0x01; + static final int SURROGATE_ACTIVE = 0x02; + // @GuardedBy(this) + private int bundlesActive = 0; + // @GuardedBy(this) + private CompositeServiceTracker shareToChildServices; + // @GuardedBy(this) + private CompositeServiceTracker shareToParentServices; + + void startedComposite() throws BundleException { + open(COMPOSITE_ACTIVE); + getSurrogateBundle().start(Bundle.START_TRANSIENT); + } + + void startedSurrogate() { + open(SURROGATE_ACTIVE); + } + + void stoppedComposite() { + try { + getSurrogateBundle().stop(Bundle.STOP_TRANSIENT); + } catch (BundleException e) { + // nothing + } catch (IllegalStateException e) { + // child framework must have been stoped + } + close(COMPOSITE_ACTIVE); + } + + void stoppedSurrogate() { + close(SURROGATE_ACTIVE); + } + + private synchronized void open(int bundleActive) { + bundlesActive |= bundleActive; + if ((bundlesActive & (COMPOSITE_ACTIVE | SURROGATE_ACTIVE)) != (COMPOSITE_ACTIVE | SURROGATE_ACTIVE)) + return; + // create a service tracker to track and share services from the parent framework + shareToChildServices = new CompositeServiceTracker(getBundleContext(), getSurrogateBundle().getBundleContext(), (String) getBundleContext().getBundle().getHeaders("").get(CompositeBundleFactory.COMPOSITE_SERVICE_FILTER_IMPORT)); //$NON-NLS-1$ + shareToChildServices.open(); + // create a service tracker to track and share services from the child framework + shareToParentServices = new CompositeServiceTracker(getSurrogateBundle().getBundleContext(), getBundleContext(), (String) getBundleContext().getBundle().getHeaders("").get(CompositeBundleFactory.COMPOSITE_SERVICE_FILTER_EXPORT)); //$NON-NLS-1$ + shareToParentServices.open(); + + } + + private synchronized void close(int bundleStopped) { + bundlesActive ^= bundleStopped; + // close the service tracker to stop tracking and sharing services + if (shareToChildServices != null) + shareToChildServices.close(); + if (shareToParentServices != null) + shareToParentServices.close(); + } + } +} diff --git a/bundles/org.eclipse.osgi/core/composite/org/eclipse/osgi/internal/composite/CompositeServiceTracker.java b/bundles/org.eclipse.osgi/core/composite/org/eclipse/osgi/internal/composite/CompositeServiceTracker.java new file mode 100644 index 000000000..a9a449b40 --- /dev/null +++ b/bundles/org.eclipse.osgi/core/composite/org/eclipse/osgi/internal/composite/CompositeServiceTracker.java @@ -0,0 +1,178 @@ +/******************************************************************************* + * Copyright (c) 2008 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 + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.osgi.internal.composite; + +import java.util.*; +import org.eclipse.osgi.util.ManifestElement; +import org.osgi.framework.*; +import org.osgi.util.tracker.ServiceTracker; +import org.osgi.util.tracker.ServiceTrackerCustomizer; + +class CompositeServiceTracker implements ServiceTrackerCustomizer { + final BundleContext sourceContext; + final BundleContext targetContext; + final ServiceTracker[] trackers; + final String[] filters; + /* @GuardedBy("serviceComposites") */ + final HashMap serviceComposites = new HashMap(); + + public CompositeServiceTracker(BundleContext sourceContext, BundleContext targetContext, String serviceFilters) { + this.sourceContext = sourceContext; + this.targetContext = targetContext; + filters = ManifestElement.getArrayFromList(serviceFilters, ","); //$NON-NLS-1$ + trackers = new ServiceTracker[filters.length]; + } + + synchronized void open() { + for (int i = 0; i < trackers.length; i++) { + try { + trackers[i] = new ServiceTracker(sourceContext, sourceContext.createFilter(filters[i]), this); + trackers[i].open(); + } catch (InvalidSyntaxException e) { + // TODO log + // we will skip this filter; note that trackers may have null entries + } + } + } + + synchronized void close() { + for (int i = 0; i < trackers.length; i++) { + if (trackers[i] != null) + trackers[i].close(); + } + } + + public Object addingService(ServiceReference reference) { + ServiceLink serviceLink; + int useCount; + synchronized (serviceComposites) { + serviceLink = (ServiceLink) serviceComposites.get(reference); + if (serviceLink == null) { + serviceLink = new ServiceLink(reference); + serviceComposites.put(reference, serviceLink); + } + useCount = serviceLink.incrementUse(); + } + // register service outside of the sync block + if (useCount == 1) + serviceLink.register(); + return serviceLink; + } + + public void modifiedService(ServiceReference reference, Object service) { + ServiceLink serviceLink = (ServiceLink) service; + Dictionary serviceProps = null; + synchronized (serviceComposites) { + serviceProps = serviceLink.getRefreshProperties(); + } + // set service properties out side the sync block + if (serviceProps != null) + ((ServiceLink) service).setServiceProperties(serviceProps); + } + + public void removedService(ServiceReference reference, Object service) { + int useCount; + synchronized (serviceComposites) { + useCount = ((ServiceLink) service).decrementUse(); + if (useCount == 0) + serviceComposites.remove(reference); + } + // unregister outside the sync block + if (useCount == 0) + ((ServiceLink) service).unregister(); + } + + class ServiceLink implements ServiceFactory { + private final ServiceReference reference; + private volatile ServiceRegistration registration; + /* @GuardedBy("this") */ + private Object service; + /* @GuardedBy("serviceLinks") */ + private int useCount; + + ServiceLink(ServiceReference reference) { + this.reference = reference; + } + + /* @GuardedBy("serviceLinks") */ + Dictionary getRefreshProperties() { + Dictionary result = getServiceProperties(); + if (useCount <= 1) + return result; + // need to do an expensive properties check to avoid multiple registration property changes + String[] originalKeys = registration.getReference().getPropertyKeys(); + for (int i = 0; i < originalKeys.length; i++) { + if (!Constants.OBJECTCLASS.equals(originalKeys[i]) && !Constants.SERVICE_ID.equals(originalKeys[i])) + // identity compare is done on purpose here to catch any kind of change + if (registration.getReference().getProperty(originalKeys[i]) != result.get(originalKeys[i])) + return result; + } + for (Enumeration eKeys = result.keys(); eKeys.hasMoreElements();) { + String key = (String) eKeys.nextElement(); + if (!Constants.OBJECTCLASS.equals(key) && !Constants.SERVICE_ID.equals(key)) + // identity compare is done on purpose here to catch any kind of change + if (result.get(key) != registration.getReference().getProperty(key)) + return result; + } + return null; + } + + /* @GuardedBy("serviceLinks") */ + int decrementUse() { + return --useCount; + } + + /* @GuardedBy("serviceLinks") */ + int incrementUse() { + return ++useCount; + } + + /* @GuardedBy("serviceLinks") */ + int getUse() { + return useCount; + } + + void setServiceProperties(Dictionary props) { + ServiceRegistration current = registration; + if (current != null) + current.setProperties(props); + } + + void register() { + Dictionary props = getServiceProperties(); + registration = targetContext.registerService((String[]) props.get(Constants.OBJECTCLASS), this, props); + } + + void unregister() { + ServiceRegistration current = registration; + if (current != null) + current.unregister(); + } + + private Dictionary getServiceProperties() { + String[] keys = reference.getPropertyKeys(); + Hashtable serviceProps = new Hashtable(keys.length); + for (int i = 0; i < keys.length; i++) + serviceProps.put(keys[i], reference.getProperty(keys[i])); + return serviceProps; + } + + public synchronized Object getService(Bundle bundle, ServiceRegistration reg) { + if (service == null) + service = sourceContext.getService(reference); + return service; + } + + public void ungetService(Bundle bundle, ServiceRegistration reg, Object serv) { + // nothing + } + } +} diff --git a/bundles/org.eclipse.osgi/core/composite/org/eclipse/osgi/internal/composite/SurrogateImpl.java b/bundles/org.eclipse.osgi/core/composite/org/eclipse/osgi/internal/composite/SurrogateImpl.java new file mode 100644 index 000000000..c77d687fd --- /dev/null +++ b/bundles/org.eclipse.osgi/core/composite/org/eclipse/osgi/internal/composite/SurrogateImpl.java @@ -0,0 +1,93 @@ +/******************************************************************************* + * Copyright (c) 2008 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 + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.osgi.internal.composite; + +import org.eclipse.osgi.framework.adaptor.BundleData; +import org.eclipse.osgi.framework.internal.core.FrameworkProperties; +import org.eclipse.osgi.internal.module.ResolverBundle; +import org.eclipse.osgi.service.internal.composite.CompositeModule; +import org.eclipse.osgi.service.resolver.BundleDescription; +import org.eclipse.osgi.service.resolver.ExportPackageDescription; +import org.osgi.framework.*; +import org.osgi.framework.launch.Framework; +import org.osgi.service.framework.SurrogateBundle; + +public class SurrogateImpl extends CompositeBase implements SurrogateBundle { + + public SurrogateImpl(BundleData bundledata, org.eclipse.osgi.framework.internal.core.Framework framework) throws BundleException { + super(bundledata, framework); + } + + protected Framework findCompanionFramework(org.eclipse.osgi.framework.internal.core.Framework thisFramework, BundleData thisData) { + // just get the property which was set when creating the child framework + return (Framework) FrameworkProperties.getProperties().get(PROP_PARENTFRAMEWORK); + } + + public BundleContext getCompositeBundleContext() { + Bundle composite = getCompanionBundle(); + return composite == null ? null : composite.getBundleContext(); + } + + protected boolean isSurrogate() { + return true; + } + + public boolean giveExports(ExportPackageDescription[] matchingExports) { + if (matchingExports == null) { + // set the surrogate to disabled to prevent resolution this go around + CompositeHelper.setDisabled(true, getBundleDescription()); + // refresh the composite bundle (in the parent framework) asynchronously and enable it + // should only do this if the composite is not in the process of refreshing this + // surrogate bundle + if (refreshing.get() == null) + ((CompositeModule) getCompanionBundle()).refreshContent(false); + return true; + } + return validExports(matchingExports); + } + + private boolean validExports(ExportPackageDescription[] matchingExports) { + // make sure each matching exports matches the export signature of the composite + CompositeModule composite = (CompositeModule) getCompanionBundle(); + BundleDescription childDesc = composite.getCompositeDescription(); + ExportPackageDescription[] childExports = childDesc.getExportPackages(); + for (int i = 0; i < matchingExports.length; i++) { + for (int j = 0; j < childExports.length; j++) { + if (matchingExports[i].getName().equals(childExports[j].getName())) { + if (!validateExport(matchingExports[i], childExports[j])) + return false; + continue; + } + } + } + return true; + } + + private boolean validateExport(ExportPackageDescription matchingExport, ExportPackageDescription childExport) { + Version matchingVersion = matchingExport.getVersion(); + Version childVersion = childExport.getVersion(); + if (!childVersion.equals(Version.emptyVersion) && !matchingVersion.equals(childVersion)) + return false; + if (!ResolverBundle.equivalentMaps(childExport.getAttributes(), matchingExport.getAttributes(), false)) + return false; + if (!ResolverBundle.equivalentMaps(childExport.getDirectives(), matchingExport.getDirectives(), false)) + return false; + return true; + } + + protected void startHook() { + ((CompositeModule) getCompanionBundle()).started(this); + } + + protected void stopHook() { + ((CompositeModule) getCompanionBundle()).stopped(this); + } +} diff --git a/bundles/org.eclipse.osgi/core/composite/org/eclipse/osgi/service/internal/composite/CompositeModule.java b/bundles/org.eclipse.osgi/core/composite/org/eclipse/osgi/service/internal/composite/CompositeModule.java new file mode 100644 index 000000000..502ed47da --- /dev/null +++ b/bundles/org.eclipse.osgi/core/composite/org/eclipse/osgi/service/internal/composite/CompositeModule.java @@ -0,0 +1,28 @@ +package org.eclipse.osgi.service.internal.composite; + +import java.io.InputStream; +import org.eclipse.osgi.framework.adaptor.ClassLoaderDelegate; +import org.eclipse.osgi.service.resolver.BundleDescription; +import org.osgi.framework.BundleException; + +/** + * An internal interface only used by the composite implementation + * + * @noextend This interface is not intended to be extended by clients. + * @noimplement This interface is not intended to be implemented by clients. + */ +public interface CompositeModule { + public void updateContent(InputStream content) throws BundleException; + + public void refreshContent(boolean synchronously); + + public boolean resolveContent(); + + public BundleDescription getCompositeDescription(); + + public ClassLoaderDelegate getDelegate(); + + public void started(CompositeModule compositeBundle); + + public void stopped(CompositeModule compositeBundle); +} 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 545b03dc5..fdd41d081 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 @@ -20,6 +20,8 @@ import java.util.*; import org.eclipse.osgi.framework.adaptor.*; import org.eclipse.osgi.framework.debug.Debug; import org.eclipse.osgi.framework.util.KeyedElement; +import org.eclipse.osgi.internal.composite.CompositeImpl; +import org.eclipse.osgi.internal.composite.SurrogateImpl; import org.eclipse.osgi.internal.loader.BundleLoader; import org.eclipse.osgi.internal.permadmin.EquinoxProtectionDomain; import org.eclipse.osgi.internal.permadmin.EquinoxSecurityManager; @@ -63,6 +65,10 @@ public abstract class AbstractBundle implements Bundle, Comparable, KeyedElement AbstractBundle result; if ((bundledata.getType() & BundleData.TYPE_FRAGMENT) > 0) result = new BundleFragment(bundledata, framework); + else if ((bundledata.getType() & BundleData.TYPE_COMPOSITEBUNDLE) > 0) + result = new CompositeImpl(bundledata, framework); + else if ((bundledata.getType() & BundleData.TYPE_SURROGATEBUNDLE) > 0) + result = new SurrogateImpl(bundledata, framework); else result = new BundleHost(bundledata, framework); if (setBundle) diff --git a/bundles/org.eclipse.osgi/core/framework/org/eclipse/osgi/framework/internal/core/BundleHost.java b/bundles/org.eclipse.osgi/core/framework/org/eclipse/osgi/framework/internal/core/BundleHost.java index 4e5224dd7..d85c02a8e 100644 --- a/bundles/org.eclipse.osgi/core/framework/org/eclipse/osgi/framework/internal/core/BundleHost.java +++ b/bundles/org.eclipse.osgi/core/framework/org/eclipse/osgi/framework/internal/core/BundleHost.java @@ -346,7 +346,7 @@ public class BundleHost extends AbstractBundle { } try { context.start(); - + startHook(); if (framework.active) { state = ACTIVE; @@ -363,6 +363,7 @@ public class BundleHost extends AbstractBundle { state = STOPPING; framework.publishBundleEvent(BundleEvent.STOPPING, this); + stopHook(); context.close(); context = null; @@ -388,6 +389,13 @@ public class BundleHost extends AbstractBundle { } } + /** + * @throws BundleException + */ + protected void startHook() throws BundleException { + // do nothing by default + } + protected boolean readyToResume() { // Return false if the bundle is not at the correct start-level if (getStartLevel() > framework.startLevelManager.getStartLevel()) @@ -461,6 +469,7 @@ public class BundleHost extends AbstractBundle { if (context != null) context.stop(); } finally { + stopHook(); if (context != null) { context.close(); context = null; @@ -485,6 +494,13 @@ public class BundleHost extends AbstractBundle { } /** + * @throws BundleException + */ + protected void stopHook() throws BundleException { + // do nothing + } + + /** * Provides a list of {@link ServiceReference}s for the services * registered by this bundle * or <code>null</code> if the bundle has no registered diff --git a/bundles/org.eclipse.osgi/core/framework/org/eclipse/osgi/framework/internal/core/PackageAdminImpl.java b/bundles/org.eclipse.osgi/core/framework/org/eclipse/osgi/framework/internal/core/PackageAdminImpl.java index af85e59c9..9483699a6 100644 --- a/bundles/org.eclipse.osgi/core/framework/org/eclipse/osgi/framework/internal/core/PackageAdminImpl.java +++ b/bundles/org.eclipse.osgi/core/framework/org/eclipse/osgi/framework/internal/core/PackageAdminImpl.java @@ -148,26 +148,35 @@ public class PackageAdminImpl implements PackageAdmin { } public void refreshPackages(Bundle[] input) { + refreshPackages(input, false); + } + + public void refreshPackages(Bundle[] input, boolean synchronously) { framework.checkAdminPermission(framework.systemBundle, AdminPermission.RESOLVE); - AbstractBundle[] copy = null; + final AbstractBundle[] copy; if (input != null) { synchronized (input) { copy = new AbstractBundle[input.length]; System.arraycopy(input, 0, copy, 0, input.length); } - } - - final AbstractBundle[] bundles = copy; - Thread refresh = framework.secureAction.createThread(new Runnable() { - public void run() { - doResolveBundles(bundles, true); - if (framework.isForcedRestart()) - framework.shutdown(FrameworkEvent.STOPPED_BOOTCLASSPATH_MODIFIED); - } - }, "Refresh Packages"); //$NON-NLS-1$ + } else + copy = null; - refresh.start(); + if (synchronously) { + doResolveBundles(copy, true); + if (framework.isForcedRestart()) + framework.systemBundle.stop(); + } else { + Thread refresh = framework.secureAction.createThread(new Runnable() { + public void run() { + doResolveBundles(copy, true); + if (framework.isForcedRestart()) + framework.shutdown(FrameworkEvent.STOPPED_BOOTCLASSPATH_MODIFIED); + } + }, "Refresh Packages"); //$NON-NLS-1$ + refresh.start(); + } } public boolean resolveBundles(Bundle[] bundles) { diff --git a/bundles/org.eclipse.osgi/core/framework/org/eclipse/osgi/internal/loader/BundleLoaderProxy.java b/bundles/org.eclipse.osgi/core/framework/org/eclipse/osgi/internal/loader/BundleLoaderProxy.java index 55e3c9d8e..6f2e494d2 100644 --- a/bundles/org.eclipse.osgi/core/framework/org/eclipse/osgi/internal/loader/BundleLoaderProxy.java +++ b/bundles/org.eclipse.osgi/core/framework/org/eclipse/osgi/internal/loader/BundleLoaderProxy.java @@ -17,6 +17,7 @@ import org.eclipse.osgi.framework.internal.core.*; import org.eclipse.osgi.framework.internal.core.Constants; import org.eclipse.osgi.framework.util.KeyedHashSet; import org.eclipse.osgi.framework.util.SecureAction; +import org.eclipse.osgi.internal.composite.CompositeBase; import org.eclipse.osgi.service.resolver.BundleDescription; import org.eclipse.osgi.service.resolver.ExportPackageDescription; import org.osgi.framework.*; @@ -183,7 +184,7 @@ public class BundleLoaderProxy implements RequiredBundle { } public boolean inUse() { - return description.getDependents().length > 0; + return (description.getDependents().length > 0) || ((bundle instanceof CompositeBase) && description.getResolvedImports().length > 0); } boolean forceSourceCreation(ExportPackageDescription export) { diff --git a/bundles/org.eclipse.osgi/core/framework/org/eclipse/osgi/launch/EquinoxFWClassLoader.java b/bundles/org.eclipse.osgi/core/framework/org/eclipse/osgi/launch/EquinoxFWClassLoader.java index 90604d5ad..2168b7fc3 100644 --- a/bundles/org.eclipse.osgi/core/framework/org/eclipse/osgi/launch/EquinoxFWClassLoader.java +++ b/bundles/org.eclipse.osgi/core/framework/org/eclipse/osgi/launch/EquinoxFWClassLoader.java @@ -15,7 +15,7 @@ import java.net.URLClassLoader; class EquinoxFWClassLoader extends URLClassLoader { - private static final String[] DELEGATE_PARENT = {"java.", "org.osgi.", "org.eclipse.osgi.launch.", "org.eclipse.osgi.service."}; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ + private static final String[] DELEGATE_PARENT = {"java.", "org.osgi.", "org.eclipse.osgi.launch.", "org.eclipse.osgi.service.", "org.eclipse.osgi.framework.log", "org.eclipse.osgi.framework.adaptor", "org.eclipse.osgi.framework.internal.core.ReferenceInputStream"}; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ private static final String[] SKIP_PARENT = {"org.osgi.framework.AdminPermission", "org.osgi.framework.FrameworkUtil", "org.osgi.service.condpermadmin.BundleSignerCondition"}; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ private final ClassLoader parent; diff --git a/bundles/org.eclipse.osgi/defaultAdaptor/src/org/eclipse/osgi/internal/baseadaptor/BaseStorageHook.java b/bundles/org.eclipse.osgi/defaultAdaptor/src/org/eclipse/osgi/internal/baseadaptor/BaseStorageHook.java index a8efe77d4..03f932ea4 100644 --- a/bundles/org.eclipse.osgi/defaultAdaptor/src/org/eclipse/osgi/internal/baseadaptor/BaseStorageHook.java +++ b/bundles/org.eclipse.osgi/defaultAdaptor/src/org/eclipse/osgi/internal/baseadaptor/BaseStorageHook.java @@ -43,6 +43,9 @@ public class BaseStorageHook implements StorageHook, AdaptorHook { public static final String EXTERNAL_LIB_PREFIX = "external:"; //$NON-NLS-1$ public static final String VARIABLE_DELIM_STRING = "$"; //$NON-NLS-1$ public static final char VARIABLE_DELIM_CHAR = '$'; + public static String COMPOSITE_HEADER = "Equinox-CompositeBundle"; //$NON-NLS-1$ + public static String COMPOSITE_BUNDLE = "composite"; //$NON-NLS-1$ + public static String SURROGATE_BUNDLE = "surrogate"; //$NON-NLS-1$ /** bundle's file name */ private String fileName; @@ -110,6 +113,14 @@ public class BaseStorageHook implements StorageHook, AdaptorHook { else if (extensionType.equals("extclasspath")) //$NON-NLS-1$ bundleType |= BundleData.TYPE_EXTCLASSPATH_EXTENSION; } + } else { + String composite = (String) manifest.get(COMPOSITE_HEADER); + if (composite != null) { + if (COMPOSITE_BUNDLE.equals(composite)) + bundleType |= BundleData.TYPE_COMPOSITEBUNDLE; + else + bundleType |= BundleData.TYPE_SURROGATEBUNDLE; + } } target.setType(bundleType); target.setExecutionEnvironment((String) manifest.get(Constants.BUNDLE_REQUIREDEXECUTIONENVIRONMENT)); diff --git a/bundles/org.eclipse.osgi/defaultAdaptor/src/org/eclipse/osgi/internal/baseadaptor/SystemBundleData.java b/bundles/org.eclipse.osgi/defaultAdaptor/src/org/eclipse/osgi/internal/baseadaptor/SystemBundleData.java index 75b4c3185..7148b0b6e 100644 --- a/bundles/org.eclipse.osgi/defaultAdaptor/src/org/eclipse/osgi/internal/baseadaptor/SystemBundleData.java +++ b/bundles/org.eclipse.osgi/defaultAdaptor/src/org/eclipse/osgi/internal/baseadaptor/SystemBundleData.java @@ -165,10 +165,6 @@ public class SystemBundleData extends BaseData { // do nothing } - public File getDataFile(String path) { - return null; - } - public int getStartLevel() { return 0; } diff --git a/bundles/org.eclipse.osgi/hookconfigurators.properties b/bundles/org.eclipse.osgi/hookconfigurators.properties index bb97ef345..162960246 100644 --- a/bundles/org.eclipse.osgi/hookconfigurators.properties +++ b/bundles/org.eclipse.osgi/hookconfigurators.properties @@ -18,5 +18,6 @@ hook.configurators= \ org.eclipse.core.runtime.internal.adaptor.EclipseClassLoadingHook,\ org.eclipse.core.runtime.internal.adaptor.EclipseLazyStarter,\ org.eclipse.core.runtime.internal.stats.StatsManager,\ - org.eclipse.osgi.internal.signedcontent.SignedBundleHook + org.eclipse.osgi.internal.signedcontent.SignedBundleHook,\ + org.eclipse.osgi.internal.composite.CompositeConfigurator builtin.hooks = true diff --git a/bundles/org.eclipse.osgi/osgi/src/org/osgi/service/framework/CompositeBundle.java b/bundles/org.eclipse.osgi/osgi/src/org/osgi/service/framework/CompositeBundle.java new file mode 100644 index 000000000..072bbfc11 --- /dev/null +++ b/bundles/org.eclipse.osgi/osgi/src/org/osgi/service/framework/CompositeBundle.java @@ -0,0 +1,151 @@ +/* + * Copyright (c) OSGi Alliance (2008). 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.service.framework; + +import java.io.InputStream; +import java.util.Map; + +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleException; +import org.osgi.framework.launch.Framework; + +/** + * Composite bundles are composed of other bundles. The component bundles which + * make up the content of a composite bundle are installed into a child + * framework. Like a normal bundle, a composite bundle may import packages and + * use services from other bundles which are installed in the same framework as + * the composite bundle. The packages imported and the services used by a + * composite bundle are shared with the components of a composite bundle through + * the surrogate bundle installed in the child framework. Also like a normal + * bundle, a composite bundle may export packages and register services which + * can be used by bundles installed in the same framework as the composite + * bundle. The packages exported and the services registered by a composite + * bundle are acquired from the components of a composite bundle by the + * surrogate bundle installed in the child framework + * <p> + * A framework has one composite bundle for each of its child frameworks. A + * framework can have zero or more composite bundles installed. A child + * framework must have one and only one surrogate bundle which represents the + * composite bundle. In other words a parent framework can have many children + * frameworks but a child framework can only have one parent. + * <p> + * A composite bundle does the following as specified by the composite manifest + * map: + * <ul> + * <li>Exports packages to the parent framework from the child framework. These + * packages are imported by the surrogate bundle installed in the child + * framework.</li> + * <li>Imports packages from the parent framework. These packages are exported + * by the surrogate bundle installed in the child framework.</li> + * <li>Registers services to the parent framework from the child framework. + * These services are acquired by the surrogate bundle installed in the child + * framework.</li> + * <li>Acquires services from the parent framework. These services are + * registered by the surrogate bundle installed in the child framework.</li> + * </ul> + * + * A newly created child <code>Framework</code> will be in the + * {@link Bundle#STARTING STARTING} state. This child <code>Framework</code> can + * then be used to manage and control the child framework instance. The child + * framework instance is persistent and uses a storage area in the composite + * bundle's data area. The child framework's lifecycle is tied to its composite + * bundle's lifecycle in the following ways: + * <p> + * <ul> + * <li>If the composite bundle is marked to be persistently started (see + * StartLevel.isBundlePersistentlyStarted(Bundle)) then the child framework + * instance will automatically be started when the composite bundle's + * start-level is met.</li> + * <li>The child framework instance will be stopped if the composite bundle is + * persistently stopped or its start level is no longer met. Performing + * operations which transiently stop a composite bundle do not cause the child + * framework to stop (e.g. {@link Bundle#stop(int) stop(Bundle.STOP_TRANSIENT)}, + * {@link Bundle#update() update}, refreshPackage etc.).</li> + * </ul> + * <p> + * The child framework may be persistently started and stopped by persistently + * starting and stopping the composite bundle, but it is still possible to + * initialize and start the child framework explicitly while the composite + * bundle is not persistently started. This allows for the child framework to be + * initialized and populated with a set of bundles before starting the composite + * bundle. The set of bundles installed into the child framework are the + * component bundles which compose the composite bundle. + * <p> + * If the child framework is started while the composite bundle is not + * persistently started then the child framework lifecycle is tied to its parent + * framework lifecycle. When the parent <code>Framework</code> enters the + * {@link Bundle#STOPPING STOPPING} state then all active child frameworks of + * that parent are shutdown using the to the {@link Framework#stop()} method. + * The parent <code>Framework</code> must not enter the {@link Bundle#RESOLVED} + * state until all the child frameworks have completed their shutdown process. + * After the parent framework has completed the shutdown process then all child + * framework instances become invalid and must not be allowed to re-initialize + * or re-start. + * + * @see SurrogateBundle + * @ThreadSafe + * @version $Revision: 1.1.2.2 $ + */ +public interface CompositeBundle extends Bundle { + /** + * Returns the composite framework associated with this composite bundle. + * + * @return the companion framework. + */ + Framework getCompositeFramework(); + + /** + * Returns the surrogate bundle associated with this composite bundle. The + * surrogate bundle is installed in the composite framework. + * + * @return the surrogate bundle. + */ + SurrogateBundle getSurrogateBundle(); + + /** + * Updates this child composite bundle with the specified manifest. + * + * @param compositeManifest the new composite manifest. + * @throws BundleException If the update fails. + * @see CompositeBundleFactory#installCompositeBundle(Map, String, Map) + */ + void update(Map /* <String, String> */compositeManifest) + throws BundleException; + + /** + * This operation is not supported for composite bundles. A + * <code>BundleException</code> of type + * {@link BundleException#INVALID_OPERATION invalid operation} must be + * thrown. + */ + void update() throws BundleException; + + /** + * This operation is not supported for composite bundles. A + * <code>BundleException</code> of type + * {@link BundleException#INVALID_OPERATION invalid operation} must be + * thrown. + */ + void update(InputStream input) throws BundleException; + + /** + * Uninstalls this composite bundle and its companion bundle. + * <p> + * If this composite bundle is a child composite then the companion child + * framework is shutdown and its persistent storage area is deleted. + */ + void uninstall() throws BundleException; +} diff --git a/bundles/org.eclipse.osgi/osgi/src/org/osgi/service/framework/CompositeBundleFactory.java b/bundles/org.eclipse.osgi/osgi/src/org/osgi/service/framework/CompositeBundleFactory.java new file mode 100644 index 000000000..797327cb0 --- /dev/null +++ b/bundles/org.eclipse.osgi/osgi/src/org/osgi/service/framework/CompositeBundleFactory.java @@ -0,0 +1,147 @@ +/* + * Copyright (c) OSGi Alliance (2008). 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.service.framework; + +import java.util.Map; + +import org.osgi.framework.BundleEvent; +import org.osgi.framework.BundleException; +import org.osgi.framework.Constants; +import org.osgi.framework.launch.Framework; + +/** + * Framework service that is used to create composite bundles. + * <p> + * If present, there will only be a single instance of this service registered + * with the Framework. + * + * @ThreadSafe + * @version $Revision: 1.1.2.2 $ + */ +// TODO javadoc needs review +public interface CompositeBundleFactory { + /** + * Manifest header (named "CompositeServiceFilter-Import") + * identifying the service filters that are used by a child composite bundle + * to select services that will be registered into a child framework from a + * parent composite bundle. + */ + public static final String COMPOSITE_SERVICE_FILTER_IMPORT = "CompositeServiceFilter-Import"; + + /** + * Manifest header (named "CompositeServiceFilter-Export") + * identifying the service filters that are used by a parent composite + * bundle to select services that will be registered into a parent framework + * from a child composite bundle. + */ + public static final String COMPOSITE_SERVICE_FILTER_EXPORT = "CompositeServiceFilter-Export"; + + /** + * Installs a <code>CompositeBundle</code>. The composite bundle has a new + * child <code>Framework</code> associated with it and a surrogate bundle + * which is installed in the child framework. Composite bundles share + * packages and services between parent framework they are installed in and + * the the child framework. + * <p> + * The following steps are required to create a composite bundle: + * <ol> + * <li>If a bundle containing the same location string is already installed, + * then if the Bundle object is a <code>CompositeBundle</code> then that + * composite bundle is returned; otherwise a BundleException is thrown + * indicating that an incompatible bundle is already installed at the + * specified location.</li> + * <li>The composite bundle's associated resources are allocated. The + * associated resources minimally consist of a unique identifier and a + * persistent storage area. If this step fails, a BundleException is thrown. + * <li>The compositeManifest map is used to provide the headers for the + * composite bundle and its surrogate bundle. + * <p> + * If composite manifest map does not contain the following header(s) then a + * BundleException is thrown: + * <ul> + * <li> {@link Constants#BUNDLE_SYMBOLICNAME Bundle-SymbolicName} the + * symbolic name used for the composite bundle and its surrogate bundle. + * </ul> + * </p> + * The composite manifest map may optionally contain the following + * header(s): + * <ul> + * <li> {@link Constants#BUNDLE_VERSION Bundle-Version} the bundle version + * used for the composite bundle and its surrogate bundle.</li> + * <li> {@link Constants#IMPORT_PACKAGE Import-Package} the packages which + * are imported from the parent framework by the composite bundle and are + * exported to the child framework by the surrogate bundle.</li> + * <li>{@link Constants#EXPORT_PACKAGE Export-Package} the packages which + * are imported from the child framework by the surrogate bundle and are + * exported to the parent framework by the composite bundle.</li> + * <li>{@link #COMPOSITE_SERVICE_FILTER_IMPORT + * CompositeServiceFilter-Import} the service filters which are acquired + * from the parent framework by the composite bundle and are registered in + * the child framework by the surrogate bundle.</li> + * <li>{@link #COMPOSITE_SERVICE_FILTER_EXPORT + * CompositeServiceFilter-Export} the service filters which are acquired + * from the child framework by the surrogate bundle and are registered in + * the parent framework by the composite bundle.</li> + * <li>{@link Constants#BUNDLE_MANIFESTVERSION Bundle-ManifestVersion} the + * bundle manifest version. If this header is not specified then the default + * is to use version 2. A BundleException is thrown if this header is + * specified and the version is less than 2.</li> + * </ul> + * The composite manifest map must not contain the following headers. If a + * composite manifest map does contain one of the following headers then a + * BundleException is thrown: + * <ul> + * <li> {@link Constants#BUNDLE_ACTIVATIONPOLICY Bundle-ActivationPolicy}</li> + * <li> {@link Constants#BUNDLE_ACTIVATOR Bundle-Activator}</li> + * <li> {@link Constants#BUNDLE_CLASSPATH Bundle-ClassPath}</li> + * <li> {@link Constants#BUNDLE_LOCALIZATION Bundle-Localization}</li> + * <li> {@link Constants#BUNDLE_NATIVECODE Bundle-NativeCode}</li> + * <li> {@link Constants#DYNAMICIMPORT_PACKAGE DynamicImport-Package}</li> + * <li> {@link Constants#FRAGMENT_HOST Fragment-Host}</li> + * <li> {@link Constants#REQUIRE_BUNDLE Require-Bundle}</li> + * </ul> + * <li>A child framework is created which uses a storage area under the + * composite bundle's associated persistent storage. Note that if the + * framework configuration property {@link Constants#FRAMEWORK_STORAGE + * org.osgi.framework.storage} is specified in the framework config then it + * is ignored.</li> + * <li>The child framework is initialized (see {@link Framework#init()}). + * <li>A surrogate bundle is installed into the child framework</li> + * <li>The composite bundle's state is set to INSTALLED.</li> + * <li>A bundle event of type {@link BundleEvent#INSTALLED} is fired for the + * composite bundle. + * <li>The <code>CompositeBundle</code> object for the newly composite + * bundle is returned + * </ol> + * <p> + * + * @param frameworkConfig the child framework configuration. + * @param location the bundle location used for the child composite bundle + * and its companion bundle. + * @param compositeManifest the manifest used to create the composite bundle + * @return A new child composite bundle. + * @throws BundleException If the composite manifest is invalid or there is + * some other problem with installing the composite bundle. + * @throws SecurityException If the caller does not have + * <code>AllPermission</code>. + * @see Framework + * @see CompositeBundle + */ + CompositeBundle installCompositeBundle( + Map /* <String, String> */frameworkConfig, String location, + Map /* <String, String> */compositeManifest) throws BundleException; + +} diff --git a/bundles/org.eclipse.osgi/osgi/src/org/osgi/service/framework/SurrogateBundle.java b/bundles/org.eclipse.osgi/osgi/src/org/osgi/service/framework/SurrogateBundle.java new file mode 100644 index 000000000..5c95e7860 --- /dev/null +++ b/bundles/org.eclipse.osgi/osgi/src/org/osgi/service/framework/SurrogateBundle.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) OSGi Alliance (2008). 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.service.framework; + +import java.io.InputStream; + +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.BundleException; + +/** + * A surrogate bundle represents a composite bundle within a child framework. + * A surrogate bundle does the following as specified by the composite manifest: + * <ul> + * <li>Exports packages to the child framework from the parent framework. These + * packages are imported by the composite bundle installed in the parent + * framework.</li> + * <li>Imports packages from the child framework. These packages are exported + * by the composite bundle installed in the parent framework.</li> + * <li>Registers services with the child framework from the parent framework. + * These services are acquired by the composite bundle installed in the + * parent framework.</li> + * <li>Acquires services from the child framework. These services are + * registered by the composite bundle installed in the parent framework.</li> + * </ul> + * <p> + * @see CompositeBundle + */ +public interface SurrogateBundle extends Bundle { + /** + * Returns the bundle context of the composite bundle which this + * surrogate bundle represents. + * @return the bundle context of the composite bundle. A value + * of <code>null</code> is returned if the composite bundle does + * not have a valid bundle context. + */ + BundleContext getCompositeBundleContext(); + + /** + * This operation is not supported for surrogate bundles. A + * <code>BundleException</code> of type + * {@link BundleException#INVALID_OPERATION invalid operation} must be + * thrown. + */ + void update() throws BundleException; + + /** + * This operation is not supported for surrogate bundles. A + * <code>BundleException</code> of type + * {@link BundleException#INVALID_OPERATION invalid operation} must be + * thrown. + */ + void update(InputStream input) throws BundleException; + + /** + * This operation is not supported for surrogate bundles. A + * <code>BundleException</code> of type + * {@link BundleException#INVALID_OPERATION invalid operation} must be + * thrown. + */ + void uninstall() throws BundleException; +} diff --git a/bundles/org.eclipse.osgi/resolver/src/org/eclipse/osgi/internal/module/CompositeResolveHelper.java b/bundles/org.eclipse.osgi/resolver/src/org/eclipse/osgi/internal/module/CompositeResolveHelper.java new file mode 100644 index 000000000..5f7cdba7d --- /dev/null +++ b/bundles/org.eclipse.osgi/resolver/src/org/eclipse/osgi/internal/module/CompositeResolveHelper.java @@ -0,0 +1,17 @@ +/******************************************************************************* + * Copyright (c) 2008 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 + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.osgi.internal.module; + +import org.eclipse.osgi.service.resolver.ExportPackageDescription; + +public interface CompositeResolveHelper { + public boolean giveExports(ExportPackageDescription[] matchingExports); +} diff --git a/bundles/org.eclipse.osgi/resolver/src/org/eclipse/osgi/internal/module/CompositeResolveHelperRegistry.java b/bundles/org.eclipse.osgi/resolver/src/org/eclipse/osgi/internal/module/CompositeResolveHelperRegistry.java new file mode 100644 index 000000000..f4cd6fb80 --- /dev/null +++ b/bundles/org.eclipse.osgi/resolver/src/org/eclipse/osgi/internal/module/CompositeResolveHelperRegistry.java @@ -0,0 +1,17 @@ +/******************************************************************************* + * Copyright (c) 2008 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 + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.osgi.internal.module; + +import org.eclipse.osgi.service.resolver.BundleDescription; + +public interface CompositeResolveHelperRegistry { + public CompositeResolveHelper getCompositeResolveHelper(BundleDescription bundle); +} diff --git a/bundles/org.eclipse.osgi/resolver/src/org/eclipse/osgi/internal/module/ResolverBundle.java b/bundles/org.eclipse.osgi/resolver/src/org/eclipse/osgi/internal/module/ResolverBundle.java index dcae3876c..d44e4416f 100644 --- a/bundles/org.eclipse.osgi/resolver/src/org/eclipse/osgi/internal/module/ResolverBundle.java +++ b/bundles/org.eclipse.osgi/resolver/src/org/eclipse/osgi/internal/module/ResolverBundle.java @@ -392,19 +392,19 @@ public class ResolverBundle extends VersionSupplier implements Comparable { return false; if (!existingDescription.getVersion().equals(newDescription.getVersion())) return false; - if (!equivalentMaps(existingDescription.getAttributes(), newDescription.getAttributes())) + if (!equivalentMaps(existingDescription.getAttributes(), newDescription.getAttributes(), true)) return false; - if (!equivalentMaps(existingDescription.getDirectives(), newDescription.getDirectives())) + if (!equivalentMaps(existingDescription.getDirectives(), newDescription.getDirectives(), true)) return false; return true; } - private boolean equivalentMaps(Map existingDirectives, Map newDirectives) { + public static boolean equivalentMaps(Map existingDirectives, Map newDirectives, boolean exactMatch) { if (existingDirectives == null && newDirectives == null) return true; if (existingDirectives == null ? newDirectives != null : newDirectives == null) return false; - if (existingDirectives.size() != newDirectives.size()) + if (exactMatch && existingDirectives.size() != newDirectives.size()) return false; for (Iterator entries = existingDirectives.entrySet().iterator(); entries.hasNext();) { Entry entry = (Entry) entries.next(); diff --git a/bundles/org.eclipse.osgi/resolver/src/org/eclipse/osgi/internal/module/ResolverImpl.java b/bundles/org.eclipse.osgi/resolver/src/org/eclipse/osgi/internal/module/ResolverImpl.java index 2d80e5263..19eff3497 100644 --- a/bundles/org.eclipse.osgi/resolver/src/org/eclipse/osgi/internal/module/ResolverImpl.java +++ b/bundles/org.eclipse.osgi/resolver/src/org/eclipse/osgi/internal/module/ResolverImpl.java @@ -72,6 +72,7 @@ public class ResolverImpl implements org.eclipse.osgi.service.resolver.Resolver private GroupingChecker groupingChecker; private Comparator selectionPolicy; private boolean developmentMode = false; + private volatile CompositeResolveHelperRegistry compositeHelpers; public ResolverImpl(BundleContext context, boolean checkPermissions) { this.permissionChecker = new PermissionChecker(context, checkPermissions, this); @@ -535,46 +536,76 @@ public class ResolverImpl implements org.eclipse.osgi.service.resolver.Resolver resolveFragment(unresolved[i]); } checkUsesConstraints(bundles, platformProperties, rejectedSingletons); + checkComposites(bundles, platformProperties, rejectedSingletons); + } + + private void checkComposites(ResolverBundle[] bundles, Dictionary[] platformProperties, ArrayList rejectedSingletons) { + CompositeResolveHelperRegistry helpers = getCompositeHelpers(); + if (helpers == null) + return; + Set exclude = null; + for (int i = 0; i < bundles.length; i++) { + CompositeResolveHelper helper = helpers.getCompositeResolveHelper(bundles[i].getBundle()); + if (helper == null) + continue; + if (!bundles[i].isResolved()) + continue; + if (!helper.giveExports(getExportsWiredTo(bundles[i]))) { + state.addResolverError(bundles[i].getBundle(), ResolverError.DISABLED_BUNDLE, null, null); + bundles[i].setResolvable(false); + bundles[i].clearRefs(); + setBundleUnresolved(bundles[i], false, developmentMode); + if (exclude == null) + exclude = new HashSet(1); + exclude.add(bundles[i]); + } + } + reResolveBundles(exclude, bundles, platformProperties, rejectedSingletons); } private void checkUsesConstraints(ResolverBundle[] bundles, Dictionary[] platformProperties, ArrayList rejectedSingletons) { ArrayList conflictingConstraints = findBestCombination(bundles); + if (conflictingConstraints == null) + return; Set conflictedBundles = null; - if (conflictingConstraints != null) { - for (Iterator conflicts = conflictingConstraints.iterator(); conflicts.hasNext();) { - ResolverConstraint conflict = (ResolverConstraint) conflicts.next(); - if (conflict.isOptional()) { - conflict.clearPossibleSuppliers(); - continue; - } + for (Iterator conflicts = conflictingConstraints.iterator(); conflicts.hasNext();) { + ResolverConstraint conflict = (ResolverConstraint) conflicts.next(); + if (conflict.isOptional()) { + conflict.clearPossibleSuppliers(); + continue; + } + if (conflictedBundles == null) conflictedBundles = new HashSet(conflictingConstraints.size()); - ResolverBundle conflictedBundle; - if (conflict.isFromFragment()) - conflictedBundle = (ResolverBundle) bundleMapping.get(conflict.getVersionConstraint().getBundle()); - else - conflictedBundle = conflict.getBundle(); - if (conflictedBundle != null) { - if (DEBUG_USES) - System.out.println("Found conflicting constraint: " + conflict + " in bundle " + conflictedBundle); //$NON-NLS-1$//$NON-NLS-2$ - conflictedBundles.add(conflictedBundle); - int type = conflict instanceof ResolverImport ? ResolverError.IMPORT_PACKAGE_USES_CONFLICT : ResolverError.REQUIRE_BUNDLE_USES_CONFLICT; - state.addResolverError(conflictedBundle.getBundle(), type, conflict.getVersionConstraint().toString(), conflict.getVersionConstraint()); - conflictedBundle.setResolvable(false); - conflictedBundle.clearRefs(); - setBundleUnresolved(conflictedBundle, false, developmentMode); - } + ResolverBundle conflictedBundle; + if (conflict.isFromFragment()) + conflictedBundle = (ResolverBundle) bundleMapping.get(conflict.getVersionConstraint().getBundle()); + else + conflictedBundle = conflict.getBundle(); + if (conflictedBundle != null) { + if (DEBUG_USES) + System.out.println("Found conflicting constraint: " + conflict + " in bundle " + conflictedBundle); //$NON-NLS-1$//$NON-NLS-2$ + conflictedBundles.add(conflictedBundle); + int type = conflict instanceof ResolverImport ? ResolverError.IMPORT_PACKAGE_USES_CONFLICT : ResolverError.REQUIRE_BUNDLE_USES_CONFLICT; + state.addResolverError(conflictedBundle.getBundle(), type, conflict.getVersionConstraint().toString(), conflict.getVersionConstraint()); + conflictedBundle.setResolvable(false); + conflictedBundle.clearRefs(); + setBundleUnresolved(conflictedBundle, false, developmentMode); } - if (conflictedBundles != null && conflictedBundles.size() > 0) { - ArrayList remainingUnresolved = new ArrayList(); - for (int i = 0; i < bundles.length; i++) { - if (!conflictedBundles.contains(bundles[i])) { - setBundleUnresolved(bundles[i], false, developmentMode); - remainingUnresolved.add(bundles[i]); - } - } - resolveBundles0((ResolverBundle[]) remainingUnresolved.toArray(new ResolverBundle[remainingUnresolved.size()]), platformProperties, rejectedSingletons); + } + reResolveBundles(conflictedBundles, bundles, platformProperties, rejectedSingletons); + } + + private void reResolveBundles(Set exclude, ResolverBundle[] bundles, Dictionary[] platformProperties, ArrayList rejectedSingletons) { + if (exclude == null || exclude.size() == 0) + return; + ArrayList remainingUnresolved = new ArrayList(); + for (int i = 0; i < bundles.length; i++) { + if (!exclude.contains(bundles[i])) { + setBundleUnresolved(bundles[i], false, developmentMode); + remainingUnresolved.add(bundles[i]); } } + resolveBundles0((ResolverBundle[]) remainingUnresolved.toArray(new ResolverBundle[remainingUnresolved.size()]), platformProperties, rejectedSingletons); } private ArrayList findBestCombination(ResolverBundle[] bundles) { @@ -1434,12 +1465,7 @@ public class ResolverImpl implements org.eclipse.osgi.service.resolver.Resolver ExportPackageDescription[] substitutedExportsArray = (ExportPackageDescription[]) substitutedExports.toArray(new ExportPackageDescription[substitutedExports.size()]); // Gather exports that have been wired to - ResolverImport[] imports = rb.getImportPackages(); - ArrayList exportsWiredTo = new ArrayList(imports.length); - for (int i = 0; i < imports.length; i++) - if (imports[i].getSelectedSupplier() != null) - exportsWiredTo.add(imports[i].getSelectedSupplier().getBaseDescription()); - ExportPackageDescription[] exportsWiredToArray = (ExportPackageDescription[]) exportsWiredTo.toArray(new ExportPackageDescription[exportsWiredTo.size()]); + ExportPackageDescription[] exportsWiredToArray = getExportsWiredTo(rb); // Gather bundles that have been wired to BundleConstraint[] requires = rb.getRequires(); @@ -1472,6 +1498,16 @@ public class ResolverImpl implements org.eclipse.osgi.service.resolver.Resolver state.resolveBundle(rb.getBundle(), rb.isResolved(), hostBundles, selectedExportsArray, substitutedExportsArray, bundlesWiredToArray, exportsWiredToArray); } + private static ExportPackageDescription[] getExportsWiredTo(ResolverBundle rb) { + // Gather exports that have been wired to + ResolverImport[] imports = rb.getImportPackages(); + ArrayList exportsWiredTo = new ArrayList(imports.length); + for (int i = 0; i < imports.length; i++) + if (imports[i].getSelectedSupplier() != null) + exportsWiredTo.add(imports[i].getSelectedSupplier().getBaseDescription()); + return (ExportPackageDescription[]) exportsWiredTo.toArray(new ExportPackageDescription[exportsWiredTo.size()]); + } + // Resolve dynamic import public synchronized ExportPackageDescription resolveDynamicImport(BundleDescription importingBundle, String requestedPackage) { if (state == null) @@ -1616,6 +1652,12 @@ public class ResolverImpl implements org.eclipse.osgi.service.resolver.Resolver if (!bundle.getBundle().isResolved() && !developmentMode) return; + CompositeResolveHelperRegistry currentLinks = compositeHelpers; + if (currentLinks != null) { + CompositeResolveHelper helper = currentLinks.getCompositeResolveHelper(bundle.getBundle()); + if (helper != null) + helper.giveExports(null); + } // if not removed then add to the list of unresolvedBundles, // passing false for devmode because we need all fragments detached setBundleUnresolved(bundle, removed, false); @@ -1739,4 +1781,12 @@ public class ResolverImpl implements org.eclipse.osgi.service.resolver.Resolver public Comparator getSelectionPolicy() { return selectionPolicy; } + + public void setCompositeResolveHelperRegistry(CompositeResolveHelperRegistry compositeHelpers) { + this.compositeHelpers = compositeHelpers; + } + + CompositeResolveHelperRegistry getCompositeHelpers() { + return compositeHelpers; + } } diff --git a/bundles/org.eclipse.osgi/resolver/src/org/eclipse/osgi/internal/resolver/StateImpl.java b/bundles/org.eclipse.osgi/resolver/src/org/eclipse/osgi/internal/resolver/StateImpl.java index 39b8c553f..d369399c9 100644 --- a/bundles/org.eclipse.osgi/resolver/src/org/eclipse/osgi/internal/resolver/StateImpl.java +++ b/bundles/org.eclipse.osgi/resolver/src/org/eclipse/osgi/internal/resolver/StateImpl.java @@ -19,6 +19,7 @@ import org.eclipse.osgi.framework.internal.core.Constants; import org.eclipse.osgi.framework.internal.core.FilterImpl; import org.eclipse.osgi.framework.util.*; import org.eclipse.osgi.internal.baseadaptor.StateManager; +import org.eclipse.osgi.internal.loader.BundleLoaderProxy; import org.eclipse.osgi.service.resolver.*; import org.eclipse.osgi.util.ManifestElement; import org.eclipse.osgi.util.NLS; @@ -121,7 +122,7 @@ public abstract class StateImpl implements State { if (getSystemBundle().equals(newDescription.getSymbolicName())) resetSystemExports(); if (resolver != null) { - boolean pending = existing.getDependents().length > 0; + boolean pending = isInUse(existing); resolver.bundleUpdated(newDescription, existing, pending); if (pending) { getDelta().recordBundleRemovalPending(existing); @@ -161,7 +162,7 @@ public abstract class StateImpl implements State { getDelta().recordBundleRemoved((BundleDescriptionImpl) toRemove); ((BundleDescriptionImpl) toRemove).setStateBit(BundleDescriptionImpl.REMOVAL_PENDING, true); if (resolver != null) { - boolean pending = toRemove.getDependents().length > 0; + boolean pending = isInUse(toRemove); resolver.bundleRemoved(toRemove, pending); if (pending) { getDelta().recordBundleRemovalPending((BundleDescriptionImpl) toRemove); @@ -182,6 +183,13 @@ public abstract class StateImpl implements State { } } + private boolean isInUse(BundleDescription bundle) { + Object userObject = bundle.getUserObject(); + if (userObject instanceof BundleLoaderProxy) + return ((BundleLoaderProxy) userObject).inUse(); + return bundle.getDependents().length > 0; + } + public StateDelta getChanges() { synchronized (this.monitor) { return getDelta(); |