diff options
Diffstat (limited to 'bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core')
27 files changed, 12026 insertions, 0 deletions
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/AbstractBundle.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/AbstractBundle.java new file mode 100644 index 000000000..d2b51fd00 --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/AbstractBundle.java @@ -0,0 +1,1545 @@ +/******************************************************************************* + * Copyright (c) 2003, 2012 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.framework.internal.core; + +import java.io.*; +import java.net.URL; +import java.net.URLConnection; +import java.security.*; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +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.loader.BundleLoader; +import org.eclipse.osgi.internal.permadmin.EquinoxSecurityManager; +import org.eclipse.osgi.service.resolver.BundleDescription; +import org.eclipse.osgi.service.resolver.ResolverError; +import org.eclipse.osgi.signedcontent.*; +import org.eclipse.osgi.util.NLS; +import org.osgi.framework.*; +import org.osgi.framework.hooks.bundle.CollisionHook; +import org.osgi.framework.startlevel.BundleStartLevel; +import org.osgi.framework.wiring.*; + +/** + * This object is given out to bundles and wraps the internal Bundle object. It + * is destroyed when a bundle is uninstalled and reused if a bundle is updated. + * This class is abstract and is extended by BundleHost and BundleFragment. + */ +public abstract class AbstractBundle implements Bundle, Comparable<Bundle>, KeyedElement, BundleStartLevel, BundleReference, BundleRevisions { + private final static long STATE_CHANGE_TIMEOUT; + static { + long stateChangeWait = 5000; + try { + String prop = FrameworkProperties.getProperty("equinox.statechange.timeout"); //$NON-NLS-1$ + if (prop != null) + stateChangeWait = Long.parseLong(prop); + } catch (Throwable t) { + // use default 5000 + stateChangeWait = 5000; + } + STATE_CHANGE_TIMEOUT = stateChangeWait; + } + /** The Framework this bundle is part of */ + protected final Framework framework; + /** The state of the bundle. */ + protected volatile int state; + /** A flag to denote whether a bundle state change is in progress */ + protected volatile Thread stateChanging; + /** Bundle's BundleData object */ + protected BundleData bundledata; + /** Internal object used for state change synchronization */ + protected final Object statechangeLock = new Object(); + /** ProtectionDomain for the bundle */ + protected BundleProtectionDomain domain; + + volatile protected ManifestLocalization manifestLocalization = null; + + /** + * Bundle object constructor. This constructor should not perform any real + * work. + * + * @param bundledata + * BundleData for this bundle + * @param framework + * Framework this bundle is running in + */ + protected static AbstractBundle createBundle(BundleData bundledata, Framework framework, boolean setBundle) throws BundleException { + AbstractBundle result; + if ((bundledata.getType() & BundleData.TYPE_FRAGMENT) > 0) + result = new BundleFragment(bundledata, framework); + else + result = new BundleHost(bundledata, framework); + if (setBundle) + bundledata.setBundle(result); + return result; + } + + /** + * Bundle object constructor. This constructor should not perform any real + * work. + * + * @param bundledata + * BundleData for this bundle + * @param framework + * Framework this bundle is running in + */ + protected AbstractBundle(BundleData bundledata, Framework framework) { + state = INSTALLED; + stateChanging = null; + this.bundledata = bundledata; + this.framework = framework; + } + + /** + * Load the bundle. + */ + protected abstract void load(); + + /** + * Reload from a new bundle. This method must be called while holding the + * bundles lock. + * + * @param newBundle + * Dummy Bundle which contains new data. + * @return true if an exported package is "in use". i.e. it has been + * imported by a bundle + */ + protected abstract boolean reload(AbstractBundle newBundle); + + /** + * Refresh the bundle. This is called by Framework.refreshPackages. This + * method must be called while holding the bundles lock. + * this.loader.unimportPackages must have already been called before + * calling this method! + */ + protected abstract void refresh(); + + /** + * Unload the bundle. This method must be called while holding the bundles + * lock. + * + * @return true if an exported package is "in use". i.e. it has been + * imported by a bundle + */ + protected abstract boolean unload(); + + /** + * Close the the Bundle's file. + * + */ + protected void close() { + if (Debug.DEBUG_GENERAL) { + if ((state & (INSTALLED)) == 0) { + Debug.println("Bundle.close called when state != INSTALLED: " + this); //$NON-NLS-1$ + Debug.printStackTrace(new Exception("Stack trace")); //$NON-NLS-1$ + } + } + state = UNINSTALLED; + } + + /** + * Load and instantiate bundle's BundleActivator class + */ + protected BundleActivator loadBundleActivator() throws BundleException { + /* load Bundle's BundleActivator if it has one */ + String activatorClassName = bundledata.getActivator(); + if (activatorClassName != null) { + try { + Class<?> activatorClass = loadClass(activatorClassName, false); + /* Create the activator for the bundle */ + return (BundleActivator) (activatorClass.newInstance()); + } catch (Throwable t) { + if (Debug.DEBUG_GENERAL) { + Debug.printStackTrace(t); + } + throw new BundleException(NLS.bind(Msg.BUNDLE_INVALID_ACTIVATOR_EXCEPTION, activatorClassName, bundledata.getSymbolicName()), BundleException.ACTIVATOR_ERROR, t); + } + } + return (null); + } + + /** + * This method loads a class from the bundle. + * + * @param name + * the name of the desired Class. + * @param checkPermission + * indicates whether a permission check should be done. + * @return the resulting Class + * @exception java.lang.ClassNotFoundException + * if the class definition was not found. + */ + protected abstract Class<?> loadClass(String name, boolean checkPermission) throws ClassNotFoundException; + + /** + * Returns the current state of the bundle. + * + * A bundle can only be in one state at any time. + * + * @return bundle's state. + */ + public int getState() { + return (state); + } + + public Framework getFramework() { + return framework; + } + + /** + * Return true if the bundle is starting or active. + * + */ + protected boolean isActive() { + return ((state & (ACTIVE | STARTING)) != 0); + } + + boolean isLazyStart() { + int status = bundledata.getStatus(); + return (status & Constants.BUNDLE_ACTIVATION_POLICY) != 0 && (status & Constants.BUNDLE_LAZY_START) != 0; + } + + /** + * Return true if the bundle is resolved. + * + */ + public boolean isResolved() { + return (state & (INSTALLED | UNINSTALLED)) == 0; + } + + /** + * Start this bundle. + * + * If the current start level is less than this bundle's start level, then + * the Framework must persistently mark this bundle as started and delay + * the starting of this bundle until the Framework's current start level + * becomes equal or more than the bundle's start level. + * <p> + * Otherwise, the following steps are required to start a bundle: + * <ol> + * <li>If the bundle is {@link #UNINSTALLED}then an <code>IllegalStateException</code> + * is thrown. + * <li>If the bundle is {@link #ACTIVE}or {@link #STARTING}then this + * method returns immediately. + * <li>If the bundle is {@link #STOPPING}then this method may wait for + * the bundle to return to the {@link #RESOLVED}state before continuing. + * If this does not occur in a reasonable time, a {@link BundleException} + * is thrown to indicate the bundle was unable to be started. + * <li>If the bundle is not {@link #RESOLVED}, an attempt is made to + * resolve the bundle. If the bundle cannot be resolved, a + * {@link BundleException}is thrown. + * <li>The state of the bundle is set to {@link #STARTING}. + * <li>The {@link BundleActivator#start(BundleContext) start}method of the bundle's + * {@link BundleActivator}, if one is specified, is called. If the + * {@link BundleActivator}is invalid or throws an exception, the state of + * the bundle is set back to {@link #RESOLVED}, the bundle's listeners, if + * any, are removed, service's registered by the bundle, if any, are + * unregistered, and service's used by the bundle, if any, are released. A + * {@link BundleException}is then thrown. + * <li>It is recorded that this bundle has been started, so that when the + * framework is restarted, this bundle will be automatically started. + * <li>The state of the bundle is set to {@link #ACTIVE}. + * <li>A {@link BundleEvent}of type {@link BundleEvent#STARTED}is + * broadcast. + * </ol> + * + * <h5>Preconditons</h5> + * <ul> + * <li>getState() in {{@link #INSTALLED},{@link #RESOLVED}}. + * </ul> + * <h5>Postconditons, no exceptions thrown</h5> + * <ul> + * <li>getState() in {{@link #ACTIVE}}. + * <li>{@link BundleActivator#start(BundleContext) BundleActivator.start}has been called + * and did not throw an exception. + * </ul> + * <h5>Postconditions, when an exception is thrown</h5> + * <ul> + * <li>getState() not in {{@link #STARTING},{@link #ACTIVE}}. + * </ul> + * + * @exception BundleException + * If the bundle couldn't be started. This could be because + * a code dependency could not be resolved or the specified + * BundleActivator could not be loaded or threw an + * exception. + * @exception java.lang.IllegalStateException + * If the bundle has been uninstalled or the bundle tries to + * change its own state. + * @exception java.lang.SecurityException + * If the caller does not have {@link AdminPermission} + * permission and the Java runtime environment supports + * permissions. + */ + public void start() throws BundleException { + start(0); + } + + public void start(int options) throws BundleException { + framework.checkAdminPermission(this, AdminPermission.EXECUTE); + checkValid(); + beginStateChange(); + try { + startWorker(options); + } finally { + completeStateChange(); + } + } + + /** + * Internal worker to start a bundle. + * + * @param options + */ + protected abstract void startWorker(int options) throws BundleException; + + /** + * This method does the following + * <ol> + * <li> Return false if the bundle is a fragment + * <li> Return false if the bundle is not at the correct start-level + * <li> Return false if the bundle is not persistently marked for start + * <li> Return true if the bundle's activation policy is persistently ignored + * <li> Return true if the bundle does not define an activation policy + * <li> Transition to STARTING state and Fire LAZY_ACTIVATION event + * <li> Return false + * </ol> + * @return true if the bundle should be resumed + */ + protected boolean readyToResume() { + return false; + } + + /** + * Start this bundle w/o marking is persistently started. + * + * <p> + * The following steps are followed to start a bundle: + * <ol> + * <li>If the bundle is {@link #UNINSTALLED}then an <code>IllegalStateException</code> + * is thrown. + * <li>If the bundle is {@link #ACTIVE}or {@link #STARTING}then this + * method returns immediately. + * <li>If the bundle is {@link #STOPPING}then this method may wait for + * the bundle to return to the {@link #RESOLVED}state before continuing. + * If this does not occur in a reasonable time, a {@link BundleException} + * is thrown to indicate the bundle was unable to be started. + * <li>If the bundle is not {@link #RESOLVED}, an attempt is made to + * resolve the bundle. If the bundle cannot be resolved, a + * {@link BundleException}is thrown. + * <li>The state of the bundle is set to {@link #STARTING}. + * <li>The {@link BundleActivator#start(BundleContext) start}method of the bundle's + * {@link BundleActivator}, if one is specified, is called. If the + * {@link BundleActivator}is invalid or throws an exception, the state of + * the bundle is set back to {@link #RESOLVED}, the bundle's listeners, if + * any, are removed, service's registered by the bundle, if any, are + * unregistered, and service's used by the bundle, if any, are released. A + * {@link BundleException}is then thrown. + * <li>The state of the bundle is set to {@link #ACTIVE}. + * <li>A {@link BundleEvent}of type {@link BundleEvent#STARTED}is + * broadcast. + * </ol> + * + * <h5>Preconditons</h5> + * <ul> + * <li>getState() in {{@link #INSTALLED},{@link #RESOLVED}}. + * </ul> + * <h5>Postconditons, no exceptions thrown</h5> + * <ul> + * <li>getState() in {{@link #ACTIVE}}. + * <li>{@link BundleActivator#start(BundleContext) BundleActivator.start}has been called + * and did not throw an exception. + * </ul> + * <h5>Postconditions, when an exception is thrown</h5> + * <ul> + * <li>getState() not in {{@link #STARTING},{@link #ACTIVE}}. + * </ul> + * + * @exception BundleException + * If the bundle couldn't be started. This could be because + * a code dependency could not be resolved or the specified + * BundleActivator could not be loaded or threw an + * exception. + * @exception java.lang.IllegalStateException + * If the bundle tries to change its own state. + */ + protected void resume() throws BundleException { + if (state == UNINSTALLED) { + return; + } + beginStateChange(); + try { + if (readyToResume()) + startWorker(START_TRANSIENT); + } finally { + completeStateChange(); + } + } + + /** + * Stop this bundle. + * + * Any services registered by this bundle will be unregistered. Any + * services used by this bundle will be released. Any listeners registered + * by this bundle will be removed. + * + * <p> + * The following steps are followed to stop a bundle: + * <ol> + * <li>If the bundle is {@link #UNINSTALLED}then an <code>IllegalStateException</code> + * is thrown. + * <li>If the bundle is {@link #STOPPING},{@link #RESOLVED}, or + * {@link #INSTALLED}then this method returns immediately. + * <li>If the bundle is {@link #STARTING}then this method may wait for + * the bundle to reach the {@link #ACTIVE}state before continuing. If this + * does not occur in a reasonable time, a {@link BundleException}is thrown + * to indicate the bundle was unable to be stopped. + * <li>The state of the bundle is set to {@link #STOPPING}. + * <li>It is recorded that this bundle has been stopped, so that when the + * framework is restarted, this bundle will not be automatically started. + * <li>The {@link BundleActivator#stop(BundleContext) stop}method of the bundle's + * {@link BundleActivator}, if one is specified, is called. If the + * {@link BundleActivator}throws an exception, this method will continue + * to stop the bundle. A {@link BundleException}will be thrown after + * completion of the remaining steps. + * <li>The bundle's listeners, if any, are removed, service's registered + * by the bundle, if any, are unregistered, and service's used by the + * bundle, if any, are released. + * <li>The state of the bundle is set to {@link #RESOLVED}. + * <li>A {@link BundleEvent}of type {@link BundleEvent#STOPPED}is + * broadcast. + * </ol> + * + * <h5>Preconditons</h5> + * <ul> + * <li>getState() in {{@link #ACTIVE}}. + * </ul> + * <h5>Postconditons, no exceptions thrown</h5> + * <ul> + * <li>getState() not in {{@link #ACTIVE},{@link #STOPPING}}. + * <li>{@link BundleActivator#stop(BundleContext) BundleActivator.stop}has been called + * and did not throw an exception. + * </ul> + * <h5>Postconditions, when an exception is thrown</h5> + * <ul> + * <li>None. + * </ul> + * + * @exception BundleException + * If the bundle's BundleActivator could not be loaded or + * threw an exception. + * @exception java.lang.IllegalStateException + * If the bundle has been uninstalled or the bundle tries to + * change its own state. + * @exception java.lang.SecurityException + * If the caller does not have {@link AdminPermission} + * permission and the Java runtime environment supports + * permissions. + */ + public void stop() throws BundleException { + stop(0); + } + + public void stop(int options) throws BundleException { + framework.checkAdminPermission(this, AdminPermission.EXECUTE); + checkValid(); + beginStateChange(); + try { + stopWorker(options); + } finally { + completeStateChange(); + } + } + + /** + * Internal worker to stop a bundle. + * + * @param options + */ + protected abstract void stopWorker(int options) throws BundleException; + + /** + * Set the persistent status bit for the bundle. + * + * @param mask + * Mask for bit to set/clear + * @param state + * true to set bit, false to clear bit + */ + protected void setStatus(final int mask, final boolean state) { + try { + AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() { + public Object run() throws IOException { + int status = bundledata.getStatus(); + boolean test = ((status & mask) != 0); + if (test != state) { + bundledata.setStatus(state ? (status | mask) : (status & ~mask)); + bundledata.save(); + } + return null; + } + }); + } catch (PrivilegedActionException pae) { + framework.publishFrameworkEvent(FrameworkEvent.ERROR, this, pae.getException()); + } + } + + /** + * Stop this bundle w/o marking is persistently stopped. + * + * Any services registered by this bundle will be unregistered. Any + * services used by this bundle will be released. Any listeners registered + * by this bundle will be removed. + * + * <p> + * The following steps are followed to stop a bundle: + * <ol> + * <li>If the bundle is {@link #UNINSTALLED}then an <code>IllegalStateException</code> + * is thrown. + * <li>If the bundle is {@link #STOPPING},{@link #RESOLVED}, or + * {@link #INSTALLED}then this method returns immediately. + * <li>If the bundle is {@link #STARTING}then this method may wait for + * the bundle to reach the {@link #ACTIVE}state before continuing. If this + * does not occur in a reasonable time, a {@link BundleException}is thrown + * to indicate the bundle was unable to be stopped. + * <li>The state of the bundle is set to {@link #STOPPING}. + * <li>The {@link BundleActivator#stop(BundleContext) stop}method of the bundle's + * {@link BundleActivator}, if one is specified, is called. If the + * {@link BundleActivator}throws an exception, this method will continue + * to stop the bundle. A {@link BundleException}will be thrown after + * completion of the remaining steps. + * <li>The bundle's listeners, if any, are removed, service's registered + * by the bundle, if any, are unregistered, and service's used by the + * bundle, if any, are released. + * <li>The state of the bundle is set to {@link #RESOLVED}. + * <li>A {@link BundleEvent}of type {@link BundleEvent#STOPPED}is + * broadcast. + * </ol> + * + * <h5>Preconditons</h5> + * <ul> + * <li>getState() in {{@link #ACTIVE}}. + * </ul> + * <h5>Postconditons, no exceptions thrown</h5> + * <ul> + * <li>getState() not in {{@link #ACTIVE},{@link #STOPPING}}. + * <li>{@link BundleActivator#stop(BundleContext) BundleActivator.stop}has been called + * and did not throw an exception. + * </ul> + * <h5>Postconditions, when an exception is thrown</h5> + * <ul> + * <li>None. + * </ul> + * + * @param lock + * true if state change lock should be held when returning from + * this method. + * @exception BundleException + * If the bundle's BundleActivator could not be loaded or + * threw an exception. + * @exception java.lang.IllegalStateException + * If the bundle tries to change its own state. + */ + protected void suspend(boolean lock) throws BundleException { + if (state == UNINSTALLED) { + return; + } + beginStateChange(); + try { + stopWorker(STOP_TRANSIENT); + } finally { + if (!lock) { + completeStateChange(); + } + } + } + + public void update() throws BundleException { + update(null); + } + + public void update(final InputStream in) throws BundleException { + if (Debug.DEBUG_GENERAL) { + Debug.println("update location " + bundledata.getLocation()); //$NON-NLS-1$ + Debug.println(" from: " + in); //$NON-NLS-1$ + } + framework.checkAdminPermission(this, AdminPermission.LIFECYCLE); + if ((bundledata.getType() & (BundleData.TYPE_BOOTCLASSPATH_EXTENSION | BundleData.TYPE_FRAMEWORK_EXTENSION | BundleData.TYPE_EXTCLASSPATH_EXTENSION)) != 0) + // need special permission to update extensions + framework.checkAdminPermission(this, AdminPermission.EXTENSIONLIFECYCLE); + checkValid(); + beginStateChange(); + try { + final AccessControlContext callerContext = AccessController.getContext(); + //note AdminPermission is checked again after updated bundle is loaded + updateWorker(new PrivilegedExceptionAction<Object>() { + public Object run() throws BundleException { + /* compute the update location */ + URLConnection source = null; + if (in == null) { + String updateLocation = bundledata.getManifest().get(Constants.BUNDLE_UPDATELOCATION); + if (updateLocation == null) + updateLocation = bundledata.getLocation(); + if (Debug.DEBUG_GENERAL) + Debug.println(" from location: " + updateLocation); //$NON-NLS-1$ + /* Map the update location to a URLConnection */ + source = framework.adaptor.mapLocationToURLConnection(updateLocation); + } else { + /* Map the InputStream to a URLConnection */ + source = new BundleSource(in); + } + /* call the worker */ + updateWorkerPrivileged(source, callerContext); + return null; + } + }); + } finally { + completeStateChange(); + } + } + + /** + * Update worker. Assumes the caller has the state change lock. + */ + protected void updateWorker(PrivilegedExceptionAction<Object> action) throws BundleException { + int previousState = 0; + if (!isFragment()) + previousState = state; + if ((previousState & (ACTIVE | STARTING)) != 0) { + try { + stopWorker(STOP_TRANSIENT); + } catch (BundleException e) { + framework.publishFrameworkEvent(FrameworkEvent.ERROR, this, e); + if ((state & (ACTIVE | STARTING)) != 0) /* if the bundle is still active */{ + throw e; + } + } + } + try { + AccessController.doPrivileged(action); + framework.publishBundleEvent(BundleEvent.UPDATED, this); + } catch (PrivilegedActionException pae) { + if (pae.getException() instanceof RuntimeException) + throw (RuntimeException) pae.getException(); + throw (BundleException) pae.getException(); + } finally { + if ((previousState & (ACTIVE | STARTING)) != 0) { + try { + startWorker(START_TRANSIENT | ((previousState & STARTING) != 0 ? START_ACTIVATION_POLICY : 0)); + } catch (BundleException e) { + framework.publishFrameworkEvent(FrameworkEvent.ERROR, this, e); + } + } + } + } + + /** + * Update worker. Assumes the caller has the state change lock. + */ + protected void updateWorkerPrivileged(URLConnection source, AccessControlContext callerContext) throws BundleException { + AbstractBundle oldBundle = AbstractBundle.createBundle(bundledata, framework, false); + boolean reloaded = false; + BundleOperation storage = framework.adaptor.updateBundle(this.bundledata, source); + BundleRepository bundles = framework.getBundles(); + try { + BundleData newBundleData = storage.begin(); + // Must call framework createBundle to check execution environment. + final AbstractBundle newBundle = framework.createAndVerifyBundle(CollisionHook.UPDATING, this, newBundleData, false); + boolean exporting; + int st = getState(); + synchronized (bundles) { + String oldBSN = this.getSymbolicName(); + exporting = reload(newBundle); + // update this to flush the old BSN/version etc. + bundles.update(oldBSN, this); + manifestLocalization = null; + } + // indicate we have loaded from the new version of the bundle + reloaded = true; + if (System.getSecurityManager() != null) { + final boolean extension = (bundledata.getType() & (BundleData.TYPE_BOOTCLASSPATH_EXTENSION | BundleData.TYPE_FRAMEWORK_EXTENSION | BundleData.TYPE_EXTCLASSPATH_EXTENSION)) != 0; + // must check for AllPermission before allow a bundle extension to be updated + if (extension && !hasPermission(new AllPermission())) + throw new BundleException(Msg.BUNDLE_EXTENSION_PERMISSION, BundleException.SECURITY_ERROR, new SecurityException(Msg.BUNDLE_EXTENSION_PERMISSION)); + try { + AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() { + public Object run() throws Exception { + framework.checkAdminPermission(newBundle, AdminPermission.LIFECYCLE); + if (extension) // need special permission to update extension bundles + framework.checkAdminPermission(newBundle, AdminPermission.EXTENSIONLIFECYCLE); + return null; + } + }, callerContext); + } catch (PrivilegedActionException e) { + throw e.getException(); + } + } + // send out unresolved events outside synch block (defect #80610) + if (st == RESOLVED) + framework.publishBundleEvent(BundleEvent.UNRESOLVED, this); + storage.commit(exporting); + } catch (Throwable t) { + try { + storage.undo(); + if (reloaded) + /* + * if we loaded from the new version of the + * bundle + */{ + synchronized (bundles) { + String oldBSN = this.getSymbolicName(); + reload(oldBundle); + // update this to flush the new BSN/version back to the old one etc. + bundles.update(oldBSN, this); + } + } + } catch (BundleException ee) { + /* if we fail to revert then we are in big trouble */ + framework.publishFrameworkEvent(FrameworkEvent.ERROR, this, ee); + } + if (t instanceof SecurityException) + throw (SecurityException) t; + if (t instanceof BundleException) + throw (BundleException) t; + throw new BundleException(t.getMessage(), t); + } + } + + /** + * Uninstall this bundle. + * <p> + * This method removes all traces of the bundle, including any data in the + * persistent storage area provided for the bundle by the framework. + * + * <p> + * The following steps are followed to uninstall a bundle: + * <ol> + * <li>If the bundle is {@link #UNINSTALLED}then an <code>IllegalStateException</code> + * is thrown. + * <li>If the bundle is {@link #ACTIVE}or {@link #STARTING}, the bundle + * is stopped as described in the {@link #stop()}method. If {@link #stop()} + * throws an exception, a {@link FrameworkEvent}of type + * {@link FrameworkEvent#ERROR}is broadcast containing the exception. + * <li>A {@link BundleEvent}of type {@link BundleEvent#UNINSTALLED}is + * broadcast. + * <li>The state of the bundle is set to {@link #UNINSTALLED}. + * <li>The bundle and the persistent storage area provided for the bundle + * by the framework, if any, is removed. + * </ol> + * + * <h5>Preconditions</h5> + * <ul> + * <li>getState() not in {{@link #UNINSTALLED}}. + * </ul> + * <h5>Postconditons, no exceptions thrown</h5> + * <ul> + * <li>getState() in {{@link #UNINSTALLED}}. + * <li>The bundle has been uninstalled. + * </ul> + * <h5>Postconditions, when an exception is thrown</h5> + * <ul> + * <li>getState() not in {{@link #UNINSTALLED}}. + * <li>The Bundle has not been uninstalled. + * </ul> + * + * @exception BundleException + * If the uninstall failed. + * @exception java.lang.IllegalStateException + * If the bundle has been uninstalled or the bundle tries to + * change its own state. + * @exception java.lang.SecurityException + * If the caller does not have {@link AdminPermission} + * permission and the Java runtime environment supports + * permissions. + * @see #stop() + */ + public void uninstall() throws BundleException { + if (Debug.DEBUG_GENERAL) { + Debug.println("uninstall location: " + bundledata.getLocation()); //$NON-NLS-1$ + } + framework.checkAdminPermission(this, AdminPermission.LIFECYCLE); + if ((bundledata.getType() & (BundleData.TYPE_BOOTCLASSPATH_EXTENSION | BundleData.TYPE_FRAMEWORK_EXTENSION | BundleData.TYPE_EXTCLASSPATH_EXTENSION)) != 0) + // need special permission to uninstall extensions + framework.checkAdminPermission(this, AdminPermission.EXTENSIONLIFECYCLE); + checkValid(); + beginStateChange(); + try { + uninstallWorker(new PrivilegedExceptionAction<Object>() { + public Object run() throws BundleException { + uninstallWorkerPrivileged(); + return null; + } + }); + } finally { + completeStateChange(); + } + } + + /** + * Uninstall worker. Assumes the caller has the state change lock. + */ + protected void uninstallWorker(PrivilegedExceptionAction<Object> action) throws BundleException { + boolean bundleActive = false; + if (!isFragment()) + bundleActive = (state & (ACTIVE | STARTING)) != 0; + if (bundleActive) { + try { + stopWorker(STOP_TRANSIENT); + } catch (BundleException e) { + framework.publishFrameworkEvent(FrameworkEvent.ERROR, this, e); + } + } + try { + AccessController.doPrivileged(action); + } catch (PrivilegedActionException pae) { + if (bundleActive) /* if we stopped the bundle */{ + try { + startWorker(START_TRANSIENT); + } catch (BundleException e) { + /* + * if we fail to start the original bundle then we are in + * big trouble + */ + framework.publishFrameworkEvent(FrameworkEvent.ERROR, this, e); + } + } + throw (BundleException) pae.getException(); + } + framework.publishBundleEvent(BundleEvent.UNINSTALLED, this); + } + + /** + * Uninstall worker. Assumes the caller has the state change lock. + */ + protected void uninstallWorkerPrivileged() throws BundleException { + BundleWatcher bundleStats = framework.adaptor.getBundleWatcher(); + if (bundleStats != null) + bundleStats.watchBundle(this, BundleWatcher.START_UNINSTALLING); + boolean unloaded = false; + //cache the bundle's headers + getHeaders(); + BundleOperation storage = framework.adaptor.uninstallBundle(this.bundledata); + BundleRepository bundles = framework.getBundles(); + try { + storage.begin(); + boolean exporting; + int st = getState(); + synchronized (bundles) { + bundles.remove(this); /* remove before calling unload */ + exporting = unload(); + } + // send out unresolved events outside synch block (defect #80610) + if (st == RESOLVED) + framework.publishBundleEvent(BundleEvent.UNRESOLVED, this); + unloaded = true; + storage.commit(exporting); + close(); + } catch (BundleException e) { + try { + storage.undo(); + if (unloaded) /* if we unloaded the bundle */{ + synchronized (bundles) { + load(); /* reload the bundle */ + bundles.add(this); + } + } + } catch (BundleException ee) { + /* + * if we fail to load the original bundle then we are in big + * trouble + */ + framework.publishFrameworkEvent(FrameworkEvent.ERROR, this, ee); + } + throw e; + } finally { + if (bundleStats != null) + bundleStats.watchBundle(this, BundleWatcher.END_UNINSTALLING); + } + } + + /** + * Return the bundle's manifest headers and values from the manifest's + * preliminary section. That is all the manifest's headers and values prior + * to the first blank line. + * + * <p> + * Manifest header names are case-insensitive. The methods of the returned + * <code>Dictionary</code> object will operate on header names in a + * case-insensitive manner. + * + * <p> + * For example, the following manifest headers and values are included if + * they are present in the manifest: + * + * <pre> + * Bundle-Name + * Bundle-Vendor + * Bundle-Version + * Bundle-Description + * Bundle-DocURL + * Bundle-ContactAddress + * </pre> + * + * <p> + * This method will continue to return this information when the bundle is + * in the {@link #UNINSTALLED}state. + * + * @return A <code>Dictionary</code> object containing the bundle's + * manifest headers and values. + * @exception java.lang.SecurityException + * If the caller does not have {@link AdminPermission} + * permission and the Java runtime environment supports + * permissions. + */ + public Dictionary<String, String> getHeaders() { + return getHeaders(null); + } + + /** + * Returns this bundle's Manifest headers and values. This method returns + * all the Manifest headers and values from the main section of the + * bundle's Manifest file; that is, all lines prior to the first blank + * line. + * + * <p> + * Manifest header names are case-insensitive. The methods of the returned + * <tt>Dictionary</tt> object will operate on header names in a + * case-insensitive manner. + * + * If a Manifest header begins with a '%', it will be evaluated with the + * specified properties file for the specied Locale. + * + * <p> + * For example, the following Manifest headers and values are included if + * they are present in the Manifest file: + * + * <pre> + * Bundle-Name + * Bundle-Vendor + * Bundle-Version + * Bundle-Description + * Bundle-DocURL + * Bundle-ContactAddress + * </pre> + * + * <p> + * This method will continue to return Manifest header information while + * this bundle is in the <tt>UNINSTALLED</tt> state. + * + * @return A <tt>Dictionary</tt> object containing this bundle's Manifest + * headers and values. + * + * @exception java.lang.SecurityException + * If the caller does not have the <tt>AdminPermission</tt>, + * and the Java Runtime Environment supports permissions. + */ + public Dictionary<String, String> getHeaders(String localeString) { + framework.checkAdminPermission(this, AdminPermission.METADATA); + ManifestLocalization localization; + try { + localization = getManifestLocalization(); + } catch (BundleException e) { + framework.publishFrameworkEvent(FrameworkEvent.ERROR, this, e); + // return an empty dictinary. + return new Hashtable<String, String>(); + } + if (localeString == null) + localeString = Locale.getDefault().toString(); + return localization.getHeaders(localeString); + } + + /** + * Retrieve the bundle's unique identifier, which the framework assigned to + * this bundle when it was installed. + * + * <p> + * The unique identifier has the following attributes: + * <ul> + * <li>It is unique and persistent. + * <li>The identifier is a long. + * <li>Once its value is assigned to a bundle, that value is not reused + * for another bundle, even after the bundle is uninstalled. + * <li>Its value does not change as long as the bundle remains installed. + * <li>Its value does not change when the bundle is updated + * </ul> + * + * <p> + * This method will continue to return the bundle's unique identifier when + * the bundle is in the {@link #UNINSTALLED}state. + * + * @return This bundle's unique identifier. + */ + public long getBundleId() { + return (bundledata.getBundleID()); + } + + /** + * Retrieve the location identifier of the bundle. This is typically the + * location passed to + * {@link BundleContextImpl#installBundle(String) BundleContext.installBundle}when the + * bundle was installed. The location identifier of the bundle may change + * during bundle update. Calling this method while framework is updating + * the bundle results in undefined behavior. + * + * <p> + * This method will continue to return the bundle's location identifier + * when the bundle is in the {@link #UNINSTALLED}state. + * + * @return A string that is the location identifier of the bundle. + * @exception java.lang.SecurityException + * If the caller does not have {@link AdminPermission} + * permission and the Java runtime environment supports + * permissions. + */ + public String getLocation() { + framework.checkAdminPermission(this, AdminPermission.METADATA); + return (bundledata.getLocation()); + } + + /** + * Determine whether the bundle has the requested permission. + * + * <p> + * If the Java runtime environment does not supports permissions this + * method always returns <code>true</code>. The permission parameter is + * of type <code>Object</code> to avoid referencing the <code>java.security.Permission</code> + * class directly. This is to allow the framework to be implemented in Java + * environments which do not support permissions. + * + * @param permission + * The requested permission. + * @return <code>true</code> if the bundle has the requested permission + * or <code>false</code> if the bundle does not have the + * permission or the permission parameter is not an <code>instanceof java.security.Permission</code>. + * @exception java.lang.IllegalStateException + * If the bundle has been uninstalled. + */ + public boolean hasPermission(Object permission) { + checkValid(); + if (domain != null) { + if (permission instanceof Permission) { + SecurityManager sm = System.getSecurityManager(); + if (sm instanceof EquinoxSecurityManager) { + /* + * If the FrameworkSecurityManager is active, we need to do checks the "right" way. + * We can exploit our knowledge that the security context of FrameworkSecurityManager + * is an AccessControlContext to invoke it properly with the ProtectionDomain. + */ + AccessControlContext acc = getAccessControlContext(); + try { + sm.checkPermission((Permission) permission, acc); + return true; + } catch (Exception e) { + return false; + } + } + return domain.implies((Permission) permission); + } + return false; + } + return true; + } + + /** + * This method marks the bundle's state as changing so that other calls to + * start/stop/suspend/update/uninstall can wait until the state change is + * complete. If stateChanging is non-null when this method is called, we + * will wait for the state change to complete. If the timeout expires + * without changing state (this may happen if the state change is back up + * our call stack), a BundleException is thrown so that we don't wait + * forever. + * + * A call to this method should be immediately followed by a try block + * whose finally block calls completeStateChange(). + * + * beginStateChange(); try { // change the bundle's state here... } finally { + * completeStateChange(); } + * + * @exception org.osgi.framework.BundleException + * if the bundles state is still changing after waiting for + * the timeout. + */ + protected void beginStateChange() throws BundleException { + synchronized (statechangeLock) { + boolean doubleFault = false; + while (true) { + if (stateChanging == null) { + stateChanging = Thread.currentThread(); + return; + } + if (doubleFault || (stateChanging == Thread.currentThread())) { + throw new BundleException(NLS.bind(Msg.BUNDLE_STATE_CHANGE_EXCEPTION, getBundleData().getLocation(), stateChanging.getName()), BundleException.STATECHANGE_ERROR, new BundleStatusException(null, StatusException.CODE_WARNING, stateChanging)); + } + try { + long start = 0; + if (Debug.DEBUG_GENERAL) { + Debug.println(" Waiting for state to change in bundle " + this); //$NON-NLS-1$ + start = System.currentTimeMillis(); + } + statechangeLock.wait(STATE_CHANGE_TIMEOUT); + /* + * wait for other thread to + * finish changing state + */ + if (Debug.DEBUG_GENERAL) { + long end = System.currentTimeMillis(); + if (end - start > 0) + System.out.println("Waiting... : " + getSymbolicName() + ' ' + (end - start)); //$NON-NLS-1$ + } + } catch (InterruptedException e) { + //Nothing to do + } + doubleFault = true; + } + } + } + + /** + * This method completes the bundle state change by setting stateChanging + * to null and notifying one waiter that the state change has completed. + */ + protected void completeStateChange() { + synchronized (statechangeLock) { + if (stateChanging == Thread.currentThread()) { + stateChanging = null; + statechangeLock.notify(); + /* + * notify one waiting thread that the + * state change is complete + */ + } + } + } + + /** + * Return a string representation of this bundle. + * + * @return String + */ + public String toString() { + String name = bundledata.getSymbolicName(); + if (name == null) + name = "unknown"; //$NON-NLS-1$ + return (name + '_' + bundledata.getVersion() + " [" + getBundleId() + "]"); //$NON-NLS-1$ //$NON-NLS-2$ + } + + /** + * Answers an integer indicating the relative positions of the receiver and + * the argument in the natural order of elements of the receiver's class. + * + * @return int which should be <0 if the receiver should sort before the + * argument, 0 if the receiver should sort in the same position as + * the argument, and >0 if the receiver should sort after the + * argument. + * @param obj + * another Bundle an object to compare the receiver to + * @exception ClassCastException + * if the argument can not be converted into something + * comparable with the receiver. + */ + public int compareTo(Bundle obj) { + int slcomp = getInternalStartLevel() - ((AbstractBundle) obj).getInternalStartLevel(); + if (slcomp != 0) { + return slcomp; + } + long idcomp = getBundleId() - ((AbstractBundle) obj).getBundleId(); + return (idcomp < 0L) ? -1 : ((idcomp > 0L) ? 1 : 0); + } + + /** + * This method checks that the bundle is not uninstalled. If the bundle is + * uninstalled, an IllegalStateException is thrown. + * + * @exception java.lang.IllegalStateException + * If the bundle is uninstalled. + */ + protected void checkValid() { + if (state == UNINSTALLED) { + throw new IllegalStateException(NLS.bind(Msg.BUNDLE_UNINSTALLED_EXCEPTION, getBundleData().getLocation())); + } + } + + /** + * Get the bundle's ProtectionDomain. + * + * @return bundle's ProtectionDomain. + */ + public BundleProtectionDomain getProtectionDomain() { + return domain; + } + + private AccessControlContext getAccessControlContext() { + return new AccessControlContext(new ProtectionDomain[] {domain}); + } + + protected BundleFragment[] getFragments() { + checkValid(); + return null; + } + + protected boolean isFragment() { + return false; + } + + BundleHost[] getHosts() { + checkValid(); + return null; + } + + /* + * (non-Javadoc) + * + * @see org.osgi.framework.Bundle#findClass(java.lang.String) + */ + public Class<?> loadClass(String classname) throws ClassNotFoundException { + return loadClass(classname, true); + } + + /* + * (non-Javadoc) + * + * @see org.osgi.framework.Bundle#getResourcePaths(java.lang.String) + */ + public Enumeration<String> getEntryPaths(final String path) { + try { + framework.checkAdminPermission(this, AdminPermission.RESOURCE); + } catch (SecurityException e) { + return null; + } + checkValid(); + // TODO this doPrivileged is probably not needed. The adaptor isolates callers from disk access + return AccessController.doPrivileged(new PrivilegedAction<Enumeration<String>>() { + public Enumeration<String> run() { + return bundledata.getEntryPaths(path); + } + }); + } + + /* + * (non-Javadoc) + * + * @see org.osgi.framework.Bundle#getFile(java.lang.String) + */ + public URL getEntry(String fileName) { + try { + framework.checkAdminPermission(this, AdminPermission.RESOURCE); + } catch (SecurityException e) { + return null; + } + return getEntry0(fileName); + } + + URL getEntry0(String fileName) { + checkValid(); + return bundledata.getEntry(fileName); + } + + public String getSymbolicName() { + return bundledata.getSymbolicName(); + } + + public long getLastModified() { + return bundledata.getLastModified(); + } + + public BundleData getBundleData() { + return bundledata; + } + + public Version getVersion() { + return bundledata.getVersion(); + } + + public BundleDescription getBundleDescription() { + return framework.adaptor.getState().getBundle(getBundleId()); + } + + int getInternalStartLevel() { + return bundledata.getStartLevel(); + } + + protected abstract BundleLoader getBundleLoader(); + + /** + * Mark this bundle as resolved. + */ + protected void resolve() { + if (Debug.DEBUG_GENERAL) { + if ((state & (INSTALLED)) == 0) { + Debug.println("Bundle.resolve called when state != INSTALLED: " + this); //$NON-NLS-1$ + Debug.printStackTrace(new Exception("Stack trace")); //$NON-NLS-1$ + } + } + if (state == INSTALLED) { + state = RESOLVED; + // Do not publish RESOLVED event here. This is done by caller + // to resolve if appropriate. + } + } + + public BundleContext getBundleContext() { + framework.checkAdminPermission(this, AdminPermission.CONTEXT); + return getContext(); + } + + /** + * Return the current context for this bundle. + * + * @return BundleContext for this bundle. + */ + abstract protected BundleContextImpl getContext(); + + public BundleException getResolutionFailureException() { + BundleDescription bundleDescription = getBundleDescription(); + if (bundleDescription == null) + return new BundleException(NLS.bind(Msg.BUNDLE_UNRESOLVED_EXCEPTION, this.toString()), BundleException.RESOLVE_ERROR); + // just a sanity check - this would be an inconsistency between the framework and the state + if (bundleDescription.isResolved()) + return new BundleException(Msg.BUNDLE_UNRESOLVED_STATE_CONFLICT, BundleException.RESOLVE_ERROR); + return getResolverError(bundleDescription); + } + + private BundleException getResolverError(BundleDescription bundleDesc) { + ResolverError[] errors = framework.adaptor.getState().getResolverErrors(bundleDesc); + if (errors == null || errors.length == 0) + return new BundleException(NLS.bind(Msg.BUNDLE_UNRESOLVED_EXCEPTION, this.toString()), BundleException.RESOLVE_ERROR); + StringBuffer message = new StringBuffer(); + int errorType = BundleException.RESOLVE_ERROR; + for (int i = 0; i < errors.length; i++) { + if ((errors[i].getType() & ResolverError.INVALID_NATIVECODE_PATHS) != 0) + errorType = BundleException.NATIVECODE_ERROR; + message.append(errors[i].toString()); + if (i < errors.length - 1) + message.append(", "); //$NON-NLS-1$ + } + return new BundleException(NLS.bind(Msg.BUNDLE_UNRESOLVED_UNSATISFIED_CONSTRAINT_EXCEPTION, this.toString(), message.toString()), errorType); + } + + public int getKeyHashCode() { + long id = getBundleId(); + return (int) (id ^ (id >>> 32)); + } + + public boolean compare(KeyedElement other) { + return getBundleId() == ((AbstractBundle) other).getBundleId(); + } + + public Object getKey() { + return new Long(getBundleId()); + } + + /* This method is used by the Bundle Localization Service to obtain + * a ResourceBundle that resides in a bundle. This is not an OSGi + * defined method for org.osgi.framework.Bundle + * + */ + public ResourceBundle getResourceBundle(String localeString) { + ManifestLocalization localization; + try { + localization = getManifestLocalization(); + } catch (BundleException ex) { + return (null); + } + String defaultLocale = Locale.getDefault().toString(); + if (localeString == null) { + localeString = defaultLocale; + } + return localization.getResourceBundle(localeString, defaultLocale.equals(localeString)); + } + + private synchronized ManifestLocalization getManifestLocalization() throws BundleException { + ManifestLocalization currentLocalization = manifestLocalization; + if (currentLocalization == null) { + Dictionary<String, String> rawHeaders = bundledata.getManifest(); + manifestLocalization = currentLocalization = new ManifestLocalization(this, rawHeaders); + } + return currentLocalization; + } + + public boolean testStateChanging(Object thread) { + return stateChanging == thread; + } + + public Thread getStateChanging() { + return stateChanging; + } + + public Enumeration<URL> findEntries(String path, String filePattern, boolean recurse) { + try { + framework.checkAdminPermission(this, AdminPermission.RESOURCE); + } catch (SecurityException e) { + return null; + } + checkValid(); + // check to see if the bundle is resolved + if (!isResolved()) + framework.packageAdmin.resolveBundles(new Bundle[] {this}); + + // if this bundle is a host to fragments then search the fragments + BundleFragment[] fragments = getFragments(); + List<BundleData> datas = new ArrayList<BundleData>((fragments == null ? 0 : fragments.length) + 1); + datas.add(getBundleData()); + if (fragments != null) + for (BundleFragment fragment : fragments) + datas.add(fragment.getBundleData()); + int options = recurse ? BundleWiring.FINDENTRIES_RECURSE : 0; + return framework.getAdaptor().findEntries(datas, path, filePattern, options); + } + + class BundleStatusException extends Throwable implements StatusException { + private static final long serialVersionUID = 7201911791818929100L; + private int code; + private transient Object status; + + BundleStatusException(String message, int code, Object status) { + super(message); + this.code = code; + this.status = status; + } + + public Object getStatus() { + return status; + } + + public int getStatusCode() { + return code; + } + + } + + public Map<X509Certificate, List<X509Certificate>> getSignerCertificates(int signersType) { + @SuppressWarnings("unchecked") + final Map<X509Certificate, List<X509Certificate>> empty = Collections.EMPTY_MAP; + if (signersType != SIGNERS_ALL && signersType != SIGNERS_TRUSTED) + throw new IllegalArgumentException("Invalid signers type: " + signersType); //$NON-NLS-1$ + if (framework == null) + return empty; + SignedContentFactory factory = framework.getSignedContentFactory(); + if (factory == null) + return empty; + try { + SignedContent signedContent = factory.getSignedContent(this); + SignerInfo[] infos = signedContent.getSignerInfos(); + if (infos.length == 0) + return empty; + Map<X509Certificate, List<X509Certificate>> results = new HashMap<X509Certificate, List<X509Certificate>>(infos.length); + for (int i = 0; i < infos.length; i++) { + if (signersType == SIGNERS_TRUSTED && !infos[i].isTrusted()) + continue; + Certificate[] certs = infos[i].getCertificateChain(); + if (certs == null || certs.length == 0) + continue; + List<X509Certificate> certChain = new ArrayList<X509Certificate>(); + for (int j = 0; j < certs.length; j++) + certChain.add((X509Certificate) certs[j]); + results.put((X509Certificate) certs[0], certChain); + } + return results; + } catch (Exception e) { + return empty; + } + } + + public final <A> A adapt(Class<A> adapterType) { + checkAdaptPermission(adapterType); + return adapt0(adapterType); + } + + public List<BundleRevision> getRevisions() { + List<BundleRevision> revisions = new ArrayList<BundleRevision>(); + BundleDescription current = getBundleDescription(); + if (current != null) + revisions.add(current); + BundleDescription[] removals = framework.adaptor.getState().getRemovalPending(); + for (BundleDescription removed : removals) { + if (removed.getBundleId() == getBundleId() && removed != current) { + revisions.add(removed); + } + } + return revisions; + } + + @SuppressWarnings("unchecked") + protected <A> A adapt0(Class<A> adapterType) { + if (adapterType.isInstance(this)) + return (A) this; + if (BundleContext.class.equals(adapterType)) { + try { + return (A) getBundleContext(); + } catch (SecurityException e) { + return null; + } + } + + if (AccessControlContext.class.equals(adapterType)) { + return (A) getAccessControlContext(); + } + + if (BundleWiring.class.equals(adapterType)) { + if (state == UNINSTALLED) + return null; + BundleDescription description = getBundleDescription(); + return (A) description.getWiring(); + } + + if (BundleRevision.class.equals(adapterType)) { + if (state == UNINSTALLED) + return null; + return (A) getBundleDescription(); + } + return null; + } + + /** + * Check for permission to get a service. + */ + private <A> void checkAdaptPermission(Class<A> adapterType) { + SecurityManager sm = System.getSecurityManager(); + if (sm == null) { + return; + } + sm.checkPermission(new AdaptPermission(adapterType.getName(), this, AdaptPermission.ADAPT)); + } + + public File getDataFile(String filename) { + return framework.getDataFile(this, filename); + } + + public Bundle getBundle() { + return this; + } + + public int getStartLevel() { + if (getState() == Bundle.UNINSTALLED) + throw new IllegalArgumentException(NLS.bind(Msg.BUNDLE_UNINSTALLED_EXCEPTION, getBundleData().getLocation())); + return getInternalStartLevel(); + } + + public void setStartLevel(int startlevel) { + framework.startLevelManager.setBundleStartLevel(this, startlevel); + } + + public boolean isPersistentlyStarted() { + if (getState() == Bundle.UNINSTALLED) + throw new IllegalArgumentException(NLS.bind(Msg.BUNDLE_UNINSTALLED_EXCEPTION, getBundleData().getLocation())); + return (getBundleData().getStatus() & Constants.BUNDLE_STARTED) != 0; + } + + public boolean isActivationPolicyUsed() { + if (getState() == Bundle.UNINSTALLED) + throw new IllegalArgumentException(NLS.bind(Msg.BUNDLE_UNINSTALLED_EXCEPTION, getBundleData().getLocation())); + return (getBundleData().getStatus() & Constants.BUNDLE_ACTIVATION_POLICY) != 0; + } + +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/AliasMapper.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/AliasMapper.java new file mode 100644 index 000000000..728745362 --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/AliasMapper.java @@ -0,0 +1,155 @@ +/******************************************************************************* + * Copyright (c) 2003, 2010 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.framework.internal.core; + +import java.io.*; +import java.util.*; +import org.eclipse.osgi.framework.debug.Debug; + +/** + * This class maps aliases. + */ +public class AliasMapper { + private static Map<String, Object> processorAliasTable; + private static Map<String, Object> osnameAliasTable; + + // Safe lazy initialization + private static synchronized Map<String, Object> getProcessorAliasTable() { + if (processorAliasTable == null) { + InputStream in = AliasMapper.class.getResourceAsStream(Constants.OSGI_PROCESSOR_ALIASES); + if (in != null) { + try { + processorAliasTable = initAliases(in); + } finally { + try { + in.close(); + } catch (IOException ee) { + // nothing + } + } + } + } + return processorAliasTable; + } + + // Safe lazy initialization + private static synchronized Map<String, Object> getOSNameAliasTable() { + if (osnameAliasTable == null) { + InputStream in = AliasMapper.class.getResourceAsStream(Constants.OSGI_OSNAME_ALIASES); + if (in != null) { + try { + osnameAliasTable = initAliases(in); + } finally { + try { + in.close(); + } catch (IOException ee) { + // nothing + } + } + } + } + return osnameAliasTable; + } + + /** + * Return the master alias for the processor. + * + * @param processor Input name + * @return aliased name (if any) + */ + public String aliasProcessor(String processor) { + processor = processor.toLowerCase(); + Map<String, Object> aliases = getProcessorAliasTable(); + if (aliases != null) { + String alias = (String) aliases.get(processor); + if (alias != null) { + processor = alias; + } + } + return processor; + } + + /** + * Return the master alias for the osname. + * + * @param osname Input name + * @return aliased name (if any) + */ + public Object aliasOSName(String osname) { + osname = osname.toLowerCase(); + Map<String, Object> aliases = getOSNameAliasTable(); + if (aliases != null) { + Object aliasObject = aliases.get(osname); + //String alias = (String) osnameAliasTable.get(osname); + if (aliasObject != null) + if (aliasObject instanceof String) { + osname = (String) aliasObject; + } else { + return aliasObject; + } + } + return osname; + } + + /** + * Read alias data and populate a Map. + * + * @param in InputStream from which to read alias data. + * @return Map of aliases. + */ + protected static Map<String, Object> initAliases(InputStream in) { + Map<String, Object> aliases = new HashMap<String, Object>(37); + try { + BufferedReader br; + try { + br = new BufferedReader(new InputStreamReader(in, "UTF8")); //$NON-NLS-1$ + } catch (UnsupportedEncodingException e) { + br = new BufferedReader(new InputStreamReader(in)); + } + while (true) { + String line = br.readLine(); + if (line == null) /* EOF */{ + break; /* done */ + } + Tokenizer tokenizer = new Tokenizer(line); + String master = tokenizer.getString("# \t"); //$NON-NLS-1$ + if (master != null) { + aliases.put(master.toLowerCase(), master); + parseloop: while (true) { + String alias = tokenizer.getString("# \t"); //$NON-NLS-1$ + if (alias == null) { + break parseloop; + } + String lowerCaseAlias = alias.toLowerCase(); + Object storedMaster = aliases.get(lowerCaseAlias); + if (storedMaster == null) { + aliases.put(lowerCaseAlias, master); + } else if (storedMaster instanceof String) { + List<String> newMaster = new ArrayList<String>(); + newMaster.add((String) storedMaster); + newMaster.add(master); + aliases.put(lowerCaseAlias, newMaster); + } else { + @SuppressWarnings("unchecked") + List<String> newMaster = ((List<String>) storedMaster); + newMaster.add(master); + } + } + } + } + } catch (IOException e) { + if (Debug.DEBUG_GENERAL) { + Debug.printStackTrace(e); + } + } + return aliases; + } +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/BundleContextImpl.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/BundleContextImpl.java new file mode 100644 index 000000000..a6581cd2d --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/BundleContextImpl.java @@ -0,0 +1,974 @@ +/******************************************************************************* + * Copyright (c) 2003, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + +package org.eclipse.osgi.framework.internal.core; + +import java.io.File; +import java.io.InputStream; +import java.security.*; +import java.util.*; +import org.eclipse.osgi.event.BatchBundleListener; +import org.eclipse.osgi.framework.debug.Debug; +import org.eclipse.osgi.framework.eventmgr.EventDispatcher; +import org.eclipse.osgi.internal.profile.Profile; +import org.eclipse.osgi.internal.serviceregistry.*; +import org.eclipse.osgi.util.NLS; +import org.osgi.framework.*; + +/** + * Bundle's execution context. + * + * This object is given out to bundles and provides the + * implementation to the BundleContext for a host bundle. + * It is destroyed when a bundle is stopped. + */ + +public class BundleContextImpl implements BundleContext, EventDispatcher<Object, Object, Object> { + private static boolean SET_TCCL = "true".equals(FrameworkProperties.getProperty("eclipse.bundle.setTCCL", "true")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + /** true if the bundle context is still valid */ + private volatile boolean valid; + + /** Bundle object this context is associated with. */ + // This slot is accessed directly by the Framework instead of using + // the getBundle() method because the Framework needs access to the bundle + // even when the context is invalid while the close method is being called. + final BundleHost bundle; + + /** Internal framework object. */ + final Framework framework; + + /** Services that bundle is using. Key is ServiceRegistrationImpl, + Value is ServiceUse */ + /* @GuardedBy("contextLock") */ + private HashMap<ServiceRegistrationImpl<?>, ServiceUse<?>> servicesInUse; + + /** The current instantiation of the activator. */ + protected BundleActivator activator; + + /** private object for locking */ + private final Object contextLock = new Object(); + + /** + * Construct a BundleContext which wrappers the framework for a + * bundle + * + * @param bundle The bundle we are wrapping. + */ + protected BundleContextImpl(BundleHost bundle) { + this.bundle = bundle; + valid = true; + framework = bundle.framework; + synchronized (contextLock) { + servicesInUse = null; + } + activator = null; + } + + /** + * Destroy the wrapper. This is called when the bundle is stopped. + * + */ + protected void close() { + valid = false; /* invalidate context */ + + final ServiceRegistry registry = framework.getServiceRegistry(); + + registry.removeAllServiceListeners(this); + framework.removeAllListeners(this); + + /* service's registered by the bundle, if any, are unregistered. */ + registry.unregisterServices(this); + + /* service's used by the bundle, if any, are released. */ + registry.releaseServicesInUse(this); + + synchronized (contextLock) { + servicesInUse = null; + } + } + + /** + * Retrieve the value of the named environment property. + * + * @param key The name of the requested property. + * @return The value of the requested property, or <code>null</code> if + * the property is undefined. + */ + public String getProperty(String key) { + SecurityManager sm = System.getSecurityManager(); + + if (sm != null) { + sm.checkPropertyAccess(key); + } + + return (framework.getProperty(key)); + } + + /** + * Retrieve the Bundle object for the context bundle. + * + * @return The context bundle's Bundle object. + */ + public Bundle getBundle() { + checkValid(); + + return getBundleImpl(); + } + + public AbstractBundle getBundleImpl() { + return bundle; + } + + public Bundle installBundle(String location) throws BundleException { + return installBundle(location, null); + } + + public Bundle installBundle(String location, InputStream in) throws BundleException { + checkValid(); + //note AdminPermission is checked after bundle is loaded + return framework.installBundle(location, in, this); + } + + /** + * Retrieve the bundle that has the given unique identifier. + * + * @param id The identifier of the bundle to retrieve. + * @return A Bundle object, or <code>null</code> + * if the identifier doesn't match any installed bundle. + */ + public Bundle getBundle(long id) { + return framework.getBundle(this, id); + } + + public Bundle getBundle(String location) { + return framework.getBundleByLocation(location); + } + + /** + * Retrieve the bundle that has the given location. + * + * @param location The location string of the bundle to retrieve. + * @return A Bundle object, or <code>null</code> + * if the location doesn't match any installed bundle. + */ + public AbstractBundle getBundleByLocation(String location) { + return (framework.getBundleByLocation(location)); + } + + /** + * Retrieve a list of all installed bundles. + * The list is valid at the time + * of the call to getBundles, but the framework is a very dynamic + * environment and bundles can be installed or uninstalled at anytime. + * + * @return An array of {@link AbstractBundle} objects, one + * object per installed bundle. + */ + public Bundle[] getBundles() { + return framework.getBundles(this); + } + + /** + * Add a service listener with a filter. + * {@link ServiceListener}s are notified when a service has a lifecycle + * state change. + * See {@link #getServiceReferences(String, String) getServiceReferences} + * for a description of the filter syntax. + * The listener is added to the context bundle's list of listeners. + * See {@link #getBundle() getBundle()} + * for a definition of context bundle. + * + * <p>The listener is called if the filter criteria is met. + * To filter based upon the class of the service, the filter + * should reference the "objectClass" property. + * If the filter paramater is <code>null</code>, all services + * are considered to match the filter. + * <p>If the Java runtime environment supports permissions, then additional + * filtering is done. + * {@link AbstractBundle#hasPermission(Object) Bundle.hasPermission} is called for the + * bundle which defines the listener to validate that the listener has the + * {@link ServicePermission} permission to <code>"get"</code> the service + * using at least one of the named classes the service was registered under. + * + * @param listener The service listener to add. + * @param filter The filter criteria. + * @exception InvalidSyntaxException If the filter parameter contains + * an invalid filter string which cannot be parsed. + * @see ServiceEvent + * @see ServiceListener + * @exception java.lang.IllegalStateException + * If the bundle context has stopped. + */ + public void addServiceListener(ServiceListener listener, String filter) throws InvalidSyntaxException { + checkValid(); + + if (listener == null) { + throw new IllegalArgumentException(); + } + framework.getServiceRegistry().addServiceListener(this, listener, filter); + } + + /** + * Add a service listener. + * + * <p>This method is the same as calling + * {@link #addServiceListener(ServiceListener, String)} + * with filter set to <code>null</code>. + * + * @see #addServiceListener(ServiceListener, String) + */ + public void addServiceListener(ServiceListener listener) { + try { + addServiceListener(listener, null); + } catch (InvalidSyntaxException e) { + if (Debug.DEBUG_GENERAL) { + Debug.println("InvalidSyntaxException w/ null filter" + e.getMessage()); //$NON-NLS-1$ + Debug.printStackTrace(e); + } + } + } + + /** + * Remove a service listener. + * The listener is removed from the context bundle's list of listeners. + * See {@link #getBundle() getBundle()} + * for a definition of context bundle. + * + * <p>If this method is called with a listener which is not registered, + * then this method does nothing. + * + * @param listener The service listener to remove. + * @exception java.lang.IllegalStateException + * If the bundle context has stopped. + */ + public void removeServiceListener(ServiceListener listener) { + checkValid(); + + if (listener == null) { + throw new IllegalArgumentException(); + } + framework.getServiceRegistry().removeServiceListener(this, listener); + } + + /** + * Add a bundle listener. + * {@link BundleListener}s are notified when a bundle has a lifecycle + * state change. + * The listener is added to the context bundle's list of listeners. + * See {@link #getBundle() getBundle()} + * for a definition of context bundle. + * + * @param listener The bundle listener to add. + * @exception java.lang.IllegalStateException + * If the bundle context has stopped. + * @see BundleEvent + * @see BundleListener + */ + public void addBundleListener(BundleListener listener) { + checkValid(); + if (listener == null) { + throw new IllegalArgumentException(); + } + + if (Debug.DEBUG_EVENTS) { + String listenerName = listener.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(listener)); //$NON-NLS-1$ + Debug.println("addBundleListener[" + bundle + "](" + listenerName + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + + framework.addBundleListener(listener, this); + } + + /** + * Remove a bundle listener. + * The listener is removed from the context bundle's list of listeners. + * See {@link #getBundle() getBundle()} + * for a definition of context bundle. + * + * <p>If this method is called with a listener which is not registered, + * then this method does nothing. + * + * @param listener The bundle listener to remove. + * @exception java.lang.IllegalStateException + * If the bundle context has stopped. + */ + public void removeBundleListener(BundleListener listener) { + checkValid(); + if (listener == null) { + throw new IllegalArgumentException(); + } + + if (Debug.DEBUG_EVENTS) { + String listenerName = listener.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(listener)); //$NON-NLS-1$ + Debug.println("removeBundleListener[" + bundle + "](" + listenerName + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + + framework.removeBundleListener(listener, this); + } + + /** + * Add a general framework listener. + * {@link FrameworkListener}s are notified of general framework events. + * The listener is added to the context bundle's list of listeners. + * See {@link #getBundle() getBundle()} + * for a definition of context bundle. + * + * @param listener The framework listener to add. + * @exception java.lang.IllegalStateException + * If the bundle context has stopped. + * @see FrameworkEvent + * @see FrameworkListener + */ + public void addFrameworkListener(FrameworkListener listener) { + checkValid(); + if (listener == null) { + throw new IllegalArgumentException(); + } + + if (Debug.DEBUG_EVENTS) { + String listenerName = listener.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(listener)); //$NON-NLS-1$ + Debug.println("addFrameworkListener[" + bundle + "](" + listenerName + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + + framework.addFrameworkListener(listener, this); + } + + /** + * Remove a framework listener. + * The listener is removed from the context bundle's list of listeners. + * See {@link #getBundle() getBundle()} + * for a definition of context bundle. + * + * <p>If this method is called with a listener which is not registered, + * then this method does nothing. + * + * @param listener The framework listener to remove. + * @exception java.lang.IllegalStateException + * If the bundle context has stopped. + */ + public void removeFrameworkListener(FrameworkListener listener) { + checkValid(); + if (listener == null) { + throw new IllegalArgumentException(); + } + + if (Debug.DEBUG_EVENTS) { + String listenerName = listener.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(listener)); //$NON-NLS-1$ + Debug.println("removeFrameworkListener[" + bundle + "](" + listenerName + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + + framework.removeFrameworkListener(listener, this); + } + + /** + * Register a service with multiple names. + * This method registers the given service object with the given properties + * under the given class names. + * A {@link ServiceRegistration} object is returned. + * The {@link ServiceRegistration} object is for the private use of the bundle + * registering the service and should not be shared with other bundles. + * The registering bundle is defined to be the context bundle. + * See {@link #getBundle()} for a definition of context bundle. + * Other bundles can locate the service by using either the + * {@link #getServiceReferences getServiceReferences} or + * {@link #getServiceReference getServiceReference} method. + * + * <p>A bundle can register a service object that implements the + * {@link ServiceFactory} interface to + * have more flexiblity in providing service objects to different + * bundles. + * + * <p>The following steps are followed to register a service: + * <ol> + * <li>If the service parameter is not a {@link ServiceFactory}, + * an <code>IllegalArgumentException</code> is thrown if the + * service parameter is not an <code>instanceof</code> + * all the classes named. + * <li>The service is added to the framework's service registry + * and may now be used by other bundles. + * <li>A {@link ServiceEvent} of type {@link ServiceEvent#REGISTERED} + * is synchronously sent. + * <li>A {@link ServiceRegistration} object for this registration + * is returned. + * </ol> + * + * @param clazzes The class names under which the service can be located. + * The class names in this array will be stored in the service's + * properties under the key "objectClass". + * @param service The service object or a {@link ServiceFactory} object. + * @param properties The properties for this service. + * The keys in the properties object must all be Strings. + * Changes should not be made to this object after calling this method. + * To update the service's properties call the + * {@link ServiceRegistration#setProperties ServiceRegistration.setProperties} + * method. + * This parameter may be <code>null</code> if the service has no properties. + * @return A {@link ServiceRegistration} object for use by the bundle + * registering the service to update the + * service's properties or to unregister the service. + * @exception java.lang.IllegalArgumentException If one of the following is true: + * <ul> + * <li>The service parameter is null. + * <li>The service parameter is not a {@link ServiceFactory} and is not an + * <code>instanceof</code> all the named classes in the clazzes parameter. + * </ul> + * @exception java.lang.SecurityException If the caller does not have + * {@link ServicePermission} permission to "register" the service for + * all the named classes + * and the Java runtime environment supports permissions. + * @exception java.lang.IllegalStateException + * If the bundle context has stopped. + * @see ServiceRegistration + * @see ServiceFactory + */ + public ServiceRegistration<?> registerService(String[] clazzes, Object service, Dictionary<String, ?> properties) { + checkValid(); + return framework.getServiceRegistry().registerService(this, clazzes, service, properties); + } + + /** + * Register a service with a single name. + * This method registers the given service object with the given properties + * under the given class name. + * + * <p>This method is otherwise identical to + * {@link #registerService(java.lang.String[], java.lang.Object, java.util.Dictionary)} + * and is provided as a convenience when the service parameter will only be registered + * under a single class name. + * + * @see #registerService(java.lang.String[], java.lang.Object, java.util.Dictionary) + */ + public ServiceRegistration<?> registerService(String clazz, Object service, Dictionary<String, ?> properties) { + String[] clazzes = new String[] {clazz}; + + return registerService(clazzes, service, properties); + } + + /** + * Returns a list of <tt>ServiceReference</tt> objects. This method returns a list of + * <tt>ServiceReference</tt> objects for services which implement and were registered under + * the specified class and match the specified filter criteria. + * + * <p>The list is valid at the time of the call to this method, however as the Framework is + * a very dynamic environment, services can be modified or unregistered at anytime. + * + * <p><tt>filter</tt> is used to select the registered service whose + * properties objects contain keys and values which satisfy the filter. + * See {@link Filter}for a description of the filter string syntax. + * + * <p>If <tt>filter</tt> is <tt>null</tt>, all registered services + * are considered to match the filter. + * <p>If <tt>filter</tt> cannot be parsed, an {@link InvalidSyntaxException} will + * be thrown with a human readable message where the filter became unparsable. + * + * <p>The following steps are required to select a service: + * <ol> + * <li>If the Java Runtime Environment supports permissions, the caller is checked for the + * <tt>ServicePermission</tt> to get the service with the specified class. + * If the caller does not have the correct permission, <tt>null</tt> is returned. + * <li>If the filter string is not <tt>null</tt>, the filter string is + * parsed and the set of registered services which satisfy the filter is + * produced. + * If the filter string is <tt>null</tt>, then all registered services + * are considered to satisfy the filter. + * <li>If <code>clazz</code> is not <tt>null</tt>, the set is further reduced to + * those services which are an <tt>instanceof</tt> and were registered under the specified class. + * The complete list of classes of which a service is an instance and which + * were specified when the service was registered is available from the + * service's {@link Constants#OBJECTCLASS}property. + * <li>An array of <tt>ServiceReference</tt> to the selected services is returned. + * </ol> + * + * @param clazz The class name with which the service was registered, or + * <tt>null</tt> for all services. + * @param filter The filter criteria. + * @return An array of <tt>ServiceReference</tt> objects, or + * <tt>null</tt> if no services are registered which satisfy the search. + * @exception InvalidSyntaxException If <tt>filter</tt> contains + * an invalid filter string which cannot be parsed. + */ + public ServiceReference<?>[] getServiceReferences(String clazz, String filter) throws InvalidSyntaxException { + checkValid(); + return framework.getServiceRegistry().getServiceReferences(this, clazz, filter, false); + } + + public ServiceReference<?>[] getAllServiceReferences(String clazz, String filter) throws InvalidSyntaxException { + checkValid(); + return framework.getServiceRegistry().getServiceReferences(this, clazz, filter, true); + } + + /** + * Get a service reference. + * Retrieves a {@link ServiceReference} for a service + * which implements the named class. + * + * <p>This reference is valid at the time + * of the call to this method, but since the framework is a very dynamic + * environment, services can be modified or unregistered at anytime. + * + * <p>This method is provided as a convenience for when the caller is + * interested in any service which implements a named class. This method is + * the same as calling {@link #getServiceReferences getServiceReferences} + * with a <code>null</code> filter string but only a single {@link ServiceReference} + * is returned. + * + * @param clazz The class name which the service must implement. + * @return A {@link ServiceReference} object, or <code>null</code> + * if no services are registered which implement the named class. + * @see #getServiceReferences + */ + public ServiceReference<?> getServiceReference(String clazz) { + checkValid(); + + return framework.getServiceRegistry().getServiceReference(this, clazz); + } + + /** + * Get a service's service object. + * Retrieves the service object for a service. + * A bundle's use of a service is tracked by a + * use count. Each time a service's service object is returned by + * {@link #getService}, the context bundle's use count for the service + * is incremented by one. Each time the service is release by + * {@link #ungetService}, the context bundle's use count + * for the service is decremented by one. + * When a bundle's use count for a service + * drops to zero, the bundle should no longer use the service. + * See {@link #getBundle()} for a definition of context bundle. + * + * <p>This method will always return <code>null</code> when the + * service associated with this reference has been unregistered. + * + * <p>The following steps are followed to get the service object: + * <ol> + * <li>If the service has been unregistered, + * <code>null</code> is returned. + * <li>The context bundle's use count for this service is incremented by one. + * <li>If the context bundle's use count for the service is now one and + * the service was registered with a {@link ServiceFactory}, + * the {@link ServiceFactory#getService ServiceFactory.getService} method + * is called to create a service object for the context bundle. + * This service object is cached by the framework. + * While the context bundle's use count for the service is greater than zero, + * subsequent calls to get the services's service object for the context bundle + * will return the cached service object. + * <br>If the service object returned by the {@link ServiceFactory} + * is not an <code>instanceof</code> + * all the classes named when the service was registered or + * the {@link ServiceFactory} throws an exception, + * <code>null</code> is returned and a + * {@link FrameworkEvent} of type {@link FrameworkEvent#ERROR} is broadcast. + * <li>The service object for the service is returned. + * </ol> + * + * @param reference A reference to the service whose service object is desired. + * @return A service object for the service associated with this + * reference, or <code>null</code> if the service is not registered. + * @exception java.lang.SecurityException If the caller does not have + * {@link ServicePermission} permission to "get" the service + * using at least one of the named classes the service was registered under + * and the Java runtime environment supports permissions. + * @exception java.lang.IllegalStateException + * If the bundle context has stopped. + * @see #ungetService + * @see ServiceFactory + */ + public <S> S getService(ServiceReference<S> reference) { + checkValid(); + if (reference == null) + throw new NullPointerException("A null service reference is not allowed."); //$NON-NLS-1$ + synchronized (contextLock) { + if (servicesInUse == null) + // Cannot predict how many services a bundle will use, start with a small table. + servicesInUse = new HashMap<ServiceRegistrationImpl<?>, ServiceUse<?>>(10); + } + + @SuppressWarnings("unchecked") + S service = (S) framework.getServiceRegistry().getService(this, (ServiceReferenceImpl<S>) reference); + return service; + } + + /** + * Unget a service's service object. + * Releases the service object for a service. + * If the context bundle's use count for the service is zero, this method + * returns <code>false</code>. Otherwise, the context bundle's use count for the + * service is decremented by one. + * See {@link #getBundle()} for a definition of context bundle. + * + * <p>The service's service object + * should no longer be used and all references to it should be destroyed + * when a bundle's use count for the service + * drops to zero. + * + * <p>The following steps are followed to unget the service object: + * <ol> + * <li>If the context bundle's use count for the service is zero or + * the service has been unregistered, + * <code>false</code> is returned. + * <li>The context bundle's use count for this service is decremented by one. + * <li>If the context bundle's use count for the service is now zero and + * the service was registered with a {@link ServiceFactory}, + * the {@link ServiceFactory#ungetService ServiceFactory.ungetService} method + * is called to release the service object for the context bundle. + * <li><code>true</code> is returned. + * </ol> + * + * @param reference A reference to the service to be released. + * @return <code>false</code> if the context bundle's use count for the service + * is zero or if the service has been unregistered, + * otherwise <code>true</code>. + * @exception java.lang.IllegalStateException + * If the bundle context has stopped. + * @see #getService + * @see ServiceFactory + */ + public boolean ungetService(ServiceReference<?> reference) { + checkValid(); + + return framework.getServiceRegistry().ungetService(this, (ServiceReferenceImpl<?>) reference); + } + + /** + * Creates a <code>File</code> object for a file in the + * persistent storage area provided for the bundle by the framework. + * If the adaptor does not have file system support, this method will + * return <code>null</code>. + * + * <p>A <code>File</code> object for the base directory of the + * persistent storage area provided for the context bundle by the framework + * can be obtained by calling this method with the empty string ("") + * as the parameter. + * See {@link #getBundle()} for a definition of context bundle. + * + * <p>If the Java runtime environment supports permissions, + * the framework the will ensure that the bundle has + * <code>java.io.FilePermission</code> with actions + * "read","write","execute","delete" for all files (recursively) in the + * persistent storage area provided for the context bundle by the framework. + * + * @param filename A relative name to the file to be accessed. + * @return A <code>File</code> object that represents the requested file or + * <code>null</code> if the adaptor does not have file system support. + * @exception java.lang.IllegalStateException + * If the bundle context has stopped. + */ + public File getDataFile(String filename) { + checkValid(); + + return (framework.getDataFile(bundle, filename)); + } + + /** + * Call bundle's BundleActivator.start() + * This method is called by Bundle.startWorker to start the bundle. + * + * @exception BundleException if + * the bundle has a class that implements the BundleActivator interface, + * but Framework couldn't instantiate it, or the BundleActivator.start() + * method failed + */ + protected void start() throws BundleException { + activator = bundle.loadBundleActivator(); + + if (activator != null) { + try { + startActivator(activator); + } catch (BundleException be) { + activator = null; + throw be; + } + } + + /* activator completed successfully. We must use this + same activator object when we stop this bundle. */ + } + + /** + * Calls the start method of a BundleActivator. + * @param bundleActivator that activator to start + */ + protected void startActivator(final BundleActivator bundleActivator) throws BundleException { + if (Profile.PROFILE && Profile.STARTUP) + Profile.logEnter("BundleContextImpl.startActivator()", null); //$NON-NLS-1$ + try { + AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() { + public Object run() throws Exception { + if (bundleActivator != null) { + if (Profile.PROFILE && Profile.STARTUP) + Profile.logTime("BundleContextImpl.startActivator()", "calling " + bundle.getLocation() + " bundle activator"); //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$ + // make sure the context class loader is set correctly + Object previousTCCL = setContextFinder(); + /* Start the bundle synchronously */ + try { + bundleActivator.start(BundleContextImpl.this); + } finally { + if (previousTCCL != Boolean.FALSE) + Thread.currentThread().setContextClassLoader((ClassLoader) previousTCCL); + } + if (Profile.PROFILE && Profile.STARTUP) + Profile.logTime("BundleContextImpl.startActivator()", "returned from " + bundle.getLocation() + " bundle activator"); //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$ + } + return null; + } + }); + } catch (Throwable t) { + if (t instanceof PrivilegedActionException) { + t = ((PrivilegedActionException) t).getException(); + } + + if (Debug.DEBUG_GENERAL) { + Debug.printStackTrace(t); + } + + String clazz = null; + clazz = bundleActivator.getClass().getName(); + + throw new BundleException(NLS.bind(Msg.BUNDLE_ACTIVATOR_EXCEPTION, new Object[] {clazz, "start", bundle.getSymbolicName() == null ? "" + bundle.getBundleId() : bundle.getSymbolicName()}), BundleException.ACTIVATOR_ERROR, t); //$NON-NLS-1$ //$NON-NLS-2$ + } finally { + if (Profile.PROFILE && Profile.STARTUP) + Profile.logExit("BundleContextImpl.startActivator()"); //$NON-NLS-1$ + } + } + + Object setContextFinder() { + if (!SET_TCCL) + return Boolean.FALSE; + Thread currentThread = Thread.currentThread(); + ClassLoader previousTCCL = currentThread.getContextClassLoader(); + ClassLoader contextFinder = framework.getContextFinder(); + if (previousTCCL != contextFinder) { + currentThread.setContextClassLoader(framework.getContextFinder()); + return previousTCCL; + } + return Boolean.FALSE; + } + + /** + * Call bundle's BundleActivator.stop() + * This method is called by Bundle.stopWorker to stop the bundle. + * + * @exception BundleException if + * the bundle has a class that implements the BundleActivator interface, + * and the BundleActivator.stop() method failed + */ + protected void stop() throws BundleException { + try { + AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() { + public Object run() throws Exception { + if (activator != null) { + // make sure the context class loader is set correctly + Object previousTCCL = setContextFinder(); + try { + /* Stop the bundle synchronously */ + activator.stop(BundleContextImpl.this); + } finally { + if (previousTCCL != Boolean.FALSE) + Thread.currentThread().setContextClassLoader((ClassLoader) previousTCCL); + } + } + return null; + } + }); + } catch (Throwable t) { + if (t instanceof PrivilegedActionException) { + t = ((PrivilegedActionException) t).getException(); + } + + if (Debug.DEBUG_GENERAL) { + Debug.printStackTrace(t); + } + + String clazz = (activator == null) ? "" : activator.getClass().getName(); //$NON-NLS-1$ + + throw new BundleException(NLS.bind(Msg.BUNDLE_ACTIVATOR_EXCEPTION, new Object[] {clazz, "stop", bundle.getSymbolicName() == null ? "" + bundle.getBundleId() : bundle.getSymbolicName()}), BundleException.ACTIVATOR_ERROR, t); //$NON-NLS-1$ //$NON-NLS-2$ + } finally { + activator = null; + } + } + + /** + * Return the map of ServiceRegistrationImpl to ServiceUse for services being + * used by this context. + * @return A map of ServiceRegistrationImpl to ServiceUse for services in use by + * this context. + */ + public Map<ServiceRegistrationImpl<?>, ServiceUse<?>> getServicesInUseMap() { + synchronized (contextLock) { + return servicesInUse; + } + } + + /** + * Bottom level event dispatcher for the BundleContext. + * + * @param originalListener listener object registered under. + * @param l listener to call (may be filtered). + * @param action Event class type + * @param object Event object + */ + public void dispatchEvent(Object originalListener, Object l, int action, Object object) { + // save the bundle ref to a local variable + // to avoid interference from another thread closing this context + AbstractBundle tmpBundle = bundle; + Object previousTCCL = setContextFinder(); + try { + if (isValid()) /* if context still valid */{ + switch (action) { + case Framework.BUNDLEEVENT : + case Framework.BUNDLEEVENTSYNC : { + BundleListener listener = (BundleListener) l; + + if (Debug.DEBUG_EVENTS) { + String listenerName = listener.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(listener)); //$NON-NLS-1$ + Debug.println("dispatchBundleEvent[" + tmpBundle + "](" + listenerName + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + + BundleEvent event = (BundleEvent) object; + switch (event.getType()) { + case Framework.BATCHEVENT_BEGIN : { + if (listener instanceof BatchBundleListener) + ((BatchBundleListener) listener).batchBegin(); + break; + } + case Framework.BATCHEVENT_END : { + if (listener instanceof BatchBundleListener) + ((BatchBundleListener) listener).batchEnd(); + break; + } + default : { + listener.bundleChanged((BundleEvent) object); + } + } + break; + } + + case ServiceRegistry.SERVICEEVENT : { + ServiceEvent event = (ServiceEvent) object; + + ServiceListener listener = (ServiceListener) l; + if (Debug.DEBUG_EVENTS) { + String listenerName = listener.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(listener)); //$NON-NLS-1$ + Debug.println("dispatchServiceEvent[" + tmpBundle + "](" + listenerName + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + listener.serviceChanged(event); + + break; + } + + case Framework.FRAMEWORKEVENT : { + FrameworkListener listener = (FrameworkListener) l; + + if (Debug.DEBUG_EVENTS) { + String listenerName = listener.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(listener)); //$NON-NLS-1$ + Debug.println("dispatchFrameworkEvent[" + tmpBundle + "](" + listenerName + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + + listener.frameworkEvent((FrameworkEvent) object); + break; + } + default : { + throw new InternalError(); + } + } + } + } catch (Throwable t) { + if (Debug.DEBUG_GENERAL) { + Debug.println("Exception in bottom level event dispatcher: " + t.getMessage()); //$NON-NLS-1$ + Debug.printStackTrace(t); + } + // allow the adaptor to handle this unexpected error + framework.adaptor.handleRuntimeError(t); + publisherror: { + if (action == Framework.FRAMEWORKEVENT) { + FrameworkEvent event = (FrameworkEvent) object; + if (event.getType() == FrameworkEvent.ERROR) { + break publisherror; // avoid infinite loop + } + } + + framework.publishFrameworkEvent(FrameworkEvent.ERROR, tmpBundle, t); + } + } finally { + if (previousTCCL != Boolean.FALSE) + Thread.currentThread().setContextClassLoader((ClassLoader) previousTCCL); + } + } + + /** + * Construct a Filter object. This filter object may be used + * to match a ServiceReference or a Dictionary. + * See Filter + * for a description of the filter string syntax. + * + * @param filter The filter string. + * @return A Filter object encapsulating the filter string. + * @exception InvalidSyntaxException If the filter parameter contains + * an invalid filter string which cannot be parsed. + */ + public Filter createFilter(String filter) throws InvalidSyntaxException { + checkValid(); + + return FilterImpl.newInstance(filter); + } + + /** + * This method checks that the context is still valid. If the context is + * no longer valid, an IllegalStateException is thrown. + * + * @exception java.lang.IllegalStateException + * If the context bundle has stopped. + */ + public void checkValid() { + if (!isValid()) { + throw new IllegalStateException(Msg.BUNDLE_CONTEXT_INVALID_EXCEPTION); + } + } + + /** + * This method checks that the context is still valid. + * + * @return true if the context is still valid; false otherwise + */ + protected boolean isValid() { + return valid; + } + + public Framework getFramework() { + return framework; + } + + public <S> ServiceRegistration<S> registerService(Class<S> clazz, S service, Dictionary<String, ?> properties) { + @SuppressWarnings("unchecked") + ServiceRegistration<S> registration = (ServiceRegistration<S>) registerService(clazz.getName(), service, properties); + return registration; + } + + public <S> ServiceReference<S> getServiceReference(Class<S> clazz) { + @SuppressWarnings("unchecked") + ServiceReference<S> reference = (ServiceReference<S>) getServiceReference(clazz.getName()); + return reference; + } + + public <S> Collection<ServiceReference<S>> getServiceReferences(Class<S> clazz, String filter) throws InvalidSyntaxException { + @SuppressWarnings("unchecked") + ServiceReference<S>[] refs = (ServiceReference<S>[]) getServiceReferences(clazz.getName(), filter); + if (refs == null) { + @SuppressWarnings("unchecked") + Collection<ServiceReference<S>> empty = Collections.EMPTY_LIST; + return empty; + } + List<ServiceReference<S>> result = new ArrayList<ServiceReference<S>>(refs.length); + for (ServiceReference<S> b : refs) { + result.add(b); + } + return result; + } +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/BundleFragment.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/BundleFragment.java new file mode 100644 index 000000000..98ca8ee24 --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/BundleFragment.java @@ -0,0 +1,334 @@ +/******************************************************************************* + * Copyright (c) 2003, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + +package org.eclipse.osgi.framework.internal.core; + +import java.io.IOException; +import java.net.URL; +import java.util.Enumeration; +import org.eclipse.osgi.framework.adaptor.BundleData; +import org.eclipse.osgi.framework.debug.Debug; +import org.eclipse.osgi.internal.loader.BundleLoader; +import org.eclipse.osgi.util.NLS; +import org.osgi.framework.*; + +public class BundleFragment extends AbstractBundle { + + /** The resolved host that this fragment is attached to */ + protected BundleHost[] hosts; + + /** + * @param bundledata + * @param framework + * @throws BundleException + */ + public BundleFragment(BundleData bundledata, Framework framework) throws BundleException { + super(bundledata, framework); + hosts = null; + } + + /** + * Load the bundle. + */ + protected void load() { + if (Debug.DEBUG_GENERAL) { + if ((state & (INSTALLED)) == 0) { + Debug.println("Bundle.load called when state != INSTALLED: " + this); //$NON-NLS-1$ + Debug.printStackTrace(new Exception("Stack trace")); //$NON-NLS-1$ + } + } + + if (framework.isActive()) { + SecurityManager sm = System.getSecurityManager(); + if (sm != null && framework.securityAdmin != null) { + domain = framework.securityAdmin.createProtectionDomain(this); + } + } + } + + /** + * Reload from a new bundle. + * This method must be called while holding the bundles lock. + * + * @param newBundle Dummy Bundle which contains new data. + * @return true if an exported package is "in use". i.e. it has been imported by a bundle + */ + protected boolean reload(AbstractBundle newBundle) { + if (Debug.DEBUG_GENERAL) { + if ((state & (INSTALLED | RESOLVED)) == 0) { + Debug.println("Bundle.reload called when state != INSTALLED | RESOLVED: " + this); //$NON-NLS-1$ + Debug.printStackTrace(new Exception("Stack trace")); //$NON-NLS-1$ + } + } + + boolean exporting = false; + if (framework.isActive()) { + if (hosts != null) { + if (state == RESOLVED) { + exporting = true; // if we have a host we cannot be removed until the host is refreshed + hosts = null; + state = INSTALLED; + } + } + } else { + /* close the outgoing jarfile */ + try { + this.bundledata.close(); + } catch (IOException e) { + // Do Nothing + } + } + if (!exporting) { + /* close the outgoing jarfile */ + try { + this.bundledata.close(); + } catch (IOException e) { + // Do Nothing + } + } + + this.bundledata = newBundle.bundledata; + this.bundledata.setBundle(this); + // create a new domain for the bundle because its signers/symbolic-name may have changed + if (framework.isActive() && System.getSecurityManager() != null && framework.securityAdmin != null) + domain = framework.securityAdmin.createProtectionDomain(this); + return (exporting); + } + + /** + * Refresh the bundle. This is called by Framework.refreshPackages. + * This method must be called while holding the bundles lock. + * this.loader.unimportPackages must have already been called before calling + * this method! + */ + protected void refresh() { + if (Debug.DEBUG_GENERAL) { + if ((state & (UNINSTALLED | INSTALLED | RESOLVED)) == 0) { + Debug.println("Bundle.refresh called when state != UNINSTALLED | INSTALLED | RESOLVED: " + this); //$NON-NLS-1$ + Debug.printStackTrace(new Exception("Stack trace")); //$NON-NLS-1$ + } + } + + if (state == RESOLVED) { + hosts = null; + state = INSTALLED; + // Do not publish UNRESOLVED event here. This is done by caller + // to resolve if appropriate. + } + manifestLocalization = null; + } + + /** + * Unload the bundle. + * This method must be called while holding the bundles lock. + * + * @return true if an exported package is "in use". i.e. it has been imported by a bundle + */ + protected boolean unload() { + if (Debug.DEBUG_GENERAL) { + if ((state & (UNINSTALLED | INSTALLED | RESOLVED)) == 0) { + Debug.println("Bundle.unload called when state != UNINSTALLED | INSTALLED | RESOLVED: " + this); //$NON-NLS-1$ + Debug.printStackTrace(new Exception("Stack trace")); //$NON-NLS-1$ + } + } + + boolean exporting = false; + if (framework.isActive()) { + if (hosts != null) { + if (state == RESOLVED) { + exporting = true; // if we have a host we cannot be removed until the host is refreshed + hosts = null; + state = INSTALLED; + } + domain = null; + } + } + if (!exporting) { + try { + this.bundledata.close(); + } catch (IOException e) { // Do Nothing. + } + } + + return (exporting); + } + + /** + * This method loads a class from the bundle. + * + * @param name the name of the desired Class. + * @param checkPermission indicates whether a permission check should be done. + * @return the resulting Class + * @exception java.lang.ClassNotFoundException if the class definition was not found. + */ + protected Class<?> loadClass(String name, boolean checkPermission) throws ClassNotFoundException { + if (checkPermission) { + try { + framework.checkAdminPermission(this, AdminPermission.CLASS); + } catch (SecurityException e) { + throw new ClassNotFoundException(name, e); + } + checkValid(); + } + // cannot load a class from a fragment because there is no classloader + // associated with fragments. + throw new ClassNotFoundException(NLS.bind(Msg.BUNDLE_FRAGMENT_CNFE, name)); + } + + /** + * Find the specified resource in this bundle. + * + * This bundle's class loader is called to search for the named resource. + * If this bundle's state is <tt>INSTALLED</tt>, then only this bundle will + * be searched for the specified resource. Imported packages cannot be searched + * when a bundle has not been resolved. + * + * @param name The name of the resource. + * See <tt>java.lang.ClassLoader.getResource</tt> for a description of + * the format of a resource name. + * @return a URL to the named resource, or <tt>null</tt> if the resource could + * not be found or if the caller does not have + * the <tt>AdminPermission</tt>, and the Java Runtime Environment supports permissions. + * + * @exception java.lang.IllegalStateException If this bundle has been uninstalled. + */ + public URL getResource(String name) { + checkValid(); + // cannot get a resource for a fragment because there is no classloader + // associated with fragments. + return (null); + + } + + public Enumeration<URL> getResources(String name) { + checkValid(); + // cannot get a resource for a fragment because there is no classloader + // associated with fragments. + return null; + } + + /** + * Internal worker to start a bundle. + * + * @param options + */ + protected void startWorker(int options) throws BundleException { + throw new BundleException(NLS.bind(Msg.BUNDLE_FRAGMENT_START, this), BundleException.INVALID_OPERATION); + } + + /** + * Internal worker to stop a bundle. + * + * @param options + */ + protected void stopWorker(int options) throws BundleException { + throw new BundleException(NLS.bind(Msg.BUNDLE_FRAGMENT_STOP, this), BundleException.INVALID_OPERATION); + } + + /** + * Provides a list of {@link ServiceReference}s for the services + * registered by this bundle + * or <code>null</code> if the bundle has no registered + * services. + * + * <p>The list is valid at the time + * of the call to this method, but the framework is a very dynamic + * environment and services can be modified or unregistered at anytime. + * + * @return An array of {@link ServiceReference} or <code>null</code>. + * @exception java.lang.IllegalStateException If the + * bundle has been uninstalled. + * @see ServiceRegistration + * @see ServiceReference + */ + public ServiceReference<?>[] getRegisteredServices() { + checkValid(); + // Fragments cannot have a BundleContext and therefore + // cannot have any services registered. + return null; + } + + /** + * Provides a list of {@link ServiceReference}s for the + * services this bundle is using, + * or <code>null</code> if the bundle is not using any services. + * A bundle is considered to be using a service if the bundle's + * use count for the service is greater than zero. + * + * <p>The list is valid at the time + * of the call to this method, but the framework is a very dynamic + * environment and services can be modified or unregistered at anytime. + * + * @return An array of {@link ServiceReference} or <code>null</code>. + * @exception java.lang.IllegalStateException If the + * bundle has been uninstalled. + * @see ServiceReference + */ + public ServiceReference<?>[] getServicesInUse() { + checkValid(); + // Fragments cannot have a BundleContext and therefore + // cannot have any services in use. + return null; + } + + synchronized BundleHost[] getHosts() { + return hosts; + } + + protected boolean isFragment() { + return true; + } + + /** + * Adds a host bundle for this fragment. + * @param host the BundleHost to add to the set of host bundles + */ + boolean addHost(BundleHost host) { + if (host == null) + return false; + try { + host.attachFragment(this); + } catch (BundleException be) { + framework.publishFrameworkEvent(FrameworkEvent.ERROR, host, be); + return false; + } + synchronized (this) { + if (hosts == null) { + hosts = new BundleHost[] {host}; + return true; + } + for (int i = 0; i < hosts.length; i++) { + if (host == hosts[i]) + return true; // already a host + } + BundleHost[] newHosts = new BundleHost[hosts.length + 1]; + System.arraycopy(hosts, 0, newHosts, 0, hosts.length); + newHosts[newHosts.length - 1] = host; + hosts = newHosts; + } + return true; + } + + protected BundleLoader getBundleLoader() { + // Fragments cannot have a BundleLoader. + return null; + } + + /** + * Return the current context for this bundle. + * + * @return BundleContext for this bundle. + */ + protected BundleContextImpl getContext() { + // Fragments cannot have a BundleContext. + return null; + } +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/BundleHost.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/BundleHost.java new file mode 100644 index 000000000..cfc55b0a3 --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/BundleHost.java @@ -0,0 +1,686 @@ +/******************************************************************************* + * Copyright (c) 2003, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + +package org.eclipse.osgi.framework.internal.core; + +import java.io.IOException; +import java.net.URL; +import java.util.Enumeration; +import org.eclipse.osgi.framework.adaptor.*; +import org.eclipse.osgi.framework.debug.Debug; +import org.eclipse.osgi.framework.log.FrameworkLogEntry; +import org.eclipse.osgi.internal.loader.BundleLoader; +import org.eclipse.osgi.internal.loader.BundleLoaderProxy; +import org.eclipse.osgi.service.resolver.BundleDescription; +import org.eclipse.osgi.service.resolver.ResolverHookException; +import org.eclipse.osgi.util.NLS; +import org.osgi.framework.*; + +public class BundleHost extends AbstractBundle { + public static final int LAZY_TRIGGER = 0x40000000; + /** + * The BundleLoader proxy; a lightweight object that acts as a proxy + * to the BundleLoader and allows lazy creation of the BundleLoader object + */ + private BundleLoaderProxy proxy; + + /** The BundleContext that represents this Bundle and all of its fragments */ + protected BundleContextImpl context; + + /** The List of BundleFragments */ + protected BundleFragment[] fragments; + + public BundleHost(BundleData bundledata, Framework framework) { + super(bundledata, framework); + context = null; + fragments = null; + } + + /** + * Load the bundle. + */ + protected void load() { + if (Debug.DEBUG_GENERAL) { + if ((state & (INSTALLED)) == 0) { + Debug.println("Bundle.load called when state != INSTALLED: " + this); //$NON-NLS-1$ + Debug.printStackTrace(new Exception("Stack trace")); //$NON-NLS-1$ + } + if (proxy != null) { + Debug.println("Bundle.load called when proxy != null: " + this); //$NON-NLS-1$ + Debug.printStackTrace(new Exception("Stack trace")); //$NON-NLS-1$ + } + } + + if (framework.isActive()) { + SecurityManager sm = System.getSecurityManager(); + if (sm != null && framework.securityAdmin != null) { + domain = framework.securityAdmin.createProtectionDomain(this); + } + } + proxy = null; + } + + /** + * Reload from a new bundle. + * This method must be called while holding the bundles lock. + * + * @param newBundle Dummy Bundle which contains new data. + * @return true if an exported package is "in use". i.e. it has been imported by a bundle + */ + protected boolean reload(AbstractBundle newBundle) { + if (Debug.DEBUG_GENERAL) { + if ((state & (INSTALLED | RESOLVED)) == 0) { + Debug.println("Bundle.reload called when state != INSTALLED | RESOLVED: " + this); //$NON-NLS-1$ + Debug.printStackTrace(new Exception("Stack trace")); //$NON-NLS-1$ + } + } + + boolean exporting = false; + + if (framework.isActive()) { + if (state == RESOLVED) { + BundleLoaderProxy curProxy = getLoaderProxy(); + exporting = curProxy.inUse(); + if (exporting) { + // make sure the BundleLoader is created. + curProxy.getBundleLoader().createClassLoader(); + } else + BundleLoader.closeBundleLoader(proxy); + state = INSTALLED; + proxy = null; + fragments = null; + } + + } else { + /* close the outgoing jarfile */ + try { + this.bundledata.close(); + } catch (IOException e) { + // Do Nothing + } + } + this.bundledata = newBundle.bundledata; + this.bundledata.setBundle(this); + // create a new domain for the bundle because its signers/symbolic-name may have changed + if (framework.isActive() && System.getSecurityManager() != null && framework.securityAdmin != null) + domain = framework.securityAdmin.createProtectionDomain(this); + return (exporting); + } + + /** + * Refresh the bundle. This is called by Framework.refreshPackages. + * This method must be called while holding the bundles lock. + */ + protected void refresh() { + if (Debug.DEBUG_GENERAL) { + if ((state & (UNINSTALLED | INSTALLED | RESOLVED)) == 0) { + Debug.println("Bundle.reload called when state != UNINSTALLED | INSTALLED | RESOLVED: " + this); //$NON-NLS-1$ + Debug.printStackTrace(new Exception("Stack trace")); //$NON-NLS-1$ + } + } + if (state == RESOLVED) { + BundleLoader.closeBundleLoader(proxy); + proxy = null; + fragments = null; + state = INSTALLED; + // Do not publish UNRESOLVED event here. This is done by caller + // to resolve if appropriate. + } + manifestLocalization = null; + } + + /** + * Unload the bundle. + * This method must be called while holding the bundles lock. + * + * @return true if an exported package is "in use". i.e. it has been imported by a bundle + */ + protected boolean unload() { + if (Debug.DEBUG_GENERAL) { + if ((state & (UNINSTALLED | INSTALLED | RESOLVED)) == 0) { + Debug.println("Bundle.unload called when state != UNINSTALLED | INSTALLED | RESOLVED: " + this); //$NON-NLS-1$ + Debug.printStackTrace(new Exception("Stack trace")); //$NON-NLS-1$ + } + } + + boolean exporting = false; + + if (framework.isActive()) { + if (state == RESOLVED) { + BundleLoaderProxy curProxy = getLoaderProxy(); + exporting = curProxy.inUse(); + if (exporting) { + // make sure the BundleLoader is created. + curProxy.getBundleLoader().createClassLoader(); + } else + BundleLoader.closeBundleLoader(proxy); + + state = INSTALLED; + proxy = null; + fragments = null; + domain = null; + } + } + if (!exporting) { + try { + this.bundledata.close(); + } catch (IOException e) { // Do Nothing. + } + } + + return (exporting); + } + + private BundleLoader checkLoader() { + checkValid(); + + // check to see if the bundle is resolved + if (!isResolved()) { + if (!framework.packageAdmin.resolveBundles(new Bundle[] {this})) { + return null; + } + } + if (Debug.DEBUG_GENERAL) { + if ((state & (STARTING | ACTIVE | STOPPING | RESOLVED)) == 0) { + Debug.println("Bundle.checkLoader() called when state != STARTING | ACTIVE | STOPPING | RESOLVED: " + this); //$NON-NLS-1$ + Debug.printStackTrace(new Exception("Stack trace")); //$NON-NLS-1$ + } + } + + BundleLoader loader = getBundleLoader(); + if (loader == null) { + if (Debug.DEBUG_GENERAL) { + Debug.println("Bundle.checkLoader() called when loader == null: " + this); //$NON-NLS-1$ + Debug.printStackTrace(new Exception("Stack trace")); //$NON-NLS-1$ + } + return null; + } + return loader; + } + + /** + * This method loads a class from the bundle. + * + * @param name the name of the desired Class. + * @param checkPermission indicates whether a permission check should be done. + * @return the resulting Class + * @exception java.lang.ClassNotFoundException if the class definition was not found. + */ + protected Class<?> loadClass(String name, boolean checkPermission) throws ClassNotFoundException { + if (checkPermission) { + try { + framework.checkAdminPermission(this, AdminPermission.CLASS); + } catch (SecurityException e) { + throw new ClassNotFoundException(name, e); + } + } + BundleLoader loader = checkLoader(); + if (loader == null) + throw new ClassNotFoundException(NLS.bind(Msg.BUNDLE_CNFE_NOT_RESOLVED, name, getBundleData().getLocation())); + try { + return (loader.loadClass(name)); + } catch (ClassNotFoundException e) { + // this is to support backward compatibility in eclipse + // we always attempted to start a bundle even if the class was not found + if (!(e instanceof StatusException) && (bundledata.getStatus() & Constants.BUNDLE_LAZY_START) != 0 && !testStateChanging(Thread.currentThread())) + try { + // only start the bundle if this is a simple CNFE + loader.setLazyTrigger(); + } catch (BundleException be) { + framework.adaptor.getFrameworkLog().log(new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.WARNING, 0, be.getMessage(), 0, be, null)); + } + throw e; + } + } + + /** + * Find the specified resource in this bundle. + * + * This bundle's class loader is called to search for the named resource. + * If this bundle's state is <tt>INSTALLED</tt>, then only this bundle will + * be searched for the specified resource. Imported packages cannot be searched + * when a bundle has not been resolved. + * + * @param name The name of the resource. + * See <tt>java.lang.ClassLoader.getResource</tt> for a description of + * the format of a resource name. + * @return a URL to the named resource, or <tt>null</tt> if the resource could + * not be found or if the caller does not have + * the <tt>AdminPermission</tt>, and the Java Runtime Environment supports permissions. + * + * @exception java.lang.IllegalStateException If this bundle has been uninstalled. + */ + public URL getResource(String name) { + BundleLoader loader = null; + try { + framework.checkAdminPermission(this, AdminPermission.RESOURCE); + } catch (SecurityException ee) { + return null; + } + loader = checkLoader(); + if (loader == null) { + Enumeration<URL> result = bundledata.findLocalResources(name); + if (result != null && result.hasMoreElements()) + return result.nextElement(); + return null; + } + return loader.findResource(name); + } + + public Enumeration<URL> getResources(String name) throws IOException { + BundleLoader loader = null; + try { + framework.checkAdminPermission(this, AdminPermission.RESOURCE); + } catch (SecurityException ee) { + return null; + } + Enumeration<URL> result; + loader = checkLoader(); + if (loader == null) + result = bundledata.findLocalResources(name); + else + result = loader.getResources(name); + if (result != null && result.hasMoreElements()) + return result; + return null; + } + + /** + * Internal worker to start a bundle. + * + * @param options the start options + */ + protected void startWorker(int options) throws BundleException { + if ((options & START_TRANSIENT) == 0) { + setStatus(Constants.BUNDLE_STARTED, true); + setStatus(Constants.BUNDLE_ACTIVATION_POLICY, (options & START_ACTIVATION_POLICY) != 0); + if (Debug.MONITOR_ACTIVATION) + new Exception("A persistent start has been called on bundle: " + getBundleData()).printStackTrace(); //$NON-NLS-1$ + } + if (!framework.active || (state & ACTIVE) != 0) + return; + if (getInternalStartLevel() > framework.startLevelManager.getStartLevel()) { + if ((options & LAZY_TRIGGER) == 0 && (options & START_TRANSIENT) != 0) { + // throw exception if this is a transient start + String msg = NLS.bind(Msg.BUNDLE_TRANSIENT_START_ERROR, this); + // Use a StatusException to indicate to the lazy starter that this should result in a warning + throw new BundleException(msg, BundleException.INVALID_OPERATION, new BundleStatusException(msg, StatusException.CODE_WARNING, this)); + } + return; + } + + if (state == INSTALLED) { + try { + if (!framework.packageAdmin.resolveBundles(new Bundle[] {this}, true)) + throw getResolutionFailureException(); + } catch (IllegalStateException e) { + // Can happen if the resolver detects a nested resolve process + throw new BundleException("Unexpected resolution exception.", BundleException.RESOLVE_ERROR, e); //$NON-NLS-1$ + } catch (ResolverHookException e) { + throw new BundleException("Unexpected resolution exception.", BundleException.REJECTED_BY_HOOK, e.getCause()); //$NON-NLS-1$ + } + + } + + if ((options & START_ACTIVATION_POLICY) != 0 && (bundledata.getStatus() & Constants.BUNDLE_LAZY_START) != 0) { + // the bundle must use the activation policy here. + if ((state & RESOLVED) != 0) { + // now we must publish the LAZY_ACTIVATION event and return + state = STARTING; + // release the state change lock before sending lazy activation event (bug 258659) + completeStateChange(); + framework.publishBundleEvent(BundleEvent.LAZY_ACTIVATION, this); + } + return; + } + + if (Debug.DEBUG_GENERAL) { + Debug.println("Bundle: Active sl = " + framework.startLevelManager.getStartLevel() + "; Bundle " + getBundleId() + " sl = " + getInternalStartLevel()); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + + if ((options & LAZY_TRIGGER) != 0) { + if ((state & RESOLVED) != 0) { + // Should publish the lazy activation event here before the starting event + // This can happen if another bundle in the same start-level causes a class load from the lazy start bundle. + state = STARTING; + // release the state change lock before sending lazy activation event (bug 258659) + completeStateChange(); + framework.publishBundleEvent(BundleEvent.LAZY_ACTIVATION, this); + beginStateChange(); + if (state != STARTING) { + // while firing the LAZY_ACTIVATION event some one else caused the bundle to transition + // out of STARTING. This could have happened because some listener called start on the bundle + // or another class load could have caused the start trigger to get fired again. + return; + } + } + } + state = STARTING; + framework.publishBundleEvent(BundleEvent.STARTING, this); + context = getContext(); + //STARTUP TIMING Start here + long start = 0; + + BundleWatcher bundleStats = framework.adaptor.getBundleWatcher(); + if (bundleStats != null) + bundleStats.watchBundle(this, BundleWatcher.START_ACTIVATION); + if (Debug.DEBUG_BUNDLE_TIME) { + start = System.currentTimeMillis(); + System.out.println("Starting " + getSymbolicName()); //$NON-NLS-1$ + } + + try { + context.start(); + startHook(); + if (framework.active) { + state = ACTIVE; + + if (Debug.DEBUG_GENERAL) { + Debug.println("->started " + this); //$NON-NLS-1$ + } + // release the state change lock before sending lazy activation event (bug 258659) + completeStateChange(); + framework.publishBundleEvent(BundleEvent.STARTED, this); + } + + } catch (BundleException e) { + // we must fire the stopping event + state = STOPPING; + framework.publishBundleEvent(BundleEvent.STOPPING, this); + + stopHook(); + context.close(); + context = null; + + state = RESOLVED; + // if this is a lazy start bundle that fails to start then + // we must fire the stopped event + framework.publishBundleEvent(BundleEvent.STOPPED, this); + throw e; + } finally { + if (bundleStats != null) + bundleStats.watchBundle(this, BundleWatcher.END_ACTIVATION); + if (Debug.DEBUG_BUNDLE_TIME) + System.out.println("End starting " + getSymbolicName() + " " + (System.currentTimeMillis() - start)); //$NON-NLS-1$ //$NON-NLS-2$ + + } + + if (state == UNINSTALLED) { + context.close(); + context = null; + throw new BundleException(NLS.bind(Msg.BUNDLE_UNINSTALLED_EXCEPTION, getBundleData().getLocation()), BundleException.STATECHANGE_ERROR); + } + } + + /** + * @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 (getInternalStartLevel() > framework.startLevelManager.getStartLevel()) + return false; + int status = bundledata.getStatus(); + // Return false if the bundle is not persistently marked for start + if ((status & Constants.BUNDLE_STARTED) == 0) + return false; + if ((status & Constants.BUNDLE_ACTIVATION_POLICY) == 0 || (status & Constants.BUNDLE_LAZY_START) == 0 || isLazyTriggerSet()) + return true; + if (!isResolved()) { + if (framework.getAdaptor().getState().isResolved() || !framework.packageAdmin.resolveBundles(new Bundle[] {this})) + // should never transition from UNRESOLVED -> STARTING + return false; + } + // now we can publish the LAZY_ACTIVATION event + state = STARTING; + // release the state change lock before sending lazy activation event (bug 258659) + completeStateChange(); + framework.publishBundleEvent(BundleEvent.LAZY_ACTIVATION, this); + return false; + } + + private synchronized boolean isLazyTriggerSet() { + if (proxy == null) + return false; + BundleLoader loader = proxy.getBasicBundleLoader(); + return loader != null ? loader.isLazyTriggerSet() : false; + } + + /** + * Create a BundleContext for this bundle. + * + * @return BundleContext for this bundle. + */ + protected BundleContextImpl createContext() { + return (new BundleContextImpl(this)); + } + + /** + * Return the current context for this bundle. + * + * @return BundleContext for this bundle. + */ + protected synchronized BundleContextImpl getContext() { + if (context == null) { + // only create the context if we are starting, active or stopping + // this is so that SCR can get the context for lazy-start bundles + if ((state & (STARTING | ACTIVE | STOPPING)) != 0) + context = createContext(); + } + return (context); + } + + /** + * Internal worker to stop a bundle. + * + * @param options the stop options + */ + protected void stopWorker(int options) throws BundleException { + if ((options & STOP_TRANSIENT) == 0) { + setStatus(Constants.BUNDLE_STARTED, false); + setStatus(Constants.BUNDLE_ACTIVATION_POLICY, false); + if (Debug.MONITOR_ACTIVATION) + new Exception("A persistent start has been called on bundle: " + getBundleData()).printStackTrace(); //$NON-NLS-1$ + } + if (framework.active) { + if ((state & (STOPPING | RESOLVED | INSTALLED)) != 0) { + return; + } + + BundleWatcher bundleStats = framework.adaptor.getBundleWatcher(); + if (bundleStats != null) + bundleStats.watchBundle(this, BundleWatcher.START_DEACTIVATION); + + state = STOPPING; + framework.publishBundleEvent(BundleEvent.STOPPING, this); + try { + // context may be null if a lazy-start bundle is STARTING + if (context != null) + context.stop(); + } finally { + stopHook(); + if (context != null) { + context.close(); + context = null; + } + + checkValid(); + + state = RESOLVED; + + if (Debug.DEBUG_GENERAL) { + Debug.println("->stopped " + this); //$NON-NLS-1$ + } + + framework.publishBundleEvent(BundleEvent.STOPPED, this); + if (bundleStats != null) + bundleStats.watchBundle(this, BundleWatcher.END_DEACTIVATION); + + } + } + } + + /** + * @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 + * services. + * + * <p>The list is valid at the time + * of the call to this method, but the framework is a very dynamic + * environment and services can be modified or unregistered at anytime. + * + * @return An array of {@link ServiceReference} or <code>null</code>. + * @exception java.lang.IllegalStateException If the + * bundle has been uninstalled. + * @see ServiceRegistration + * @see ServiceReference + */ + public ServiceReference<?>[] getRegisteredServices() { + checkValid(); + + if (context == null) { + return null; + } + + return context.getFramework().getServiceRegistry().getRegisteredServices(context); + } + + /** + * Provides a list of {@link ServiceReference}s for the + * services this bundle is using, + * or <code>null</code> if the bundle is not using any services. + * A bundle is considered to be using a service if the bundle's + * use count for the service is greater than zero. + * + * <p>The list is valid at the time + * of the call to this method, but the framework is a very dynamic + * environment and services can be modified or unregistered at anytime. + * + * @return An array of {@link ServiceReference} or <code>null</code>. + * @exception java.lang.IllegalStateException If the + * bundle has been uninstalled. + * @see ServiceReference + */ + public ServiceReference<?>[] getServicesInUse() { + checkValid(); + + if (context == null) { + return null; + } + + return context.getFramework().getServiceRegistry().getServicesInUse(context); + } + + public BundleFragment[] getFragments() { + synchronized (framework.bundles) { + if (fragments == null) + return null; + BundleFragment[] result = new BundleFragment[fragments.length]; + System.arraycopy(fragments, 0, result, 0, result.length); + return result; + } + } + + /** + * Attaches a fragment to this BundleHost. Fragments must be attached to + * the host by ID order. If the ClassLoader of the host is already created + * then the fragment must be attached to the host ClassLoader + * @param fragment The fragment bundle to attach + * return true if the fragment successfully attached; false if the fragment + * could not be logically inserted at the end of the fragment chain. + */ + protected void attachFragment(BundleFragment fragment) throws BundleException { + // do not force the creation of the bundle loader here + BundleLoader loader = getLoaderProxy().getBasicBundleLoader(); + // If the Host ClassLoader exists then we must attach + // the fragment to the ClassLoader. + if (loader != null) + loader.attachFragment(fragment); + + if (fragments == null) { + fragments = new BundleFragment[] {fragment}; + } else { + boolean inserted = false; + // We must keep our fragments ordered by bundle ID; or + // install order. + BundleFragment[] newFragments = new BundleFragment[fragments.length + 1]; + for (int i = 0; i < fragments.length; i++) { + if (fragment == fragments[i]) + return; // this fragment is already attached + // need to flush the other attached fragment manifest caches in case the attaching fragment provides translations (bug 339211) + fragments[i].manifestLocalization = null; + if (!inserted && fragment.getBundleId() < fragments[i].getBundleId()) { + // if the loader has already been created + // then we cannot attach a fragment into the middle + // of the fragment chain. + if (loader != null) { + throw new BundleException(NLS.bind(Msg.BUNDLE_LOADER_ATTACHMENT_ERROR, fragments[i].getSymbolicName(), getSymbolicName()), BundleException.INVALID_OPERATION); + } + newFragments[i] = fragment; + inserted = true; + } + newFragments[inserted ? i + 1 : i] = fragments[i]; + } + if (!inserted) + newFragments[newFragments.length - 1] = fragment; + fragments = newFragments; + } + // need to flush the manifest cache in case the attaching fragment provides translations + manifestLocalization = null; + } + + protected BundleLoader getBundleLoader() { + BundleLoaderProxy curProxy = getLoaderProxy(); + return curProxy == null ? null : curProxy.getBundleLoader(); + } + + public synchronized BundleLoaderProxy getLoaderProxy() { + if (proxy != null) + return proxy; + BundleDescription bundleDescription = getBundleDescription(); + if (bundleDescription == null) + return null; + proxy = new BundleLoaderProxy(this, bundleDescription); + // Note that BundleLoaderProxy is a BundleReference + // this is necessary to ensure the resolver can continue + // to provide BundleRevision objects to resolver hooks. + bundleDescription.setUserObject(proxy); + return proxy; + } + + /** + * Gets the class loader for the host bundle. This may end up + * creating the bundle class loader if it was not already created. + * A null value may be returned if the bundle is not resolved. + * @return the bundle class loader or null if the bundle is not resolved. + */ + public ClassLoader getClassLoader() { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) + sm.checkPermission(new RuntimePermission("getClassLoader")); //$NON-NLS-1$ + BundleLoaderProxy curProxy = getLoaderProxy(); + BundleLoader loader = curProxy == null ? null : curProxy.getBundleLoader(); + BundleClassLoader bcl = loader == null ? null : loader.createClassLoader(); + return (bcl instanceof ClassLoader) ? (ClassLoader) bcl : null; + } + +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/BundleRepository.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/BundleRepository.java new file mode 100644 index 000000000..f10edf828 --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/BundleRepository.java @@ -0,0 +1,196 @@ +/******************************************************************************* + * Copyright (c) 2003, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + * Rob Harrop - SpringSource Inc. (bug 247521) + *******************************************************************************/ + +package org.eclipse.osgi.framework.internal.core; + +import java.util.*; +import org.eclipse.osgi.framework.util.KeyedHashSet; +import org.osgi.framework.Version; + +/** + * The BundleRepository holds all installed Bundle object for the + * Framework. The BundleRepository is also used to mark and unmark + * bundle dependancies. + * + * <p> + * This class is internally synchronized and supports client locking. Clients + * wishing to perform threadsafe composite operations on instances of this + * class can synchronize on the instance itself when doing these operations. + */ +public final class BundleRepository { + /** bundles by install order */ + private List<AbstractBundle> bundlesByInstallOrder; + + /** bundles keyed by bundle Id */ + private KeyedHashSet bundlesById; + + /** bundles keyed by SymbolicName */ + private Map<String, AbstractBundle[]> bundlesBySymbolicName; + + public BundleRepository(int initialCapacity) { + synchronized (this) { + bundlesByInstallOrder = new ArrayList<AbstractBundle>(initialCapacity); + bundlesById = new KeyedHashSet(initialCapacity, true); + bundlesBySymbolicName = new HashMap<String, AbstractBundle[]>(initialCapacity); + } + } + + /** + * Gets a list of bundles ordered by install order. + * @return List of bundles by install order. + */ + public synchronized List<AbstractBundle> getBundles() { + return bundlesByInstallOrder; + } + + /** + * Gets a bundle by its bundle Id. + * @param bundleId + * @return a bundle with the specified id or null if one does not exist + */ + public synchronized AbstractBundle getBundle(long bundleId) { + Long key = new Long(bundleId); + return (AbstractBundle) bundlesById.getByKey(key); + } + + public synchronized AbstractBundle[] getBundles(String symbolicName) { + if (Constants.SYSTEM_BUNDLE_SYMBOLICNAME.equals(symbolicName)) + symbolicName = Constants.getInternalSymbolicName(); + return bundlesBySymbolicName.get(symbolicName); + } + + @SuppressWarnings("unchecked") + public synchronized List<AbstractBundle> getBundles(String symbolicName, Version version) { + AbstractBundle[] bundles = getBundles(symbolicName); + List<AbstractBundle> result = null; + if (bundles != null) { + if (bundles.length > 0) { + for (int i = 0; i < bundles.length; i++) { + if (bundles[i].getVersion().equals(version)) { + if (result == null) + result = new ArrayList<AbstractBundle>(); + result.add(bundles[i]); + } + } + } + } + return result == null ? Collections.EMPTY_LIST : result; + } + + public synchronized void add(AbstractBundle bundle) { + bundlesByInstallOrder.add(bundle); + bundlesById.add(bundle); + addSymbolicName(bundle); + } + + private void addSymbolicName(AbstractBundle bundle) { + String symbolicName = bundle.getSymbolicName(); + if (symbolicName == null) + return; + AbstractBundle[] bundles = bundlesBySymbolicName.get(symbolicName); + if (bundles == null) { + // making the initial capacity on this 1 since it + // should be rare that multiple version exist + bundles = new AbstractBundle[1]; + bundles[0] = bundle; + bundlesBySymbolicName.put(symbolicName, bundles); + return; + } + + List<AbstractBundle> list = new ArrayList<AbstractBundle>(bundles.length + 1); + // find place to insert the bundle + Version newVersion = bundle.getVersion(); + boolean added = false; + for (int i = 0; i < bundles.length; i++) { + AbstractBundle oldBundle = bundles[i]; + Version oldVersion = oldBundle.getVersion(); + if (!added && newVersion.compareTo(oldVersion) >= 0) { + added = true; + list.add(bundle); + } + list.add(oldBundle); + } + if (!added) { + list.add(bundle); + } + + bundles = new AbstractBundle[list.size()]; + list.toArray(bundles); + bundlesBySymbolicName.put(symbolicName, bundles); + } + + public synchronized boolean remove(AbstractBundle bundle) { + // remove by bundle ID + boolean found = bundlesById.remove(bundle); + if (!found) + return false; + + // remove by install order + bundlesByInstallOrder.remove(bundle); + // remove by symbolic name + String symbolicName = bundle.getSymbolicName(); + if (symbolicName == null) + return true; + removeSymbolicName(symbolicName, bundle); + return true; + } + + private void removeSymbolicName(String symbolicName, AbstractBundle bundle) { + AbstractBundle[] bundles = bundlesBySymbolicName.get(symbolicName); + if (bundles == null) + return; + + // found some bundles with the global name. + // remove all references to the specified bundle. + int numRemoved = 0; + for (int i = 0; i < bundles.length; i++) { + if (bundle == bundles[i]) { + numRemoved++; + bundles[i] = null; + } + } + if (numRemoved > 0) { + if (bundles.length - numRemoved <= 0) { + // no bundles left in the array remove the array from the hash + bundlesBySymbolicName.remove(symbolicName); + } else { + // create a new array with the null entries removed. + AbstractBundle[] newBundles = new AbstractBundle[bundles.length - numRemoved]; + int indexCnt = 0; + for (int i = 0; i < bundles.length; i++) { + if (bundles[i] != null) { + newBundles[indexCnt] = bundles[i]; + indexCnt++; + } + } + bundlesBySymbolicName.put(symbolicName, newBundles); + } + } + } + + public synchronized void update(String oldSymbolicName, AbstractBundle bundle) { + if (oldSymbolicName != null) { + if (!oldSymbolicName.equals(bundle.getSymbolicName())) { + removeSymbolicName(oldSymbolicName, bundle); + addSymbolicName(bundle); + } + } else { + addSymbolicName(bundle); + } + } + + public synchronized void removeAllBundles() { + bundlesByInstallOrder.clear(); + bundlesById = new KeyedHashSet(); + bundlesBySymbolicName.clear(); + } +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/BundleResourceHandler.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/BundleResourceHandler.java new file mode 100644 index 000000000..68d28e9ca --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/BundleResourceHandler.java @@ -0,0 +1,304 @@ +/******************************************************************************* + * Copyright (c) 2004, 2010 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.framework.internal.core; + +import java.io.IOException; +import java.net.*; +import org.eclipse.osgi.baseadaptor.BaseAdaptor; +import org.eclipse.osgi.baseadaptor.bundlefile.BundleEntry; +import org.eclipse.osgi.baseadaptor.loader.BaseClassLoader; +import org.eclipse.osgi.framework.adaptor.FrameworkAdaptor; +import org.eclipse.osgi.framework.internal.protocol.ProtocolActivator; +import org.eclipse.osgi.internal.baseadaptor.AdaptorMsg; +import org.eclipse.osgi.internal.loader.BundleLoader; +import org.eclipse.osgi.util.NLS; +import org.osgi.framework.*; + +/** + * URLStreamHandler the bundleentry and bundleresource protocols. + */ + +public abstract class BundleResourceHandler extends URLStreamHandler implements ProtocolActivator { + public static final String SECURITY_CHECKED = "SECURITY_CHECKED"; //$NON-NLS-1$ + public static final String SECURITY_UNCHECKED = "SECURITY_UNCHECKED"; //$NON-NLS-1$ + public static final String BID_FWKID_SEPARATOR = ".fwk"; //$NON-NLS-1$ + private BaseAdaptor adaptor; + protected BundleEntry bundleEntry; + + /** + * Constructor for a bundle protocol resource URLStreamHandler. + */ + public BundleResourceHandler() { + this(null, null); + } + + public BundleResourceHandler(BundleEntry bundleEntry, BaseAdaptor adaptor) { + this.bundleEntry = bundleEntry; + this.adaptor = adaptor; + } + + public void start(BundleContext context, FrameworkAdaptor baseAdaptor) { + this.adaptor = (BaseAdaptor) baseAdaptor; + } + + /** + * Parse reference URL. + */ + protected void parseURL(URL url, String str, int start, int end) { + if (end < start) + return; + if (url.getPath() != null) + // A call to a URL constructor has been made that uses an authorized URL as its context. + // Null out bundleEntry because it will not be valid for the new path + bundleEntry = null; + String spec = ""; //$NON-NLS-1$ + if (start < end) + spec = str.substring(start, end); + end -= start; + //Default is to use path and bundleId from context + String path = url.getPath(); + String host = url.getHost(); + int resIndex = url.getPort(); + if (resIndex < 0) // -1 indicates port was not set; must default to 0 + resIndex = 0; + int pathIdx = 0; + if (spec.startsWith("//")) { //$NON-NLS-1$ + int bundleIdIdx = 2; + pathIdx = spec.indexOf('/', bundleIdIdx); + if (pathIdx == -1) { + pathIdx = end; + // Use default + path = ""; //$NON-NLS-1$ + } + int bundleIdEnd = spec.indexOf(':', bundleIdIdx); + if (bundleIdEnd > pathIdx || bundleIdEnd == -1) + bundleIdEnd = pathIdx; + if (bundleIdEnd < pathIdx - 1) + try { + resIndex = Integer.parseInt(spec.substring(bundleIdEnd + 1, pathIdx)); + } catch (NumberFormatException e) { + // do nothing; results in resIndex == 0 + } + host = spec.substring(bundleIdIdx, bundleIdEnd); + } + if (pathIdx < end && spec.charAt(pathIdx) == '/') + path = spec.substring(pathIdx, end); + else if (end > pathIdx) { + if (path == null || path.equals("")) //$NON-NLS-1$ + path = "/"; //$NON-NLS-1$ + int last = path.lastIndexOf('/') + 1; + if (last == 0) + path = spec.substring(pathIdx, end); + else + path = path.substring(0, last) + spec.substring(pathIdx, end); + } + if (path == null) + path = ""; //$NON-NLS-1$ + //modify path if there's any relative references + // see RFC2396 Section 5.2 + // Note: For ".." references above the root the approach taken is removing them from the resolved path + if (path.endsWith("/.") || path.endsWith("/..")) //$NON-NLS-1$ //$NON-NLS-2$ + path = path + '/'; + int dotIndex; + while ((dotIndex = path.indexOf("/./")) >= 0) //$NON-NLS-1$ + path = path.substring(0, dotIndex + 1) + path.substring(dotIndex + 3); + while ((dotIndex = path.indexOf("/../")) >= 0) { //$NON-NLS-1$ + if (dotIndex != 0) + path = path.substring(0, path.lastIndexOf('/', dotIndex - 1)) + path.substring(dotIndex + 3); + else + path = path.substring(dotIndex + 3); + } + while ((dotIndex = path.indexOf("//")) >= 0) //$NON-NLS-1$ + path = path.substring(0, dotIndex + 1) + path.substring(dotIndex + 2); + + // Check the permission of the caller to see if they + // are allowed access to the resource. + String authorized = SECURITY_UNCHECKED; + long bundleId = getBundleID(host); + Bundle bundle = adaptor == null ? null : adaptor.getBundle(bundleId); + if (checkAuthorization(bundle)) + authorized = SECURITY_CHECKED; + // Always force the use of the hash from the adaptor + if (adaptor != null) + host = Long.toString(bundleId) + BID_FWKID_SEPARATOR + Integer.toString(adaptor.hashCode()); + // Setting the authority portion of the URL to SECURITY_ATHORIZED + // ensures that this URL was created by using this parseURL + // method. The openConnection method will only open URLs + // that have the authority set to this. + setURL(url, url.getProtocol(), host, resIndex, authorized, null, path, null, url.getRef()); + } + + /** + * Establishes a connection to the resource specified by <code>URL</code>. + * Since different protocols may have unique ways of connecting, it must be + * overridden by the subclass. + * + * @return java.net.URLConnection + * @param url java.net.URL + * + * @exception IOException thrown if an IO error occurs during connection establishment + */ + protected URLConnection openConnection(URL url) throws IOException { + if (bundleEntry != null) // if the bundleEntry is not null then return quick + return (new BundleURLConnection(url, bundleEntry)); + + String host = url.getHost(); + if (host == null) { + throw new IOException(NLS.bind(AdaptorMsg.URL_NO_BUNDLE_ID, url.toExternalForm())); + } + AbstractBundle bundle = null; + long bundleID; + try { + bundleID = getBundleID(host); + } catch (NumberFormatException nfe) { + throw (MalformedURLException) new MalformedURLException(NLS.bind(AdaptorMsg.URL_INVALID_BUNDLE_ID, host)).initCause(nfe); + } + bundle = adaptor == null ? null : (AbstractBundle) adaptor.getBundle(bundleID); + if (bundle == null) + throw new IOException(NLS.bind(AdaptorMsg.URL_NO_BUNDLE_FOUND, url.toExternalForm())); + // check to make sure that this URL was created using the + // parseURL method. This ensures the security check was done + // at URL construction. + if (!url.getAuthority().equals(SECURITY_CHECKED)) { + // No admin security check was made better check now. + checkAuthorization(bundle); + } + return (new BundleURLConnection(url, findBundleEntry(url, bundle))); + } + + /** + * Finds the bundle entry for this protocal. This is handled + * differently for Bundle.gerResource() and Bundle.getEntry() + * because getResource uses the bundle classloader and getEntry + * only used the base bundle file. + * @param url The URL to find the BundleEntry for. + * @return the bundle entry + */ + abstract protected BundleEntry findBundleEntry(URL url, AbstractBundle bundle) throws IOException; + + /** + * Converts a bundle URL to a String. + * + * @param url the URL. + * @return a string representation of the URL. + */ + protected String toExternalForm(URL url) { + StringBuffer result = new StringBuffer(url.getProtocol()); + result.append("://"); //$NON-NLS-1$ + + String host = url.getHost(); + if ((host != null) && (host.length() > 0)) + result.append(host); + int index = url.getPort(); + if (index > 0) + result.append(':').append(index); + + String path = url.getPath(); + if (path != null) { + if ((path.length() > 0) && (path.charAt(0) != '/')) /* if name doesn't have a leading slash */ + { + result.append("/"); //$NON-NLS-1$ + } + + result.append(path); + } + String ref = url.getRef(); + if (ref != null && ref.length() > 0) + result.append('#').append(ref); + + return (result.toString()); + } + + protected int hashCode(URL url) { + int hash = 0; + String protocol = url.getProtocol(); + if (protocol != null) + hash += protocol.hashCode(); + + String host = url.getHost(); + if (host != null) + hash += host.hashCode(); + + hash += url.getPort(); + + String path = url.getPath(); + if (path != null) + hash += path.hashCode(); + + if (adaptor != null) + hash += adaptor.hashCode(); + return hash; + } + + protected boolean equals(URL url1, URL url2) { + return sameFile(url1, url2); + } + + protected synchronized InetAddress getHostAddress(URL url) { + return null; + } + + protected boolean hostsEqual(URL url1, URL url2) { + String host1 = url1.getHost(); + String host2 = url2.getHost(); + if (host1 != null && host2 != null) + return host1.equalsIgnoreCase(host2); + return (host1 == null && host2 == null); + } + + protected boolean sameFile(URL url1, URL url2) { + // do a hashcode test to allow each handler to check the adaptor first + if (url1.hashCode() != url2.hashCode()) + return false; + String p1 = url1.getProtocol(); + String p2 = url2.getProtocol(); + if (!((p1 == p2) || (p1 != null && p1.equalsIgnoreCase(p2)))) + return false; + + if (!hostsEqual(url1, url2)) + return false; + + if (url1.getPort() != url2.getPort()) + return false; + + String path1 = url1.getPath(); + String path2 = url2.getPath(); + if (!((path1 == path2) || (path1 != null && path1.equals(path2)))) + return false; + + return true; + // note that the authority is not checked here because it can be different for two + // URLs depending on how they were constructed. + } + + protected boolean checkAuthorization(Bundle bundle) { + SecurityManager sm = System.getSecurityManager(); + if (sm == null) + return true; + if (bundle == null) + return false; + sm.checkPermission(new AdminPermission(bundle, AdminPermission.RESOURCE)); + return true; + } + + protected static BaseClassLoader getBundleClassLoader(AbstractBundle bundle) { + BundleLoader loader = bundle.getBundleLoader(); + if (loader == null) + return null; + return (BaseClassLoader) loader.createClassLoader(); + } + + private long getBundleID(String host) { + int dotIndex = host.indexOf('.'); + return (dotIndex >= 0 && dotIndex < host.length() - 1) ? Long.parseLong(host.substring(0, dotIndex)) : Long.parseLong(host); + } +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/BundleSource.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/BundleSource.java new file mode 100644 index 000000000..a27e1ee9a --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/BundleSource.java @@ -0,0 +1,45 @@ +/******************************************************************************* + * Copyright (c) 2003, 2010 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.framework.internal.core; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URLConnection; + +/** + * BundleSource class to wrap in InputStream. + * + * <p>This class implements a URLConnection which + * wraps an InputStream. + */ +public class BundleSource extends URLConnection { + private InputStream in; + + protected BundleSource(InputStream in) { + super(null); + this.in = in; + } + + /** + * @throws IOException + */ + public void connect() throws IOException { + connected = true; + } + + /** + * @throws IOException + */ + public InputStream getInputStream() throws IOException { + return (in); + } +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/BundleURLConnection.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/BundleURLConnection.java new file mode 100644 index 000000000..c0163c4cc --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/BundleURLConnection.java @@ -0,0 +1,131 @@ +/******************************************************************************* + * Copyright (c) 2004, 2006 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.framework.internal.core; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLConnection; +import org.eclipse.osgi.baseadaptor.bundlefile.BundleEntry; +import org.eclipse.osgi.internal.baseadaptor.AdaptorMsg; +import org.eclipse.osgi.util.NLS; + +/** + * URLConnection for BundleClassLoader resources. + */ + +public class BundleURLConnection extends URLConnection { + /** BundleEntry that the URL is associated. */ + protected final BundleEntry bundleEntry; + + /** InputStream for this URLConnection. */ + protected InputStream in; + + /** content type for this URLConnection */ + protected String contentType; + + /** + * Constructor for a BundleClassLoader resource URLConnection. + * + * @param url URL for this URLConnection. + * @param bundleEntry BundleEntry that the URLConnection is associated. + */ + public BundleURLConnection(URL url, BundleEntry bundleEntry) { + super(url); + + this.bundleEntry = bundleEntry; + this.in = null; + this.contentType = null; + } + + public synchronized void connect() throws IOException { + if (!connected) { + if (bundleEntry != null) { + in = bundleEntry.getInputStream(); + connected = true; + } else { + throw new IOException(NLS.bind(AdaptorMsg.RESOURCE_NOT_FOUND_EXCEPTION, url)); + } + } + } + + public int getContentLength() { + return ((int) bundleEntry.getSize()); + } + + public String getContentType() { + if (contentType == null) { + contentType = guessContentTypeFromName(bundleEntry.getName()); + + if (contentType == null) { + if (!connected) { + try { + connect(); + } catch (IOException e) { + return (null); + } + } + try { + if (in.markSupported()) + contentType = guessContentTypeFromStream(in); + } catch (IOException e) { + // do nothing + } + } + } + + return (contentType); + } + + public boolean getDoInput() { + return (true); + } + + public boolean getDoOutput() { + return (false); + } + + public InputStream getInputStream() throws IOException { + if (!connected) { + connect(); + } + + return (in); + } + + public long getLastModified() { + long lastModified = bundleEntry.getTime(); + + if (lastModified == -1) { + return (0); + } + + return (lastModified); + } + + /** + * Converts the URL to a common local URL protocol (i.e file: or jar: protocol) + * @return the local URL using a common local protocol + */ + public URL getLocalURL() { + return bundleEntry.getLocalURL(); + } + + /** + * Converts the URL to a URL that uses the file: protocol. The content of this + * URL may be downloaded or extracted onto the local filesystem to create a file URL. + * @return the local URL that uses the file: protocol + */ + public URL getFileURL() { + return bundleEntry.getFileURL(); + } +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/ConsoleManager.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/ConsoleManager.java new file mode 100644 index 000000000..14b3f8f3a --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/ConsoleManager.java @@ -0,0 +1,82 @@ +/******************************************************************************* + * Copyright (c) 2008, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.osgi.framework.internal.core; + +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleException; + +public class ConsoleManager { + + public static final String PROP_CONSOLE = "osgi.console"; //$NON-NLS-1$ + private static final String PROP_SYSTEM_IN_OUT = "console.systemInOut"; //$NON-NLS-1$ + private static final String CONSOLE_NAME = "OSGi Console"; //$NON-NLS-1$ + public static final String CONSOLE_BUNDLE = "org.eclipse.equinox.console"; //$NON-NLS-1$ + public static final String PROP_CONSOLE_ENABLED = "osgi.console.enable.builtin"; //$NON-NLS-1$ + + private final Framework framework; + private final String consoleBundle; + private final String consolePort; + + public ConsoleManager(Framework framework, String consolePropValue) { + String port = null; + if (consolePropValue != null) { + int index = consolePropValue.lastIndexOf(":"); //$NON-NLS-1$ + port = consolePropValue.substring(index + 1); + } + this.consolePort = port != null ? port.trim() : port; + String enabled = FrameworkProperties.getProperty(PROP_CONSOLE_ENABLED, CONSOLE_BUNDLE); + this.framework = framework; + if (!"true".equals(enabled) || "none".equals(consolePort)) { //$NON-NLS-1$ //$NON-NLS-2$ + this.consoleBundle = "false".equals(enabled) ? CONSOLE_BUNDLE : enabled; //$NON-NLS-1$ + if (consolePort == null || consolePort.length() > 0) { + // no -console was specified or it has specified none or a port for telnet; + // need to make sure the gogo shell does not create an interactive console on standard in/out + FrameworkProperties.setProperty("gosh.args", "--nointeractive"); //$NON-NLS-1$//$NON-NLS-2$ + } else { + // Need to make sure we don't shutdown the framework if no console is around (bug 362412) + FrameworkProperties.setProperty("gosh.args", "--noshutdown"); //$NON-NLS-1$//$NON-NLS-2$ + } + return; + } + this.consoleBundle = "unknown"; //$NON-NLS-1$ + } + + public static ConsoleManager startConsole(Framework framework) { + ConsoleManager consoleManager = new ConsoleManager(framework, FrameworkProperties.getProperty(PROP_CONSOLE)); + return consoleManager; + } + + public void checkForConsoleBundle() throws BundleException { + if ("none".equals(consolePort)) //$NON-NLS-1$ + return; + // otherwise we need to check for the equinox console bundle and start it + Bundle[] consoles = framework.getBundleBySymbolicName(consoleBundle); + if (consoles == null || consoles.length == 0) { + if (consolePort != null) + throw new BundleException("Could not find bundle: " + consoleBundle, BundleException.UNSUPPORTED_OPERATION); //$NON-NLS-1$ + return; + } + try { + consoles[0].start(Bundle.START_TRANSIENT); + } catch (BundleException e) { + throw new BundleException("Could not start bundle: " + consoleBundle, BundleException.UNSUPPORTED_OPERATION, e); //$NON-NLS-1$ + } + } + + /** + * Stops the OSGi Command console + * + */ + public void stopConsole() { + // nothing + } + +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/Constants.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/Constants.java new file mode 100644 index 000000000..1bd279453 --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/Constants.java @@ -0,0 +1,254 @@ +/******************************************************************************* + * Copyright (c) 2004, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + +package org.eclipse.osgi.framework.internal.core; + +/** + * This interface contains the constants used by the eclipse + * OSGi implementation. + */ + +public class Constants implements org.osgi.framework.Constants { + /** Default framework version */ + public static final String OSGI_FRAMEWORK_VERSION = "1.3"; //$NON-NLS-1$ + + /** Framework vendor */ + public static final String OSGI_FRAMEWORK_VENDOR = "Eclipse"; //$NON-NLS-1$ + + /** Bundle manifest name */ + public static final String OSGI_BUNDLE_MANIFEST = "META-INF/MANIFEST.MF"; //$NON-NLS-1$ + + /** OSGi framework package name. */ + public static final String OSGI_FRAMEWORK_PACKAGE = "org.osgi.framework"; //$NON-NLS-1$ + + /** Bundle resource URL protocol */ + public static final String OSGI_RESOURCE_URL_PROTOCOL = "bundleresource"; //$NON-NLS-1$ + + /** Bundle entry URL protocol */ + public static final String OSGI_ENTRY_URL_PROTOCOL = "bundleentry"; //$NON-NLS-1$ + + /** Processor aliases resource */ + public static final String OSGI_PROCESSOR_ALIASES = "processor.aliases"; //$NON-NLS-1$ + + /** OS name aliases resource */ + public static final String OSGI_OSNAME_ALIASES = "osname.aliases"; //$NON-NLS-1$ + + /** Default permissions for bundles with no permission set + * and there are no default permissions set. + */ + public static final String OSGI_DEFAULT_DEFAULT_PERMISSIONS = "default.permissions"; //$NON-NLS-1$ + + /** Base implied permissions for all bundles */ + public static final String OSGI_BASE_IMPLIED_PERMISSIONS = "implied.permissions"; //$NON-NLS-1$ + + /** Name of OSGi LogService */ + public static final String OSGI_LOGSERVICE_NAME = "org.osgi.service.log.LogService"; //$NON-NLS-1$ + + /** Name of OSGi PackageAdmin */ + public static final String OSGI_PACKAGEADMIN_NAME = "org.osgi.service.packageadmin.PackageAdmin"; //$NON-NLS-1$ + + /** Name of OSGi PermissionAdmin */ + public static final String OSGI_PERMISSIONADMIN_NAME = "org.osgi.service.permissionadmin.PermissionAdmin"; //$NON-NLS-1$ + + /** Name of OSGi StartLevel */ + public static final String OSGI_STARTLEVEL_NAME = "org.osgi.service.startlevel.StartLevel"; //$NON-NLS-1$ + + /** JVM java.vm.name property name */ + public static final String JVM_VM_NAME = "java.vm.name"; //$NON-NLS-1$ + + /** JVM os.arch property name */ + public static final String JVM_OS_ARCH = "os.arch"; //$NON-NLS-1$ + + /** JVM os.name property name */ + public static final String JVM_OS_NAME = "os.name"; //$NON-NLS-1$ + + /** JVM os.version property name */ + public static final String JVM_OS_VERSION = "os.version"; //$NON-NLS-1$ + + /** JVM user.language property name */ + public static final String JVM_USER_LANGUAGE = "user.language"; //$NON-NLS-1$ + + /** JVM user.region property name */ + public static final String JVM_USER_REGION = "user.region"; //$NON-NLS-1$ + + /** J2ME configuration property name */ + public static final String J2ME_MICROEDITION_CONFIGURATION = "microedition.configuration"; //$NON-NLS-1$ + + /** J2ME profile property name */ + public static final String J2ME_MICROEDITION_PROFILES = "microedition.profiles"; //$NON-NLS-1$ + + /** Persistent start bundle status */ + public static final int BUNDLE_STARTED = 0x00000001; + /** Lazy start flag bundle status */ + public static final int BUNDLE_LAZY_START = 0x00000002; + public static final int BUNDLE_ACTIVATION_POLICY = 0x00000004; + + /** Property file locations and default names. */ + public static final String OSGI_PROPERTIES = "osgi.framework.properties"; //$NON-NLS-1$ + public static final String DEFAULT_OSGI_PROPERTIES = "osgi.properties"; //$NON-NLS-1$ + + private static String INTERNAL_SYSTEM_BUNDLE = "org.eclipse.osgi"; //$NON-NLS-1$ + + public static String getInternalSymbolicName() { + return INTERNAL_SYSTEM_BUNDLE; + } + + static void setInternalSymbolicName(String name) { + INTERNAL_SYSTEM_BUNDLE = name; + } + + /** OSGI implementation version properties key */ + public static final String OSGI_IMPL_VERSION_KEY = "osgi.framework.version"; //$NON-NLS-1$ + /** OSGi java profile; used to give a URL to a java profile */ + public static final String OSGI_JAVA_PROFILE = "osgi.java.profile"; //$NON-NLS-1$ + public static final String OSGI_JAVA_PROFILE_NAME = "osgi.java.profile.name"; //$NON-NLS-1$ + /** + * OSGi java profile bootdelegation; used to indicate how the org.osgi.framework.bootdelegation + * property defined in the java profile should be processed, (ingnore, override, none). default is ignore + */ + public static final String OSGI_JAVA_PROFILE_BOOTDELEGATION = "osgi.java.profile.bootdelegation"; //$NON-NLS-1$ + /** indicates that the org.osgi.framework.bootdelegation in the java profile should be ingored */ + public static final String OSGI_BOOTDELEGATION_IGNORE = "ignore"; //$NON-NLS-1$ + /** indicates that the org.osgi.framework.bootdelegation in the java profile should override the system property */ + public static final String OSGI_BOOTDELEGATION_OVERRIDE = "override"; //$NON-NLS-1$ + /** indicates that the org.osgi.framework.bootdelegation in the java profile AND the system properties should be ignored */ + public static final String OSGI_BOOTDELEGATION_NONE = "none"; //$NON-NLS-1$ + /** OSGi strict delegation **/ + public static final String OSGI_RESOLVER_MODE = "osgi.resolverMode"; //$NON-NLS-1$ + public static final String STRICT_MODE = "strict"; //$NON-NLS-1$ + public static final String DEVELOPMENT_MODE = "development"; //$NON-NLS-1$ + + public static final String STATE_SYSTEM_BUNDLE = "osgi.system.bundle"; //$NON-NLS-1$ + + public static final String PROP_OSGI_RELAUNCH = "osgi.framework.relaunch"; //$NON-NLS-1$ + + public static String OSGI_COMPATIBILITY_BOOTDELEGATION = "osgi.compatibility.bootdelegation"; //$NON-NLS-1$ + + /** Eclipse-SystemBundle header */ + public static final String ECLIPSE_SYSTEMBUNDLE = "Eclipse-SystemBundle"; //$NON-NLS-1$ + public static final String ECLIPSE_PLATFORMFILTER = "Eclipse-PlatformFilter"; //$NON-NLS-1$ + public static final String Eclipse_JREBUNDLE = "Eclipse-JREBundle"; //$NON-NLS-1$ + /** + * Manifest Export-Package directive indicating that the exported package should only + * be made available when the resolver is not in strict mode. + */ + public static final String INTERNAL_DIRECTIVE = "x-internal"; //$NON-NLS-1$ + + /** + * Manifest Export-Package directive indicating that the exported package should only + * be made available to friends of the exporting bundle. + */ + public static final String FRIENDS_DIRECTIVE = "x-friends"; //$NON-NLS-1$ + + /** + * Manifest header (named "Provide-Package") + * identifying the packages name + * provided to other bundles which require the bundle. + * + * <p> + * NOTE: this is only used for backwards compatibility, bundles manifest using + * syntax version 2 will not recognize this header. + * + * <p>The attribute value may be retrieved from the + * <tt>Dictionary</tt> object returned by the <tt>Bundle.getHeaders</tt> method. + * @deprecated + */ + public final static String PROVIDE_PACKAGE = "Provide-Package"; //$NON-NLS-1$ + + /** + * Manifest header attribute (named "reprovide") + * for Require-Bundle + * identifying that any packages that are provided + * by the required bundle must be reprovided by the requiring bundle. + * The default value is <tt>false</tt>. + * <p> + * The attribute value is encoded in the Require-Bundle manifest + * header like: + * <pre> + * Require-Bundle: com.acme.module.test; reprovide="true" + * </pre> + * <p> + * NOTE: this is only used for backwards compatibility, bundles manifest using + * syntax version 2 will not recognize this attribute. + * @deprecated + */ + public final static String REPROVIDE_ATTRIBUTE = "reprovide"; //$NON-NLS-1$ + + /** + * Manifest header attribute (named "optional") + * for Require-Bundle + * identifying that a required bundle is optional and that + * the requiring bundle can be resolved if there is no + * suitable required bundle. + * The default value is <tt>false</tt>. + * + * <p>The attribute value is encoded in the Require-Bundle manifest + * header like: + * <pre> + * Require-Bundle: com.acme.module.test; optional="true" + * </pre> + * <p> + * NOTE: this is only used for backwards compatibility, bundles manifest using + * syntax version 2 will not recognize this attribute. + * @since 1.3 <b>EXPERIMENTAL</b> + * @deprecated + */ + public final static String OPTIONAL_ATTRIBUTE = "optional"; //$NON-NLS-1$ + + /** + * The key used to designate the buddy loader associated with a given bundle. + */ + public final static String BUDDY_LOADER = "Eclipse-BuddyPolicy"; //$NON-NLS-1$ + + public final static String REGISTERED_POLICY = "Eclipse-RegisterBuddy"; //$NON-NLS-1$ + + static public final String INTERNAL_HANDLER_PKGS = "equinox.interal.handler.pkgs"; //$NON-NLS-1$ + + // TODO rename it to Eclipse-PluginClass + public static final String PLUGIN_CLASS = "Plugin-Class"; //$NON-NLS-1$ + + /** Manifest header used to specify the lazy start properties of a bundle */ + public static final String ECLIPSE_LAZYSTART = "Eclipse-LazyStart"; //$NON-NLS-1$ + + /** An Eclipse-LazyStart attribute used to specify exception classes for auto start */ + public static final String ECLIPSE_LAZYSTART_EXCEPTIONS = "exceptions"; //$NON-NLS-1$ + + /** + * Manifest header used to specify the auto start properties of a bundle + * @deprecated use {@link #ECLIPSE_LAZYSTART} + */ + public static final String ECLIPSE_AUTOSTART = "Eclipse-AutoStart"; //$NON-NLS-1$ + + /** + * @deprecated use {@link #ECLIPSE_LAZYSTART_EXCEPTIONS} + */ + public static final String ECLIPSE_AUTOSTART_EXCEPTIONS = ECLIPSE_LAZYSTART_EXCEPTIONS; + + /** + * Framework launching property specifying whether Equinox's FrameworkWiring + * implementation should refresh bundles with equal symbolic names. + * + * <p> + * Default value is <b>TRUE</b> in this release of the Equinox. + * This default may change to <b>FALSE</b> in a future Equinox release. + * Therefore, code must not assume the default behavior is + * <b>TRUE</b> and should interrogate the value of this property to + * determine the behavior. + * + * <p> + * The value of this property may be retrieved by calling the + * {@code BundleContext.getProperty} method. + * @see <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=351519">bug 351519</a> + * @since 3.7.1 + */ + public static final String REFRESH_DUPLICATE_BSN = "equinox.refresh.duplicate.bsn"; //$NON-NLS-1$ + +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/CoreResolverHookFactory.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/CoreResolverHookFactory.java new file mode 100644 index 000000000..986de041d --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/CoreResolverHookFactory.java @@ -0,0 +1,216 @@ +/******************************************************************************* + * Copyright (c) 2010, 2012 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.framework.internal.core; + +import java.util.*; +import org.eclipse.osgi.framework.debug.Debug; +import org.eclipse.osgi.internal.serviceregistry.*; +import org.eclipse.osgi.service.resolver.ResolverHookException; +import org.eclipse.osgi.util.NLS; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.framework.hooks.resolver.ResolverHook; +import org.osgi.framework.hooks.resolver.ResolverHookFactory; +import org.osgi.framework.wiring.*; + +/** + * This class encapsulates the delegation to ResolverHooks that are registered with the service + * registry. This way the resolver implementation only has to call out to a single hook + * which does all the necessary service registry lookups. + * + * This class is not thread safe and expects external synchronization. + * + */ +public class CoreResolverHookFactory implements ResolverHookFactory { + // need a tuple to hold the service reference and hook object + // do not use a map for performance reasons; no need to hash based on a key. + static class HookReference { + public HookReference(ServiceReferenceImpl<ResolverHookFactory> reference, ResolverHook hook) { + this.reference = reference; + this.hook = hook; + } + + final ServiceReferenceImpl<ResolverHookFactory> reference; + final ResolverHook hook; + } + + private final BundleContextImpl context; + private final ServiceRegistry registry; + + public CoreResolverHookFactory(BundleContextImpl context, ServiceRegistry registry) { + this.context = context; + this.registry = registry; + } + + void handleHookException(Throwable t, Object hook, String method) { + if (Debug.DEBUG_HOOKS) { + Debug.println(hook.getClass().getName() + "." + method + "() exception:"); //$NON-NLS-1$ //$NON-NLS-2$ + if (t != null) + Debug.printStackTrace(t); + } + String message = NLS.bind(Msg.SERVICE_FACTORY_EXCEPTION, hook.getClass().getName(), method); + throw new ResolverHookException(message, t); + } + + private ServiceReferenceImpl<ResolverHookFactory>[] getHookReferences() { + try { + @SuppressWarnings("unchecked") + ServiceReferenceImpl<ResolverHookFactory>[] result = (ServiceReferenceImpl<ResolverHookFactory>[]) registry.getServiceReferences(context, ResolverHookFactory.class.getName(), null, false, false); + return result; + } catch (InvalidSyntaxException e) { + // cannot happen; no filter + return null; + } + } + + public ResolverHook begin(Collection<BundleRevision> triggers) { + if (Debug.DEBUG_HOOKS) { + Debug.println("ResolverHook.begin"); //$NON-NLS-1$ + } + ServiceReferenceImpl<ResolverHookFactory>[] refs = getHookReferences(); + @SuppressWarnings("unchecked") + List<HookReference> hookRefs = refs == null ? Collections.EMPTY_LIST : new ArrayList<CoreResolverHookFactory.HookReference>(refs.length); + if (refs != null) + for (ServiceReferenceImpl<ResolverHookFactory> hookRef : refs) { + ResolverHookFactory factory = context.getService(hookRef); + if (factory != null) { + try { + ResolverHook hook = factory.begin(triggers); + if (hook != null) + hookRefs.add(new HookReference(hookRef, hook)); + } catch (Throwable t) { + // need to force an end call on the ResolverHooks we got and release them + try { + new CoreResolverHook(hookRefs).end(); + } catch (Throwable endError) { + // we are already in failure mode; just continue + } + handleHookException(t, factory, "begin"); //$NON-NLS-1$ + } + } + } + return new CoreResolverHook(hookRefs); + } + + void releaseHooks(List<HookReference> hookRefs) { + for (HookReference hookRef : hookRefs) + context.ungetService(hookRef.reference); + hookRefs.clear(); + } + + class CoreResolverHook implements ResolverHook { + private final List<HookReference> hooks; + + CoreResolverHook(List<HookReference> hooks) { + this.hooks = hooks; + } + + public void filterResolvable(Collection<BundleRevision> candidates) { + if (Debug.DEBUG_HOOKS) { + Debug.println("ResolverHook.filterResolvable(" + candidates + ")"); //$NON-NLS-1$ //$NON-NLS-2$ + } + if (hooks.isEmpty()) + return; + candidates = new ShrinkableCollection<BundleRevision>(candidates); + for (Iterator<HookReference> iHooks = hooks.iterator(); iHooks.hasNext();) { + HookReference hookRef = iHooks.next(); + if (hookRef.reference.getBundle() == null) { + handleHookException(null, hookRef.hook, "filterResolvable"); //$NON-NLS-1$ + } else { + try { + hookRef.hook.filterResolvable(candidates); + } catch (Throwable t) { + handleHookException(t, hookRef.hook, "filterResolvable"); //$NON-NLS-1$ + } + } + } + } + + public void filterSingletonCollisions(BundleCapability singleton, Collection<BundleCapability> collisionCandidates) { + if (Debug.DEBUG_HOOKS) { + Debug.println("ResolverHook.filterSingletonCollisions(" + singleton + ", " + collisionCandidates + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + if (hooks.isEmpty()) + return; + collisionCandidates = new ShrinkableCollection<BundleCapability>(collisionCandidates); + for (Iterator<HookReference> iHooks = hooks.iterator(); iHooks.hasNext();) { + HookReference hookRef = iHooks.next(); + if (hookRef.reference.getBundle() == null) { + handleHookException(null, hookRef.hook, "filterSingletonCollisions"); //$NON-NLS-1$ + } else { + try { + hookRef.hook.filterSingletonCollisions(singleton, collisionCandidates); + } catch (Throwable t) { + handleHookException(t, hookRef.hook, "filterSingletonCollisions"); //$NON-NLS-1$ + } + } + } + } + + public void filterMatches(BundleRequirement requirement, Collection<BundleCapability> candidates) { + if (Debug.DEBUG_HOOKS) { + Debug.println("ResolverHook.filterMatches(" + requirement + ", " + candidates + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + if (hooks.isEmpty()) + return; + candidates = new ShrinkableCollection<BundleCapability>(candidates); + for (Iterator<HookReference> iHooks = hooks.iterator(); iHooks.hasNext();) { + HookReference hookRef = iHooks.next(); + if (hookRef.reference.getBundle() == null) { + handleHookException(null, hookRef.hook, "filterMatches"); //$NON-NLS-1$ + } else { + try { + hookRef.hook.filterMatches(requirement, candidates); + } catch (Throwable t) { + handleHookException(t, hookRef.hook, "filterMatches"); //$NON-NLS-1$ + } + } + } + } + + public void end() { + if (Debug.DEBUG_HOOKS) { + Debug.println("ResolverHook.end"); //$NON-NLS-1$ + } + if (hooks.isEmpty()) + return; + try { + HookReference missingHook = null; + Throwable endError = null; + HookReference endBadHook = null; + for (Iterator<HookReference> iHooks = hooks.iterator(); iHooks.hasNext();) { + HookReference hookRef = iHooks.next(); + // We do not remove unregistered services here because we are going to remove all of them at the end + if (hookRef.reference.getBundle() == null) { + if (missingHook == null) + missingHook = hookRef; + } else { + try { + hookRef.hook.end(); + } catch (Throwable t) { + // Must continue on to the next hook.end method + // save the error for throwing at the end + if (endError == null) { + endError = t; + endBadHook = hookRef; + } + } + } + } + if (missingHook != null) + handleHookException(null, missingHook.hook, "end"); //$NON-NLS-1$ + if (endError != null) + handleHookException(endError, endBadHook.hook, "end"); //$NON-NLS-1$ + } finally { + releaseHooks(hooks); + } + } + } +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/EquinoxLauncher.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/EquinoxLauncher.java new file mode 100644 index 000000000..2b71f2467 --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/EquinoxLauncher.java @@ -0,0 +1,347 @@ +/******************************************************************************* + * Copyright (c) 2008, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.osgi.framework.internal.core; + +import java.io.*; +import java.net.URL; +import java.security.*; +import java.security.cert.X509Certificate; +import java.util.*; +import org.eclipse.core.runtime.adaptor.EclipseStarter; +import org.eclipse.osgi.baseadaptor.BaseAdaptor; +import org.eclipse.osgi.framework.adaptor.FrameworkAdaptor; +import org.osgi.framework.*; + +public class EquinoxLauncher implements org.osgi.framework.launch.Framework { + + private volatile Framework framework; + private volatile Bundle systemBundle; + private final Map<String, String> configuration; + private volatile ConsoleManager consoleMgr = null; + + public EquinoxLauncher(Map<String, String> configuration) { + this.configuration = configuration; + } + + public void init() { + checkAdminPermission(AdminPermission.EXECUTE); + if (System.getSecurityManager() == null) + internalInit(); + else { + AccessController.doPrivileged(new PrivilegedAction<Object>() { + public Object run() { + internalInit(); + return null; + } + }); + } + } + + synchronized Framework internalInit() { + if ((getState() & (Bundle.ACTIVE | Bundle.STARTING | Bundle.STOPPING)) != 0) + return framework; // no op + + if (System.getSecurityManager() != null && configuration.get(Constants.FRAMEWORK_SECURITY) != null) + throw new SecurityException("Cannot specify the \"" + Constants.FRAMEWORK_SECURITY + "\" configuration property when a security manager is already installed."); //$NON-NLS-1$ //$NON-NLS-2$ + + Framework current = framework; + if (current != null) { + current.close(); + framework = null; + systemBundle = null; + } + ClassLoader tccl = Thread.currentThread().getContextClassLoader(); + try { + FrameworkProperties.setProperties(configuration); + FrameworkProperties.initializeProperties(); + // make sure the active framework thread is used + setEquinoxProperties(configuration); + current = new Framework(new BaseAdaptor(new String[0])); + consoleMgr = ConsoleManager.startConsole(current); + current.launch(); + framework = current; + systemBundle = current.systemBundle; + } finally { + ClassLoader currentCCL = Thread.currentThread().getContextClassLoader(); + if (currentCCL != tccl) + Thread.currentThread().setContextClassLoader(tccl); + } + return current; + } + + private void setEquinoxProperties(Map<String, String> configuration) { + Object threadBehavior = configuration == null ? null : configuration.get(Framework.PROP_FRAMEWORK_THREAD); + if (threadBehavior == null) { + if (FrameworkProperties.getProperty(Framework.PROP_FRAMEWORK_THREAD) == null) + FrameworkProperties.setProperty(Framework.PROP_FRAMEWORK_THREAD, Framework.THREAD_NORMAL); + } else { + FrameworkProperties.setProperty(Framework.PROP_FRAMEWORK_THREAD, (String) threadBehavior); + } + + // set the compatibility boot delegation flag to false to get "standard" OSGi behavior WRT boot delegation (bug 344850) + if (FrameworkProperties.getProperty(Constants.OSGI_COMPATIBILITY_BOOTDELEGATION) == null) + FrameworkProperties.setProperty(Constants.OSGI_COMPATIBILITY_BOOTDELEGATION, "false"); //$NON-NLS-1$ + // set the support for multiple host to true to get "standard" OSGi behavior (bug 344850) + if (FrameworkProperties.getProperty("osgi.support.multipleHosts") == null) //$NON-NLS-1$ + FrameworkProperties.setProperty("osgi.support.multipleHosts", "true"); //$NON-NLS-1$ //$NON-NLS-2$ + // first check props we are required to provide reasonable defaults for + Object windowSystem = configuration == null ? null : configuration.get(Constants.FRAMEWORK_WINDOWSYSTEM); + if (windowSystem == null) { + windowSystem = FrameworkProperties.getProperty(EclipseStarter.PROP_WS); + if (windowSystem != null) + FrameworkProperties.setProperty(Constants.FRAMEWORK_WINDOWSYSTEM, (String) windowSystem); + } + // rest of props can be ignored if the configuration is null + if (configuration == null) + return; + // check each osgi clean property and set the appropriate equinox one + Object clean = configuration.get(Constants.FRAMEWORK_STORAGE_CLEAN); + if (Constants.FRAMEWORK_STORAGE_CLEAN_ONFIRSTINIT.equals(clean)) { + // remove this so we only clean on first init + configuration.remove(Constants.FRAMEWORK_STORAGE_CLEAN); + FrameworkProperties.setProperty(EclipseStarter.PROP_CLEAN, Boolean.TRUE.toString()); + } + } + + public FrameworkEvent waitForStop(long timeout) throws InterruptedException { + Framework current = framework; + if (current == null) + return new FrameworkEvent(FrameworkEvent.STOPPED, this, null); + return current.waitForStop(timeout); + } + + public Enumeration<URL> findEntries(String path, String filePattern, boolean recurse) { + Bundle current = systemBundle; + if (current == null) + return null; + return current.findEntries(path, filePattern, recurse); + } + + public BundleContext getBundleContext() { + Bundle current = systemBundle; + if (current == null) + return null; + return current.getBundleContext(); + } + + public long getBundleId() { + return 0; + } + + public URL getEntry(String path) { + Bundle current = systemBundle; + if (current == null) + return null; + return current.getEntry(path); + } + + public Enumeration<String> getEntryPaths(String path) { + Bundle current = systemBundle; + if (current == null) + return null; + return current.getEntryPaths(path); + } + + public Dictionary<String, String> getHeaders() { + Bundle current = systemBundle; + if (current == null) + return null; + return current.getHeaders(); + } + + public Dictionary<String, String> getHeaders(String locale) { + Bundle current = systemBundle; + if (current == null) + return null; + return current.getHeaders(locale); + } + + public long getLastModified() { + Bundle current = systemBundle; + if (current == null) + return System.currentTimeMillis(); + return current.getLastModified(); + } + + public String getLocation() { + return Constants.SYSTEM_BUNDLE_LOCATION; + } + + public ServiceReference<?>[] getRegisteredServices() { + Bundle current = systemBundle; + if (current == null) + return null; + return current.getRegisteredServices(); + } + + public URL getResource(String name) { + Bundle current = systemBundle; + if (current == null) + return null; + return current.getResource(name); + } + + public Enumeration<URL> getResources(String name) throws IOException { + Bundle current = systemBundle; + if (current == null) + return null; + return current.getResources(name); + } + + public ServiceReference<?>[] getServicesInUse() { + Bundle current = systemBundle; + if (current == null) + return null; + return current.getServicesInUse(); + } + + public int getState() { + Bundle current = systemBundle; + if (current == null) + return Bundle.INSTALLED; + return current.getState(); + } + + public String getSymbolicName() { + return FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME; + } + + public boolean hasPermission(Object permission) { + Bundle current = systemBundle; + if (current == null) + return false; + return current.hasPermission(permission); + } + + public Class<?> loadClass(String name) throws ClassNotFoundException { + Bundle current = systemBundle; + if (current == null) + return null; + return current.loadClass(name); + } + + public void start(int options) throws BundleException { + start(); + } + + /** + * @throws BundleException + */ + public void start() throws BundleException { + checkAdminPermission(AdminPermission.EXECUTE); + if (System.getSecurityManager() == null) + internalStart(); + else + try { + AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() { + public Object run() { + internalStart(); + return null; + } + }); + } catch (PrivilegedActionException e) { + throw (BundleException) e.getException(); + } + } + + private void checkAdminPermission(String actions) { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) + sm.checkPermission(new AdminPermission(this, actions)); + } + + void internalStart() { + if (getState() == Bundle.ACTIVE) + return; + Framework current = internalInit(); + int level = 1; + try { + level = Integer.parseInt(configuration.get(Constants.FRAMEWORK_BEGINNING_STARTLEVEL)); + } catch (Throwable t) { + // do nothing + } + current.startLevelManager.doSetStartLevel(level); + } + + public void stop(int options) throws BundleException { + stop(); + } + + public void stop() throws BundleException { + Bundle current = systemBundle; + if (current == null) + return; + ConsoleManager currentConsole = consoleMgr; + if (currentConsole != null) { + currentConsole.stopConsole(); + consoleMgr = null; + } + current.stop(); + } + + public void uninstall() throws BundleException { + throw new BundleException(Msg.BUNDLE_SYSTEMBUNDLE_UNINSTALL_EXCEPTION, BundleException.INVALID_OPERATION); + } + + public void update() throws BundleException { + Bundle current = systemBundle; + if (current == null) + return; + current.update(); + } + + public void update(InputStream in) throws BundleException { + try { + in.close(); + } catch (IOException e) { + // nothing; just being nice + } + update(); + } + + public Map<X509Certificate, List<X509Certificate>> getSignerCertificates(int signersType) { + Bundle current = systemBundle; + if (current != null) + return current.getSignerCertificates(signersType); + @SuppressWarnings("unchecked") + final Map<X509Certificate, List<X509Certificate>> empty = Collections.EMPTY_MAP; + return empty; + } + + public Version getVersion() { + Bundle current = systemBundle; + if (current != null) + return current.getVersion(); + return Version.emptyVersion; + } + + public <A> A adapt(Class<A> adapterType) { + Bundle current = systemBundle; + if (current != null) { + return current.adapt(adapterType); + } + return null; + } + + public int compareTo(Bundle o) { + Bundle current = systemBundle; + if (current != null) + return current.compareTo(o); + throw new IllegalStateException(); + } + + public File getDataFile(String filename) { + Bundle current = systemBundle; + if (current != null) + return current.getDataFile(filename); + return null; + } +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/ExportedPackageImpl.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/ExportedPackageImpl.java new file mode 100644 index 000000000..a26998541 --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/ExportedPackageImpl.java @@ -0,0 +1,99 @@ +/******************************************************************************* + * Copyright (c) 2003, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.osgi.framework.internal.core; + +import java.util.ArrayList; +import java.util.List; +import org.eclipse.osgi.internal.loader.*; +import org.eclipse.osgi.service.resolver.BundleDescription; +import org.eclipse.osgi.service.resolver.ExportPackageDescription; +import org.osgi.framework.*; +import org.osgi.framework.Constants; +import org.osgi.service.packageadmin.ExportedPackage; + +/** + * @deprecated + */ +public class ExportedPackageImpl implements ExportedPackage { + + private final ExportPackageDescription exportedPackage; + private final BundleLoaderProxy supplier; + + public ExportedPackageImpl(ExportPackageDescription exportedPackage, BundleLoaderProxy supplier) { + this.exportedPackage = exportedPackage; + this.supplier = supplier; + } + + public String getName() { + return exportedPackage.getName(); + } + + public org.osgi.framework.Bundle getExportingBundle() { + if (supplier.isStale()) + return null; + return supplier.getBundleHost(); + } + + /* + * get the bundle without checking if it is stale + */ + AbstractBundle getBundle() { + return supplier.getBundleHost(); + } + + public Bundle[] getImportingBundles() { + if (supplier.isStale()) + return null; + AbstractBundle bundle = (AbstractBundle) getExportingBundle(); + if (bundle == null) + return null; + AbstractBundle[] bundles = bundle.framework.getAllBundles(); + List<Bundle> importers = new ArrayList<Bundle>(10); + PackageSource supplierSource = supplier.createPackageSource(exportedPackage, false); + for (int i = 0; i < bundles.length; i++) { + if (!(bundles[i] instanceof BundleHost)) + continue; + BundleLoader loader = ((BundleHost) bundles[i]).getBundleLoader(); + if (loader == null || loader.getBundle() == supplier.getBundle()) + continue; // do not include include the exporter of the package + PackageSource importerSource = loader.getPackageSource(getName()); + if (supplierSource != null && supplierSource.hasCommonSource(importerSource)) + importers.add(bundles[i]); + } + return importers.toArray(new Bundle[importers.size()]); + } + + /** + * @deprecated + */ + public String getSpecificationVersion() { + return exportedPackage.getVersion().toString(); + } + + public Version getVersion() { + return exportedPackage.getVersion(); + } + + public boolean isRemovalPending() { + BundleDescription exporter = exportedPackage.getExporter(); + if (exporter != null) + return exporter.isRemovalPending(); + return true; + } + + public String toString() { + StringBuffer result = new StringBuffer(getName()); + result.append("; ").append(Constants.VERSION_ATTRIBUTE); //$NON-NLS-1$ + result.append("=\"").append(exportedPackage.getVersion().toString()).append("\""); //$NON-NLS-1$//$NON-NLS-2$ + + return result.toString(); + } +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/FilterImpl.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/FilterImpl.java new file mode 100644 index 000000000..4611c63cb --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/FilterImpl.java @@ -0,0 +1,1751 @@ +/******************************************************************************* + * Copyright (c) 2003, 2010 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.framework.internal.core; + +import java.lang.reflect.*; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.*; +import org.eclipse.osgi.framework.debug.Debug; +import org.eclipse.osgi.framework.util.Headers; +import org.eclipse.osgi.internal.serviceregistry.ServiceReferenceImpl; +import org.eclipse.osgi.util.NLS; +import org.osgi.framework.*; + +/** + * RFC 1960-based Filter. Filter objects can be created by calling + * the constructor with the desired filter string. + * A Filter object can be called numerous times to determine if the + * match argument matches the filter string that was used to create the Filter + * object. + * + * <p>The syntax of a filter string is the string representation + * of LDAP search filters as defined in RFC 1960: + * <i>A String Representation of LDAP Search Filters</i> (available at + * http://www.ietf.org/rfc/rfc1960.txt). + * It should be noted that RFC 2254: + * <i>A String Representation of LDAP Search Filters</i> + * (available at http://www.ietf.org/rfc/rfc2254.txt) supersedes + * RFC 1960 but only adds extensible matching and is not applicable for this + * API. + * + * <p>The string representation of an LDAP search filter is defined by the + * following grammar. It uses a prefix format. + * <pre> + * <filter> ::= '(' <filtercomp> ')' + * <filtercomp> ::= <and> | <or> | <not> | <item> + * <and> ::= '&' <filterlist> + * <or> ::= '|' <filterlist> + * <not> ::= '!' <filter> + * <filterlist> ::= <filter> | <filter> <filterlist> + * <item> ::= <simple> | <present> | <substring> + * <simple> ::= <attr> <filtertype> <value> + * <filtertype> ::= <equal> | <approx> | <greater> | <less> + * <equal> ::= '=' + * <approx> ::= '~=' + * <greater> ::= '>=' + * <less> ::= '<=' + * <present> ::= <attr> '=*' + * <substring> ::= <attr> '=' <initial> <any> <final> + * <initial> ::= NULL | <value> + * <any> ::= '*' <starval> + * <starval> ::= NULL | <value> '*' <starval> + * <final> ::= NULL | <value> + * </pre> + * + * <code><attr></code> is a string representing an attribute, or + * key, in the properties objects of the registered services. + * Attribute names are not case sensitive; + * that is cn and CN both refer to the same attribute. + * <code><value></code> is a string representing the value, or part of + * one, of a key in the properties objects of the registered services. + * If a <code><value></code> must + * contain one of the characters '<code>*</code>' or '<code>(</code>' + * or '<code>)</code>', these characters + * should be escaped by preceding them with the backslash '<code>\</code>' + * character. + * Note that although both the <code><substring></code> and + * <code><present></code> productions can + * produce the <code>'attr=*'</code> construct, this construct is used only to + * denote a presence filter. + * + * <p>Examples of LDAP filters are: + * + * <pre> + * "(cn=Babs Jensen)" + * "(!(cn=Tim Howes))" + * "(&(" + Constants.OBJECTCLASS + "=Person)(|(sn=Jensen)(cn=Babs J*)))" + * "(o=univ*of*mich*)" + * </pre> + * + * <p>The approximate match (<code>~=</code>) is implementation specific but + * should at least ignore case and white space differences. Optional are + * codes like soundex or other smart "closeness" comparisons. + * + * <p>Comparison of values is not straightforward. Strings + * are compared differently than numbers and it is + * possible for a key to have multiple values. Note that + * that keys in the match argument must always be strings. + * The comparison is defined by the object type of the key's + * value. The following rules apply for comparison: + * + * <blockquote> + * <TABLE BORDER=0> + * <TR><TD><b>Property Value Type </b></TD><TD><b>Comparison Type</b></TD></TR> + * <TR><TD>String </TD><TD>String comparison</TD></TR> + * <TR valign=top><TD>Integer, Long, Float, Double, Byte, Short, BigInteger, BigDecimal </TD><TD>numerical comparison</TD></TR> + * <TR><TD>Character </TD><TD>character comparison</TD></TR> + * <TR><TD>Boolean </TD><TD>equality comparisons only</TD></TR> + * <TR><TD>[] (array)</TD><TD>recursively applied to values </TD></TR> + * <TR><TD>Vector</TD><TD>recursively applied to elements </TD></TR> + * </TABLE> + * Note: arrays of primitives are also supported. + * </blockquote> + * + * A filter matches a key that has multiple values if it + * matches at least one of those values. For example, + * <pre> + * Dictionary d = new Hashtable(); + * d.put( "cn", new String[] { "a", "b", "c" } ); + * </pre> + * d will match <code>(cn=a)</code> and also <code>(cn=b)</code> + * + * <p>A filter component that references a key having an unrecognizable + * data type will evaluate to <code>false</code> . + */ + +public class FilterImpl implements Filter /* since Framework 1.1 */{ + /* public methods in org.osgi.framework.Filter */ + + /** + * Constructs a {@link FilterImpl} object. This filter object may be used + * to match a {@link ServiceReferenceImpl} or a Dictionary. + * + * <p> If the filter cannot be parsed, an {@link InvalidSyntaxException} + * will be thrown with a human readable message where the + * filter became unparsable. + * + * @param filterString the filter string. + * @exception InvalidSyntaxException If the filter parameter contains + * an invalid filter string that cannot be parsed. + */ + public static FilterImpl newInstance(String filterString) throws InvalidSyntaxException { + return new Parser(filterString).parse(); + } + + /** + * Filter using a service's properties. + * <p> + * This {@code Filter} is executed using the keys and values of the + * referenced service's properties. The keys are looked up in a case + * insensitive manner. + * + * @param reference The reference to the service whose properties are used + * in the match. + * @return {@code true} if the service's properties match this + * {@code Filter}; {@code false} otherwise. + */ + public boolean match(ServiceReference<?> reference) { + if (reference instanceof ServiceReferenceImpl) { + return matchCase(((ServiceReferenceImpl<?>) reference).getRegistration().getProperties()); + } + return matchCase(new ServiceReferenceDictionary(reference)); + } + + /** + * Filter using a {@code Dictionary} with case insensitive key lookup. This + * {@code Filter} is executed using the specified {@code Dictionary}'s keys + * and values. The keys are looked up in a case insensitive manner. + * + * @param dictionary The {@code Dictionary} whose key/value pairs are used + * in the match. + * @return {@code true} if the {@code Dictionary}'s values match this + * filter; {@code false} otherwise. + * @throws IllegalArgumentException If {@code dictionary} contains case + * variants of the same key name. + */ + public boolean match(Dictionary<String, ?> dictionary) { + if (dictionary != null) { + dictionary = new Headers<String, Object>(dictionary); + } + + return matchCase(dictionary); + } + + /** + * Filter using a {@code Dictionary}. This {@code Filter} is executed using + * the specified {@code Dictionary}'s keys and values. The keys are looked + * up in a normal manner respecting case. + * + * @param dictionary The {@code Dictionary} whose key/value pairs are used + * in the match. + * @return {@code true} if the {@code Dictionary}'s values match this + * filter; {@code false} otherwise. + * @since 1.3 + */ + public boolean matchCase(Dictionary<String, ?> dictionary) { + switch (op) { + case AND : { + FilterImpl[] filters = (FilterImpl[]) value; + for (FilterImpl f : filters) { + if (!f.matchCase(dictionary)) { + return false; + } + } + + return true; + } + + case OR : { + FilterImpl[] filters = (FilterImpl[]) value; + for (FilterImpl f : filters) { + if (f.matchCase(dictionary)) { + return true; + } + } + + return false; + } + + case NOT : { + FilterImpl filter = (FilterImpl) value; + + return !filter.matchCase(dictionary); + } + + case SUBSTRING : + case EQUAL : + case GREATER : + case LESS : + case APPROX : { + Object prop = (dictionary == null) ? null : dictionary.get(attr); + + return compare(op, prop, value); + } + + case PRESENT : { + if (Debug.DEBUG_FILTER) { + Debug.println("PRESENT(" + attr + ")"); //$NON-NLS-1$ //$NON-NLS-2$ + } + + Object prop = (dictionary == null) ? null : dictionary.get(attr); + + return prop != null; + } + } + + return false; + } + + /** + * Filter using a {@code Map}. This {@code Filter} is executed using the + * specified {@code Map}'s keys and values. The keys are looked up in a + * normal manner respecting case. + * + * @param map The {@code Map} whose key/value pairs are used in the match. + * Maps with {@code null} key or values are not supported. A + * {@code null} value is considered not present to the filter. + * @return {@code true} if the {@code Map}'s values match this filter; + * {@code false} otherwise. + * @since 1.6 + */ + public boolean matches(Map<String, ?> map) { + switch (op) { + case AND : { + FilterImpl[] filters = (FilterImpl[]) value; + for (FilterImpl f : filters) { + if (!f.matches(map)) { + return false; + } + } + + return true; + } + + case OR : { + FilterImpl[] filters = (FilterImpl[]) value; + for (FilterImpl f : filters) { + if (f.matches(map)) { + return true; + } + } + + return false; + } + + case NOT : { + FilterImpl filter = (FilterImpl) value; + + return !filter.matches(map); + } + + case SUBSTRING : + case EQUAL : + case GREATER : + case LESS : + case APPROX : { + Object prop = (map == null) ? null : map.get(attr); + + return compare(op, prop, value); + } + + case PRESENT : { + if (Debug.DEBUG_FILTER) { + Debug.println("PRESENT(" + attr + ")"); //$NON-NLS-1$ //$NON-NLS-2$ + } + + Object prop = (map == null) ? null : map.get(attr); + + return prop != null; + } + } + + return false; + } + + /** + * Returns this <code>Filter</code> object's filter string. + * <p> + * The filter string is normalized by removing whitespace which does not + * affect the meaning of the filter. + * + * @return Filter string. + */ + + public String toString() { + String result = filterString; + if (result == null) { + filterString = result = normalize().toString(); + } + return result; + } + + /** + * Returns this <code>Filter</code>'s normalized filter string. + * <p> + * The filter string is normalized by removing whitespace which does not + * affect the meaning of the filter. + * + * @return This <code>Filter</code>'s filter string. + */ + private StringBuffer normalize() { + StringBuffer sb = new StringBuffer(); + sb.append('('); + + switch (op) { + case AND : { + sb.append('&'); + + FilterImpl[] filters = (FilterImpl[]) value; + for (FilterImpl f : filters) { + sb.append(f.normalize()); + } + + break; + } + + case OR : { + sb.append('|'); + + FilterImpl[] filters = (FilterImpl[]) value; + for (FilterImpl f : filters) { + sb.append(f.normalize()); + } + + break; + } + + case NOT : { + sb.append('!'); + FilterImpl filter = (FilterImpl) value; + sb.append(filter.normalize()); + + break; + } + + case SUBSTRING : { + sb.append(attr); + sb.append('='); + + String[] substrings = (String[]) value; + + for (String substr : substrings) { + if (substr == null) /* * */{ + sb.append('*'); + } else /* xxx */{ + sb.append(encodeValue(substr)); + } + } + + break; + } + case EQUAL : { + sb.append(attr); + sb.append('='); + sb.append(encodeValue((String) value)); + + break; + } + case GREATER : { + sb.append(attr); + sb.append(">="); //$NON-NLS-1$ + sb.append(encodeValue((String) value)); + + break; + } + case LESS : { + sb.append(attr); + sb.append("<="); //$NON-NLS-1$ + sb.append(encodeValue((String) value)); + + break; + } + case APPROX : { + sb.append(attr); + sb.append("~="); //$NON-NLS-1$ + sb.append(encodeValue(approxString((String) value))); + + break; + } + + case PRESENT : { + sb.append(attr); + sb.append("=*"); //$NON-NLS-1$ + + break; + } + } + + sb.append(')'); + + return sb; + } + + /** + * Compares this <code>Filter</code> object to another object. + * + * @param obj The object to compare against this <code>Filter</code> + * object. + * @return If the other object is a <code>Filter</code> object, then + * returns <code>this.toString().equals(obj.toString()</code>; + * <code>false</code> otherwise. + */ + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + + if (!(obj instanceof Filter)) { + return false; + } + + return this.toString().equals(obj.toString()); + } + + /** + * Returns the hashCode for this <code>Filter</code> object. + * + * @return The hashCode of the filter string; that is, + * <code>this.toString().hashCode()</code>. + */ + public int hashCode() { + return this.toString().hashCode(); + } + + /* non public fields and methods for the Filter implementation */ + + /** filter operation */ + private final int op; + private static final int EQUAL = 1; + private static final int APPROX = 2; + private static final int GREATER = 3; + private static final int LESS = 4; + private static final int PRESENT = 5; + private static final int SUBSTRING = 6; + private static final int AND = 7; + private static final int OR = 8; + private static final int NOT = 9; + + /** filter attribute or null if operation AND, OR or NOT */ + private final String attr; + /** filter operands */ + private final Object value; + + /* normalized filter string for topLevel Filter object */ + private transient volatile String filterString; + + FilterImpl(int operation, String attr, Object value) { + this.op = operation; + this.attr = attr; + this.value = value; + } + + /** + * Encode the value string such that '(', '*', ')' + * and '\' are escaped. + * + * @param value unencoded value string. + * @return encoded value string. + */ + private static String encodeValue(String value) { + boolean encoded = false; + int inlen = value.length(); + int outlen = inlen << 1; /* inlen * 2 */ + + char[] output = new char[outlen]; + value.getChars(0, inlen, output, inlen); + + int cursor = 0; + for (int i = inlen; i < outlen; i++) { + char c = output[i]; + + switch (c) { + case '(' : + case '*' : + case ')' : + case '\\' : { + output[cursor] = '\\'; + cursor++; + encoded = true; + + break; + } + } + + output[cursor] = c; + cursor++; + } + + return encoded ? new String(output, 0, cursor) : value; + } + + private boolean compare(int operation, Object value1, Object value2) { + if (value1 == null) { + if (Debug.DEBUG_FILTER) { + Debug.println("compare(" + value1 + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + + return false; + } + + if (value1 instanceof String) { + return compare_String(operation, (String) value1, value2); + } + + Class<?> clazz = value1.getClass(); + if (clazz.isArray()) { + Class<?> type = clazz.getComponentType(); + if (type.isPrimitive()) { + return compare_PrimitiveArray(operation, type, value1, value2); + } + return compare_ObjectArray(operation, (Object[]) value1, value2); + } + if (value1 instanceof Collection<?>) { + return compare_Collection(operation, (Collection<?>) value1, value2); + } + + if (value1 instanceof Integer) { + return compare_Integer(operation, ((Integer) value1).intValue(), value2); + } + + if (value1 instanceof Long) { + return compare_Long(operation, ((Long) value1).longValue(), value2); + } + + if (value1 instanceof Byte) { + return compare_Byte(operation, ((Byte) value1).byteValue(), value2); + } + + if (value1 instanceof Short) { + return compare_Short(operation, ((Short) value1).shortValue(), value2); + } + + if (value1 instanceof Character) { + return compare_Character(operation, ((Character) value1).charValue(), value2); + } + + if (value1 instanceof Float) { + return compare_Float(operation, ((Float) value1).floatValue(), value2); + } + + if (value1 instanceof Double) { + return compare_Double(operation, ((Double) value1).doubleValue(), value2); + } + + if (value1 instanceof Boolean) { + return compare_Boolean(operation, ((Boolean) value1).booleanValue(), value2); + } + if (value1 instanceof Comparable<?>) { + @SuppressWarnings("unchecked") + Comparable<Object> comparable = (Comparable<Object>) value1; + return compare_Comparable(operation, comparable, value2); + } + + return compare_Unknown(operation, value1, value2); // RFC 59 + } + + private boolean compare_Collection(int operation, Collection<?> collection, Object value2) { + for (Object value1 : collection) { + if (compare(operation, value1, value2)) { + return true; + } + } + + return false; + } + + private boolean compare_ObjectArray(int operation, Object[] array, Object value2) { + for (Object value1 : array) { + if (compare(operation, value1, value2)) { + return true; + } + } + + return false; + } + + private boolean compare_PrimitiveArray(int operation, Class<?> type, Object primarray, Object value2) { + if (Integer.TYPE.isAssignableFrom(type)) { + int[] array = (int[]) primarray; + for (int value1 : array) { + if (compare_Integer(operation, value1, value2)) { + return true; + } + } + + return false; + } + + if (Long.TYPE.isAssignableFrom(type)) { + long[] array = (long[]) primarray; + for (long value1 : array) { + if (compare_Long(operation, value1, value2)) { + return true; + } + } + + return false; + } + + if (Byte.TYPE.isAssignableFrom(type)) { + byte[] array = (byte[]) primarray; + for (byte value1 : array) { + if (compare_Byte(operation, value1, value2)) { + return true; + } + } + + return false; + } + + if (Short.TYPE.isAssignableFrom(type)) { + short[] array = (short[]) primarray; + for (short value1 : array) { + if (compare_Short(operation, value1, value2)) { + return true; + } + } + + return false; + } + + if (Character.TYPE.isAssignableFrom(type)) { + char[] array = (char[]) primarray; + for (char value1 : array) { + if (compare_Character(operation, value1, value2)) { + return true; + } + } + + return false; + } + + if (Float.TYPE.isAssignableFrom(type)) { + float[] array = (float[]) primarray; + for (float value1 : array) { + if (compare_Float(operation, value1, value2)) { + return true; + } + } + + return false; + } + + if (Double.TYPE.isAssignableFrom(type)) { + double[] array = (double[]) primarray; + for (double value1 : array) { + if (compare_Double(operation, value1, value2)) { + return true; + } + } + + return false; + } + + if (Boolean.TYPE.isAssignableFrom(type)) { + boolean[] array = (boolean[]) primarray; + for (boolean value1 : array) { + if (compare_Boolean(operation, value1, value2)) { + return true; + } + } + + return false; + } + + return false; + } + + private boolean compare_String(int operation, String string, Object value2) { + switch (operation) { + case SUBSTRING : { + if (Debug.DEBUG_FILTER) { + Debug.println("SUBSTRING(" + string + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + + String[] substrings = (String[]) value2; + int pos = 0; + for (int i = 0, size = substrings.length; i < size; i++) { + String substr = substrings[i]; + + if (i + 1 < size) /* if this is not that last substr */{ + if (substr == null) /* * */{ + String substr2 = substrings[i + 1]; + + if (substr2 == null) /* ** */ + continue; /* ignore first star */ + /* *xxx */ + if (Debug.DEBUG_FILTER) { + Debug.println("indexOf(\"" + substr2 + "\"," + pos + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + int index = string.indexOf(substr2, pos); + if (index == -1) { + return false; + } + + pos = index + substr2.length(); + if (i + 2 < size) // if there are more substrings, increment over the string we just matched; otherwise need to do the last substr check + i++; + } else /* xxx */{ + int len = substr.length(); + + if (Debug.DEBUG_FILTER) { + Debug.println("regionMatches(" + pos + ",\"" + substr + "\")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + if (string.regionMatches(pos, substr, 0, len)) { + pos += len; + } else { + return false; + } + } + } else /* last substr */{ + if (substr == null) /* * */{ + return true; + } + /* xxx */ + if (Debug.DEBUG_FILTER) { + Debug.println("regionMatches(" + pos + "," + substr + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return string.endsWith(substr); + } + } + + return true; + } + case EQUAL : { + if (Debug.DEBUG_FILTER) { + Debug.println("EQUAL(" + string + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return string.equals(value2); + } + case APPROX : { + if (Debug.DEBUG_FILTER) { + Debug.println("APPROX(" + string + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + + string = approxString(string); + String string2 = approxString((String) value2); + + return string.equalsIgnoreCase(string2); + } + case GREATER : { + if (Debug.DEBUG_FILTER) { + Debug.println("GREATER(" + string + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return string.compareTo((String) value2) >= 0; + } + case LESS : { + if (Debug.DEBUG_FILTER) { + Debug.println("LESS(" + string + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return string.compareTo((String) value2) <= 0; + } + } + + return false; + } + + private boolean compare_Integer(int operation, int intval, Object value2) { + if (operation == SUBSTRING) { + if (Debug.DEBUG_FILTER) { + Debug.println("SUBSTRING(" + intval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return false; + } + + int intval2; + try { + intval2 = Integer.parseInt(((String) value2).trim()); + } catch (IllegalArgumentException e) { + return false; + } + switch (operation) { + case EQUAL : { + if (Debug.DEBUG_FILTER) { + Debug.println("EQUAL(" + intval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return intval == intval2; + } + case APPROX : { + if (Debug.DEBUG_FILTER) { + Debug.println("APPROX(" + intval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return intval == intval2; + } + case GREATER : { + if (Debug.DEBUG_FILTER) { + Debug.println("GREATER(" + intval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return intval >= intval2; + } + case LESS : { + if (Debug.DEBUG_FILTER) { + Debug.println("LESS(" + intval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return intval <= intval2; + } + } + + return false; + } + + private boolean compare_Long(int operation, long longval, Object value2) { + if (operation == SUBSTRING) { + if (Debug.DEBUG_FILTER) { + Debug.println("SUBSTRING(" + longval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return false; + } + + long longval2; + try { + longval2 = Long.parseLong(((String) value2).trim()); + } catch (IllegalArgumentException e) { + return false; + } + switch (operation) { + case EQUAL : { + if (Debug.DEBUG_FILTER) { + Debug.println("EQUAL(" + longval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return longval == longval2; + } + case APPROX : { + if (Debug.DEBUG_FILTER) { + Debug.println("APPROX(" + longval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return longval == longval2; + } + case GREATER : { + if (Debug.DEBUG_FILTER) { + Debug.println("GREATER(" + longval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return longval >= longval2; + } + case LESS : { + if (Debug.DEBUG_FILTER) { + Debug.println("LESS(" + longval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return longval <= longval2; + } + } + + return false; + } + + private boolean compare_Byte(int operation, byte byteval, Object value2) { + if (operation == SUBSTRING) { + if (Debug.DEBUG_FILTER) { + Debug.println("SUBSTRING(" + byteval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return false; + } + + byte byteval2; + try { + byteval2 = Byte.parseByte(((String) value2).trim()); + } catch (IllegalArgumentException e) { + return false; + } + switch (operation) { + case EQUAL : { + if (Debug.DEBUG_FILTER) { + Debug.println("EQUAL(" + byteval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return byteval == byteval2; + } + case APPROX : { + if (Debug.DEBUG_FILTER) { + Debug.println("APPROX(" + byteval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return byteval == byteval2; + } + case GREATER : { + if (Debug.DEBUG_FILTER) { + Debug.println("GREATER(" + byteval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return byteval >= byteval2; + } + case LESS : { + if (Debug.DEBUG_FILTER) { + Debug.println("LESS(" + byteval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return byteval <= byteval2; + } + } + + return false; + } + + private boolean compare_Short(int operation, short shortval, Object value2) { + if (operation == SUBSTRING) { + if (Debug.DEBUG_FILTER) { + Debug.println("SUBSTRING(" + shortval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return false; + } + + short shortval2; + try { + shortval2 = Short.parseShort(((String) value2).trim()); + } catch (IllegalArgumentException e) { + return false; + } + switch (operation) { + case EQUAL : { + if (Debug.DEBUG_FILTER) { + Debug.println("EQUAL(" + shortval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return shortval == shortval2; + } + case APPROX : { + if (Debug.DEBUG_FILTER) { + Debug.println("APPROX(" + shortval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return shortval == shortval2; + } + case GREATER : { + if (Debug.DEBUG_FILTER) { + Debug.println("GREATER(" + shortval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return shortval >= shortval2; + } + case LESS : { + if (Debug.DEBUG_FILTER) { + Debug.println("LESS(" + shortval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return shortval <= shortval2; + } + } + + return false; + } + + private boolean compare_Character(int operation, char charval, Object value2) { + if (operation == SUBSTRING) { + if (Debug.DEBUG_FILTER) { + Debug.println("SUBSTRING(" + charval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return false; + } + + char charval2; + try { + charval2 = ((String) value2).charAt(0); + } catch (IndexOutOfBoundsException e) { + return false; + } + switch (operation) { + case EQUAL : { + if (Debug.DEBUG_FILTER) { + Debug.println("EQUAL(" + charval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return charval == charval2; + } + case APPROX : { + if (Debug.DEBUG_FILTER) { + Debug.println("APPROX(" + charval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return (charval == charval2) || (Character.toUpperCase(charval) == Character.toUpperCase(charval2)) || (Character.toLowerCase(charval) == Character.toLowerCase(charval2)); + } + case GREATER : { + if (Debug.DEBUG_FILTER) { + Debug.println("GREATER(" + charval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return charval >= charval2; + } + case LESS : { + if (Debug.DEBUG_FILTER) { + Debug.println("LESS(" + charval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return charval <= charval2; + } + } + + return false; + } + + private boolean compare_Boolean(int operation, boolean boolval, Object value2) { + if (operation == SUBSTRING) { + if (Debug.DEBUG_FILTER) { + Debug.println("SUBSTRING(" + boolval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return false; + } + + boolean boolval2 = Boolean.valueOf(((String) value2).trim()).booleanValue(); + switch (operation) { + case EQUAL : { + if (Debug.DEBUG_FILTER) { + Debug.println("EQUAL(" + boolval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return boolval == boolval2; + } + case APPROX : { + if (Debug.DEBUG_FILTER) { + Debug.println("APPROX(" + boolval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return boolval == boolval2; + } + case GREATER : { + if (Debug.DEBUG_FILTER) { + Debug.println("GREATER(" + boolval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return boolval == boolval2; + } + case LESS : { + if (Debug.DEBUG_FILTER) { + Debug.println("LESS(" + boolval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return boolval == boolval2; + } + } + + return false; + } + + private boolean compare_Float(int operation, float floatval, Object value2) { + if (operation == SUBSTRING) { + if (Debug.DEBUG_FILTER) { + Debug.println("SUBSTRING(" + floatval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return false; + } + + float floatval2; + try { + floatval2 = Float.parseFloat(((String) value2).trim()); + } catch (IllegalArgumentException e) { + return false; + } + switch (operation) { + case EQUAL : { + if (Debug.DEBUG_FILTER) { + Debug.println("EQUAL(" + floatval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return Float.compare(floatval, floatval2) == 0; + } + case APPROX : { + if (Debug.DEBUG_FILTER) { + Debug.println("APPROX(" + floatval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return Float.compare(floatval, floatval2) == 0; + } + case GREATER : { + if (Debug.DEBUG_FILTER) { + Debug.println("GREATER(" + floatval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return Float.compare(floatval, floatval2) >= 0; + } + case LESS : { + if (Debug.DEBUG_FILTER) { + Debug.println("LESS(" + floatval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return Float.compare(floatval, floatval2) <= 0; + } + } + + return false; + } + + private boolean compare_Double(int operation, double doubleval, Object value2) { + if (operation == SUBSTRING) { + if (Debug.DEBUG_FILTER) { + Debug.println("SUBSTRING(" + doubleval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return false; + } + + double doubleval2; + try { + doubleval2 = Double.parseDouble(((String) value2).trim()); + } catch (IllegalArgumentException e) { + return false; + } + switch (operation) { + case EQUAL : { + if (Debug.DEBUG_FILTER) { + Debug.println("EQUAL(" + doubleval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return Double.compare(doubleval, doubleval2) == 0; + } + case APPROX : { + if (Debug.DEBUG_FILTER) { + Debug.println("APPROX(" + doubleval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return Double.compare(doubleval, doubleval2) == 0; + } + case GREATER : { + if (Debug.DEBUG_FILTER) { + Debug.println("GREATER(" + doubleval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return Double.compare(doubleval, doubleval2) >= 0; + } + case LESS : { + if (Debug.DEBUG_FILTER) { + Debug.println("LESS(" + doubleval + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return Double.compare(doubleval, doubleval2) <= 0; + } + } + + return false; + } + + private static Object valueOf(Class<?> target, String value2) { + do { + Method method; + try { + method = target.getMethod("valueOf", String.class); //$NON-NLS-1$ + } catch (NoSuchMethodException e) { + break; + } + if (Modifier.isStatic(method.getModifiers()) && target.isAssignableFrom(method.getReturnType())) { + setAccessible(method); + try { + return method.invoke(null, value2.trim()); + } catch (IllegalAccessException e) { + return null; + } catch (InvocationTargetException e) { + return null; + } + } + } while (false); + + do { + Constructor<?> constructor; + try { + constructor = target.getConstructor(String.class); + } catch (NoSuchMethodException e) { + break; + } + setAccessible(constructor); + try { + return constructor.newInstance(value2.trim()); + } catch (IllegalAccessException e) { + return null; + } catch (InvocationTargetException e) { + return null; + } catch (InstantiationException e) { + return null; + } + } while (false); + + return null; + } + + private static void setAccessible(AccessibleObject accessible) { + if (!accessible.isAccessible()) { + AccessController.doPrivileged(new SetAccessibleAction(accessible)); + } + } + + private boolean compare_Comparable(int operation, Comparable<Object> value1, Object value2) { + if (operation == SUBSTRING) { + if (Debug.DEBUG_FILTER) { + Debug.println("SUBSTRING(" + value1 + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return false; + } + value2 = valueOf(value1.getClass(), (String) value2); + if (value2 == null) { + return false; + } + + try { + switch (operation) { + case EQUAL : { + if (Debug.DEBUG_FILTER) { + Debug.println("EQUAL(" + value1 + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return value1.compareTo(value2) == 0; + } + case APPROX : { + if (Debug.DEBUG_FILTER) { + Debug.println("APPROX(" + value1 + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return value1.compareTo(value2) == 0; + } + case GREATER : { + if (Debug.DEBUG_FILTER) { + Debug.println("GREATER(" + value1 + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return value1.compareTo(value2) >= 0; + } + case LESS : { + if (Debug.DEBUG_FILTER) { + Debug.println("LESS(" + value1 + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return value1.compareTo(value2) <= 0; + } + } + } catch (Exception e) { + // if the compareTo method throws an exception; return false + return false; + } + return false; + } + + private boolean compare_Unknown(int operation, Object value1, Object value2) { //RFC 59 + if (operation == SUBSTRING) { + if (Debug.DEBUG_FILTER) { + Debug.println("SUBSTRING(" + value1 + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return false; + } + value2 = valueOf(value1.getClass(), (String) value2); + if (value2 == null) { + return false; + } + + try { + switch (operation) { + case EQUAL : { + if (Debug.DEBUG_FILTER) { + Debug.println("EQUAL(" + value1 + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return value1.equals(value2); + } + case APPROX : { + if (Debug.DEBUG_FILTER) { + Debug.println("APPROX(" + value1 + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return value1.equals(value2); + } + case GREATER : { + if (Debug.DEBUG_FILTER) { + Debug.println("GREATER(" + value1 + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return value1.equals(value2); + } + case LESS : { + if (Debug.DEBUG_FILTER) { + Debug.println("LESS(" + value1 + "," + value2 + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + return value1.equals(value2); + } + } + } catch (Exception e) { + // if the equals method throws an exception; return false + return false; + } + + return false; + } + + /** + * Map a string for an APPROX (~=) comparison. + * + * This implementation removes white spaces. + * This is the minimum implementation allowed by + * the OSGi spec. + * + * @param input Input string. + * @return String ready for APPROX comparison. + */ + private static String approxString(String input) { + boolean changed = false; + char[] output = input.toCharArray(); + int cursor = 0; + for (char c : output) { + if (Character.isWhitespace(c)) { + changed = true; + continue; + } + + output[cursor] = c; + cursor++; + } + + return changed ? new String(output, 0, cursor) : input; + } + + /** + * Returns the leftmost required objectClass value for the filter to evaluate to true. + * + * @return The leftmost required objectClass value or null if none could be determined. + */ + public String getRequiredObjectClass() { + return getPrimaryKeyValue(Constants.OBJECTCLASS); + } + + /** + * Returns the leftmost required primary key value for the filter to evaluate to true. + * This is useful for indexing candidates to match against this filter. + * @param primaryKey the primary key + * @return The leftmost required primary key value or null if none could be determined. + */ + public String getPrimaryKeyValue(String primaryKey) { + // just checking for simple filters here where primaryKey is the only attr or it is one attr of a base '&' clause + // (primaryKey=org.acme.BrickService) OK + // (&(primaryKey=org.acme.BrickService)(|(vendor=IBM)(vendor=SUN))) OK + // (primaryKey=org.acme.*) NOT OK + // (|(primaryKey=org.acme.BrickService)(primaryKey=org.acme.CementService)) NOT OK + // (&(primaryKey=org.acme.BrickService)(primaryKey=org.acme.CementService)) OK but only the first objectClass is returned + switch (op) { + case EQUAL : + if (attr.equalsIgnoreCase(primaryKey) && (value instanceof String)) + return (String) value; + break; + case AND : + FilterImpl[] clauses = (FilterImpl[]) value; + for (FilterImpl clause : clauses) + if (clause.op == EQUAL) { + String result = clause.getPrimaryKeyValue(primaryKey); + if (result != null) + return result; + } + break; + } + return null; + } + + /** + * Returns all the attributes contained within this filter + * @return all the attributes contained within this filter + */ + public String[] getAttributes() { + List<String> results = new ArrayList<String>(); + getAttributesInternal(results); + return results.toArray(new String[results.size()]); + } + + private void getAttributesInternal(List<String> results) { + if (value instanceof FilterImpl[]) { + FilterImpl[] children = (FilterImpl[]) value; + for (FilterImpl child : children) + child.getAttributesInternal(results); + return; + } else if (value instanceof FilterImpl) { + // The NOT operation only has one child filter (bug 188075) + FilterImpl child = ((FilterImpl) value); + child.getAttributesInternal(results); + return; + } + if (attr != null) + results.add(attr); + } + + /** + * Parser class for OSGi filter strings. This class parses + * the complete filter string and builds a tree of Filter + * objects rooted at the parent. + */ + private static class Parser { + private final String filterstring; + private final char[] filterChars; + private int pos; + + Parser(String filterstring) { + this.filterstring = filterstring; + filterChars = filterstring.toCharArray(); + pos = 0; + } + + FilterImpl parse() throws InvalidSyntaxException { + FilterImpl filter; + try { + filter = parse_filter(); + } catch (ArrayIndexOutOfBoundsException e) { + throw new InvalidSyntaxException(Msg.FILTER_TERMINATED_ABRUBTLY, filterstring); + } + + if (pos != filterChars.length) { + throw new InvalidSyntaxException(NLS.bind(Msg.FILTER_TRAILING_CHARACTERS, filterstring.substring(pos)), filterstring); + } + return filter; + } + + private FilterImpl parse_filter() throws InvalidSyntaxException { + FilterImpl filter; + skipWhiteSpace(); + + if (filterChars[pos] != '(') { + throw new InvalidSyntaxException(NLS.bind(Msg.FILTER_MISSING_LEFTPAREN, filterstring.substring(pos)), filterstring); + } + + pos++; + + filter = parse_filtercomp(); + + skipWhiteSpace(); + + if (filterChars[pos] != ')') { + throw new InvalidSyntaxException(NLS.bind(Msg.FILTER_MISSING_RIGHTPAREN, filterstring.substring(pos)), filterstring); + } + + pos++; + + skipWhiteSpace(); + + return filter; + } + + private FilterImpl parse_filtercomp() throws InvalidSyntaxException { + skipWhiteSpace(); + + char c = filterChars[pos]; + + switch (c) { + case '&' : { + pos++; + return parse_and(); + } + case '|' : { + pos++; + return parse_or(); + } + case '!' : { + pos++; + return parse_not(); + } + } + return parse_item(); + } + + private FilterImpl parse_and() throws InvalidSyntaxException { + int lookahead = pos; + skipWhiteSpace(); + + if (filterChars[pos] != '(') { + pos = lookahead - 1; + return parse_item(); + } + + List<FilterImpl> operands = new ArrayList<FilterImpl>(10); + + while (filterChars[pos] == '(') { + FilterImpl child = parse_filter(); + operands.add(child); + } + + return new FilterImpl(FilterImpl.AND, null, operands.toArray(new FilterImpl[operands.size()])); + } + + private FilterImpl parse_or() throws InvalidSyntaxException { + int lookahead = pos; + skipWhiteSpace(); + + if (filterChars[pos] != '(') { + pos = lookahead - 1; + return parse_item(); + } + + List<FilterImpl> operands = new ArrayList<FilterImpl>(10); + + while (filterChars[pos] == '(') { + FilterImpl child = parse_filter(); + operands.add(child); + } + + return new FilterImpl(FilterImpl.OR, null, operands.toArray(new FilterImpl[operands.size()])); + } + + private FilterImpl parse_not() throws InvalidSyntaxException { + int lookahead = pos; + skipWhiteSpace(); + + if (filterChars[pos] != '(') { + pos = lookahead - 1; + return parse_item(); + } + + FilterImpl child = parse_filter(); + + return new FilterImpl(FilterImpl.NOT, null, child); + } + + private FilterImpl parse_item() throws InvalidSyntaxException { + String attr = parse_attr(); + + skipWhiteSpace(); + + switch (filterChars[pos]) { + case '~' : { + if (filterChars[pos + 1] == '=') { + pos += 2; + return new FilterImpl(FilterImpl.APPROX, attr, parse_value()); + } + break; + } + case '>' : { + if (filterChars[pos + 1] == '=') { + pos += 2; + return new FilterImpl(FilterImpl.GREATER, attr, parse_value()); + } + break; + } + case '<' : { + if (filterChars[pos + 1] == '=') { + pos += 2; + return new FilterImpl(FilterImpl.LESS, attr, parse_value()); + } + break; + } + case '=' : { + if (filterChars[pos + 1] == '*') { + int oldpos = pos; + pos += 2; + skipWhiteSpace(); + if (filterChars[pos] == ')') { + return new FilterImpl(FilterImpl.PRESENT, attr, null); + } + pos = oldpos; + } + + pos++; + Object string = parse_substring(); + + if (string instanceof String) { + return new FilterImpl(FilterImpl.EQUAL, attr, string); + } + return new FilterImpl(FilterImpl.SUBSTRING, attr, string); + } + } + + throw new InvalidSyntaxException(NLS.bind(Msg.FILTER_INVALID_OPERATOR, filterstring.substring(pos)), filterstring); + } + + private String parse_attr() throws InvalidSyntaxException { + skipWhiteSpace(); + + int begin = pos; + int end = pos; + + char c = filterChars[pos]; + + while (c != '~' && c != '<' && c != '>' && c != '=' && c != '(' && c != ')') { + pos++; + + if (!Character.isWhitespace(c)) { + end = pos; + } + + c = filterChars[pos]; + } + + int length = end - begin; + + if (length == 0) { + throw new InvalidSyntaxException(NLS.bind(Msg.FILTER_MISSING_ATTR, filterstring.substring(pos)), filterstring); + } + + return new String(filterChars, begin, length); + } + + private String parse_value() throws InvalidSyntaxException { + StringBuffer sb = new StringBuffer(filterChars.length - pos); + + parseloop: while (true) { + char c = filterChars[pos]; + + switch (c) { + case ')' : { + break parseloop; + } + + case '(' : { + throw new InvalidSyntaxException(NLS.bind(Msg.FILTER_INVALID_VALUE, filterstring.substring(pos)), filterstring); + } + + case '\\' : { + pos++; + c = filterChars[pos]; + /* fall through into default */ + } + + default : { + sb.append(c); + pos++; + break; + } + } + } + + if (sb.length() == 0) { + throw new InvalidSyntaxException(NLS.bind(Msg.FILTER_MISSING_VALUE, filterstring.substring(pos)), filterstring); + } + + return sb.toString(); + } + + private Object parse_substring() throws InvalidSyntaxException { + StringBuffer sb = new StringBuffer(filterChars.length - pos); + + List<String> operands = new ArrayList<String>(10); + + parseloop: while (true) { + char c = filterChars[pos]; + + switch (c) { + case ')' : { + if (sb.length() > 0) { + operands.add(sb.toString()); + } + + break parseloop; + } + + case '(' : { + throw new InvalidSyntaxException(NLS.bind(Msg.FILTER_INVALID_VALUE, filterstring.substring(pos)), filterstring); + } + + case '*' : { + if (sb.length() > 0) { + operands.add(sb.toString()); + } + + sb.setLength(0); + + operands.add(null); + pos++; + + break; + } + + case '\\' : { + pos++; + c = filterChars[pos]; + /* fall through into default */ + } + + default : { + sb.append(c); + pos++; + break; + } + } + } + + int size = operands.size(); + + if (size == 0) { + return ""; //$NON-NLS-1$ + } + + if (size == 1) { + Object single = operands.get(0); + + if (single != null) { + return single; + } + } + + return operands.toArray(new String[size]); + } + + private void skipWhiteSpace() { + for (int length = filterChars.length; (pos < length) && Character.isWhitespace(filterChars[pos]);) { + pos++; + } + } + } + + /** + * This Dictionary is used for key lookup from a ServiceReference during + * filter evaluation. This Dictionary implementation only supports the get + * operation using a String key as no other operations are used by the + * Filter implementation. + * + */ + private static class ServiceReferenceDictionary extends Dictionary<String, Object> { + private final ServiceReference<?> reference; + + ServiceReferenceDictionary(ServiceReference<?> reference) { + this.reference = reference; + } + + public Object get(Object key) { + if (reference == null) { + return null; + } + return reference.getProperty((String) key); + } + + public boolean isEmpty() { + throw new UnsupportedOperationException(); + } + + public Enumeration<String> keys() { + throw new UnsupportedOperationException(); + } + + public Enumeration<Object> elements() { + throw new UnsupportedOperationException(); + } + + public Object put(String key, Object value) { + throw new UnsupportedOperationException(); + } + + public Object remove(Object key) { + throw new UnsupportedOperationException(); + } + + public int size() { + throw new UnsupportedOperationException(); + } + } + + private static class SetAccessibleAction implements PrivilegedAction<Object> { + private final AccessibleObject accessible; + + SetAccessibleAction(AccessibleObject accessible) { + this.accessible = accessible; + } + + public Object run() { + accessible.setAccessible(true); + return null; + } + } +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/Framework.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/Framework.java new file mode 100644 index 000000000..d3aeaf645 --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/Framework.java @@ -0,0 +1,2007 @@ +/******************************************************************************* + * Copyright (c) 2003, 2012 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.framework.internal.core; + +import java.io.*; +import java.lang.reflect.*; +import java.net.*; +import java.security.*; +import java.util.*; +import org.eclipse.core.runtime.internal.adaptor.ContextFinder; +import org.eclipse.osgi.baseadaptor.BaseAdaptor; +import org.eclipse.osgi.framework.adaptor.*; +import org.eclipse.osgi.framework.debug.Debug; +import org.eclipse.osgi.framework.eventmgr.*; +import org.eclipse.osgi.framework.internal.protocol.ContentHandlerFactory; +import org.eclipse.osgi.framework.internal.protocol.StreamHandlerFactory; +import org.eclipse.osgi.framework.log.FrameworkLogEntry; +import org.eclipse.osgi.framework.util.SecureAction; +import org.eclipse.osgi.internal.loader.*; +import org.eclipse.osgi.internal.permadmin.EquinoxSecurityManager; +import org.eclipse.osgi.internal.permadmin.SecurityAdmin; +import org.eclipse.osgi.internal.profile.Profile; +import org.eclipse.osgi.internal.serviceregistry.*; +import org.eclipse.osgi.signedcontent.SignedContentFactory; +import org.eclipse.osgi.util.ManifestElement; +import org.eclipse.osgi.util.NLS; +import org.osgi.framework.*; +import org.osgi.framework.hooks.bundle.*; +import org.osgi.util.tracker.ServiceTracker; + +/** + * Core OSGi Framework class. + */ +public class Framework implements EventPublisher, Runnable { + // System property used to set the context classloader parent classloader type (ccl is the default) + private static final String PROP_CONTEXTCLASSLOADER_PARENT = "osgi.contextClassLoaderParent"; //$NON-NLS-1$ + private static final String CONTEXTCLASSLOADER_PARENT_APP = "app"; //$NON-NLS-1$ + private static final String CONTEXTCLASSLOADER_PARENT_EXT = "ext"; //$NON-NLS-1$ + private static final String CONTEXTCLASSLOADER_PARENT_BOOT = "boot"; //$NON-NLS-1$ + private static final String CONTEXTCLASSLOADER_PARENT_FWK = "fwk"; //$NON-NLS-1$ + + public static final String PROP_FRAMEWORK_THREAD = "osgi.framework.activeThreadType"; //$NON-NLS-1$ + public static final String THREAD_NORMAL = "normal"; //$NON-NLS-1$ + public static final String PROP_EQUINOX_SECURITY = "eclipse.security"; //$NON-NLS-1$ + public static final String SECURITY_OSGI = "osgi"; //$NON-NLS-1$ + + private static String J2SE = "J2SE-"; //$NON-NLS-1$ + private static String JAVASE = "JavaSE-"; //$NON-NLS-1$ + private static String PROFILE_EXT = ".profile"; //$NON-NLS-1$ + /** FrameworkAdaptor specific functions. */ + protected FrameworkAdaptor adaptor; + /** Framework properties object. A reference to the + * System.getProperies() object. The properties from + * the adaptor will be merged into these properties. + */ + protected Properties properties; + /** Has the framework been started */ + protected boolean active; + /** Event indicating the reason for shutdown*/ + private FrameworkEvent[] shutdownEvent; + /** The bundles installed in the framework */ + protected BundleRepository bundles; + /** Package Admin object. This object manages the exported packages. */ + protected PackageAdminImpl packageAdmin; + /** PermissionAdmin and ConditionalPermissionAdmin impl. This object manages the bundle permissions. */ + protected SecurityAdmin securityAdmin; + /** Startlevel object. This object manages the framework and bundle startlevels */ + protected StartLevelManager startLevelManager; + /** The ServiceRegistry */ + private ServiceRegistry serviceRegistry; + private final int BSN_VERSION; + private static final int BSN_VERSION_SINGLE = 1; + private static final int BSN_VERSION_MULTIPLE = 2; + private static final int BSN_VERSION_MANAGED = 3; + + /* + * The following maps objects keep track of event listeners + * by BundleContext. Each element is a Map that is the set + * of event listeners for a particular BundleContext. The max number of + * elements each of the following maps will have is the number of bundles + * installed in the Framework. + */ + // Map of BundleContexts for bundle's BundleListeners. + private final Map<BundleContextImpl, CopyOnWriteIdentityMap<BundleListener, BundleListener>> allBundleListeners = new HashMap<BundleContextImpl, CopyOnWriteIdentityMap<BundleListener, BundleListener>>(); + protected static final int BUNDLEEVENT = 1; + // Map of BundleContexts for bundle's SynchronousBundleListeners. + private final Map<BundleContextImpl, CopyOnWriteIdentityMap<SynchronousBundleListener, SynchronousBundleListener>> allSyncBundleListeners = new HashMap<BundleContextImpl, CopyOnWriteIdentityMap<SynchronousBundleListener, SynchronousBundleListener>>(); + protected static final int BUNDLEEVENTSYNC = 2; + /* SERVICEEVENT(3) is now handled by ServiceRegistry */ + // Map of BundleContexts for bundle's FrameworkListeners. + private final Map<BundleContextImpl, CopyOnWriteIdentityMap<FrameworkListener, FrameworkListener>> allFrameworkListeners = new HashMap<BundleContextImpl, CopyOnWriteIdentityMap<FrameworkListener, FrameworkListener>>(); + protected static final int FRAMEWORKEVENT = 4; + protected static final int BATCHEVENT_BEGIN = Integer.MIN_VALUE + 1; + protected static final int BATCHEVENT_END = Integer.MIN_VALUE; + static final String eventHookName = EventHook.class.getName(); + static final String findHookName = FindHook.class.getName(); + static final String collisionHookName = CollisionHook.class.getName(); + /** EventManager for event delivery. */ + protected EventManager eventManager; + /* Reservation object for install synchronization */ + private Map<String, Thread> installLock; + /** System Bundle object */ + protected InternalSystemBundle systemBundle; + private String[] bootDelegation; + private String[] bootDelegationStems; + private boolean bootDelegateAll = false; + public final boolean contextBootDelegation = "true".equals(FrameworkProperties.getProperty("osgi.context.bootdelegation", "true")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + public final boolean compatibiltyBootDelegation = "true".equals(FrameworkProperties.getProperty(Constants.OSGI_COMPATIBILITY_BOOTDELEGATION, "true")); //$NON-NLS-1$ //$NON-NLS-2$ + private final boolean allowRefreshDuplicateBSN = Boolean.TRUE.toString().equals(FrameworkProperties.getProperty(Constants.REFRESH_DUPLICATE_BSN, "true")); //$NON-NLS-1$ + ClassLoaderDelegateHook[] delegateHooks; + private volatile boolean forcedRestart = false; + /** + * The AliasMapper used to alias OS Names. + */ + protected static AliasMapper aliasMapper = new AliasMapper(); + SecureAction secureAction = AccessController.doPrivileged(SecureAction.createSecureAction()); + // cache of AdminPermissions keyed by Bundle ID + private final Map<Long, Map<String, AdminPermission>> adminPermissions = new HashMap<Long, Map<String, AdminPermission>>(); + + // we need to hold these so that we can unregister them at shutdown + private StreamHandlerFactory streamHandlerFactory; + private ContentHandlerFactory contentHandlerFactory; + + private volatile ServiceTracker<SignedContentFactory, SignedContentFactory> signedContentFactory; + private volatile ContextFinder contextFinder; + + /* + * We need to make sure that the GetDataFileAction class loads early to prevent a ClassCircularityError when checking permissions. + * see bug 161561 + */ + static { + Class<?> c; + c = GetDataFileAction.class; + c.getName(); // to prevent compiler warnings + } + + static class GetDataFileAction implements PrivilegedAction<File> { + private AbstractBundle bundle; + private String filename; + + public GetDataFileAction(AbstractBundle bundle, String filename) { + this.bundle = bundle; + this.filename = filename; + } + + public File run() { + return bundle.getBundleData().getDataFile(filename); + } + } + + /** + * Constructor for the Framework instance. This method initializes the + * framework to an unlaunched state. + * + */ + public Framework(FrameworkAdaptor adaptor) { + if (Profile.PROFILE && Profile.STARTUP) + Profile.logEnter("Framework.initialze()", null); //$NON-NLS-1$ + String bsnVersion = FrameworkProperties.getProperty(Constants.FRAMEWORK_BSNVERSION); + if (Constants.FRAMEWORK_BSNVERSION_SINGLE.equals(bsnVersion)) { + BSN_VERSION = BSN_VERSION_SINGLE; + } else if (Constants.FRAMEWORK_BSNVERSION_MULTIPLE.equals(bsnVersion)) { + BSN_VERSION = BSN_VERSION_MULTIPLE; + } else { + BSN_VERSION = BSN_VERSION_MANAGED; + } + long start = System.currentTimeMillis(); + this.adaptor = adaptor; + delegateHooks = adaptor instanceof BaseAdaptor ? ((BaseAdaptor) adaptor).getHookRegistry().getClassLoaderDelegateHooks() : null; + active = false; + installSecurityManager(); + if (Debug.DEBUG_SECURITY) { + Debug.println("SecurityManager: " + System.getSecurityManager()); //$NON-NLS-1$ + Debug.println("ProtectionDomain of Framework.class: \n" + this.getClass().getProtectionDomain()); //$NON-NLS-1$ + } + setNLSFrameworkLog(); + // initialize ContextFinder + initializeContextFinder(); + /* initialize the adaptor */ + adaptor.initialize(this); + if (Profile.PROFILE && Profile.STARTUP) + Profile.logTime("Framework.initialze()", "adapter initialized"); //$NON-NLS-1$//$NON-NLS-2$ + try { + adaptor.initializeStorage(); + } catch (IOException e) /* fatal error */{ + throw new RuntimeException(e.getMessage(), e); + } + if (Profile.PROFILE && Profile.STARTUP) + Profile.logTime("Framework.initialze()", "adapter storage initialized"); //$NON-NLS-1$//$NON-NLS-2$ + /* + * This must be done before calling any of the framework getProperty + * methods. + */ + initializeProperties(adaptor.getProperties()); + /* initialize admin objects */ + packageAdmin = new PackageAdminImpl(this); + try { + // always create security admin even with security off + securityAdmin = new SecurityAdmin(null, this, adaptor.getPermissionStorage()); + } catch (IOException e) /* fatal error */{ + e.printStackTrace(); + throw new RuntimeException(e.getMessage(), e); + } + if (Profile.PROFILE && Profile.STARTUP) + Profile.logTime("Framework.initialze()", "done init props & new PermissionAdminImpl"); //$NON-NLS-1$//$NON-NLS-2$ + startLevelManager = new StartLevelManager(this); + /* create the event manager and top level event dispatchers */ + eventManager = new EventManager("Framework Event Dispatcher"); //$NON-NLS-1$ + if (Profile.PROFILE && Profile.STARTUP) + Profile.logTime("Framework.initialze()", "done new EventManager"); //$NON-NLS-1$ //$NON-NLS-2$ + /* create the service registry */ + serviceRegistry = new ServiceRegistry(this); + // Initialize the installLock; there is no way of knowing + // what the initial size should be, at most it will be the number + // of threads trying to install a bundle (probably a very low number). + installLock = new HashMap<String, Thread>(10); + /* create the system bundle */ + createSystemBundle(); + loadVMProfile(); // load VM profile after the system bundle has been created + setBootDelegation(); //set boot delegation property after system exports have been set + if (Profile.PROFILE && Profile.STARTUP) + Profile.logTime("Framework.initialze()", "done createSystemBundle"); //$NON-NLS-1$ //$NON-NLS-2$ + /* install URLStreamHandlerFactory */ + installURLStreamHandlerFactory(systemBundle.context, adaptor); + /* install ContentHandlerFactory for OSGi URLStreamHandler support */ + installContentHandlerFactory(systemBundle.context, adaptor); + if (Profile.PROFILE && Profile.STARTUP) + Profile.logTime("Framework.initialze()", "done new URLStream/Content HandlerFactory"); //$NON-NLS-1$//$NON-NLS-2$ + /* create bundle objects for all installed bundles. */ + BundleData[] bundleDatas = adaptor.getInstalledBundles(); + bundles = new BundleRepository(bundleDatas == null ? 10 : bundleDatas.length + 1); + /* add the system bundle to the Bundle Repository */ + bundles.add(systemBundle); + if (bundleDatas != null) { + for (int i = 0; i < bundleDatas.length; i++) { + try { + AbstractBundle bundle = AbstractBundle.createBundle(bundleDatas[i], this, true); + bundles.add(bundle); + } catch (BundleException be) { + // This is not a fatal error. Publish the framework event. + publishFrameworkEvent(FrameworkEvent.ERROR, systemBundle, be); + } + } + } + if (Debug.DEBUG_GENERAL) + System.out.println("Initialize the framework: " + (System.currentTimeMillis() - start)); //$NON-NLS-1$ + if (Profile.PROFILE && Profile.STARTUP) + Profile.logExit("Framework.initialize()"); //$NON-NLS-1$ + } + + public FrameworkAdaptor getAdaptor() { + return adaptor; + } + + public ClassLoaderDelegateHook[] getDelegateHooks() { + return delegateHooks; + } + + public ServiceRegistry getServiceRegistry() { + return serviceRegistry; + } + + private void setNLSFrameworkLog() { + try { + Field frameworkLogField = NLS.class.getDeclaredField("frameworkLog"); //$NON-NLS-1$ + frameworkLogField.setAccessible(true); + frameworkLogField.set(null, adaptor.getFrameworkLog()); + } catch (Exception e) { + adaptor.getFrameworkLog().log(new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.ERROR, 0, e.getMessage(), 0, e, null)); + } + } + + private void createSystemBundle() { + try { + systemBundle = new InternalSystemBundle(this); + systemBundle.getBundleData().setBundle(systemBundle); + } catch (BundleException e) { // fatal error + e.printStackTrace(); + throw new RuntimeException(NLS.bind(Msg.OSGI_SYSTEMBUNDLE_CREATE_EXCEPTION, e.getMessage()), e); + } + } + + /** + * Initialize the System properties by copying properties from the adaptor + * properties object. This method is called by the initialize method. + * + */ + protected void initializeProperties(Properties adaptorProperties) { + properties = FrameworkProperties.getProperties(); + Enumeration<?> enumKeys = adaptorProperties.propertyNames(); + while (enumKeys.hasMoreElements()) { + String key = (String) enumKeys.nextElement(); + if (properties.getProperty(key) == null) { + properties.put(key, adaptorProperties.getProperty(key)); + } + } + properties.put(Constants.FRAMEWORK_VENDOR, Constants.OSGI_FRAMEWORK_VENDOR); + properties.put(Constants.FRAMEWORK_VERSION, Constants.OSGI_FRAMEWORK_VERSION); + String value = properties.getProperty(Constants.FRAMEWORK_PROCESSOR); + if (value == null) { + value = properties.getProperty(Constants.JVM_OS_ARCH); + if (value != null) { + properties.put(Constants.FRAMEWORK_PROCESSOR, aliasMapper.aliasProcessor(value)); + } + } + value = properties.getProperty(Constants.FRAMEWORK_OS_NAME); + if (value == null) { + value = properties.getProperty(Constants.JVM_OS_NAME); + try { + String canonicalValue = (String) aliasMapper.aliasOSName(value); + if (canonicalValue != null) { + value = canonicalValue; + } + } catch (ClassCastException ex) { + //A vector was returned from the alias mapper. + //The alias mapped to more than one canonical value + //such as "win32" for example + } + if (value != null) { + properties.put(Constants.FRAMEWORK_OS_NAME, value); + } + } + value = properties.getProperty(Constants.FRAMEWORK_OS_VERSION); + if (value == null) { + value = properties.getProperty(Constants.JVM_OS_VERSION); + if (value != null) { + // only use the value upto the first space + int space = value.indexOf(' '); + if (space > 0) { + value = value.substring(0, space); + } + // fix up cases where the os version does not make a valid Version string. + int major = 0, minor = 0, micro = 0; + String qualifier = ""; //$NON-NLS-1$ + try { + StringTokenizer st = new StringTokenizer(value, ".", true); //$NON-NLS-1$ + major = parseVersionInt(st.nextToken()); + + if (st.hasMoreTokens()) { + st.nextToken(); // consume delimiter + minor = parseVersionInt(st.nextToken()); + + if (st.hasMoreTokens()) { + st.nextToken(); // consume delimiter + micro = parseVersionInt(st.nextToken()); + + if (st.hasMoreTokens()) { + st.nextToken(); // consume delimiter + qualifier = st.nextToken(); + } + } + } + } catch (NoSuchElementException e) { + // ignore, use the values parsed so far + } + try { + value = new Version(major, minor, micro, qualifier).toString(); + } catch (IllegalArgumentException e) { + // must be an invalid qualifier; just ignore it + value = new Version(major, minor, micro).toString(); + } + properties.put(Constants.FRAMEWORK_OS_VERSION, value); + } + } + value = properties.getProperty(Constants.FRAMEWORK_LANGUAGE); + if (value == null) + // set the value of the framework language property + properties.put(Constants.FRAMEWORK_LANGUAGE, Locale.getDefault().getLanguage()); + // set the support properties for fragments and require-bundle (bug 173090) + properties.put(Constants.SUPPORTS_FRAMEWORK_FRAGMENT, "true"); //$NON-NLS-1$ + properties.put(Constants.SUPPORTS_FRAMEWORK_REQUIREBUNDLE, "true"); //$NON-NLS-1$ + properties.put(Constants.FRAMEWORK_UUID, new UniversalUniqueIdentifier().toString()); + } + + private int parseVersionInt(String value) { + try { + return Integer.parseInt(value); + } catch (NumberFormatException e) { + // try up to the first non-number char + StringBuffer sb = new StringBuffer(value.length()); + char[] chars = value.toCharArray(); + for (int i = 0; i < chars.length; i++) { + if (!Character.isDigit(chars[i])) + break; + sb.append(chars[i]); + } + if (sb.length() > 0) + return Integer.parseInt(sb.toString()); + return 0; + } + } + + private void setBootDelegation() { + // set the boot delegation according to the osgi boot delegation property + String bootDelegationProp = properties.getProperty(Constants.FRAMEWORK_BOOTDELEGATION); + if (bootDelegationProp == null) + return; + if (bootDelegationProp.trim().length() == 0) + return; + String[] bootPackages = ManifestElement.getArrayFromList(bootDelegationProp); + List<String> exactMatch = new ArrayList<String>(bootPackages.length); + List<String> stemMatch = new ArrayList<String>(bootPackages.length); + for (int i = 0; i < bootPackages.length; i++) { + if (bootPackages[i].equals("*")) { //$NON-NLS-1$ + bootDelegateAll = true; + return; + } else if (bootPackages[i].endsWith("*")) { //$NON-NLS-1$ + if (bootPackages[i].length() > 2 && bootPackages[i].endsWith(".*")) //$NON-NLS-1$ + stemMatch.add(bootPackages[i].substring(0, bootPackages[i].length() - 1)); + } else { + exactMatch.add(bootPackages[i]); + } + } + if (!exactMatch.isEmpty()) + bootDelegation = exactMatch.toArray(new String[exactMatch.size()]); + if (!stemMatch.isEmpty()) + bootDelegationStems = stemMatch.toArray(new String[stemMatch.size()]); + } + + @SuppressWarnings("deprecation") + private void loadVMProfile() { + Properties profileProps = findVMProfile(); + String systemExports = properties.getProperty(Constants.FRAMEWORK_SYSTEMPACKAGES); + // set the system exports property using the vm profile; only if the property is not already set + if (systemExports == null) { + systemExports = profileProps.getProperty(Constants.FRAMEWORK_SYSTEMPACKAGES); + if (systemExports != null) + properties.put(Constants.FRAMEWORK_SYSTEMPACKAGES, systemExports); + } + // set the org.osgi.framework.bootdelegation property according to the java profile + String type = properties.getProperty(Constants.OSGI_JAVA_PROFILE_BOOTDELEGATION); // a null value means ignore + String profileBootDelegation = profileProps.getProperty(Constants.FRAMEWORK_BOOTDELEGATION); + if (Constants.OSGI_BOOTDELEGATION_OVERRIDE.equals(type)) { + if (profileBootDelegation == null) + properties.remove(Constants.FRAMEWORK_BOOTDELEGATION); // override with a null value + else + properties.put(Constants.FRAMEWORK_BOOTDELEGATION, profileBootDelegation); // override with the profile value + } else if (Constants.OSGI_BOOTDELEGATION_NONE.equals(type)) + properties.remove(Constants.FRAMEWORK_BOOTDELEGATION); // remove the bootdelegation property in case it was set + // set the org.osgi.framework.executionenvironment property according to the java profile + if (properties.getProperty(Constants.FRAMEWORK_EXECUTIONENVIRONMENT) == null) { + // get the ee from the java profile; if no ee is defined then try the java profile name + String ee = profileProps.getProperty(Constants.FRAMEWORK_EXECUTIONENVIRONMENT, profileProps.getProperty(Constants.OSGI_JAVA_PROFILE_NAME)); + if (ee != null) + properties.put(Constants.FRAMEWORK_EXECUTIONENVIRONMENT, ee); + } + // set the org.osgi.framework.system.capabilities property according to the java profile + if (properties.getProperty(Constants.FRAMEWORK_SYSTEMCAPABILITIES) == null) { + String systemCapabilities = profileProps.getProperty(Constants.FRAMEWORK_SYSTEMCAPABILITIES); + if (systemCapabilities != null) + properties.put(Constants.FRAMEWORK_SYSTEMCAPABILITIES, systemCapabilities); + } + } + + private Properties findVMProfile() { + Properties result = new Properties(); + // Find the VM profile name using J2ME properties + String j2meConfig = properties.getProperty(Constants.J2ME_MICROEDITION_CONFIGURATION); + String j2meProfiles = properties.getProperty(Constants.J2ME_MICROEDITION_PROFILES); + String vmProfile = null; + String javaEdition = null; + Version javaVersion = null; + if (j2meConfig != null && j2meConfig.length() > 0 && j2meProfiles != null && j2meProfiles.length() > 0) { + // save the vmProfile based off of the config and profile + // use the last profile; assuming that is the highest one + String[] j2meProfileList = ManifestElement.getArrayFromList(j2meProfiles, " "); //$NON-NLS-1$ + if (j2meProfileList != null && j2meProfileList.length > 0) + vmProfile = j2meConfig + '_' + j2meProfileList[j2meProfileList.length - 1]; + } else { + // No J2ME properties; use J2SE properties + // Note that the CDC spec appears not to require VM implementations to set the + // javax.microedition properties!! So we will try to fall back to the + // java.specification.name property, but this is pretty ridiculous!! + String javaSpecVersion = properties.getProperty("java.specification.version"); //$NON-NLS-1$ + // set the profile and EE based off of the java.specification.version + // TODO We assume J2ME Foundation and J2SE here. need to support other profiles J2EE ... + if (javaSpecVersion != null) { + StringTokenizer st = new StringTokenizer(javaSpecVersion, " _-"); //$NON-NLS-1$ + javaSpecVersion = st.nextToken(); + String javaSpecName = properties.getProperty("java.specification.name"); //$NON-NLS-1$ + // See bug 291269 we check for Foundation Specification and Foundation Profile Specification + if (javaSpecName != null && (javaSpecName.indexOf("Foundation Specification") >= 0 || javaSpecName.indexOf("Foundation Profile Specification") >= 0)) //$NON-NLS-1$ //$NON-NLS-2$ + vmProfile = "CDC-" + javaSpecVersion + "_Foundation-" + javaSpecVersion; //$NON-NLS-1$ //$NON-NLS-2$ + else { + // look for JavaSE if 1.6 or greater; otherwise look for J2SE + Version v16 = new Version("1.6"); //$NON-NLS-1$ + javaEdition = J2SE; + try { + javaVersion = new Version(javaSpecVersion); + if (v16.compareTo(javaVersion) <= 0) + javaEdition = JAVASE; + } catch (IllegalArgumentException e) { + // do nothing + } + vmProfile = javaEdition + javaSpecVersion; + } + } + } + URL url = null; + // check for the java profile property for a url + String propJavaProfile = FrameworkProperties.getProperty(Constants.OSGI_JAVA_PROFILE); + if (propJavaProfile != null) + try { + // we assume a URL + url = new URL(propJavaProfile); + } catch (MalformedURLException e1) { + // try using a relative path in the system bundle + url = findInSystemBundle(propJavaProfile); + } + if (url == null && vmProfile != null) { + // look for a profile in the system bundle based on the vm profile + String javaProfile = vmProfile + PROFILE_EXT; + url = findInSystemBundle(javaProfile); + if (url == null) + url = getNextBestProfile(javaEdition, javaVersion); + } + if (url == null) + // the profile url is still null then use the osgi min profile in OSGi by default + url = findInSystemBundle("OSGi_Minimum-1.2.profile"); //$NON-NLS-1$ + if (url != null) { + InputStream in = null; + try { + in = url.openStream(); + result.load(new BufferedInputStream(in)); + } catch (IOException e) { + // TODO consider logging ... + } finally { + if (in != null) + try { + in.close(); + } catch (IOException ee) { + // do nothing + } + } + } + // set the profile name if it does not provide one + if (result.getProperty(Constants.OSGI_JAVA_PROFILE_NAME) == null) + if (vmProfile != null) + result.put(Constants.OSGI_JAVA_PROFILE_NAME, vmProfile.replace('_', '/')); + else + // last resort; default to the absolute minimum profile name for the framework + result.put(Constants.OSGI_JAVA_PROFILE_NAME, "OSGi/Minimum-1.2"); //$NON-NLS-1$ + return result; + } + + private URL getNextBestProfile(String javaEdition, Version javaVersion) { + if (javaVersion == null || (javaEdition != J2SE && javaEdition != JAVASE)) + return null; // we cannot automatically choose the next best profile unless this is a J2SE or JavaSE vm + URL bestProfile = findNextBestProfile(javaEdition, javaVersion); + if (bestProfile == null && javaEdition == JAVASE) + // if this is a JavaSE VM then search for a lower J2SE profile + bestProfile = findNextBestProfile(J2SE, javaVersion); + return bestProfile; + } + + private URL findNextBestProfile(String javaEdition, Version javaVersion) { + URL result = null; + int minor = javaVersion.getMinor(); + do { + result = findInSystemBundle(javaEdition + javaVersion.getMajor() + "." + minor + PROFILE_EXT); //$NON-NLS-1$ + minor = minor - 1; + } while (result == null && minor > 0); + return result; + } + + private URL findInSystemBundle(String entry) { + URL result = systemBundle.getEntry0(entry); + if (result == null) { + // Check the ClassLoader in case we're launched off the Java boot classpath + ClassLoader loader = getClass().getClassLoader(); + result = loader == null ? ClassLoader.getSystemResource(entry) : loader.getResource(entry); + } + return result; + } + + /** + * This method return the state of the framework. + * + */ + protected boolean isActive() { + return (active); + } + + /** + * This method is called to destory the framework instance. + * + */ + public synchronized void close() { + if (adaptor == null) + return; + if (active) + shutdown(FrameworkEvent.STOPPED); + + synchronized (bundles) { + List<AbstractBundle> allBundles = bundles.getBundles(); + int size = allBundles.size(); + for (int i = 0; i < size; i++) { + AbstractBundle bundle = allBundles.get(i); + bundle.close(); + } + bundles.removeAllBundles(); + } + serviceRegistry = null; + allBundleListeners.clear(); + allSyncBundleListeners.clear(); + allFrameworkListeners.clear(); + if (eventManager != null) { + eventManager.close(); + eventManager = null; + } + secureAction = null; + packageAdmin = null; + adaptor = null; + uninstallURLStreamHandlerFactory(); + uninstallContentHandlerFactory(); + if (System.getSecurityManager() instanceof EquinoxSecurityManager) + System.setSecurityManager(null); + } + + /** + * Start the framework. + * + * When the framework is started. The following actions occur: 1. Event + * handling is enabled. Events can now be delivered to listeners. 2. All + * bundles which are recorded as started are started as described in the + * Bundle.start() method. These bundles are the bundles that were started + * when the framework was last stopped. Reports any exceptions that occur + * during startup using FrameworkEvents. 3. A FrameworkEvent of type + * FrameworkEvent.STARTED is broadcast. + * + */ + public synchronized void launch() { + /* Return if framework already started */ + if (active) { + return; + } + /* mark framework as started */ + active = true; + shutdownEvent = new FrameworkEvent[1]; + if (THREAD_NORMAL.equals(FrameworkProperties.getProperty(PROP_FRAMEWORK_THREAD, THREAD_NORMAL))) { + Thread fwkThread = new Thread(this, "Framework Active Thread"); //$NON-NLS-1$ + fwkThread.setDaemon(false); + fwkThread.start(); + } + /* Resume systembundle */ + if (Debug.DEBUG_GENERAL) { + Debug.println("Trying to launch framework"); //$NON-NLS-1$ + } + systemBundle.resume(); + signedContentFactory = new ServiceTracker<SignedContentFactory, SignedContentFactory>(systemBundle.getBundleContext(), SignedContentFactory.class.getName(), null); + signedContentFactory.open(); + } + + /** + * Stop the framework. + * + * When the framework is stopped. The following actions occur: 1. Suspend + * all started bundles as described in the Bundle.stop method except that + * the bundle is recorded as started. These bundles will be restarted when + * the framework is next started. Reports any exceptions that occur during + * stopping using FrameworkEvents. 2. Event handling is disabled. + * + */ + public synchronized void shutdown(int eventType) { + /* Return if framework already stopped */ + if (!active) + return; + this.shutdownEvent[0] = new FrameworkEvent(eventType, systemBundle, null); + /* + * set the state of the System Bundle to STOPPING. + * this must be done first according to section 4.19.2 from the OSGi R3 spec. + */ + systemBundle.state = Bundle.STOPPING; + publishBundleEvent(BundleEvent.STOPPING, systemBundle); // need to send system bundle stopping event + /* call the FrameworkAdaptor.frameworkStopping method first */ + try { + adaptor.frameworkStopping(systemBundle.getContext()); + } catch (Throwable t) { + publishFrameworkEvent(FrameworkEvent.ERROR, systemBundle, t); + } + /* Suspend systembundle */ + if (Debug.DEBUG_GENERAL) { + Debug.println("Trying to shutdown Framework"); //$NON-NLS-1$ + } + systemBundle.suspend(); + try { + adaptor.compactStorage(); + } catch (IOException e) { + publishFrameworkEvent(FrameworkEvent.ERROR, systemBundle, e); + } + if (signedContentFactory != null) + signedContentFactory.close(); + /* mark framework as stopped */ + active = false; + notifyAll(); + } + + /** + * Create a new Bundle object. + * @param bundledata the BundleData of the Bundle to create + */ + AbstractBundle createAndVerifyBundle(int operationType, Bundle target, BundleData bundledata, boolean setBundle) throws BundleException { + // Check for a bundle already installed with the same symbolic name and version. + if (BSN_VERSION != BSN_VERSION_MULTIPLE && bundledata.getSymbolicName() != null) { + List<AbstractBundle> installedBundles = getBundleBySymbolicName(bundledata.getSymbolicName(), bundledata.getVersion()); + if (operationType == CollisionHook.UPDATING) { + installedBundles.remove(target); + } + if (BSN_VERSION == BSN_VERSION_MANAGED && !installedBundles.isEmpty()) { + notifyCollisionHooks(operationType, target, installedBundles); + } + if (!installedBundles.isEmpty()) { + Bundle installedBundle = installedBundles.iterator().next(); + String msg = NLS.bind(Msg.BUNDLE_INSTALL_SAME_UNIQUEID, new Object[] {installedBundle.getSymbolicName(), installedBundle.getVersion().toString(), installedBundle.getLocation()}); + throw new DuplicateBundleException(msg, installedBundle); + } + } + return AbstractBundle.createBundle(bundledata, this, setBundle); + } + + private class DuplicateBundleException extends BundleException implements StatusException { + private static final long serialVersionUID = 135669822846323624L; + private transient Bundle duplicate; + + public DuplicateBundleException(String msg, Bundle duplicate) { + super(msg, BundleException.DUPLICATE_BUNDLE_ERROR); + this.duplicate = duplicate; + } + + public Object getStatus() { + return duplicate; + } + + public int getStatusCode() { + return StatusException.CODE_OK; + } + + } + + /** + * Retrieve the value of the named environment property. Values are + * provided for the following properties: + * <dl> + * <dt><code>org.osgi.framework.version</code> + * <dd>The version of the framework. + * <dt><code>org.osgi.framework.vendor</code> + * <dd>The vendor of this framework implementation. + * <dt><code>org.osgi.framework.language</code> + * <dd>The language being used. See ISO 639 for possible values. + * <dt><code>org.osgi.framework.os.name</code> + * <dd>The name of the operating system of the hosting computer. + * <dt><code>org.osgi.framework.os.version</code> + * <dd>The version number of the operating system of the hosting computer. + * <dt><code>org.osgi.framework.processor</code> + * <dd>The name of the processor of the hosting computer. + * </dl> + * + * <p> + * Note: These last four properties are used by the <code>Bundle-NativeCode</code> + * manifest header's matching algorithm for selecting native code. + * + * @param key + * The name of the requested property. + * @return The value of the requested property, or <code>null</code> if + * the property is undefined. + */ + public String getProperty(String key) { + return properties.getProperty(key); + } + + /** + * Retrieve the value of the named environment property. Values are + * provided for the following properties: + * <dl> + * <dt><code>org.osgi.framework.version</code> + * <dd>The version of the framework. + * <dt><code>org.osgi.framework.vendor</code> + * <dd>The vendor of this framework implementation. + * <dt><code>org.osgi.framework.language</code> + * <dd>The language being used. See ISO 639 for possible values. + * <dt><code>org.osgi.framework.os.name</code> + * <dd>The name of the operating system of the hosting computer. + * <dt><code>org.osgi.framework.os.version</code> + * <dd>The version number of the operating system of the hosting computer. + * <dt><code>org.osgi.framework.processor</code> + * <dd>The name of the processor of the hosting computer. + * </dl> + * + * <p> + * Note: These last four properties are used by the <code>Bundle-NativeCode</code> + * manifest header's matching algorithm for selecting native code. + * + * @param key + * The name of the requested property. + * @param def + * A default value is the requested property is not present. + * @return The value of the requested property, or the default value if the + * property is undefined. + */ + protected String getProperty(String key, String def) { + return properties.getProperty(key, def); + } + + /** + * Set a system property. + * + * @param key + * The name of the property to set. + * @param value + * The value to set. + * @return The previous value of the property or null if the property was + * not previously set. + */ + protected Object setProperty(String key, String value) { + return properties.put(key, value); + } + + /** + * Install a bundle from an InputStream. + * + * @param location + * The location identifier of the bundle to install. + * @param in + * The InputStream from which the bundle will be read. If null + * then the location is used to get the bundle content. + * @return The Bundle of the installed bundle. + */ + AbstractBundle installBundle(final String location, final InputStream in, final BundleContextImpl origin) throws BundleException { + if (Debug.DEBUG_GENERAL) { + Debug.println("install from inputstream: " + location + ", " + in); //$NON-NLS-1$ //$NON-NLS-2$ + } + final AccessControlContext callerContext = AccessController.getContext(); + return installWorker(location, new PrivilegedExceptionAction<AbstractBundle>() { + public AbstractBundle run() throws BundleException { + /* Map the InputStream or location to a URLConnection */ + URLConnection source = in != null ? new BundleSource(in) : adaptor.mapLocationToURLConnection(location); + /* call the worker to install the bundle */ + return installWorkerPrivileged(location, source, callerContext, origin); + } + }, origin); + } + + /** + * Worker method to install a bundle. It obtains the reservation for the + * location and calls the specified action. + * + * @param location + * The location identifier of the bundle to install. + * @param action + * A PrivilegedExceptionAction which calls the real worker. + * @return The {@link AbstractBundle}of the installed bundle. + * @exception BundleException + * If the action throws an error. + */ + protected AbstractBundle installWorker(String location, PrivilegedExceptionAction<AbstractBundle> action, BundleContext origin) throws BundleException { + synchronized (installLock) { + while (true) { + /* Check that the bundle is not already installed. */ + AbstractBundle bundle = getBundleByLocation(location); + /* If already installed, return bundle object */ + if (bundle != null) { + Bundle visible = origin.getBundle(bundle.getBundleId()); + if (visible == null) { + BundleData data = bundle.getBundleData(); + String msg = NLS.bind(Msg.BUNDLE_INSTALL_SAME_UNIQUEID, new Object[] {data.getSymbolicName(), data.getVersion().toString(), data.getLocation()}); + throw new BundleException(msg, BundleException.REJECTED_BY_HOOK); + } + return bundle; + } + Thread current = Thread.currentThread(); + /* Check for and make reservation */ + Thread reservation = installLock.put(location, current); + /* if the location is not already reserved */ + if (reservation == null) { + /* we have made the reservation and can continue */ + break; + } + /* the location was already reserved */ + /* + * If the reservation is held by the current thread, we have + * recursed to install the same bundle! + */ + if (current.equals(reservation)) { + throw new BundleException(Msg.BUNDLE_INSTALL_RECURSION_EXCEPTION, BundleException.STATECHANGE_ERROR); + } + try { + /* wait for the reservation to be released */ + installLock.wait(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new BundleException("Thread has been interrupted while waiting for the location lock.", e); //$NON-NLS-1$ + } + } + } + /* Don't call adaptor while holding the install lock */ + try { + AbstractBundle bundle = AccessController.doPrivileged(action); + publishBundleEvent(new BundleEvent(BundleEvent.INSTALLED, bundle, origin.getBundle())); + return bundle; + } catch (PrivilegedActionException e) { + if (e.getException() instanceof RuntimeException) + throw (RuntimeException) e.getException(); + throw (BundleException) e.getException(); + } finally { + synchronized (installLock) { + /* release reservation */ + installLock.remove(location); + /* wake up all waiters */ + installLock.notifyAll(); + } + } + } + + /** + * Worker method to install a bundle. It calls the FrameworkAdaptor object + * to install the bundle in persistent storage. + * + * @param location + * The location identifier of the bundle to install. + * @param source + * The URLConnection from which the bundle will be read. + * @param callerContext + * The caller access control context + * @param origin + * The origin bundle context that is installing the the bundle + * @return The {@link AbstractBundle}of the installed bundle. + * @exception BundleException + * If the provided stream cannot be read. + */ + protected AbstractBundle installWorkerPrivileged(String location, URLConnection source, AccessControlContext callerContext, BundleContextImpl origin) throws BundleException { + BundleOperation storage = adaptor.installBundle(location, source); + final AbstractBundle bundle; + try { + BundleData bundledata = storage.begin(); + bundle = createAndVerifyBundle(CollisionHook.INSTALLING, origin.getBundle(), bundledata, true); + + BundleWatcher bundleStats = adaptor.getBundleWatcher(); + if (bundleStats != null) + bundleStats.watchBundle(bundle, BundleWatcher.START_INSTALLING); + + try { + bundle.load(); + if (System.getSecurityManager() != null) { + final boolean extension = (bundledata.getType() & (BundleData.TYPE_BOOTCLASSPATH_EXTENSION | BundleData.TYPE_FRAMEWORK_EXTENSION | BundleData.TYPE_EXTCLASSPATH_EXTENSION)) != 0; + // must check for AllPermission before allow a bundle extension to be installed + if (extension && !bundle.hasPermission(new AllPermission())) + throw new BundleException(Msg.BUNDLE_EXTENSION_PERMISSION, BundleException.SECURITY_ERROR, new SecurityException(Msg.BUNDLE_EXTENSION_PERMISSION)); + try { + AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() { + public Object run() throws Exception { + checkAdminPermission(bundle, AdminPermission.LIFECYCLE); + if (extension) // need special permission to install extension bundles + checkAdminPermission(bundle, AdminPermission.EXTENSIONLIFECYCLE); + return null; + } + }, callerContext); + } catch (PrivilegedActionException e) { + throw e.getException(); + } + } + // must add the bundle before committing (bug 330905) + bundles.add(bundle); + storage.commit(false); + } catch (Throwable error) { + bundles.remove(bundle); + synchronized (bundles) { + bundle.unload(); + } + bundle.close(); + throw error; + } finally { + if (bundleStats != null) + bundleStats.watchBundle(bundle, BundleWatcher.END_INSTALLING); + } + + } catch (Throwable t) { + try { + storage.undo(); + } catch (BundleException ee) { + publishFrameworkEvent(FrameworkEvent.ERROR, systemBundle, ee); + } + if (t instanceof SecurityException) + throw (SecurityException) t; + if (t instanceof BundleException) + throw (BundleException) t; + throw new BundleException(t.getMessage(), t); + } + return bundle; + } + + /** + * Retrieve the bundle that has the given unique identifier. + * + * @param id + * The identifier of the bundle to retrieve. + * @return A {@link AbstractBundle}object, or <code>null</code> if the + * identifier doesn't match any installed bundle. + */ + // changed visibility to gain access through the adaptor + public AbstractBundle getBundle(long id) { + synchronized (bundles) { + return bundles.getBundle(id); + } + } + + AbstractBundle getBundle(final BundleContextImpl context, long id) { + AbstractBundle bundle = getBundle(id); + // TODO we make the system bundle special because there are lots of places + // where we assume the system bundle can get all bundles + if (bundle == null || context.getBundle().getBundleId() == 0) + return bundle; + List<AbstractBundle> single = new ArrayList<AbstractBundle>(1); + single.add(bundle); + notifyFindHooks(context, single); + return single.size() == 0 ? null : bundle; + } + + public BundleContextImpl getSystemBundleContext() { + if (systemBundle == null) + return null; + return systemBundle.context; + } + + public PackageAdminImpl getPackageAdmin() { + return packageAdmin; + } + + /** + * Retrieve the bundles that has the given symbolic name and version. + * + * @param symbolicName + * The symbolic name of the bundle to retrieve + * @param version The version of the bundle to retrieve + * @return A collection of {@link AbstractBundle} that match the symbolic name and version + */ + public List<AbstractBundle> getBundleBySymbolicName(String symbolicName, Version version) { + synchronized (bundles) { + return bundles.getBundles(symbolicName, version); + } + } + + /** + * Retrieve the BundleRepository of all installed bundles. The list is + * valid at the time of the call to getBundles, but the framework is a very + * dynamic environment and bundles can be installed or uninstalled at + * anytime. + * + * @return The BundleRepository. + */ + protected BundleRepository getBundles() { + return (bundles); + } + + /** + * Retrieve a list of all installed bundles. The list is valid at the time + * of the call to getBundleAlls, but the framework is a very dynamic + * environment and bundles can be installed or uninstalled at anytime. + * + * @return An Array of {@link AbstractBundle}objects, one object per installed + * bundle. + */ + protected AbstractBundle[] getAllBundles() { + synchronized (bundles) { + List<AbstractBundle> allBundles = bundles.getBundles(); + int size = allBundles.size(); + if (size == 0) { + return (null); + } + AbstractBundle[] bundlelist = new AbstractBundle[size]; + allBundles.toArray(bundlelist); + return (bundlelist); + } + } + + AbstractBundle[] getBundles(final BundleContextImpl context) { + List<AbstractBundle> allBundles; + synchronized (bundles) { + allBundles = new ArrayList<AbstractBundle>(bundles.getBundles()); + } + notifyFindHooks(context, allBundles); + return allBundles.toArray(new AbstractBundle[allBundles.size()]); + } + + private void notifyFindHooks(final BundleContextImpl context, List<AbstractBundle> allBundles) { + final Collection<Bundle> shrinkable = new ShrinkableCollection<Bundle>(allBundles); + if (System.getSecurityManager() == null) { + notifyFindHooksPriviledged(context, shrinkable); + } else { + AccessController.doPrivileged(new PrivilegedAction<Object>() { + public Object run() { + notifyFindHooksPriviledged(context, shrinkable); + return null; + } + }); + } + } + + void notifyFindHooksPriviledged(final BundleContextImpl context, final Collection<Bundle> allBundles) { + if (Debug.DEBUG_HOOKS) { + Debug.println("notifyBundleFindHooks(" + allBundles + ")"); //$NON-NLS-1$ //$NON-NLS-2$ + } + getServiceRegistry().notifyHooksPrivileged(new HookContext() { + public void call(Object hook, ServiceRegistration<?> hookRegistration) throws Exception { + if (hook instanceof FindHook) { + ((FindHook) hook).find(context, allBundles); + } + } + + public String getHookClassName() { + return findHookName; + } + + public String getHookMethodName() { + return "find"; //$NON-NLS-1$ + } + }); + } + + private void notifyCollisionHooks(final int operationType, final Bundle target, List<AbstractBundle> collisionCandidates) { + final Collection<Bundle> shrinkable = new ShrinkableCollection<Bundle>(collisionCandidates); + if (System.getSecurityManager() == null) { + notifyCollisionHooksPriviledged(operationType, target, shrinkable); + } else { + AccessController.doPrivileged(new PrivilegedAction<Object>() { + public Object run() { + notifyCollisionHooksPriviledged(operationType, target, shrinkable); + return null; + } + }); + } + } + + void notifyCollisionHooksPriviledged(final int operationType, final Bundle target, final Collection<Bundle> collisionCandidates) { + if (Debug.DEBUG_HOOKS) { + Debug.println("notifyCollisionHooks(" + operationType + ", " + target + ", " + collisionCandidates + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ + } + getServiceRegistry().notifyHooksPrivileged(new HookContext() { + public void call(Object hook, ServiceRegistration<?> hookRegistration) throws Exception { + if (hook instanceof CollisionHook) { + ((CollisionHook) hook).filterCollisions(operationType, target, collisionCandidates); + } + } + + public String getHookClassName() { + return collisionHookName; + } + + public String getHookMethodName() { + return "filterCollisions"; //$NON-NLS-1$ + } + }); + } + + /** + * Resume a bundle. + * + * @param bundle + * Bundle to resume. + */ + protected void resumeBundle(AbstractBundle bundle) { + if (bundle.isActive()) { + // if bundle is active. + return; + } + try { + if (Debug.DEBUG_GENERAL) { + Debug.println("Trying to resume bundle " + bundle); //$NON-NLS-1$ + } + bundle.resume(); + } catch (BundleException be) { + if (Debug.DEBUG_GENERAL) { + Debug.println("Bundle resume exception: " + be.getMessage()); //$NON-NLS-1$ + Debug.printStackTrace(be.getNestedException() == null ? be : be.getNestedException()); + } + publishFrameworkEvent(FrameworkEvent.ERROR, bundle, be); + } + } + + /** + * Suspend a bundle. + * + * @param bundle + * Bundle to suspend. + * @param lock + * true if state change lock should be held when returning from + * this method. + * @return true if bundle was active and is now suspended. + */ + protected boolean suspendBundle(AbstractBundle bundle, boolean lock) { + boolean changed = false; + if (!bundle.isActive() || bundle.isFragment()) { + // if bundle is not active or is a fragment then do nothing. + return changed; + } + try { + if (Debug.DEBUG_GENERAL) { + Debug.println("Trying to suspend bundle " + bundle); //$NON-NLS-1$ + } + bundle.suspend(lock); + } catch (BundleException be) { + if (Debug.DEBUG_GENERAL) { + Debug.println("Bundle suspend exception: " + be.getMessage()); //$NON-NLS-1$ + Debug.printStackTrace(be.getNestedException() == null ? be : be.getNestedException()); + } + publishFrameworkEvent(FrameworkEvent.ERROR, bundle, be); + } + if (!bundle.isActive()) { + changed = true; + } + return (changed); + } + + /** + * Locate an installed bundle with a given identity. + * + * @param location + * string for the bundle + * @return Bundle object for bundle with the specified location or null if + * no bundle is installed with the specified location. + */ + protected AbstractBundle getBundleByLocation(String location) { + synchronized (bundles) { + // this is not optimized; do not think it will get called + // that much. + final String finalLocation = location; + + //Bundle.getLocation requires AdminPermission (metadata) + return AccessController.doPrivileged(new PrivilegedAction<AbstractBundle>() { + public AbstractBundle run() { + List<AbstractBundle> allBundles = bundles.getBundles(); + int size = allBundles.size(); + for (int i = 0; i < size; i++) { + AbstractBundle bundle = allBundles.get(i); + if (finalLocation.equals(bundle.getLocation())) { + return bundle; + } + } + return null; + } + }); + } + } + + /** + * Locate an installed bundle with a given symbolic name + * + * @param symbolicName + * The symbolic name for the bundle + * @return Bundle object for bundle with the specified Unique or null if no + * bundle is installed with the specified symbolicName. + */ + protected AbstractBundle[] getBundleBySymbolicName(String symbolicName) { + synchronized (bundles) { + return bundles.getBundles(symbolicName); + } + } + + /** + * Creates a <code>File</code> object for a file in the persistent + * storage area provided for the bundle by the framework. If the adaptor + * does not have file system support, this method will return <code>null</code>. + * + * <p> + * A <code>File</code> object for the base directory of the persistent + * storage area provided for the context bundle by the framework can be + * obtained by calling this method with the empty string ("") as the + * parameter. + */ + protected File getDataFile(final AbstractBundle bundle, final String filename) { + return AccessController.doPrivileged(new GetDataFileAction(bundle, filename)); + } + + /** + * Check for specific AdminPermission (RFC 73) + */ + protected void checkAdminPermission(Bundle bundle, String action) { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) + sm.checkPermission(getAdminPermission(bundle, action)); + } + + // gets AdminPermission objects from a cache to reduce the number of AdminPermission + // objects that are created. + private AdminPermission getAdminPermission(Bundle bundle, String action) { + synchronized (adminPermissions) { + Long ID = new Long(bundle.getBundleId()); + Map<String, AdminPermission> bundlePermissions = adminPermissions.get(ID); + if (bundlePermissions == null) { + bundlePermissions = new HashMap<String, AdminPermission>(); + adminPermissions.put(ID, bundlePermissions); + } + AdminPermission result = bundlePermissions.get(action); + if (result == null) { + result = new AdminPermission(bundle, action); + bundlePermissions.put(action, result); + } + return result; + } + } + + /** + * This is necessary for running from a JXE, otherwise the SecurityManager + * is set much later than we would like! + */ + protected void installSecurityManager() { + String securityManager = FrameworkProperties.getProperty(Constants.FRAMEWORK_SECURITY, FrameworkProperties.getProperty(PROP_EQUINOX_SECURITY, FrameworkProperties.getProperty("java.security.manager"))); + if (securityManager != null) { + SecurityManager sm = System.getSecurityManager(); + if (sm == null) { + if (securityManager.length() == 0) + sm = new SecurityManager(); // use the default one from java + else if (securityManager.equals(SECURITY_OSGI)) + sm = new EquinoxSecurityManager(); // use an OSGi enabled manager that understands postponed conditions + else { + // try to use a specific classloader by classname + try { + Class<?> clazz = Class.forName(securityManager); + sm = (SecurityManager) clazz.newInstance(); + } catch (ClassNotFoundException e) { + // do nothing + } catch (ClassCastException e) { + // do nothing + } catch (InstantiationException e) { + // do nothing + } catch (IllegalAccessException e) { + // do nothing + } + } + if (sm == null) + throw new NoClassDefFoundError(securityManager); + if (Debug.DEBUG_SECURITY) + Debug.println("Setting SecurityManager to: " + sm); //$NON-NLS-1$ + System.setSecurityManager(sm); + return; + } + } + } + + void addFrameworkListener(FrameworkListener listener, BundleContextImpl context) { + synchronized (allFrameworkListeners) { + CopyOnWriteIdentityMap<FrameworkListener, FrameworkListener> listeners = allFrameworkListeners.get(context); + if (listeners == null) { + listeners = new CopyOnWriteIdentityMap<FrameworkListener, FrameworkListener>(); + allFrameworkListeners.put(context, listeners); + } + listeners.put(listener, listener); + } + } + + void removeFrameworkListener(FrameworkListener listener, BundleContextImpl context) { + synchronized (allFrameworkListeners) { + CopyOnWriteIdentityMap<FrameworkListener, FrameworkListener> listeners = allFrameworkListeners.get(context); + if (listeners != null) + listeners.remove(listener); + } + } + + void removeAllListeners(BundleContextImpl context) { + synchronized (allBundleListeners) { + allBundleListeners.remove(context); + } + synchronized (allSyncBundleListeners) { + allSyncBundleListeners.remove(context); + } + synchronized (allFrameworkListeners) { + allFrameworkListeners.remove(context); + } + } + + /** + * Deliver a FrameworkEvent. + * + * @param type + * FrameworkEvent type. + * @param bundle + * Affected bundle or null for system bundle. + * @param throwable + * Related exception or null. + */ + public void publishFrameworkEvent(int type, Bundle bundle, Throwable throwable) { + publishFrameworkEvent(type, bundle, throwable, (FrameworkListener[]) null); + } + + public void publishFrameworkEvent(int type, Bundle bundle, Throwable throwable, final FrameworkListener... listeners) { + if (bundle == null) + bundle = systemBundle; + final FrameworkEvent event = new FrameworkEvent(type, bundle, throwable); + if (System.getSecurityManager() == null) { + publishFrameworkEventPrivileged(event, listeners); + } else { + AccessController.doPrivileged(new PrivilegedAction<Object>() { + public Object run() { + publishFrameworkEventPrivileged(event, listeners); + return null; + } + }); + } + } + + public void publishFrameworkEventPrivileged(FrameworkEvent event, FrameworkListener... callerListeners) { + // Build the listener snapshot + Map<BundleContextImpl, Set<Map.Entry<FrameworkListener, FrameworkListener>>> listenerSnapshot; + synchronized (allFrameworkListeners) { + listenerSnapshot = new HashMap<BundleContextImpl, Set<Map.Entry<FrameworkListener, FrameworkListener>>>(allFrameworkListeners.size()); + for (Map.Entry<BundleContextImpl, CopyOnWriteIdentityMap<FrameworkListener, FrameworkListener>> entry : allFrameworkListeners.entrySet()) { + CopyOnWriteIdentityMap<FrameworkListener, FrameworkListener> listeners = entry.getValue(); + if (!listeners.isEmpty()) { + listenerSnapshot.put(entry.getKey(), listeners.entrySet()); + } + } + } + // If framework event hook were defined they would be called here + + // deliver the event to the snapshot + ListenerQueue<FrameworkListener, FrameworkListener, FrameworkEvent> queue = newListenerQueue(); + + // add the listeners specified by the caller first + if (callerListeners != null && callerListeners.length > 0) { + Map<FrameworkListener, FrameworkListener> listeners = new HashMap<FrameworkListener, FrameworkListener>(); + for (FrameworkListener listener : callerListeners) { + if (listener != null) + listeners.put(listener, listener); + } + // We use the system bundle context as the dispatcher + if (listeners.size() > 0) { + @SuppressWarnings({"rawtypes", "unchecked"}) + EventDispatcher<FrameworkListener, FrameworkListener, FrameworkEvent> dispatcher = (EventDispatcher) getSystemBundleContext(); + queue.queueListeners(listeners.entrySet(), dispatcher); + } + } + + for (Map.Entry<BundleContextImpl, Set<Map.Entry<FrameworkListener, FrameworkListener>>> entry : listenerSnapshot.entrySet()) { + @SuppressWarnings({"rawtypes", "unchecked"}) + EventDispatcher<FrameworkListener, FrameworkListener, FrameworkEvent> dispatcher = (EventDispatcher) entry.getKey(); + Set<Map.Entry<FrameworkListener, FrameworkListener>> listeners = entry.getValue(); + queue.queueListeners(listeners, dispatcher); + } + + queue.dispatchEventAsynchronous(FRAMEWORKEVENT, event); + } + + void addBundleListener(BundleListener listener, BundleContextImpl context) { + if (listener instanceof SynchronousBundleListener) { + checkAdminPermission(context.getBundle(), AdminPermission.LISTENER); + synchronized (allSyncBundleListeners) { + CopyOnWriteIdentityMap<SynchronousBundleListener, SynchronousBundleListener> listeners = allSyncBundleListeners.get(context); + if (listeners == null) { + listeners = new CopyOnWriteIdentityMap<SynchronousBundleListener, SynchronousBundleListener>(); + allSyncBundleListeners.put(context, listeners); + } + listeners.put((SynchronousBundleListener) listener, (SynchronousBundleListener) listener); + } + } else { + synchronized (allBundleListeners) { + CopyOnWriteIdentityMap<BundleListener, BundleListener> listeners = allBundleListeners.get(context); + if (listeners == null) { + listeners = new CopyOnWriteIdentityMap<BundleListener, BundleListener>(); + allBundleListeners.put(context, listeners); + } + listeners.put(listener, listener); + } + } + } + + void removeBundleListener(BundleListener listener, BundleContextImpl context) { + if (listener instanceof SynchronousBundleListener) { + checkAdminPermission(context.getBundle(), AdminPermission.LISTENER); + synchronized (allSyncBundleListeners) { + CopyOnWriteIdentityMap<SynchronousBundleListener, SynchronousBundleListener> listeners = allSyncBundleListeners.get(context); + if (listeners != null) + listeners.remove(listener); + } + } else { + synchronized (allBundleListeners) { + CopyOnWriteIdentityMap<BundleListener, BundleListener> listeners = allBundleListeners.get(context); + if (listeners != null) + listeners.remove(listener); + } + } + } + + /** + * Deliver a BundleEvent to SynchronousBundleListeners (synchronous). and + * BundleListeners (asynchronous). + * + * @param type + * BundleEvent type. + * @param bundle + * Affected bundle or null. + */ + public void publishBundleEvent(int type, Bundle bundle) { + publishBundleEvent(new BundleEvent(type, bundle)); + } + + private void publishBundleEvent(final BundleEvent event) { + if (System.getSecurityManager() == null) { + publishBundleEventPrivileged(event); + } else { + AccessController.doPrivileged(new PrivilegedAction<Object>() { + public Object run() { + publishBundleEventPrivileged(event); + return null; + } + }); + } + } + + public void publishBundleEventPrivileged(BundleEvent event) { + /* + * We must collect the snapshots of the sync and async listeners + * BEFORE we dispatch the event. + */ + /* Collect snapshot of SynchronousBundleListeners */ + /* Build the listener snapshot */ + Map<BundleContextImpl, Set<Map.Entry<SynchronousBundleListener, SynchronousBundleListener>>> listenersSync; + synchronized (allSyncBundleListeners) { + listenersSync = new HashMap<BundleContextImpl, Set<Map.Entry<SynchronousBundleListener, SynchronousBundleListener>>>(allSyncBundleListeners.size()); + for (Map.Entry<BundleContextImpl, CopyOnWriteIdentityMap<SynchronousBundleListener, SynchronousBundleListener>> entry : allSyncBundleListeners.entrySet()) { + CopyOnWriteIdentityMap<SynchronousBundleListener, SynchronousBundleListener> listeners = entry.getValue(); + if (!listeners.isEmpty()) { + listenersSync.put(entry.getKey(), listeners.entrySet()); + } + } + } + /* Collect snapshot of BundleListeners; only if the event is NOT STARTING or STOPPING or LAZY_ACTIVATION */ + Map<BundleContextImpl, Set<Map.Entry<BundleListener, BundleListener>>> listenersAsync = null; + if ((event.getType() & (BundleEvent.STARTING | BundleEvent.STOPPING | BundleEvent.LAZY_ACTIVATION)) == 0) { + synchronized (allBundleListeners) { + listenersAsync = new HashMap<BundleContextImpl, Set<Map.Entry<BundleListener, BundleListener>>>(allBundleListeners.size()); + for (Map.Entry<BundleContextImpl, CopyOnWriteIdentityMap<BundleListener, BundleListener>> entry : allBundleListeners.entrySet()) { + CopyOnWriteIdentityMap<BundleListener, BundleListener> listeners = entry.getValue(); + if (!listeners.isEmpty()) { + listenersAsync.put(entry.getKey(), listeners.entrySet()); + } + } + } + } + + /* shrink the snapshot. + * keySet returns a Collection which cannot be added to and + * removals from that collection will result in removals of the + * entry from the snapshot. + */ + Collection<BundleContext> shrinkable; + if (listenersAsync == null) { + shrinkable = asBundleContexts(listenersSync.keySet()); + } else { + shrinkable = new ShrinkableCollection<BundleContext>(asBundleContexts(listenersSync.keySet()), asBundleContexts(listenersAsync.keySet())); + } + notifyEventHooksPrivileged(event, shrinkable); + + /* Dispatch the event to the snapshot for sync listeners */ + if (!listenersSync.isEmpty()) { + ListenerQueue<SynchronousBundleListener, SynchronousBundleListener, BundleEvent> queue = newListenerQueue(); + for (Map.Entry<BundleContextImpl, Set<Map.Entry<SynchronousBundleListener, SynchronousBundleListener>>> entry : listenersSync.entrySet()) { + @SuppressWarnings({"rawtypes", "unchecked"}) + EventDispatcher<SynchronousBundleListener, SynchronousBundleListener, BundleEvent> dispatcher = (EventDispatcher) entry.getKey(); + Set<Map.Entry<SynchronousBundleListener, SynchronousBundleListener>> listeners = entry.getValue(); + queue.queueListeners(listeners, dispatcher); + } + queue.dispatchEventSynchronous(BUNDLEEVENTSYNC, event); + } + + /* Dispatch the event to the snapshot for async listeners */ + if ((listenersAsync != null) && !listenersAsync.isEmpty()) { + ListenerQueue<BundleListener, BundleListener, BundleEvent> queue = newListenerQueue(); + for (Map.Entry<BundleContextImpl, Set<Map.Entry<BundleListener, BundleListener>>> entry : listenersAsync.entrySet()) { + @SuppressWarnings({"rawtypes", "unchecked"}) + EventDispatcher<BundleListener, BundleListener, BundleEvent> dispatcher = (EventDispatcher) entry.getKey(); + Set<Map.Entry<BundleListener, BundleListener>> listeners = entry.getValue(); + queue.queueListeners(listeners, dispatcher); + } + queue.dispatchEventAsynchronous(BUNDLEEVENT, event); + } + } + + /** + * Coerce the generic type of a collection from Collection<BundleContextImpl> + * to Collection<BundleContext> + * @param c Collection to be coerced. + * @return c coerced to Collection<BundleContext> + */ + @SuppressWarnings("unchecked") + public static Collection<BundleContext> asBundleContexts(Collection<? extends BundleContext> c) { + return (Collection<BundleContext>) c; + } + + private void notifyEventHooksPrivileged(final BundleEvent event, final Collection<BundleContext> result) { + if (event.getType() == Framework.BATCHEVENT_BEGIN || event.getType() == Framework.BATCHEVENT_END) + return; // we don't need to send this event; it is used to book case special listeners + if (Debug.DEBUG_HOOKS) { + Debug.println("notifyBundleEventHooks(" + event.getType() + ":" + event.getBundle() + ", " + result + " )"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ + } + + getServiceRegistry().notifyHooksPrivileged(new HookContext() { + public void call(Object hook, ServiceRegistration<?> hookRegistration) throws Exception { + if (hook instanceof EventHook) { + ((EventHook) hook).event(event, result); + } + } + + public String getHookClassName() { + return eventHookName; + } + + public String getHookMethodName() { + return "event"; //$NON-NLS-1$ + } + }); + } + + public <K, V, E> ListenerQueue<K, V, E> newListenerQueue() { + return new ListenerQueue<K, V, E>(eventManager); + } + + private void initializeContextFinder() { + Thread current = Thread.currentThread(); + try { + ClassLoader parent = null; + // check property for specified parent + String type = FrameworkProperties.getProperty(PROP_CONTEXTCLASSLOADER_PARENT); + if (CONTEXTCLASSLOADER_PARENT_APP.equals(type)) + parent = ClassLoader.getSystemClassLoader(); + else if (CONTEXTCLASSLOADER_PARENT_BOOT.equals(type)) + parent = null; + else if (CONTEXTCLASSLOADER_PARENT_FWK.equals(type)) + parent = Framework.class.getClassLoader(); + else if (CONTEXTCLASSLOADER_PARENT_EXT.equals(type)) { + ClassLoader appCL = ClassLoader.getSystemClassLoader(); + if (appCL != null) + parent = appCL.getParent(); + } else { // default is ccl (null or any other value will use ccl) + parent = current.getContextClassLoader(); + } + contextFinder = new ContextFinder(parent); + current.setContextClassLoader(contextFinder); + return; + } catch (Exception e) { + FrameworkLogEntry entry = new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.INFO, 0, NLS.bind(Msg.CANNOT_SET_CONTEXTFINDER, null), 0, e, null); + adaptor.getFrameworkLog().log(entry); + } + + } + + public static Field getField(Class<?> clazz, Class<?> type, boolean instance) { + Field[] fields = clazz.getDeclaredFields(); + for (int i = 0; i < fields.length; i++) { + boolean isStatic = Modifier.isStatic(fields[i].getModifiers()); + if (instance != isStatic && fields[i].getType().equals(type)) { + fields[i].setAccessible(true); + return fields[i]; + } + } + return null; + } + + private void installContentHandlerFactory(BundleContext context, FrameworkAdaptor frameworkAdaptor) { + ContentHandlerFactory chf = new ContentHandlerFactory(context, frameworkAdaptor); + try { + // first try the standard way + URLConnection.setContentHandlerFactory(chf); + } catch (Error err) { + // ok we failed now use more drastic means to set the factory + try { + forceContentHandlerFactory(chf); + } catch (Exception ex) { + // this is unexpected, log the exception and throw the original error + adaptor.getFrameworkLog().log(new FrameworkEvent(FrameworkEvent.ERROR, context.getBundle(), ex)); + throw err; + } + } + contentHandlerFactory = chf; + } + + private static void forceContentHandlerFactory(ContentHandlerFactory chf) throws Exception { + Field factoryField = getField(URLConnection.class, java.net.ContentHandlerFactory.class, false); + if (factoryField == null) + throw new Exception("Could not find ContentHandlerFactory field"); //$NON-NLS-1$ + synchronized (URLConnection.class) { + java.net.ContentHandlerFactory factory = (java.net.ContentHandlerFactory) factoryField.get(null); + // doing a null check here just in case, but it would be really strange if it was null, + // because we failed to set the factory normally!! + + if (factory != null) { + try { + factory.getClass().getMethod("isMultiplexing", (Class[]) null); //$NON-NLS-1$ + Method register = factory.getClass().getMethod("register", new Class[] {Object.class}); //$NON-NLS-1$ + register.invoke(factory, new Object[] {chf}); + } catch (NoSuchMethodException e) { + // current factory does not support multiplexing, ok we'll wrap it + chf.setParentFactory(factory); + factory = chf; + } + } + // null out the field so that we can successfully call setContentHandlerFactory + factoryField.set(null, null); + // always attempt to clear the handlers cache + // This allows an optomization for the single framework use-case + resetContentHandlers(); + URLConnection.setContentHandlerFactory(factory); + } + } + + private void uninstallContentHandlerFactory() { + try { + Field factoryField = getField(URLConnection.class, java.net.ContentHandlerFactory.class, false); + if (factoryField == null) + return; // oh well, we tried. + synchronized (URLConnection.class) { + java.net.ContentHandlerFactory factory = (java.net.ContentHandlerFactory) factoryField.get(null); + + if (factory == contentHandlerFactory) { + factory = (java.net.ContentHandlerFactory) contentHandlerFactory.designateSuccessor(); + } else { + Method unregister = factory.getClass().getMethod("unregister", new Class[] {Object.class}); //$NON-NLS-1$ + unregister.invoke(factory, new Object[] {contentHandlerFactory}); + } + // null out the field so that we can successfully call setContentHandlerFactory + factoryField.set(null, null); + // always attempt to clear the handlers cache + // This allows an optomization for the single framework use-case + // Note that the call to setContentHandlerFactory below may clear this cache + // but we want to be sure to clear it here just incase the parent is null. + // In this case the call below would not occur. + // Also it appears most java libraries actually do not clear the cache + // when setContentHandlerFactory is called, go figure!! + resetContentHandlers(); + if (factory != null) + URLConnection.setContentHandlerFactory(factory); + } + } catch (Exception e) { + // ignore and continue closing the framework + } + } + + private static void resetContentHandlers() throws IllegalAccessException { + Field handlersField = getField(URLConnection.class, Hashtable.class, false); + if (handlersField != null) { + @SuppressWarnings("rawtypes") + Hashtable<?, ?> handlers = (Hashtable) handlersField.get(null); + if (handlers != null) + handlers.clear(); + } + } + + private void installURLStreamHandlerFactory(BundleContext context, FrameworkAdaptor frameworkAdaptor) { + StreamHandlerFactory shf = new StreamHandlerFactory(context, frameworkAdaptor); + try { + // first try the standard way + URL.setURLStreamHandlerFactory(shf); + } catch (Error err) { + try { + // ok we failed now use more drastic means to set the factory + forceURLStreamHandlerFactory(shf); + } catch (Exception ex) { + adaptor.getFrameworkLog().log(new FrameworkEvent(FrameworkEvent.ERROR, context.getBundle(), ex)); + throw err; + } + } + streamHandlerFactory = shf; + } + + private static void forceURLStreamHandlerFactory(StreamHandlerFactory shf) throws Exception { + Field factoryField = getField(URL.class, URLStreamHandlerFactory.class, false); + if (factoryField == null) + throw new Exception("Could not find URLStreamHandlerFactory field"); //$NON-NLS-1$ + // look for a lock to synchronize on + Object lock = getURLStreamHandlerFactoryLock(); + synchronized (lock) { + URLStreamHandlerFactory factory = (URLStreamHandlerFactory) factoryField.get(null); + // doing a null check here just in case, but it would be really strange if it was null, + // because we failed to set the factory normally!! + if (factory != null) { + try { + factory.getClass().getMethod("isMultiplexing", (Class[]) null); //$NON-NLS-1$ + Method register = factory.getClass().getMethod("register", new Class[] {Object.class}); //$NON-NLS-1$ + register.invoke(factory, new Object[] {shf}); + } catch (NoSuchMethodException e) { + // current factory does not support multiplexing, ok we'll wrap it + shf.setParentFactory(factory); + factory = shf; + } + } + factoryField.set(null, null); + // always attempt to clear the handlers cache + // This allows an optomization for the single framework use-case + resetURLStreamHandlers(); + URL.setURLStreamHandlerFactory(factory); + } + } + + private void uninstallURLStreamHandlerFactory() { + try { + Field factoryField = getField(URL.class, URLStreamHandlerFactory.class, false); + if (factoryField == null) + return; // oh well, we tried + Object lock = getURLStreamHandlerFactoryLock(); + synchronized (lock) { + URLStreamHandlerFactory factory = (URLStreamHandlerFactory) factoryField.get(null); + if (factory == streamHandlerFactory) { + factory = (URLStreamHandlerFactory) streamHandlerFactory.designateSuccessor(); + } else { + Method unregister = factory.getClass().getMethod("unregister", new Class[] {Object.class}); //$NON-NLS-1$ + unregister.invoke(factory, new Object[] {streamHandlerFactory}); + } + factoryField.set(null, null); + // always attempt to clear the handlers cache + // This allows an optomization for the single framework use-case + // Note that the call to setURLStreamHandlerFactory below may clear this cache + // but we want to be sure to clear it here just in case the parent is null. + // In this case the call below would not occur. + resetURLStreamHandlers(); + if (factory != null) + URL.setURLStreamHandlerFactory(factory); + } + } catch (Exception e) { + // ignore and continue closing the framework + } + } + + private static Object getURLStreamHandlerFactoryLock() throws IllegalAccessException { + Object lock; + try { + Field streamHandlerLockField = URL.class.getDeclaredField("streamHandlerLock"); //$NON-NLS-1$ + streamHandlerLockField.setAccessible(true); + lock = streamHandlerLockField.get(null); + } catch (NoSuchFieldException noField) { + // could not find the lock, lets sync on the class object + lock = URL.class; + } + return lock; + } + + private static void resetURLStreamHandlers() throws IllegalAccessException { + Field handlersField = getField(URL.class, Hashtable.class, false); + if (handlersField != null) { + @SuppressWarnings("rawtypes") + Hashtable<?, ?> handlers = (Hashtable) handlersField.get(null); + if (handlers != null) + handlers.clear(); + } + } + + /* + * (non-Javadoc) + * @see java.lang.Runnable#run() + * This thread monitors the framework active status and terminates when the framework is + * shutdown. This is needed to ensure the VM does not exist because of the lack of a + * non-daemon thread (bug 215730) + */ + public void run() { + synchronized (this) { + while (active) + try { + this.wait(1000); + } catch (InterruptedException e) { + // do nothing + } + } + } + + void setForcedRestart(boolean forcedRestart) { + this.forcedRestart = forcedRestart; + } + + boolean isForcedRestart() { + return forcedRestart; + } + + public FrameworkEvent waitForStop(long timeout) throws InterruptedException { + boolean waitForEver = timeout == 0; + long start = System.currentTimeMillis(); + long timeLeft = timeout; + synchronized (this) { + FrameworkEvent[] event = shutdownEvent; + while (event != null && event[0] == null) { + this.wait(timeLeft); + if (!waitForEver) { + timeLeft = start + timeout - System.currentTimeMillis(); + // break if we are passed the timeout + if (timeLeft <= 0) + break; + } + } + if (event == null || event[0] == null) + return new FrameworkEvent(FrameworkEvent.WAIT_TIMEDOUT, systemBundle, null); + return event[0]; + } + } + + /** + * Used by ServiceReferenceImpl for isAssignableTo + * @param registrant Bundle registering service + * @param client Bundle desiring to use service + * @param className class name to use + * @param serviceClass class of original service object + * @return true if assignable given package wiring + */ + public boolean isServiceAssignableTo(Bundle registrant, Bundle client, String className, Class<?> serviceClass) { + // always return false for fragments + AbstractBundle consumer = (AbstractBundle) client; + if (consumer.isFragment()) + return false; + // 1) if the registrant == consumer always return true + AbstractBundle producer = (AbstractBundle) registrant; + if (consumer == producer) + return true; + // 2) get the package name from the specified className + String pkgName = BundleLoader.getPackageName(className); + if (pkgName.startsWith("java.")) //$NON-NLS-1$ + return true; + BundleLoader producerBL = producer.getBundleLoader(); + if (producerBL == null) + return false; + BundleLoader consumerBL = consumer.getBundleLoader(); + if (consumerBL == null) + return false; + // 3) for the specified bundle, find the wiring for the package. If no wiring is found return true + PackageSource consumerSource = consumerBL.getPackageSource(pkgName); + if (consumerSource == null) + return true; + // work around the issue when the package is in the EE and we delegate to boot for that package + if (isBootDelegationPackage(pkgName)) { + SystemBundleLoader systemLoader = (SystemBundleLoader) systemBundle.getBundleLoader(); + if (systemLoader.isEEPackage(pkgName)) + return true; // in this case we have a common source from the EE + } + // 4) For the registrant bundle, find the wiring for the package. + PackageSource producerSource = producerBL.getPackageSource(pkgName); + if (producerSource == null) { + if (serviceClass != null && ServiceFactory.class.isAssignableFrom(serviceClass)) { + Bundle bundle = packageAdmin.getBundle(serviceClass); + if (bundle != null && bundle != registrant) + // in this case we have a wacky ServiceFactory that is doing something we cannot + // verify if it is correct. Instead of failing we allow the assignment and hope for the best + // bug 326918 + return true; + } + // 5) If no wiring is found for the registrant bundle then find the wiring for the classloader of the service object. If no wiring is found return false. + producerSource = getPackageSource(serviceClass, pkgName); + if (producerSource == null) + return false; + } + // 6) If the two wirings found are equal then return true; otherwise return false. + return producerSource.hasCommonSource(consumerSource); + } + + private PackageSource getPackageSource(Class<?> serviceClass, String pkgName) { + if (serviceClass == null) + return null; + AbstractBundle serviceBundle = (AbstractBundle) packageAdmin.getBundle(serviceClass); + if (serviceBundle == null) + return null; + BundleLoader producerBL = serviceBundle.getBundleLoader(); + if (producerBL == null) + return null; + PackageSource producerSource = producerBL.getPackageSource(pkgName); + if (producerSource != null) + return producerSource; + // try the interfaces + Class<?>[] interfaces = serviceClass.getInterfaces(); + // note that getInterfaces never returns null + for (int i = 0; i < interfaces.length; i++) { + producerSource = getPackageSource(interfaces[i], pkgName); + if (producerSource != null) + return producerSource; + } + // try super class + return getPackageSource(serviceClass.getSuperclass(), pkgName); + } + + public boolean isBootDelegationPackage(String name) { + if (bootDelegateAll) + return true; + if (bootDelegation != null) + for (int i = 0; i < bootDelegation.length; i++) + if (name.equals(bootDelegation[i])) + return true; + if (bootDelegationStems != null) + for (int i = 0; i < bootDelegationStems.length; i++) + if (name.startsWith(bootDelegationStems[i])) + return true; + return false; + } + + SignedContentFactory getSignedContentFactory() { + ServiceTracker<SignedContentFactory, SignedContentFactory> currentTracker = signedContentFactory; + return (currentTracker == null ? null : currentTracker.getService()); + } + + ContextFinder getContextFinder() { + return contextFinder; + } + + public boolean isRefreshDuplicateBSNAllowed() { + return allowRefreshDuplicateBSN; + } + +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/InternalSystemBundle.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/InternalSystemBundle.java new file mode 100644 index 000000000..fb352f0de --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/InternalSystemBundle.java @@ -0,0 +1,433 @@ +/******************************************************************************* + * Copyright (c) 2003, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + +package org.eclipse.osgi.framework.internal.core; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.security.Permission; +import java.security.ProtectionDomain; +import java.util.Dictionary; +import java.util.Enumeration; +import org.eclipse.osgi.framework.debug.Debug; +import org.osgi.framework.*; +import org.osgi.framework.startlevel.FrameworkStartLevel; +import org.osgi.framework.wiring.FrameworkWiring; + +/** + * This class subclasses Bundle to provide a system Bundle + * so that the framework can be represented as a bundle and + * can access the services provided by other bundles. + */ + +public class InternalSystemBundle extends BundleHost implements org.osgi.framework.launch.Framework { + class SystemBundleHeaders extends Dictionary<String, String> { + private final Dictionary<String, String> headers; + + public SystemBundleHeaders(Dictionary<String, String> headers) { + this.headers = headers; + } + + public Enumeration<String> elements() { + return headers.elements(); + } + + public String get(Object key) { + if (!(key instanceof String)) + return null; + if (org.osgi.framework.Constants.EXPORT_PACKAGE.equalsIgnoreCase((String) key)) { + return getExtra(org.osgi.framework.Constants.EXPORT_PACKAGE, org.osgi.framework.Constants.FRAMEWORK_SYSTEMPACKAGES, org.osgi.framework.Constants.FRAMEWORK_SYSTEMPACKAGES_EXTRA); + } else if (org.osgi.framework.Constants.PROVIDE_CAPABILITY.equalsIgnoreCase((String) key)) { + return getExtra(org.osgi.framework.Constants.PROVIDE_CAPABILITY, org.osgi.framework.Constants.FRAMEWORK_SYSTEMCAPABILITIES, org.osgi.framework.Constants.FRAMEWORK_SYSTEMCAPABILITIES_EXTRA); + } + return headers.get(key); + } + + private String getExtra(String header, String systemProp, String systemExtraProp) { + String systemValue = FrameworkProperties.getProperty(systemProp); + String systemExtraValue = FrameworkProperties.getProperty(systemExtraProp); + if (systemValue == null) + systemValue = systemExtraValue; + else if (systemExtraValue != null && systemExtraValue.trim().length() > 0) + systemValue += ", " + systemExtraValue; //$NON-NLS-1$ + String result = headers.get(header); + if (systemValue != null && systemValue.trim().length() > 0) { + if (result != null) + result += ", " + systemValue; //$NON-NLS-1$ + else + result = systemValue; + } + return result; + } + + public boolean isEmpty() { + return headers.isEmpty(); + } + + public Enumeration<String> keys() { + return headers.keys(); + } + + public String put(String key, String value) { + return headers.put(key, value); + } + + public String remove(Object key) { + return headers.remove(key); + } + + public int size() { + return headers.size(); + } + + } + + private final FrameworkStartLevel fsl; + ProtectionDomain systemDomain; + + /** + * Private SystemBundle object constructor. + * This method creates the SystemBundle and its BundleContext. + * The SystemBundle's state is set to STARTING. + * This method is called when the framework is constructed. + * + * @param framework Framework this bundle is running in + */ + protected InternalSystemBundle(Framework framework) throws BundleException { + super(framework.adaptor.createSystemBundleData(), framework); // startlevel=0 means framework stopped + Constants.setInternalSymbolicName(bundledata.getSymbolicName()); + state = Bundle.RESOLVED; + context = createContext(); + fsl = new EquinoxStartLevel(); + } + + /** + * Load the bundle. + * This methods overrides the Bundle method and does nothing. + * + */ + protected void load() { + SecurityManager sm = System.getSecurityManager(); + + if (sm != null) { + systemDomain = getClass().getProtectionDomain(); + } + } + + /** + * Reload from a new bundle. + * This methods overrides the Bundle method and does nothing. + * + * @param newBundle + * @return false + */ + protected boolean reload(AbstractBundle newBundle) { + return (false); + } + + /** + * Refresh the bundle. + * This methods overrides the Bundle method and does nothing. + * + */ + protected void refresh() { + // do nothing + } + + /** + * Unload the bundle. + * This methods overrides the Bundle method and does nothing. + * + * @return false + */ + protected boolean unload() { + return (false); + } + + /** + * Close the the Bundle's file. + * This method closes the BundleContext for the SystemBundle. + * + */ + protected void close() { + context.close(); + context = null; + } + + /** + * This method loads a class from the bundle. + * + * @param name the name of the desired Class. + * @param checkPermission indicates whether a permission check should be done. + * @return the resulting Class + * @exception java.lang.ClassNotFoundException if the class definition was not found. + */ + protected Class<?> loadClass(String name, boolean checkPermission) throws ClassNotFoundException { + if (checkPermission) { + framework.checkAdminPermission(this, AdminPermission.CLASS); + checkValid(); + } + return (Class.forName(name)); + } + + /** + * Find the specified resource in this bundle. + * This methods returns null for the system bundle. + */ + public URL getResource(String name) { + return (null); + } + + /** + * Indicate SystemBundle is resolved. + * + */ + protected boolean isUnresolved() { + return (false); + } + + /** + * Start this bundle. + * This methods overrides the Bundle method and does nothing. + * + */ + public void start() { + framework.checkAdminPermission(this, AdminPermission.EXECUTE); + } + + public void start(int options) { + framework.checkAdminPermission(this, AdminPermission.EXECUTE); + } + + /** + * Start the SystemBundle. + * This method launches the framework. + * + */ + protected void resume() { + /* initialize the startlevel service */ + framework.startLevelManager.initialize(); + + /* Load all installed bundles */ + loadInstalledBundles(framework.startLevelManager.getInstalledBundles(framework.bundles, false)); + /* Start the system bundle */ + try { + framework.systemBundle.state = Bundle.STARTING; + framework.systemBundle.context.start(); + framework.publishBundleEvent(BundleEvent.STARTING, framework.systemBundle); + } catch (BundleException be) { + if (Debug.DEBUG_STARTLEVEL) { + Debug.println("SLL: Bundle resume exception: " + be.getMessage()); //$NON-NLS-1$ + Debug.printStackTrace(be.getNestedException() == null ? be : be.getNestedException()); + } + framework.publishFrameworkEvent(FrameworkEvent.ERROR, framework.systemBundle, be); + throw new RuntimeException(be.getMessage(), be); + } + + } + + private void loadInstalledBundles(AbstractBundle[] installedBundles) { + + for (int i = 0; i < installedBundles.length; i++) { + AbstractBundle bundle = installedBundles[i]; + if (Debug.DEBUG_STARTLEVEL) { + Debug.println("SLL: Trying to load bundle " + bundle); //$NON-NLS-1$ + } + bundle.load(); + } + } + + /** + * Stop the framework. + * This method spawns a thread which will call framework.shutdown. + * + */ + public void stop() { + framework.checkAdminPermission(this, AdminPermission.EXECUTE); + + if ((state & (ACTIVE | STARTING)) != 0) { + Thread shutdown = framework.secureAction.createThread(new Runnable() { + public void run() { + try { + framework.close(); + } catch (Throwable t) { + // allow the adaptor to handle this unexpected error + framework.adaptor.handleRuntimeError(t); + } + } + }, "System Bundle Shutdown", framework.getContextFinder()); //$NON-NLS-1$ + + shutdown.start(); + } + } + + public void stop(int options) { + stop(); + } + + /** + * Stop the SystemBundle. + * This method shuts down the framework. + * + */ + protected void suspend() { + + framework.startLevelManager.shutdown(); + framework.startLevelManager.cleanup(); + + /* clean up the exporting loaders */ + framework.packageAdmin.cleanup(); + + if (Debug.DEBUG_GENERAL) { + Debug.println("->Framework shutdown"); //$NON-NLS-1$ + } + // fire the STOPPED event here. + // All bundles have been unloaded, but there may be a boot strap listener that is interested (bug 182742) + framework.publishBundleEvent(BundleEvent.STOPPED, this); + } + + protected void suspend(boolean lock) { + // do nothing + } + + /** + * Update this bundle. + * This method spawns a thread which will call framework.shutdown + * followed by framework.launch. + * + */ + public void update() { + framework.checkAdminPermission(this, AdminPermission.LIFECYCLE); + + if ((state & (ACTIVE | STARTING)) != 0) { + Thread restart = framework.secureAction.createThread(new Runnable() { + public void run() { + int sl = framework.startLevelManager.getStartLevel(); + FrameworkProperties.setProperty(Constants.PROP_OSGI_RELAUNCH, ""); //$NON-NLS-1$ + framework.shutdown(FrameworkEvent.STOPPED_UPDATE); + framework.launch(); + if (sl > 0) + framework.startLevelManager.doSetStartLevel(sl); + FrameworkProperties.clearProperty(Constants.PROP_OSGI_RELAUNCH); + } + }, "System Bundle Update", framework.getContextFinder()); //$NON-NLS-1$ + + restart.start(); + } + } + + /** + * Update this bundle from an InputStream. + * This methods overrides the Bundle method and does nothing. + * + * @param in The InputStream from which to read the new bundle. + */ + public void update(InputStream in) { + update(); + + try { + in.close(); + } catch (IOException e) { + // do nothing + } + } + + /** + * Uninstall this bundle. + * This methods overrides the Bundle method and throws an exception. + * + */ + public void uninstall() throws BundleException { + framework.checkAdminPermission(this, AdminPermission.LIFECYCLE); + + throw new BundleException(Msg.BUNDLE_SYSTEMBUNDLE_UNINSTALL_EXCEPTION, BundleException.INVALID_OPERATION); + } + + /** + * Determine whether the bundle has the requested + * permission. + * This methods overrides the Bundle method and returns <code>true</code>. + * + * @param permission The requested permission. + * @return <code>true</code> + */ + public boolean hasPermission(Object permission) { + if (systemDomain != null) { + if (permission instanceof Permission) { + return systemDomain.implies((Permission) permission); + } + + return false; + } + + return true; + } + + /** + * No work to do for the SystemBundle. + * + * @param refreshedBundles + * A list of bundles which have been refreshed as a result + * of a packageRefresh + */ + protected void unresolvePermissions(AbstractBundle[] refreshedBundles) { + // Do nothing + } + + public Dictionary<String, String> getHeaders(String localeString) { + return new SystemBundleHeaders(super.getHeaders(localeString)); + } + + public void init() { + // no op for internal representation + } + + public FrameworkEvent waitForStop(long timeout) throws InterruptedException { + return framework.waitForStop(timeout); + } + + public ClassLoader getClassLoader() { + return getClass().getClassLoader(); + } + + @SuppressWarnings("unchecked") + @Override + protected <A> A adapt0(Class<A> adapterType) { + if (FrameworkStartLevel.class.equals(adapterType)) + return (A) fsl; + else if (FrameworkWiring.class.equals(adapterType)) + return (A) framework.getPackageAdmin(); + return super.adapt0(adapterType); + } + + class EquinoxStartLevel implements FrameworkStartLevel { + public void setStartLevel(int startlevel, FrameworkListener... listeners) { + framework.startLevelManager.setStartLevel(startlevel, InternalSystemBundle.this, listeners); + } + + public int getInitialBundleStartLevel() { + return framework.startLevelManager.getInitialBundleStartLevel(); + } + + public void setInitialBundleStartLevel(int startlevel) { + framework.startLevelManager.setInitialBundleStartLevel(startlevel); + } + + public Bundle getBundle() { + return InternalSystemBundle.this; + } + + public int getStartLevel() { + return framework.startLevelManager.getStartLevel(); + } + } +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/ManifestLocalization.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/ManifestLocalization.java new file mode 100644 index 000000000..5212b1733 --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/ManifestLocalization.java @@ -0,0 +1,241 @@ +/******************************************************************************* + * Copyright (c) 2004, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.osgi.framework.internal.core; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.*; +import org.eclipse.osgi.framework.util.Headers; +import org.osgi.framework.Bundle; +import org.osgi.framework.Constants; + +/** + * This class is used by the Bundle Class to localize manifest headers. + */ +public class ManifestLocalization { + final static String DEFAULT_ROOT = FrameworkProperties.getProperty("equinox.root.locale", "en"); //$NON-NLS-1$ //$NON-NLS-2$ + private final AbstractBundle bundle; + private final Dictionary<String, String> rawHeaders; + private Dictionary<String, String> defaultLocaleHeaders = null; + private final Hashtable<String, BundleResourceBundle> cache = new Hashtable<String, BundleResourceBundle>(5); + + public ManifestLocalization(AbstractBundle bundle, Dictionary<String, String> rawHeaders) { + this.bundle = bundle; + this.rawHeaders = rawHeaders; + } + + Dictionary<String, String> getHeaders(String localeString) { + if (localeString.length() == 0) + return rawHeaders; + boolean isDefaultLocale = localeString.equals(Locale.getDefault().toString()); + Dictionary<String, String> currentDefault = defaultLocaleHeaders; + if (isDefaultLocale && currentDefault != null) { + return currentDefault; + } + try { + bundle.checkValid(); + } catch (IllegalStateException ex) { + // defaultLocaleHeaders should have been initialized on uninstall + if (currentDefault != null) + return currentDefault; + return rawHeaders; + } + ResourceBundle localeProperties = getResourceBundle(localeString, isDefaultLocale); + Enumeration<String> e = this.rawHeaders.keys(); + Headers<String, String> localeHeaders = new Headers<String, String>(this.rawHeaders.size()); + while (e.hasMoreElements()) { + String key = e.nextElement(); + String value = this.rawHeaders.get(key); + if (value.startsWith("%") && (value.length() > 1)) { //$NON-NLS-1$ + String propertiesKey = value.substring(1); + try { + value = localeProperties == null ? propertiesKey : (String) localeProperties.getObject(propertiesKey); + } catch (MissingResourceException ex) { + value = propertiesKey; + } + } + localeHeaders.set(key, value); + } + localeHeaders.setReadOnly(); + if (isDefaultLocale) { + defaultLocaleHeaders = localeHeaders; + } + return (localeHeaders); + } + + private String[] buildNLVariants(String nl) { + List<String> result = new ArrayList<String>(); + while (nl.length() > 0) { + result.add(nl); + int i = nl.lastIndexOf('_'); + nl = (i < 0) ? "" : nl.substring(0, i); //$NON-NLS-1$ + } + result.add(""); //$NON-NLS-1$ + return result.toArray(new String[result.size()]); + } + + /* + * This method find the appropriate Manifest Localization file inside the + * bundle. If not found, return null. + */ + ResourceBundle getResourceBundle(String localeString, boolean isDefaultLocale) { + BundleResourceBundle resourceBundle = lookupResourceBundle(localeString); + if (isDefaultLocale) + return (ResourceBundle) resourceBundle; + // need to determine if this is resource bundle is an empty stem + // if it is then the default locale should be used + if (resourceBundle == null || resourceBundle.isStemEmpty()) + return (ResourceBundle) lookupResourceBundle(Locale.getDefault().toString()); + return (ResourceBundle) resourceBundle; + } + + private BundleResourceBundle lookupResourceBundle(String localeString) { + // get the localization header as late as possible to avoid accessing the raw headers + // getting the first value from the raw headers forces the manifest to be parsed (bug 332039) + String localizationHeader = rawHeaders.get(Constants.BUNDLE_LOCALIZATION); + if (localizationHeader == null) + localizationHeader = Constants.BUNDLE_LOCALIZATION_DEFAULT_BASENAME; + synchronized (cache) { + BundleResourceBundle result = cache.get(localeString); + if (result != null) + return result.isEmpty() ? null : result; + String[] nlVarients = buildNLVariants(localeString); + BundleResourceBundle parent = null; + for (int i = nlVarients.length - 1; i >= 0; i--) { + BundleResourceBundle varientBundle = null; + URL varientURL = findResource(localizationHeader + (nlVarients[i].equals("") ? nlVarients[i] : '_' + nlVarients[i]) + ".properties"); //$NON-NLS-1$ //$NON-NLS-2$ + if (varientURL == null) { + varientBundle = cache.get(nlVarients[i]); + } else { + InputStream resourceStream = null; + try { + resourceStream = varientURL.openStream(); + varientBundle = new LocalizationResourceBundle(resourceStream); + } catch (IOException e) { + // ignore and continue + } finally { + if (resourceStream != null) { + try { + resourceStream.close(); + } catch (IOException e3) { + //Ignore exception + } + } + } + } + + if (varientBundle == null) { + varientBundle = new EmptyResouceBundle(nlVarients[i]); + } + if (parent != null) + varientBundle.setParent((ResourceBundle) parent); + cache.put(nlVarients[i], varientBundle); + parent = varientBundle; + } + result = cache.get(localeString); + return result.isEmpty() ? null : result; + } + } + + private URL findResource(String resource) { + AbstractBundle searchBundle = bundle; + if (bundle.isResolved()) { + if (bundle.isFragment() && bundle.getHosts() != null) { + //if the bundle is a fragment, look in the host first + searchBundle = bundle.getHosts()[0]; + if (searchBundle.getState() == Bundle.UNINSTALLED) + searchBundle = bundle; + } + return findInResolved(resource, searchBundle); + } + return searchBundle.getEntry0(resource); + } + + private static URL findInResolved(String filePath, AbstractBundle bundleHost) { + URL result = bundleHost.getEntry0(filePath); + if (result != null) + return result; + return findInFragments(filePath, bundleHost); + } + + private static URL findInFragments(String filePath, AbstractBundle searchBundle) { + BundleFragment[] fragments = searchBundle.getFragments(); + URL fileURL = null; + for (int i = 0; fragments != null && i < fragments.length && fileURL == null; i++) { + if (fragments[i].getState() != Bundle.UNINSTALLED) + fileURL = fragments[i].getEntry0(filePath); + } + return fileURL; + } + + private interface BundleResourceBundle { + void setParent(ResourceBundle parent); + + boolean isEmpty(); + + boolean isStemEmpty(); + } + + private class LocalizationResourceBundle extends PropertyResourceBundle implements BundleResourceBundle { + public LocalizationResourceBundle(InputStream in) throws IOException { + super(in); + } + + public void setParent(ResourceBundle parent) { + super.setParent(parent); + } + + public boolean isEmpty() { + return false; + } + + public boolean isStemEmpty() { + return parent == null; + } + } + + class EmptyResouceBundle extends ResourceBundle implements BundleResourceBundle { + private final String localeString; + + public EmptyResouceBundle(String locale) { + super(); + this.localeString = locale; + } + + @SuppressWarnings("unchecked") + public Enumeration<String> getKeys() { + return Collections.enumeration(Collections.EMPTY_LIST); + } + + protected Object handleGetObject(String arg0) throws MissingResourceException { + return null; + } + + public void setParent(ResourceBundle parent) { + super.setParent(parent); + } + + public boolean isEmpty() { + if (parent == null) + return true; + return ((BundleResourceBundle) parent).isEmpty(); + } + + public boolean isStemEmpty() { + if (DEFAULT_ROOT.equals(localeString)) + return false; + if (parent == null) + return true; + return ((BundleResourceBundle) parent).isStemEmpty(); + } + } +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/PackageAdminImpl.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/PackageAdminImpl.java new file mode 100644 index 000000000..f9e14605d --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/PackageAdminImpl.java @@ -0,0 +1,766 @@ +/******************************************************************************* + * Copyright (c) 2003, 2012 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.framework.internal.core; + +import java.io.IOException; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.*; +import org.eclipse.osgi.framework.adaptor.*; +import org.eclipse.osgi.framework.debug.Debug; +import org.eclipse.osgi.internal.loader.BundleLoader; +import org.eclipse.osgi.internal.loader.BundleLoaderProxy; +import org.eclipse.osgi.internal.profile.Profile; +import org.eclipse.osgi.service.resolver.*; +import org.eclipse.osgi.service.resolver.VersionRange; +import org.eclipse.osgi.util.NLS; +import org.osgi.framework.*; +import org.osgi.framework.wiring.FrameworkWiring; +import org.osgi.service.packageadmin.*; + +/** + * PackageAdmin service for the OSGi specification. + * + * Framework service which allows bundle programmers to inspect the packages + * exported in the framework and eagerly update or uninstall bundles. + * + * If present, there will only be a single instance of this service + * registered in the framework. + * + * <p> The term <i>exported package</i> (and the corresponding interface + * {@link ExportedPackage}) refers to a package that has actually been + * exported (as opposed to one that is available for export). + * + * <p> Note that the information about exported packages returned by this + * service is valid only until the next time {@link #refreshPackages(org.osgi.framework.Bundle[])} is + * called. + * If an ExportedPackage becomes stale, (that is, the package it references + * has been updated or removed as a result of calling + * PackageAdmin.refreshPackages()), + * its getName() and getSpecificationVersion() continue to return their + * old values, isRemovalPending() returns true, and getExportingBundle() + * and getImportingBundles() return null. + */ +public class PackageAdminImpl implements PackageAdmin, FrameworkWiring { + /** framework object */ + protected Framework framework; + + /* + * We need to make sure that the GetBundleAction class loads early to prevent a ClassCircularityError when checking permissions. + * See bug 161561 + */ + static { + Class<?> c; + c = GetBundleAction.class; + c.getName(); // to prevent compiler warnings + } + + static class GetBundleAction implements PrivilegedAction<Bundle> { + private Class<?> clazz; + private PackageAdminImpl impl; + + public GetBundleAction(PackageAdminImpl impl, Class<?> clazz) { + this.impl = impl; + this.clazz = clazz; + } + + public Bundle run() { + return impl.getBundlePriv(clazz); + } + } + + /** + * Constructor. + * + * @param framework Framework object. + */ + protected PackageAdminImpl(Framework framework) { + this.framework = framework; + } + + public ExportedPackage[] getExportedPackages(Bundle bundle) { + List<ExportedPackage> allExports = new ArrayList<ExportedPackage>(); + FrameworkAdaptor adaptor = framework.adaptor; + if (adaptor == null) + return null; + ExportPackageDescription[] allDescriptions = adaptor.getState().getExportedPackages(); + for (int i = 0; i < allDescriptions.length; i++) { + ExportedPackageImpl exportedPackage = createExportedPackage(allDescriptions[i]); + if (exportedPackage == null) + continue; + if (bundle == null || exportedPackage.getBundle() == bundle) + allExports.add(exportedPackage); + } + return (allExports.size() == 0 ? null : allExports.toArray(new ExportedPackage[allExports.size()])); + } + + private ExportedPackageImpl createExportedPackage(ExportPackageDescription description) { + BundleDescription exporter = description.getExporter(); + if (exporter == null || exporter.getHost() != null) + return null; + Object userObject = exporter.getUserObject(); + if (!(userObject instanceof BundleLoaderProxy)) { + BundleHost bundle = (BundleHost) framework.getBundle(exporter.getBundleId()); + if (bundle == null) + return null; + userObject = bundle.getLoaderProxy(); + } + return new ExportedPackageImpl(description, (BundleLoaderProxy) userObject); + } + + public ExportedPackage getExportedPackage(String name) { + ExportedPackage[] allExports = getExportedPackages((Bundle) null); + if (allExports == null) + return null; + ExportedPackage result = null; + for (int i = 0; i < allExports.length; i++) { + if (name.equals(allExports[i].getName())) { + if (result == null) { + result = allExports[i]; + } else { + Version curVersion = result.getVersion(); + Version newVersion = allExports[i].getVersion(); + if (newVersion.compareTo(curVersion) >= 0) + result = allExports[i]; + } + } + } + return result; + } + + public ExportedPackage[] getExportedPackages(String name) { + ExportedPackage[] allExports = getExportedPackages((Bundle) null); + if (allExports == null) + return null; + List<ExportedPackage> result = new ArrayList<ExportedPackage>(1); // rare to have more than one + for (int i = 0; i < allExports.length; i++) + if (name.equals(allExports[i].getName())) + result.add(allExports[i]); + return (result.size() == 0 ? null : result.toArray(new ExportedPackage[result.size()])); + } + + public void refreshPackages(Bundle[] input) { + refreshPackages(input, false, null); + } + + public void refreshPackages(Bundle[] input, boolean synchronously, final FrameworkListener[] listeners) { + framework.checkAdminPermission(framework.systemBundle, AdminPermission.RESOLVE); + + final AbstractBundle[] copy; + if (input != null) { + synchronized (input) { + copy = new AbstractBundle[input.length]; + System.arraycopy(input, 0, copy, 0, input.length); + } + } else + copy = null; + + if (synchronously) { + doResolveBundles(copy, true, listeners); + if (framework.isForcedRestart()) + framework.systemBundle.stop(); + } else { + Thread refresh = framework.secureAction.createThread(new Runnable() { + public void run() { + doResolveBundles(copy, true, listeners); + if (framework.isForcedRestart()) + framework.shutdown(FrameworkEvent.STOPPED_BOOTCLASSPATH_MODIFIED); + } + }, "Refresh Packages", framework.getContextFinder()); //$NON-NLS-1$ + refresh.start(); + } + } + + public boolean resolveBundles(Bundle[] bundles) { + return resolveBundles(bundles, false); + } + + boolean resolveBundles(Bundle[] bundles, boolean propagateError) { + framework.checkAdminPermission(framework.systemBundle, AdminPermission.RESOLVE); + if (bundles == null) + bundles = framework.getAllBundles(); + try { + doResolveBundles(bundles, false, null); + } catch (ResolverHookException e) { + if (propagateError) + throw e; + } + for (int i = 0; i < bundles.length; i++) + if (!((AbstractBundle) bundles[i]).isResolved()) + return false; + + return true; + } + + // This method is protected to enable a work around to bug 245251 + synchronized protected void doResolveBundles(Bundle[] bundles, boolean refreshPackages, FrameworkListener[] listeners) { + try { + if (Profile.PROFILE && Profile.STARTUP) + Profile.logEnter("resolve bundles"); //$NON-NLS-1$ + framework.publishBundleEvent(Framework.BATCHEVENT_BEGIN, framework.systemBundle); + State systemState = framework.adaptor.getState(); + BundleDescription[] descriptions = null; + int numBundles = bundles == null ? 0 : bundles.length; + if (!refreshPackages) { + List<BundleDescription> resolving = new ArrayList<BundleDescription>(); + for (Bundle bundle : bundles) { + BundleDescription description = ((AbstractBundle) bundle).getBundleDescription(); + if (((bundle.getState() & Bundle.INSTALLED) != 0) && description != null) + resolving.add(description); + } + descriptions = resolving.toArray(new BundleDescription[resolving.size()]); + } else if (numBundles > 0) { + // populate the resolved hosts package sources first (do this outside sync block: bug 280929) + populateLoaders(framework.getAllBundles()); + synchronized (framework.bundles) { + // now collect the descriptions to refresh + List<BundleDescription> results = new ArrayList<BundleDescription>(numBundles); + for (int i = 0; i < numBundles; i++) { + BundleDescription description = ((AbstractBundle) bundles[i]).getBundleDescription(); + if (description != null && description.getBundleId() != 0 && !results.contains(description)) + results.add(description); + if (framework.isRefreshDuplicateBSNAllowed()) { + // add in any bundles that have the same symbolic name see bug (169593) + AbstractBundle[] sameNames = framework.bundles.getBundles(bundles[i].getSymbolicName()); + if (sameNames != null && sameNames.length > 1) { + for (int j = 0; j < sameNames.length; j++) + if (sameNames[j] != bundles[i]) { + BundleDescription sameName = sameNames[j].getBundleDescription(); + if (sameName != null && sameName.getBundleId() != 0 && !results.contains(sameName)) { + if (checkExtensionBundle(sameName)) + results.add(sameName); + } + } + } + } + } + descriptions = (results.size() == 0 ? null : results.toArray(new BundleDescription[results.size()])); + } + } + StateDelta stateDelta = systemState.resolve(descriptions, refreshPackages); + BundleDelta[] delta = stateDelta.getChanges(); + processDelta(delta, refreshPackages, systemState); + if (stateDelta.getResovlerHookException() != null) + throw stateDelta.getResovlerHookException(); + } catch (Throwable t) { + if (Debug.DEBUG_PACKAGEADMIN) { + Debug.println("PackageAdminImpl.doResolveBundles: Error occured :"); //$NON-NLS-1$ + Debug.printStackTrace(t); + } + if (t instanceof RuntimeException) + throw (RuntimeException) t; + if (t instanceof Error) + throw (Error) t; + } finally { + if (Profile.PROFILE && Profile.STARTUP) + Profile.logExit("resolve bundles"); //$NON-NLS-1$ + if (framework.isActive()) { + framework.publishBundleEvent(Framework.BATCHEVENT_END, framework.systemBundle); + if (refreshPackages) + framework.publishFrameworkEvent(FrameworkEvent.PACKAGES_REFRESHED, framework.systemBundle, null, listeners); + } + } + } + + private void populateLoaders(AbstractBundle[] bundles) { + // populate all the loaders with their package source information + // this is needed to fix bug 259903. + for (int i = 0; i < bundles.length; i++) { + // only need to do this for host bundles which are resolved + if (bundles[i] instanceof BundleHost && bundles[i].isResolved()) { + // getting the BundleLoader object populates the require-bundle sources + BundleLoader loader = ((BundleHost) bundles[i]).getBundleLoader(); + if (loader != null) + // need to explicitly get the import package sources + loader.getImportedSources(null); + } + } + } + + private boolean checkExtensionBundle(BundleDescription sameName) { + if (sameName.getHost() == null || !sameName.isResolved()) + return true; // only do this extra check for resolved fragment bundles + // only add fragments if they are not for the system bundle + if (((BundleDescription) sameName.getHost().getSupplier()).getBundleId() != 0) + return true; + // never do this for resolved system bundle fragments + return false; + } + + private void resumeBundles(AbstractBundle[] bundles, boolean refreshPackages, int[] previousStates) { + if (Debug.DEBUG_PACKAGEADMIN) { + Debug.println("PackageAdminImpl: restart the bundles"); //$NON-NLS-1$ + } + if (bundles == null) + return; + for (int i = 0; i < bundles.length; i++) { + if (!bundles[i].isResolved() || (!refreshPackages && ((bundles[i].getBundleData().getStatus() & Constants.BUNDLE_LAZY_START) == 0 || bundles[i].testStateChanging(Thread.currentThread())))) + // skip bundles that are not resolved or + // if we are doing resolveBundles then skip non-lazy start bundles and bundles currently changing state by this thread + continue; + if (previousStates[i] == Bundle.ACTIVE) + try { + bundles[i].start(Bundle.START_TRANSIENT); + } catch (BundleException e) { + framework.publishFrameworkEvent(FrameworkEvent.ERROR, bundles[i], e); + } + else + framework.resumeBundle(bundles[i]); + } + } + + private void suspendBundle(AbstractBundle bundle) { + // attempt to suspend the bundle or obtain the state change lock + // Note that this may fail but we cannot quit the + // refreshPackages operation because of it. (bug 84169) + if (bundle.isActive() && !bundle.isFragment()) { + framework.suspendBundle(bundle, true); + } else { + if (bundle.getStateChanging() != Thread.currentThread()) + try { + bundle.beginStateChange(); + } catch (BundleException e) { + framework.publishFrameworkEvent(FrameworkEvent.ERROR, bundle, e); + } + } + + if (Debug.DEBUG_PACKAGEADMIN) { + if (bundle.stateChanging == null) { + Debug.println("Bundle state change lock is clear! " + bundle); //$NON-NLS-1$ + Debug.printStackTrace(new Exception("Stack trace")); //$NON-NLS-1$ + } + } + } + + private void applyRemovalPending(BundleDelta bundleDelta) throws BundleException { + if ((bundleDelta.getType() & BundleDelta.REMOVAL_COMPLETE) != 0) { + BundleDescription bundle = bundleDelta.getBundle(); + if (bundle.getDependents() != null && bundle.getDependents().length > 0) { + /* Reaching here is an internal error */ + if (Debug.DEBUG_PACKAGEADMIN) { + Debug.println("Bundles still depend on removed bundle! " + bundle); //$NON-NLS-1$ + Debug.printStackTrace(new Exception("Stack trace")); //$NON-NLS-1$ + } + throw new BundleException(Msg.OSGI_INTERNAL_ERROR); + } + Object userObject = bundle.getUserObject(); + if (userObject instanceof BundleLoaderProxy) { + BundleLoader.closeBundleLoader((BundleLoaderProxy) userObject); + try { + ((BundleLoaderProxy) userObject).getBundleData().close(); + } catch (IOException e) { + // ignore + } + } else if (userObject instanceof BundleData) { + try { + ((BundleData) userObject).close(); + } catch (IOException e) { + // ignore + } + } + } + } + + private AbstractBundle setResolved(BundleDescription bundleDescription) { + if (!bundleDescription.isResolved()) + return null; + AbstractBundle bundle = framework.getBundle(bundleDescription.getBundleId()); + if (bundle == null) { + BundleException be = new BundleException(NLS.bind(Msg.BUNDLE_NOT_IN_FRAMEWORK, bundleDescription)); + framework.publishFrameworkEvent(FrameworkEvent.ERROR, framework.systemBundle, be); + return null; + } + boolean resolve = true; + if (bundle.isFragment()) { + BundleDescription[] hosts = bundleDescription.getHost().getHosts(); + for (int i = 0; i < hosts.length; i++) { + BundleHost host = (BundleHost) framework.getBundle(hosts[i].getBundleId()); + resolve = ((BundleFragment) bundle).addHost(host); + } + } + if (resolve) + bundle.resolve(); + return bundle; + } + + private void applyDeltas(BundleDelta[] bundleDeltas) throws BundleException { + Arrays.sort(bundleDeltas, new Comparator<BundleDelta>() { + public int compare(BundleDelta delta0, BundleDelta delta1) { + return (int) (delta0.getBundle().getBundleId() - delta1.getBundle().getBundleId()); + } + }); + for (int i = 0; i < bundleDeltas.length; i++) { + int type = bundleDeltas[i].getType(); + if ((type & (BundleDelta.REMOVAL_PENDING | BundleDelta.REMOVAL_COMPLETE)) != 0) + applyRemovalPending(bundleDeltas[i]); + if ((type & BundleDelta.RESOLVED) != 0) { + AbstractBundle bundle = setResolved(bundleDeltas[i].getBundle()); + if (bundle != null && bundle.isResolved()) { + NativeCodeSpecification nativeCode = bundleDeltas[i].getBundle().getNativeCodeSpecification(); + if (nativeCode != null && nativeCode.getSupplier() != null) + try { + BundleData data = bundle.getBundleData(); + data.installNativeCode(((NativeCodeDescription) nativeCode.getSupplier()).getNativePaths()); + } catch (BundleException e) { + framework.publishFrameworkEvent(FrameworkEvent.ERROR, bundle, e); + } + } + } + } + } + + private AbstractBundle[] processDelta(BundleDelta[] bundleDeltas, boolean refreshPackages, State systemState) { + List<AbstractBundle> bundlesList = new ArrayList<AbstractBundle>(bundleDeltas.length); + // get all the bundles that are going to be refreshed + for (int i = 0; i < bundleDeltas.length; i++) { + if ((bundleDeltas[i].getType() & BundleDelta.REMOVAL_COMPLETE) != 0 && (bundleDeltas[i].getType() & BundleDelta.REMOVED) == 0) + // this means the bundle was previously pending removal; do not add to list because it was already removed from before. + continue; + AbstractBundle changedBundle = framework.getBundle(bundleDeltas[i].getBundle().getBundleId()); + if (changedBundle != null && !bundlesList.contains(changedBundle)) + bundlesList.add(changedBundle); + } + AbstractBundle[] refresh = bundlesList.toArray(new AbstractBundle[bundlesList.size()]); + // first sort by id/start-level order + Util.sort(refresh, 0, refresh.length); + // then sort by dependency order + framework.startLevelManager.sortByDependency(refresh); + boolean[] previouslyResolved = new boolean[refresh.length]; + int[] previousStates = new int[refresh.length]; + try { + try { + if (Debug.DEBUG_PACKAGEADMIN) { + Debug.println("refreshPackages: Suspend each bundle and acquire its state change lock"); //$NON-NLS-1$ + } + // find which bundles were previously resolved and handle extension bundles + boolean restart = false; + for (int i = refresh.length - 1; i >= 0; i--) { + previouslyResolved[i] = refresh[i].isResolved(); + if (refresh[i] == framework.systemBundle) + restart = true; + else if (((refresh[i].bundledata.getType() & BundleData.TYPE_FRAMEWORK_EXTENSION) != 0) && previouslyResolved[i]) + restart = true; + else if ((refresh[i].bundledata.getType() & BundleData.TYPE_BOOTCLASSPATH_EXTENSION) != 0) + restart = true; + else if ((refresh[i].bundledata.getType() & BundleData.TYPE_EXTCLASSPATH_EXTENSION) != 0 && previouslyResolved[i]) + restart = true; + } + if (restart) { + FrameworkProperties.setProperty("osgi.forcedRestart", "true"); //$NON-NLS-1$ //$NON-NLS-2$ + framework.setForcedRestart(true); + // do not shutdown the framework while holding the PackageAdmin lock (bug 194149) + return null; + } + // now suspend each bundle and grab its state change lock. + if (refreshPackages) + for (int i = refresh.length - 1; i >= 0; i--) { + previousStates[i] = refresh[i].getState(); + suspendBundle(refresh[i]); + } + /* + * Refresh the bundles which will unexport the packages. + * This will move RESOLVED bundles to the INSTALLED state. + */ + if (Debug.DEBUG_PACKAGEADMIN) { + Debug.println("refreshPackages: refresh the bundles"); //$NON-NLS-1$ + } + + synchronized (framework.bundles) { + for (int i = refresh.length - 1; i >= 0; i--) + refresh[i].refresh(); + } + // send out unresolved events outside synch block (defect #80610) + // send out unresolved events in reverse dependency order (defect #207505) + for (int i = refresh.length - 1; i >= 0; i--) { + // send out unresolved events + if (previouslyResolved[i]) + framework.publishBundleEvent(BundleEvent.UNRESOLVED, refresh[i]); + } + + /* + * apply Deltas. + */ + if (Debug.DEBUG_PACKAGEADMIN) { + Debug.println("refreshPackages: applying deltas to bundles"); //$NON-NLS-1$ + } + synchronized (framework.bundles) { + applyDeltas(bundleDeltas); + } + + } finally { + /* + * Release the state change locks. + */ + if (Debug.DEBUG_PACKAGEADMIN) { + Debug.println("refreshPackages: release the state change locks"); //$NON-NLS-1$ + } + if (refreshPackages) + for (int i = 0; i < refresh.length; i++) { + AbstractBundle changedBundle = refresh[i]; + changedBundle.completeStateChange(); + } + } + /* + * Take this opportunity to clean up the adaptor storage. + */ + if (refreshPackages) { + if (Debug.DEBUG_PACKAGEADMIN) + Debug.println("refreshPackages: clean up adaptor storage"); //$NON-NLS-1$ + try { + framework.adaptor.compactStorage(); + } catch (IOException e) { + if (Debug.DEBUG_PACKAGEADMIN) { + Debug.println("refreshPackages exception: " + e.getMessage()); //$NON-NLS-1$ + Debug.printStackTrace(e); + } + framework.publishFrameworkEvent(FrameworkEvent.ERROR, framework.systemBundle, new BundleException(Msg.BUNDLE_REFRESH_FAILURE, e)); + } + } + } catch (BundleException e) { + if (Debug.DEBUG_PACKAGEADMIN) { + Debug.println("refreshPackages exception: " + e.getMessage()); //$NON-NLS-1$ + Debug.printStackTrace(e.getNestedException() == null ? e : e.getNestedException()); + } + framework.publishFrameworkEvent(FrameworkEvent.ERROR, framework.systemBundle, new BundleException(Msg.BUNDLE_REFRESH_FAILURE, e)); + } + + // send out any resolved. This must be done after the state change locks have been release. + if (Debug.DEBUG_PACKAGEADMIN) + Debug.println("refreshPackages: send out RESOLVED events"); //$NON-NLS-1$ + for (int i = 0; i < refresh.length; i++) + if (refresh[i].isResolved()) + framework.publishBundleEvent(BundleEvent.RESOLVED, refresh[i]); + + // if we end up refreshing the system bundle or one of its fragments the framework will be shutdown and + // should be re-started. This call should return without doing further work. + if (!framework.isActive()) + return refresh; + if (refreshPackages) { + // must clear permission class and condition cache + framework.securityAdmin.clearCaches(); + // increment the system state timestamp if we are refreshing packages. + // this is needed incase we suspended a bundle from processing the delta (bug 167483) + if (bundleDeltas.length > 0) + systemState.setTimeStamp(systemState.getTimeStamp() == Long.MAX_VALUE ? 0 : systemState.getTimeStamp() + 1); + } + // always resume bundles incase we have lazy-start bundles + resumeBundles(refresh, refreshPackages, previousStates); + return refresh; + } + + public RequiredBundle[] getRequiredBundles(String symbolicName) { + AbstractBundle[] bundles; + if (symbolicName == null) + bundles = framework.getAllBundles(); + else + bundles = framework.getBundleBySymbolicName(symbolicName); + if (bundles == null || bundles.length == 0) + return null; + + List<RequiredBundle> result = new ArrayList<RequiredBundle>(bundles.length); + for (int i = 0; i < bundles.length; i++) { + if (bundles[i].isFragment() || !bundles[i].isResolved() || bundles[i].getSymbolicName() == null) + continue; + if (bundles[i].hasPermission(new BundlePermission(bundles[i].getSymbolicName(), BundlePermission.PROVIDE))) + result.add(((BundleHost) bundles[i]).getLoaderProxy()); + } + return result.size() == 0 ? null : result.toArray(new RequiredBundle[result.size()]); + } + + public Bundle[] getBundles(String symbolicName, String versionRange) { + if (symbolicName == null) { + throw new IllegalArgumentException(); + } + AbstractBundle bundles[] = framework.getBundleBySymbolicName(symbolicName); + if (bundles == null) + return null; + + if (versionRange == null) { + AbstractBundle[] result = new AbstractBundle[bundles.length]; + System.arraycopy(bundles, 0, result, 0, result.length); + return result; + } + + // This code depends on the array of bundles being in descending + // version order. + List<AbstractBundle> result = new ArrayList<AbstractBundle>(bundles.length); + VersionRange range = new VersionRange(versionRange); + for (int i = 0; i < bundles.length; i++) { + if (range.isIncluded(bundles[i].getVersion())) { + result.add(bundles[i]); + } + } + + if (result.size() == 0) + return null; + return result.toArray(new AbstractBundle[result.size()]); + } + + public Bundle[] getFragments(Bundle bundle) { + return ((AbstractBundle) bundle).getFragments(); + } + + public Bundle[] getHosts(Bundle bundle) { + BundleHost[] hosts = ((AbstractBundle) bundle).getHosts(); + if (hosts == null) + return null; + // copy the array to protect modification + Bundle[] result = new Bundle[hosts.length]; + for (int i = 0; i < hosts.length; i++) + result[i] = hosts[i]; + return result; + } + + Bundle getBundlePriv(Class<?> clazz) { + ClassLoader cl = clazz.getClassLoader(); + if (cl instanceof BundleClassLoader) { + ClassLoaderDelegate delegate = ((BundleClassLoader) cl).getDelegate(); + if (delegate instanceof BundleLoader) + return ((BundleLoader) delegate).getBundle(); + } + if (cl == getClass().getClassLoader()) + return framework.systemBundle; + return null; + } + + public Bundle getBundle(@SuppressWarnings("rawtypes") final Class clazz) { + if (System.getSecurityManager() == null) + return getBundlePriv(clazz); + return AccessController.doPrivileged(new GetBundleAction(this, clazz)); + } + + public int getBundleType(Bundle bundle) { + return ((AbstractBundle) bundle).isFragment() ? PackageAdmin.BUNDLE_TYPE_FRAGMENT : 0; + } + + protected void cleanup() { + //This is only called when the framework is shutting down + } + + protected void setResolvedBundles(InternalSystemBundle systemBundle) { + checkSystemBundle(systemBundle); + // Now set the actual state of the bundles from the persisted state. + State state = framework.adaptor.getState(); + BundleDescription[] descriptions = state.getBundles(); + for (int i = 0; i < descriptions.length; i++) { + if (descriptions[i].getBundleId() == 0) + setFrameworkVersion(descriptions[i]); + else + setResolved(descriptions[i]); + } + } + + private void checkSystemBundle(InternalSystemBundle systemBundle) { + try { + // first check that the system bundle has not changed since last saved state. + State state = framework.adaptor.getState(); + BundleDescription oldSystemBundle = state.getBundle(0); + boolean different = false; + if (oldSystemBundle == null || !systemBundle.getBundleData().getVersion().equals(oldSystemBundle.getVersion())) + different = true; + if (!different && FrameworkProperties.getProperty("osgi.dev") == null) //$NON-NLS-1$ + return; // return quick if not in dev mode; system bundle version changes with each build + BundleDescription newSystemBundle = state.getFactory().createBundleDescription(state, systemBundle.getHeaders(""), systemBundle.getLocation(), 0); //$NON-NLS-1$ + if (newSystemBundle == null) + throw new BundleException(Msg.OSGI_SYSTEMBUNDLE_DESCRIPTION_ERROR); + if (!different) { + // need to check to make sure the system bundle description is up to date in the state. + ExportPackageDescription[] oldPackages = oldSystemBundle.getExportPackages(); + ExportPackageDescription[] newPackages = newSystemBundle.getExportPackages(); + if (oldPackages.length >= newPackages.length) { + for (int i = 0; i < newPackages.length && !different; i++) { + if (oldPackages[i].getName().equals(newPackages[i].getName())) { + Object oldVersion = oldPackages[i].getVersion(); + Object newVersion = newPackages[i].getVersion(); + different = oldVersion == null ? newVersion != null : !oldVersion.equals(newVersion); + } else { + different = true; + } + } + } else { + different = true; + } + } + if (different) { + state.removeBundle(0); + state.addBundle(newSystemBundle); + // force resolution so packages are properly linked + state.resolve(false); + } + } catch (BundleException e) /* fatal error */{ + e.printStackTrace(); + throw new RuntimeException(NLS.bind(Msg.OSGI_SYSTEMBUNDLE_CREATE_EXCEPTION, e.getMessage()), e); + } + } + + private void setFrameworkVersion(BundleDescription systemBundle) { + ExportPackageDescription[] packages = systemBundle.getExportPackages(); + for (int i = 0; i < packages.length; i++) + if (packages[i].getName().equals(Constants.OSGI_FRAMEWORK_PACKAGE)) { + FrameworkProperties.setProperty(Constants.FRAMEWORK_VERSION, packages[i].getVersion().toString()); + break; + } + FrameworkProperties.setProperty(Constants.OSGI_IMPL_VERSION_KEY, systemBundle.getVersion().toString()); + } + + public Bundle getBundle() { + return framework.getBundle(0); + } + + public void refreshBundles(Collection<Bundle> bundles, FrameworkListener... listeners) { + refreshPackages(bundles == null ? null : bundles.toArray(new Bundle[bundles.size()]), false, listeners); + } + + public boolean resolveBundles(Collection<Bundle> bundles) { + return resolveBundles(bundles == null ? null : bundles.toArray(new Bundle[bundles.size()])); + } + + public Collection<Bundle> getRemovalPendingBundles() { + // TODO need to consolidate our removal pending tracking. + // We currently have three places this is kept (PackageAdminImpl, StateImpl and ResolverImpl) + // Using the state's because it has easy access to the uninstalled Bundle objects + BundleDescription[] removals = framework.adaptor.getState().getRemovalPending(); + Set<Bundle> result = new HashSet<Bundle>(); + for (int i = 0; i < removals.length; i++) { + Object ref = removals[i].getUserObject(); + if (ref instanceof BundleReference) + result.add(((BundleReference) ref).getBundle()); + } + return result; + } + + public Collection<Bundle> getDependencyClosure(Collection<Bundle> bundles) { + Collection<BundleDescription> descriptions = getDescriptionClosure(bundles); + Set<Bundle> result = new HashSet<Bundle>(); + for (BundleDescription description : descriptions) { + Object userObject = description.getUserObject(); + if (userObject instanceof BundleReference) { + Bundle bundle = ((BundleReference) userObject).getBundle(); + if (bundle != null) + result.add(bundle); + } + } + return result; + } + + private Collection<BundleDescription> getDescriptionClosure(Collection<Bundle> bundles) { + State state = framework.adaptor.getState(); + Collection<BundleDescription> descriptions = new ArrayList<BundleDescription>(); + for (Bundle bundle : bundles) { + BundleDescription description = state.getBundle(bundle.getBundleId()); + if (description != null) + descriptions.add(description); + } + return state.getDependencyClosure(descriptions); + } + +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/ReferenceInputStream.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/ReferenceInputStream.java new file mode 100644 index 000000000..9a1064856 --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/ReferenceInputStream.java @@ -0,0 +1,38 @@ +/******************************************************************************* + * Copyright (c) 2004, 2006 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.framework.internal.core; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; + +/** + * InputStream subclass which provides a reference (via URL) to the data + * rather than allowing the input stream to be directly read. + */ +public class ReferenceInputStream extends InputStream { + protected URL reference; + + public ReferenceInputStream(URL reference) { + this.reference = reference; + } + + /* This method should not be called. + */ + public int read() throws IOException { + throw new IOException(); + } + + public URL getReference() { + return reference; + } +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/StartLevelEvent.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/StartLevelEvent.java new file mode 100644 index 000000000..10636bc64 --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/StartLevelEvent.java @@ -0,0 +1,82 @@ +/******************************************************************************* + * Copyright (c) 2003, 2010 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.framework.internal.core; + +import java.util.EventObject; +import org.osgi.framework.FrameworkListener; + +/** + * StartLevel Event for the OSGi framework. + * + * Event which signifies that a start level change has been requested for the framework or for a bundle. + * + */ +class StartLevelEvent extends EventObject { + private static final long serialVersionUID = 3258125839085155891L; + public final static int CHANGE_BUNDLE_SL = 0x00000000; + public final static int CHANGE_FW_SL = 0x00000001; + + /** + * Event Type + */ + private final transient int type; + + /** + * StartLevel - value depends on event type: + * CHANGE_BUNDLE_SL - value is the new bundle startlevel + * CHANGE_FW_SL - value is the new framework startlevel + * + */ + private final transient int newSl; + + /** + * For a change in bundle startlevel, this is the bundle to be changed. + * For a change in framework startlevel, this is the bundle requesting the change. + */ + private final transient AbstractBundle bundle; + + /** + * A list of framework listeners that must be called at the end of the operation. + */ + private final transient FrameworkListener[] listeners; + + /** + * Creates a StartLevel event regarding the specified bundle. + * + * @param type The type of startlevel event (inc or dec) + * @param newSl the ultimate requested startlevel we are on our way to + * @param bundle The affected bundle, or system bundle if it is for the framework + */ + public StartLevelEvent(int type, int newSl, AbstractBundle bundle, FrameworkListener... listeners) { + super(bundle); + this.type = type; + this.newSl = newSl; + this.bundle = bundle; + this.listeners = listeners; + } + + public int getType() { + return this.type; + } + + public int getNewSL() { + return this.newSl; + } + + public AbstractBundle getBundle() { + return this.bundle; + } + + public FrameworkListener[] getListeners() { + return listeners; + } +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/StartLevelManager.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/StartLevelManager.java new file mode 100644 index 000000000..efd697c99 --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/StartLevelManager.java @@ -0,0 +1,678 @@ +/******************************************************************************* + * Copyright (c) 2003, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + +package org.eclipse.osgi.framework.internal.core; + +import java.io.IOException; +import java.security.*; +import java.util.*; +import org.eclipse.osgi.framework.debug.Debug; +import org.eclipse.osgi.framework.eventmgr.*; +import org.eclipse.osgi.service.resolver.BundleDescription; +import org.eclipse.osgi.util.NLS; +import org.osgi.framework.*; +import org.osgi.service.startlevel.StartLevel; + +/** + * StartLevel service implementation for the OSGi specification. + * + * Framework service which allows management of framework and bundle startlevels. + * + * This class also acts as the StartLevel service factory class, providing StartLevel objects + * to those requesting org.osgi.service.startlevel.StartLevel service. + * + * If present, there will only be a single instance of this service + * registered in the framework. + */ +public class StartLevelManager implements EventDispatcher<Object, Object, StartLevelEvent>, StartLevel { + protected static EventManager eventManager; + protected static Map<Object, Object> startLevelListeners; + + /** The initial bundle start level for newly installed bundles */ + protected int initialBundleStartLevel = 1; + // default value is 1 for compatibility mode + + /** The currently active framework start level */ + private int activeSL = 0; + + /** An object used to lock the active startlevel while it is being referenced */ + private final Object lock = new Object(); + private final Framework framework; + + /** This constructor is called by the Framework */ + protected StartLevelManager(Framework framework) { + this.framework = framework; + } + + protected void initialize() { + initialBundleStartLevel = framework.adaptor.getInitialBundleStartLevel(); + + // create an event manager and a start level listener + // note that we do not pass the ContextFinder because it is set each time doSetStartLevel is called + eventManager = new EventManager("Start Level Event Dispatcher"); //$NON-NLS-1$ + startLevelListeners = new CopyOnWriteIdentityMap<Object, Object>(); + startLevelListeners.put(this, this); + } + + protected void cleanup() { + eventManager.close(); + eventManager = null; + startLevelListeners.clear(); + startLevelListeners = null; + } + + /** + * Return the initial start level value that is assigned + * to a Bundle when it is first installed. + * + * @return The initial start level value for Bundles. + * @see #setInitialBundleStartLevel + */ + public int getInitialBundleStartLevel() { + return initialBundleStartLevel; + } + + /** + * Set the initial start level value that is assigned + * to a Bundle when it is first installed. + * + * <p>The initial bundle start level will be set to the specified start level. The + * initial bundle start level value will be persistently recorded + * by the Framework. + * + * <p>When a Bundle is installed via <tt>BundleContext.installBundle</tt>, + * it is assigned the initial bundle start level value. + * + * <p>The default initial bundle start level value is 1 + * unless this method has been + * called to assign a different initial bundle + * start level value. + * + * <p>This method does not change the start level values of installed + * bundles. + * + * @param startlevel The initial start level for newly installed bundles. + * @throws IllegalArgumentException If the specified start level is less than or + * equal to zero. + * @throws SecurityException if the caller does not have the + * <tt>AdminPermission</tt> and the Java runtime environment supports + * permissions. + */ + public void setInitialBundleStartLevel(int startlevel) { + framework.checkAdminPermission(framework.systemBundle, AdminPermission.STARTLEVEL); + if (startlevel <= 0) { + throw new IllegalArgumentException(); + } + initialBundleStartLevel = startlevel; + framework.adaptor.setInitialBundleStartLevel(startlevel); + } + + /** + * Return the active start level value of the Framework. + * + * If the Framework is in the process of changing the start level + * this method must return the active start level if this + * differs from the requested start level. + * + * @return The active start level value of the Framework. + */ + public int getStartLevel() { + return activeSL; + } + + /** + * Modify the active start level of the Framework. + * + * <p>The Framework will move to the requested start level. This method + * will return immediately to the caller and the start level + * change will occur asynchronously on another thread. + * + * <p>If the specified start level is + * higher than the active start level, the + * Framework will continue to increase the start level + * until the Framework has reached the specified start level, + * starting bundles at each + * start level which are persistently marked to be started as described in the + * <tt>Bundle.start</tt> method. + * + * At each intermediate start level value on the + * way to and including the target start level, the framework must: + * <ol> + * <li>Change the active start level to the intermediate start level value. + * <li>Start bundles at the intermediate start level in + * ascending order by <tt>Bundle.getBundleId</tt>. + * </ol> + * When this process completes after the specified start level is reached, + * the Framework will broadcast a Framework event of + * type <tt>FrameworkEvent.STARTLEVEL_CHANGED</tt> to announce it has moved to the specified + * start level. + * + * <p>If the specified start level is lower than the active start level, the + * Framework will continue to decrease the start level + * until the Framework has reached the specified start level + * stopping bundles at each + * start level as described in the <tt>Bundle.stop</tt> method except that their + * persistently recorded state indicates that they must be restarted in the + * future. + * + * At each intermediate start level value on the + * way to and including the specified start level, the framework must: + * <ol> + * <li>Stop bundles at the intermediate start level in + * descending order by <tt>Bundle.getBundleId</tt>. + * <li>Change the active start level to the intermediate start level value. + * </ol> + * When this process completes after the specified start level is reached, + * the Framework will broadcast a Framework event of + * type <tt>FrameworkEvent.STARTLEVEL_CHANGED</tt> to announce it has moved to the specified + * start level. + * + * <p>If the specified start level is equal to the active start level, then + * no bundles are started or stopped, however, the Framework must broadcast + * a Framework event of type <tt>FrameworkEvent.STARTLEVEL_CHANGED</tt> to + * announce it has finished moving to the specified start level. This + * event may arrive before the this method return. + * + * @param newSL The requested start level for the Framework. + * @throws IllegalArgumentException If the specified start level is less than or + * equal to zero. + * @throws SecurityException If the caller does not have the + * <tt>AdminPermission</tt> and the Java runtime environment supports + * permissions. + */ + public void setStartLevel(int newSL, org.osgi.framework.Bundle callerBundle, FrameworkListener... listeners) { + if (newSL <= 0) { + throw new IllegalArgumentException(NLS.bind(Msg.STARTLEVEL_EXCEPTION_INVALID_REQUESTED_STARTLEVEL, "" + newSL)); //$NON-NLS-1$ + } + framework.checkAdminPermission(framework.systemBundle, AdminPermission.STARTLEVEL); + + if (Debug.DEBUG_STARTLEVEL) { + Debug.println("StartLevelImpl: setStartLevel: " + newSL + "; callerBundle = " + callerBundle.getBundleId()); //$NON-NLS-1$ //$NON-NLS-2$ + } + issueEvent(new StartLevelEvent(StartLevelEvent.CHANGE_FW_SL, newSL, (AbstractBundle) callerBundle, listeners)); + + } + + public void setStartLevel(int newSL) { + setStartLevel(newSL, framework.systemBundle); + } + + /** + * Internal method to shut down the framework synchronously by setting the startlevel to zero + * and calling the StartLevelListener worker calls directly + * + * This method does not return until all bundles are stopped and the framework is shut down. + */ + protected void shutdown() { + doSetStartLevel(0); + } + + /** + * Internal worker method to set the startlevel + * + * @param newSL start level value + * @param callerBundle - the bundle initiating the change in start level + */ + void doSetStartLevel(int newSL, FrameworkListener... listeners) { + synchronized (lock) { + ClassLoader previousTCCL = Thread.currentThread().getContextClassLoader(); + ClassLoader contextFinder = framework.getContextFinder(); + if (contextFinder == previousTCCL) + contextFinder = null; + else + Thread.currentThread().setContextClassLoader(contextFinder); + try { + int tempSL = activeSL; + if (newSL > tempSL) { + boolean launching = tempSL == 0; + for (int i = tempSL; i < newSL; i++) { + if (Debug.DEBUG_STARTLEVEL) { + Debug.println("sync - incrementing Startlevel from " + tempSL); //$NON-NLS-1$ + } + tempSL++; + // Note that we must get a new list of installed bundles each time; + // this is because additional bundles could have been installed from the previous start-level + incFWSL(i + 1, getInstalledBundles(framework.bundles, false)); + } + if (launching) { + framework.systemBundle.state = Bundle.ACTIVE; + framework.publishBundleEvent(BundleEvent.STARTED, framework.systemBundle); + framework.publishFrameworkEvent(FrameworkEvent.STARTED, framework.systemBundle, null); + } + } else { + AbstractBundle[] sortedBundles = getInstalledBundles(framework.bundles, true); + for (int i = tempSL; i > newSL; i--) { + if (Debug.DEBUG_STARTLEVEL) { + Debug.println("sync - decrementing Startlevel from " + tempSL); //$NON-NLS-1$ + } + tempSL--; + decFWSL(i - 1, sortedBundles); + } + if (newSL == 0) { + // unload all bundles + unloadAllBundles(framework.bundles); + stopSystemBundle(); + } + } + framework.publishFrameworkEvent(FrameworkEvent.STARTLEVEL_CHANGED, framework.systemBundle, null, listeners); + if (Debug.DEBUG_STARTLEVEL) { + Debug.println("StartLevelImpl: doSetStartLevel: STARTLEVEL_CHANGED event published"); //$NON-NLS-1$ + } + } catch (Error e) { + framework.publishFrameworkEvent(FrameworkEvent.ERROR, framework.systemBundle, e, listeners); + throw e; + } catch (RuntimeException e) { + framework.publishFrameworkEvent(FrameworkEvent.ERROR, framework.systemBundle, e, listeners); + throw e; + } finally { + if (contextFinder != null) + Thread.currentThread().setContextClassLoader(previousTCCL); + } + } + } + + /** + * This method is used within the package to save the actual active startlevel value for the framework. + * Externally the setStartLevel method must be used. + * + * @param newSL - the new startlevel to save + */ + protected void saveActiveStartLevel(int newSL) { + synchronized (lock) { + activeSL = newSL; + } + } + + /** + * Return the persistent state of the specified bundle. + * + * <p>This method returns the persistent state of a bundle. + * The persistent state of a bundle indicates whether a bundle + * is persistently marked to be started when it's start level is + * reached. + * + * @return <tt>true</tt> if the bundle is persistently marked to be started, + * <tt>false</tt> if the bundle is not persistently marked to be started. + * @exception java.lang.IllegalArgumentException If the specified bundle has been uninstalled. + */ + public boolean isBundlePersistentlyStarted(org.osgi.framework.Bundle bundle) { + return ((AbstractBundle) bundle).isPersistentlyStarted(); + } + + public boolean isBundleActivationPolicyUsed(Bundle bundle) { + return ((AbstractBundle) bundle).isActivationPolicyUsed(); + } + + /** + * Return the assigned start level value for the specified Bundle. + * + * @param bundle The target bundle. + * @return The start level value of the specified Bundle. + * @exception java.lang.IllegalArgumentException If the specified bundle has been uninstalled. + */ + public int getBundleStartLevel(org.osgi.framework.Bundle bundle) { + return ((AbstractBundle) bundle).getStartLevel(); + } + + /** + * Assign a start level value to the specified Bundle. + * + * <p>The specified bundle will be assigned the specified start level. The + * start level value assigned to the bundle will be persistently recorded + * by the Framework. + * + * If the new start level for the bundle is lower than or equal to the active start level of + * the Framework, the Framework will start the specified bundle as described + * in the <tt>Bundle.start</tt> method if the bundle is persistently marked + * to be started. The actual starting of this bundle must occur asynchronously. + * + * If the new start level for the bundle is higher than the active start level of + * the Framework, the Framework will stop the specified bundle as described + * in the <tt>Bundle.stop</tt> method except that the persistently recorded + * state for the bundle indicates that the bundle must be restarted in the + * future. The actual stopping of this bundle must occur asynchronously. + * + * @param bundle The target bundle. + * @param newSL The new start level for the specified Bundle. + * @throws IllegalArgumentException + * If the specified bundle has been uninstalled or + * if the specified start level is less than or equal to zero, or the specified bundle is + * the system bundle. + * @throws SecurityException if the caller does not have the + * <tt>AdminPermission</tt> and the Java runtime environment supports + * permissions. + */ + public void setBundleStartLevel(org.osgi.framework.Bundle bundle, int newSL) { + + String exceptionText = null; + if (bundle.getBundleId() == 0) { // system bundle has id=0 + exceptionText = Msg.STARTLEVEL_CANT_CHANGE_SYSTEMBUNDLE_STARTLEVEL; + } else if (bundle.getState() == Bundle.UNINSTALLED) { + exceptionText = NLS.bind(Msg.BUNDLE_UNINSTALLED_EXCEPTION, ((AbstractBundle) bundle).getBundleData().getLocation()); + } else if (newSL <= 0) { + exceptionText = NLS.bind(Msg.STARTLEVEL_EXCEPTION_INVALID_REQUESTED_STARTLEVEL, "" + newSL); //$NON-NLS-1$ + } + if (exceptionText != null) + throw new IllegalArgumentException(exceptionText); + // first check the permission of the caller + framework.checkAdminPermission(bundle, AdminPermission.EXECUTE); + try { + // if the bundle's startlevel is not already at the requested startlevel + if (newSL != ((org.eclipse.osgi.framework.internal.core.AbstractBundle) bundle).getInternalStartLevel()) { + final AbstractBundle b = (AbstractBundle) bundle; + b.getBundleData().setStartLevel(newSL); + try { + AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() { + public Object run() throws Exception { + b.getBundleData().save(); + return null; + } + }); + } catch (PrivilegedActionException e) { + if (e.getException() instanceof IOException) { + throw (IOException) e.getException(); + } + throw (RuntimeException) e.getException(); + } + // handle starting or stopping the bundle asynchronously + issueEvent(new StartLevelEvent(StartLevelEvent.CHANGE_BUNDLE_SL, newSL, (AbstractBundle) bundle)); + } + } catch (IOException e) { + framework.publishFrameworkEvent(FrameworkEvent.ERROR, bundle, e); + } + + } + + /** + * This method sends the StartLevelEvent to the EventManager for dispatching + * + * @param sle The event to be queued to the Event Manager + */ + private void issueEvent(StartLevelEvent sle) { + + /* queue to hold set of listeners */ + ListenerQueue<Object, Object, StartLevelEvent> queue = new ListenerQueue<Object, Object, StartLevelEvent>(eventManager); + + /* add set of StartLevelListeners to queue */ + queue.queueListeners(startLevelListeners.entrySet(), this); + + /* dispatch event to set of listeners */ + queue.dispatchEventAsynchronous(sle.getType(), sle); + } + + /** + * This method is the call back that is called once for each listener. + * This method must cast the EventListener object to the appropriate listener + * class for the event type and call the appropriate listener method. + * + * @param listener This listener must be cast to the appropriate listener + * class for the events created by this source and the appropriate listener method + * must then be called. + * @param listenerObject This is the optional object that was passed to + * EventListeners.addListener when the listener was added to the EventListeners. + * @param eventAction This value was passed to the ListenerQueue object via one of its + * dispatchEvent* method calls. It can provide information (such + * as which listener method to call) so that this method + * can complete the delivery of the event to the listener. + * @param event This object was passed to the ListenerQueue object via one of its + * dispatchEvent* method calls. This object was created by the event source and + * is passed to this method. It should contain all the necessary information (such + * as what event object to pass) so that this method + * can complete the delivery of the event to the listener. + */ + public void dispatchEvent(Object listener, Object listenerObject, int eventAction, StartLevelEvent event) { + try { + switch (eventAction) { + case StartLevelEvent.CHANGE_BUNDLE_SL : + setBundleSL(event); + break; + case StartLevelEvent.CHANGE_FW_SL : + doSetStartLevel(event.getNewSL(), event.getListeners()); + break; + } + } catch (Throwable t) { + // allow the adaptor to handle this unexpected error + framework.adaptor.handleRuntimeError(t); + } + } + + /** + * Increment the active startlevel by one + */ + protected void incFWSL(int incToSL, AbstractBundle[] launchBundles) { + if (Debug.DEBUG_STARTLEVEL) { + Debug.println("SLL: incFWSL: saving activeSL of " + incToSL); //$NON-NLS-1$ + } + // save the startlevel + saveActiveStartLevel(incToSL); + // resume all bundles at the startlevel + resumeBundles(launchBundles, incToSL); + } + + /** + * Build an array of all installed bundles to be launch. + * The returned array is sorted by increasing startlevel/id order. + * @param bundles - the bundles installed in the framework + * @return A sorted array of bundles + */ + AbstractBundle[] getInstalledBundles(BundleRepository bundles, boolean sortByDependency) { + + /* make copy of bundles vector in case it is modified during launch */ + AbstractBundle[] installedBundles; + + synchronized (bundles) { + List<AbstractBundle> allBundles = bundles.getBundles(); + installedBundles = new AbstractBundle[allBundles.size()]; + allBundles.toArray(installedBundles); + + /* sort bundle array in ascending startlevel / bundle id order + * so that bundles are started in ascending order. + */ + Util.sort(installedBundles, 0, installedBundles.length); + if (sortByDependency) + sortByDependency(installedBundles); + } + return installedBundles; + } + + void sortByDependency(AbstractBundle[] bundles) { + synchronized (framework.bundles) { + if (bundles.length <= 1) + return; + int currentSL = bundles[0].getInternalStartLevel(); + int currentSLindex = 0; + boolean lazy = false; + for (int i = 0; i < bundles.length; i++) { + if (currentSL != bundles[i].getInternalStartLevel()) { + if (lazy) + sortByDependencies(bundles, currentSLindex, i); + currentSL = bundles[i].getInternalStartLevel(); + currentSLindex = i; + lazy = false; + } + lazy |= (bundles[i].getBundleData().getStatus() & Constants.BUNDLE_LAZY_START) != 0; + } + // sort the last set of bundles + if (lazy) + sortByDependencies(bundles, currentSLindex, bundles.length); + } + } + + private void sortByDependencies(AbstractBundle[] bundles, int start, int end) { + if (end - start <= 1) + return; + List<BundleDescription> descList = new ArrayList<BundleDescription>(end - start); + List<AbstractBundle> missingDescs = new ArrayList<AbstractBundle>(0); + for (int i = start; i < end; i++) { + BundleDescription desc = bundles[i].getBundleDescription(); + if (desc != null) + descList.add(desc); + else + missingDescs.add(bundles[i]); + } + if (descList.size() <= 1) + return; + BundleDescription[] descriptions = descList.toArray(new BundleDescription[descList.size()]); + framework.adaptor.getPlatformAdmin().getStateHelper().sortBundles(descriptions); + for (int i = start; i < descriptions.length + start; i++) + bundles[i] = framework.bundles.getBundle(descriptions[i - start].getBundleId()); + if (missingDescs.size() > 0) { + Iterator<AbstractBundle> missing = missingDescs.iterator(); + for (int i = start + descriptions.length; i < end && missing.hasNext(); i++) + bundles[i] = missing.next(); + } + } + + /** + * Resume all bundles in the launch list at the specified start-level + * @param launch a list of Bundle Objects to launch + * @param currentSL the current start-level that the bundles must meet to be resumed + */ + private void resumeBundles(AbstractBundle[] launch, int currentSL) { + // Resume all bundles that were previously started and whose startlevel is <= the active startlevel + // first resume the lazy activated bundles + resumeBundles(launch, true, currentSL); + // now resume all non lazy bundles + resumeBundles(launch, false, currentSL); + } + + private void resumeBundles(AbstractBundle[] launch, boolean lazyOnly, int currentSL) { + for (int i = 0; i < launch.length && !framework.isForcedRestart(); i++) { + int bsl = launch[i].getInternalStartLevel(); + if (bsl < currentSL) { + // skip bundles who should have already been started + continue; + } else if (bsl == currentSL) { + if (Debug.DEBUG_STARTLEVEL) { + Debug.println("SLL: Active sl = " + currentSL + "; Bundle " + launch[i].getBundleId() + " sl = " + bsl); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + boolean isLazyStart = launch[i].isLazyStart(); + if (lazyOnly ? isLazyStart : !isLazyStart) + framework.resumeBundle(launch[i]); + } else { + // can stop resuming bundles since any remaining bundles have a greater startlevel than the framework active startlevel + break; + } + } + } + + /** + * Decrement the active startlevel by one + * @param decToSL - the startlevel value to set the framework to + */ + protected void decFWSL(int decToSL, AbstractBundle[] shutdown) { + if (Debug.DEBUG_STARTLEVEL) { + Debug.println("SLL: decFWSL: saving activeSL of " + decToSL); //$NON-NLS-1$ + } + + saveActiveStartLevel(decToSL); + + // just decrementing the active startlevel - framework is not shutting down + // Do not check framework.isForcedRestart here because we want to stop the active bundles regardless. + for (int i = shutdown.length - 1; i >= 0; i--) { + int bsl = shutdown[i].getInternalStartLevel(); + if (bsl > decToSL + 1) + // skip bundles who should have already been stopped + continue; + else if (bsl <= decToSL) + // stopped all bundles we are going to for this start level + break; + else if (shutdown[i].isActive()) { + // if bundle is active or starting, then stop the bundle + if (Debug.DEBUG_STARTLEVEL) + Debug.println("SLL: stopping bundle " + shutdown[i].getBundleId()); //$NON-NLS-1$ + framework.suspendBundle(shutdown[i], false); + } + } + } + + /** + * Stops the system bundle + */ + private void stopSystemBundle() { + try { + framework.systemBundle.context.stop(); + } catch (BundleException sbe) { + if (Debug.DEBUG_STARTLEVEL) { + Debug.println("SLL: Bundle suspend exception: " + sbe.getMessage()); //$NON-NLS-1$ + Debug.printStackTrace(sbe.getNestedException() == null ? sbe : sbe.getNestedException()); + } + + framework.publishFrameworkEvent(FrameworkEvent.ERROR, framework.systemBundle, sbe); + } + + framework.systemBundle.state = Bundle.RESOLVED; + framework.publishBundleEvent(BundleEvent.STOPPED, framework.systemBundle); + } + + /** + * Unloads all bundles in the vector passed in. + * @param bundles list of Bundle objects to be unloaded + */ + private void unloadAllBundles(BundleRepository bundles) { + synchronized (bundles) { + /* unload all installed bundles */ + List<AbstractBundle> allBundles = bundles.getBundles(); + int size = allBundles.size(); + + for (int i = 0; i < size; i++) { + AbstractBundle bundle = allBundles.get(i); + + if (Debug.DEBUG_STARTLEVEL) { + Debug.println("SLL: Trying to unload bundle " + bundle); //$NON-NLS-1$ + } + bundle.refresh(); + try { + // make sure we close all the bundle data objects + bundle.getBundleData().close(); + } catch (IOException e) { + // ignore, we are shutting down anyway + } + } + } + } + + /** + * Set the bundle's startlevel to the new value + * This may cause the bundle to start or stop based on the active framework startlevel + * @param startLevelEvent - the event requesting change in bundle startlevel + */ + protected void setBundleSL(StartLevelEvent startLevelEvent) { + synchronized (lock) { + int currentSL = getStartLevel(); + int newSL = startLevelEvent.getNewSL(); + AbstractBundle bundle = startLevelEvent.getBundle(); + + if (Debug.DEBUG_STARTLEVEL) { + Debug.print("SLL: bundle active=" + bundle.isActive()); //$NON-NLS-1$ + Debug.print("; newSL = " + newSL); //$NON-NLS-1$ + Debug.println("; activeSL = " + currentSL); //$NON-NLS-1$ + } + + if (bundle.isActive() && (newSL > currentSL)) { + if (Debug.DEBUG_STARTLEVEL) { + Debug.println("SLL: stopping bundle " + bundle.getBundleId()); //$NON-NLS-1$ + } + framework.suspendBundle(bundle, false); + } else { + if (!bundle.isActive() && (newSL <= currentSL)) { + if (Debug.DEBUG_STARTLEVEL) { + Debug.println("SLL: starting bundle " + bundle.getBundleId()); //$NON-NLS-1$ + } + framework.resumeBundle(bundle); + } + } + if (Debug.DEBUG_STARTLEVEL) { + Debug.println("SLL: Bundle Startlevel set to " + newSL); //$NON-NLS-1$ + } + } + } +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/SystemBundleActivator.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/SystemBundleActivator.java new file mode 100644 index 000000000..f3233207d --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/SystemBundleActivator.java @@ -0,0 +1,110 @@ +/******************************************************************************* + * Copyright (c) 2003, 2010 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.framework.internal.core; + +import java.util.Dictionary; +import java.util.Hashtable; +import org.eclipse.osgi.framework.debug.FrameworkDebugOptions; +import org.eclipse.osgi.internal.resolver.StateImpl; +import org.eclipse.osgi.service.resolver.State; +import org.osgi.framework.*; +import org.osgi.service.condpermadmin.ConditionalPermissionAdmin; + +/** + * This class activates the System Bundle. + */ + +public class SystemBundleActivator implements BundleActivator { + private BundleContext context; + private InternalSystemBundle bundle; + private Framework framework; + private ServiceRegistration<?> packageAdmin; + private ServiceRegistration<?> securityAdmin; + private ServiceRegistration<?> startLevel; + private ServiceRegistration<?> debugOptions; + private ServiceRegistration<?> contextFinder; + + public void start(BundleContext bc) throws Exception { + this.context = bc; + bundle = (InternalSystemBundle) bc.getBundle(); + framework = bundle.framework; + + if (framework.packageAdmin != null) + packageAdmin = register(new String[] {Constants.OSGI_PACKAGEADMIN_NAME}, framework.packageAdmin, null); + if (framework.securityAdmin != null) + securityAdmin = register(new String[] {Constants.OSGI_PERMISSIONADMIN_NAME, ConditionalPermissionAdmin.class.getName()}, framework.securityAdmin, null); + if (framework.startLevelManager != null) + startLevel = register(new String[] {Constants.OSGI_STARTLEVEL_NAME}, framework.startLevelManager, null); + FrameworkDebugOptions dbgOptions = null; + if ((dbgOptions = FrameworkDebugOptions.getDefault()) != null) { + dbgOptions.start(bc); + debugOptions = register(new String[] {org.eclipse.osgi.service.debug.DebugOptions.class.getName()}, dbgOptions, null); + } + ClassLoader tccl = framework.getContextFinder(); + if (tccl != null) { + Dictionary<String, Object> props = new Hashtable<String, Object>(7); + props.put("equinox.classloader.type", "contextClassLoader"); //$NON-NLS-1$ //$NON-NLS-2$ + contextFinder = register(new String[] {ClassLoader.class.getName()}, tccl, props); + } + + // Always call the adaptor.frameworkStart() at the end of this method. + framework.adaptor.frameworkStart(bc); + State state = framework.adaptor.getState(); + if (state instanceof StateImpl) + ((StateImpl) state).setResolverHookFactory(new CoreResolverHookFactory((BundleContextImpl) context, framework.getServiceRegistry())); + // attempt to resolve all bundles + // this is done after the adaptor.frameworkStart has been called + // this should be the first time the resolver State is accessed + framework.packageAdmin.setResolvedBundles(bundle); + // reinitialize the system bundles localization to take into account system bundle fragments + framework.systemBundle.manifestLocalization = null; + } + + public void stop(BundleContext bc) throws Exception { + // Always call the adaptor.frameworkStop() at the begining of this method. + framework.adaptor.frameworkStop(bc); + + if (packageAdmin != null) + packageAdmin.unregister(); + if (securityAdmin != null) + securityAdmin.unregister(); + if (startLevel != null) + startLevel.unregister(); + if (debugOptions != null) { + FrameworkDebugOptions dbgOptions = FrameworkDebugOptions.getDefault(); + if (dbgOptions != null) + dbgOptions.stop(bc); + debugOptions.unregister(); + } + if (contextFinder != null) + contextFinder.unregister(); + + framework = null; + bundle = null; + this.context = null; + } + + /** + * Register a service object. + * + */ + private ServiceRegistration<?> register(String[] names, Object service, Dictionary<String, Object> properties) { + if (properties == null) + properties = new Hashtable<String, Object>(7); + Dictionary<String, String> headers = bundle.getHeaders(); + properties.put(Constants.SERVICE_VENDOR, headers.get(Constants.BUNDLE_VENDOR)); + properties.put(Constants.SERVICE_RANKING, new Integer(Integer.MAX_VALUE)); + properties.put(Constants.SERVICE_PID, bundle.getBundleId() + "." + service.getClass().getName()); //$NON-NLS-1$ + return context.registerService(names, service, properties); + } + +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/UniversalUniqueIdentifier.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/UniversalUniqueIdentifier.java new file mode 100644 index 000000000..abeab38b1 --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/UniversalUniqueIdentifier.java @@ -0,0 +1,270 @@ +/******************************************************************************* + * Copyright (c) 2000, 2011 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.osgi.framework.internal.core; + +import java.io.*; +import java.math.BigInteger; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.security.SecureRandom; +import java.util.GregorianCalendar; +import java.util.Random; + +public class UniversalUniqueIdentifier { + + /* INSTANCE FIELDS =============================================== */ + + private byte[] fBits = new byte[BYTES_SIZE]; + + /* NON-FINAL PRIVATE STATIC FIELDS =============================== */ + + private volatile static BigInteger fgPreviousClockValue; + private volatile static int fgClockAdjustment = 0; + private volatile static int fgClockSequence = -1; + private final static byte[] nodeAddress; + + static { + nodeAddress = computeNodeAddress(); + } + + /* PRIVATE STATIC FINAL FIELDS =================================== */ + + private final static Random fgRandomNumberGenerator = new Random(); + + /* PUBLIC STATIC FINAL FIELDS ==================================== */ + + public static final int BYTES_SIZE = 16; + public static final byte[] UNDEFINED_UUID_BYTES = new byte[16]; + public static final int MAX_CLOCK_SEQUENCE = 0x4000; + public static final int MAX_CLOCK_ADJUSTMENT = 0x7FFF; + public static final int TIME_FIELD_START = 0; + public static final int TIME_FIELD_STOP = 6; + public static final int TIME_HIGH_AND_VERSION = 7; + public static final int CLOCK_SEQUENCE_HIGH_AND_RESERVED = 8; + public static final int CLOCK_SEQUENCE_LOW = 9; + public static final int NODE_ADDRESS_START = 10; + public static final int NODE_ADDRESS_BYTE_SIZE = 6; + + public static final int BYTE_MASK = 0xFF; + + public static final int HIGH_NIBBLE_MASK = 0xF0; + + public static final int LOW_NIBBLE_MASK = 0x0F; + + public static final int SHIFT_NIBBLE = 4; + + public static final int ShiftByte = 8; + + /** + UniversalUniqueIdentifier default constructor returns a + new instance that has been initialized to a unique value. + */ + public UniversalUniqueIdentifier() { + this.setVersion(1); + this.setVariant(1); + this.setTimeValues(); + this.setNode(getNodeAddress()); + } + + private void appendByteString(StringBuffer buffer, byte value) { + String hexString; + + if (value < 0) + hexString = Integer.toHexString(256 + value); + else + hexString = Integer.toHexString(value); + if (hexString.length() == 1) + buffer.append("0"); //$NON-NLS-1$ + buffer.append(hexString); + } + + private static BigInteger clockValueNow() { + GregorianCalendar now = new GregorianCalendar(); + BigInteger nowMillis = BigInteger.valueOf(now.getTime().getTime()); + BigInteger baseMillis = BigInteger.valueOf(now.getGregorianChange().getTime()); + + return (nowMillis.subtract(baseMillis).multiply(BigInteger.valueOf(10000L))); + } + + /** + * Answers the node address attempting to mask the IP + * address of this machine. + * + * @return byte[] the node address + */ + private static byte[] computeNodeAddress() { + + byte[] address = new byte[NODE_ADDRESS_BYTE_SIZE]; + + // Seed the secure randomizer with some oft-varying inputs + int thread = Thread.currentThread().hashCode(); + long time = System.currentTimeMillis(); + int objectId = System.identityHashCode(new String()); + ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); + DataOutputStream out = new DataOutputStream(byteOut); + byte[] ipAddress = getIPAddress(); + + try { + if (ipAddress != null) + out.write(ipAddress); + out.write(thread); + out.writeLong(time); + out.write(objectId); + out.close(); + } catch (IOException exc) { + //ignore the failure, we're just trying to come up with a random seed + } + byte[] rand = byteOut.toByteArray(); + + SecureRandom randomizer = new SecureRandom(rand); + randomizer.nextBytes(address); + + // set the MSB of the first octet to 1 to distinguish from IEEE node addresses + address[0] = (byte) (address[0] | (byte) 0x80); + + return address; + } + + /** + Answers the IP address of the local machine using the + Java API class <code>InetAddress</code>. + + @return byte[] the network address in network order + @see java.net.InetAddress#getLocalHost() + @see java.net.InetAddress#getAddress() + */ + private static byte[] getIPAddress() { + try { + return InetAddress.getLocalHost().getAddress(); + } catch (UnknownHostException e) { + //valid for this to be thrown be a machine with no IP connection + //It is VERY important NOT to throw this exception + return null; + } catch (ArrayIndexOutOfBoundsException e) { + // there appears to be a bug in the VM if there is an alias + // see bug 354820. As above it is important not to throw this + return null; + } + } + + private static byte[] getNodeAddress() { + return nodeAddress; + } + + private static int nextClockSequence() { + + if (fgClockSequence == -1) + fgClockSequence = (int) (fgRandomNumberGenerator.nextDouble() * MAX_CLOCK_SEQUENCE); + + fgClockSequence = (fgClockSequence + 1) % MAX_CLOCK_SEQUENCE; + + return fgClockSequence; + } + + private static BigInteger nextTimestamp() { + + BigInteger timestamp = clockValueNow(); + int timestampComparison; + + timestampComparison = timestamp.compareTo(fgPreviousClockValue); + + if (timestampComparison == 0) { + if (fgClockAdjustment == MAX_CLOCK_ADJUSTMENT) { + while (timestamp.compareTo(fgPreviousClockValue) == 0) + timestamp = clockValueNow(); + timestamp = nextTimestamp(); + } else + fgClockAdjustment++; + } else { + fgClockAdjustment = 0; + + if (timestampComparison < 0) + nextClockSequence(); + } + + return timestamp; + } + + private void setClockSequence(int clockSeq) { + int clockSeqHigh = (clockSeq >>> ShiftByte) & LOW_NIBBLE_MASK; + int reserved = fBits[CLOCK_SEQUENCE_HIGH_AND_RESERVED] & HIGH_NIBBLE_MASK; + + fBits[CLOCK_SEQUENCE_HIGH_AND_RESERVED] = (byte) (reserved | clockSeqHigh); + fBits[CLOCK_SEQUENCE_LOW] = (byte) (clockSeq & BYTE_MASK); + } + + private void setNode(byte[] bytes) { + + for (int index = 0; index < NODE_ADDRESS_BYTE_SIZE; index++) + fBits[index + NODE_ADDRESS_START] = bytes[index]; + } + + private void setTimestamp(BigInteger timestamp) { + BigInteger value = timestamp; + BigInteger bigByte = BigInteger.valueOf(256L); + BigInteger[] results; + int version; + int timeHigh; + + for (int index = TIME_FIELD_START; index < TIME_FIELD_STOP; index++) { + results = value.divideAndRemainder(bigByte); + value = results[0]; + fBits[index] = (byte) results[1].intValue(); + } + version = fBits[TIME_HIGH_AND_VERSION] & HIGH_NIBBLE_MASK; + timeHigh = value.intValue() & LOW_NIBBLE_MASK; + fBits[TIME_HIGH_AND_VERSION] = (byte) (timeHigh | version); + } + + private synchronized void setTimeValues() { + this.setTimestamp(timestamp()); + this.setClockSequence(fgClockSequence); + } + + private int setVariant(int variantIdentifier) { + int clockSeqHigh = fBits[CLOCK_SEQUENCE_HIGH_AND_RESERVED] & LOW_NIBBLE_MASK; + int variant = variantIdentifier & LOW_NIBBLE_MASK; + + fBits[CLOCK_SEQUENCE_HIGH_AND_RESERVED] = (byte) ((variant << SHIFT_NIBBLE) | clockSeqHigh); + return (variant); + } + + private void setVersion(int versionIdentifier) { + int timeHigh = fBits[TIME_HIGH_AND_VERSION] & LOW_NIBBLE_MASK; + int version = versionIdentifier & LOW_NIBBLE_MASK; + + fBits[TIME_HIGH_AND_VERSION] = (byte) (timeHigh | (version << SHIFT_NIBBLE)); + } + + private static BigInteger timestamp() { + BigInteger timestamp; + + if (fgPreviousClockValue == null) { + fgClockAdjustment = 0; + nextClockSequence(); + timestamp = clockValueNow(); + } else + timestamp = nextTimestamp(); + + fgPreviousClockValue = timestamp; + return fgClockAdjustment == 0 ? timestamp : timestamp.add(BigInteger.valueOf(fgClockAdjustment)); + } + + public String toString() { + StringBuffer buffer = new StringBuffer(); + for (int i = 0; i < fBits.length; i++) { + if (i == 4 || i == 6 || i == 8 || i == 10) + buffer.append('-'); + appendByteString(buffer, fBits[i]); + } + return buffer.toString(); + } +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/Util.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/Util.java new file mode 100644 index 000000000..63ccfd2d7 --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/Util.java @@ -0,0 +1,208 @@ +/******************************************************************************* + * Copyright (c) 2003, 2010 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.framework.internal.core; + +/** + * This class contains utility functions. + */ +public class Util { + /** + * Performs a quicksort of the given objects + * by their string representation in ascending order. + * <p> + * + * @param array The array of objects to sort + */ + public static void sortByString(Object[] array) { + qSortByString(array, 0, array.length - 1); + } + + /** + * Sorts the array of objects by their string representation + * in ascending order. + * <p> + * This is a version of C.A.R Hoare's Quick Sort algorithm. + * + * @param array the array of objects to sort + * @param start the start index to begin sorting + * @param stop the end index to stop sorting + * + * @exception ArrayIndexOutOfBoundsException when <code>start < 0</code> + * or <code>end >= array.length</code> + */ + public static void qSortByString(Object[] array, int start, int stop) { + if (start >= stop) + return; + + int left = start; // left index + int right = stop; // right index + Object temp; // for swapping + + // arbitrarily establish a partition element as the midpoint of the array + String mid = String.valueOf(array[(start + stop) / 2]); + + // loop through the array until indices cross + while (left <= right) { + // find the first element that is smaller than the partition element from the left + while ((left < stop) && (String.valueOf(array[left]).compareTo(mid) < 0)) { + ++left; + } + // find an element that is smaller than the partition element from the right + while ((right > start) && (mid.compareTo(String.valueOf(array[right])) < 0)) { + --right; + } + // if the indices have not crossed, swap + if (left <= right) { + temp = array[left]; + array[left] = array[right]; + array[right] = temp; + ++left; + --right; + } + } + // sort the left partition, if the right index has not reached the left side of array + if (start < right) { + qSortByString(array, start, right); + } + // sort the right partition, if the left index has not reached the right side of array + if (left < stop) { + qSortByString(array, left, stop); + } + } + + /** + * Sorts the specified range in the array in ascending order. + * + * @param array the Object array to be sorted + * @param start the start index to sort + * @param end the last + 1 index to sort + * + * @exception ClassCastException when an element in the array does not + * implement Comparable or elements cannot be compared to each other + * @exception IllegalArgumentException when <code>start > end</code> + * @exception ArrayIndexOutOfBoundsException when <code>start < 0</code> + * or <code>end > array.size()</code> + */ + @SuppressWarnings("unchecked") + public static void sort(Object[] array, int start, int end) { + int middle = (start + end) / 2; + if (start + 1 < middle) + sort(array, start, middle); + if (middle + 1 < end) + sort(array, middle, end); + if (start + 1 >= end) + return; // this case can only happen when this method is called by the user + if (((Comparable<Object>) array[middle - 1]).compareTo(array[middle]) <= 0) + return; + if (start + 2 == end) { + Object temp = array[start]; + array[start] = array[middle]; + array[middle] = temp; + return; + } + int i1 = start, i2 = middle, i3 = 0; + Object[] merge = new Object[end - start]; + while (i1 < middle && i2 < end) { + merge[i3++] = ((Comparable<Object>) array[i1]).compareTo(array[i2]) <= 0 ? array[i1++] : array[i2++]; + } + if (i1 < middle) + System.arraycopy(array, i1, merge, i3, middle - i1); + System.arraycopy(merge, 0, array, start, i2 - start); + } + + /** + * Sorts the specified range in the array in descending order. + * + * @param array the Object array to be sorted + * @param start the start index to sort + * @param end the last + 1 index to sort + * + * @exception ClassCastException when an element in the array does not + * implement Comparable or elements cannot be compared to each other + * @exception IllegalArgumentException when <code>start > end</code> + * @exception ArrayIndexOutOfBoundsException when <code>start < 0</code> + * or <code>end > array.size()</code> + */ + public static void dsort(Object[] array, int start, int end) { + // first sort in ascending order + sort(array, start, end); + // then swap the elements in the array + swap(array); + } + + /** + * Reverse the elements in the array. + * + * @param array the Object array to be reversed + */ + public static void swap(Object[] array) { + int start = 0; + int end = array.length - 1; + while (start < end) { + Object temp = array[start]; + array[start++] = array[end]; + array[end--] = temp; + } + } + + /** + * Returns a string representation of the object + * in the given length. + * If the string representation of the given object + * is longer then it is truncated. + * If it is shorter then it is padded with the blanks + * to the given total length. + * If the given object is a number then the padding + * is done on the left, otherwise on the right. + * + * @param object the object to convert + * @param length the length the output string + */ + public static String toString(Object object, int length) { + boolean onLeft = object instanceof Number; + return toString(object, length, ' ', onLeft); + } + + /** + * Returns a string representation of the object + * in the given length. + * If the string representation of the given object + * is longer then it is truncated. + * If it is shorter then it is padded to the left or right + * with the given character to the given total length. + * + * @param object the object to convert + * @param length the length the output string + * @param pad the pad character + * @param onLeft if <code>true</code> pad on the left, otherwise an the right + */ + public static String toString(Object object, int length, char pad, boolean onLeft) { + String input = String.valueOf(object); + int size = input.length(); + if (size >= length) { + int start = (onLeft) ? size - length : 0; + return input.substring(start, length); + } + + StringBuffer padding = new StringBuffer(length - size); + for (int i = size; i < length; i++) + padding.append(pad); + + StringBuffer stringBuffer = new StringBuffer(length); + if (onLeft) + stringBuffer.append(padding.toString()); + stringBuffer.append(input); + if (!onLeft) + stringBuffer.append(padding.toString()); + return stringBuffer.toString(); + } +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/osname.aliases b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/osname.aliases new file mode 100644 index 000000000..0aff94e32 --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/osname.aliases @@ -0,0 +1,46 @@ +######################################################################## +# Copyright (c) 2003, 2012 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 +######################################################################## + +# This file must be UTF8 encoded. + +#OS Aliases Description + +AIX # IBM +DigitalUnix # Compaq +embos # Segger Embedded Software Solutions +Epoc32 SymbianOS # Symbian OS +FreeBSD # Free BSD +HPUX # Hewlett Packard +IRIX # Sillicon Graphics +Linux # Open source +MacOS "Mac OS" # Apple +MacOSX "Mac OS X" # Apple +NetBSD # Open source +Netware # Novell +OpenBSD # Open source +OS2 OS/2 # IBM +QNX procnto # QNX Neutrino 2.1 +Solaris # Sun +SunOS # Sun +VxWorks # WindRiver Systems +Windows95 "Windows 95" Win95 Win32 # Microsoft +Windows98 "Windows 98" Win98 Win32 # Microsoft +WindowsNT "Windows NT" WinNT Win32 # Microsoft +WindowsCE "Windows CE" WinCE # Microsoft +Windows2000 "Windows 2000" Win2000 Win32 # Microsoft +WindowsXP "Windows XP" WinXP Win32 # Microsoft +Windows2003 "Windows 2003" "Windows Server 2003" Win2003 Win32 # Microsoft +WindowsVista WinVista "Windows Vista" Win32 # Microsoft +Windows2008 "Windows 2008" "Windows Server 2008" Win2008 Win32 # Microsoft +WindowsServer2008 "Windows 2008" "Windows Server 2008" Win2008 Win32 # Microsoft +WindowsServer2008R2 "Windows 2008 R2" "Windows Server 2008 R2" Win2008R2 Win32 # Microsoft +Windows7 "Windows 7" Win7 Win32 # Microsoft +Windows8 "Windows 8" Win8 Win32 # Microsoft
\ No newline at end of file diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/processor.aliases b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/processor.aliases new file mode 100644 index 000000000..5fb0ff398 --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/framework/internal/core/processor.aliases @@ -0,0 +1,28 @@ +######################################################################## +# Copyright (c) 2003, 2005 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 +######################################################################## + +# This file must be UTF8 encoded. + +#Processor Aliases Description + +68k # Motorola 68000 and up +ARM # Intel Strong ARM +Alpha # Compaq (ex DEC) +Ignite psc1k # PTSC +Mips # SGI +PArisc # Hewlett Packard PA Risc +PowerPC power ppc # Motorola/IBM Power PC +Sparc # SUN +x86 pentium i386 i486 i586 i686 # Intel +s390 # IBM System 390 +s390x # IBM System 390 (64-bit) +v850e # NEC V850E +x86-64 amd64 em64t x86_64 # 64 bit x86 architecture |