/******************************************************************************* * Copyright (c) 2013 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 * and Eclipse Distribution License v1.0 which accompany this distribution. * The Eclipse Public License is available at * http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: * Jan S. Rellermeyer, IBM Research - initial API and implementation * Tim Verbelen - Bugfixing * Jochen Hiller - Bugfixing *******************************************************************************/ package org.eclipse.concierge; import java.io.BufferedInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FilePermission; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; import java.security.CodeSource; import java.security.PermissionCollection; import java.security.Permissions; import java.security.ProtectionDomain; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Dictionary; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.Hashtable; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.StringTokenizer; import java.util.Vector; import java.util.WeakHashMap; import java.util.jar.Attributes; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.JarInputStream; import java.util.jar.Manifest; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import org.eclipse.concierge.ConciergeCollections.MultiMap; import org.eclipse.concierge.ConciergeCollections.ParseResult; import org.eclipse.concierge.ConciergeCollections.Tuple; import org.eclipse.concierge.Resources.BundleCapabilityImpl; import org.eclipse.concierge.Resources.BundleRequirementImpl; import org.eclipse.concierge.Resources.ConciergeBundleWire; import org.eclipse.concierge.Resources.ConciergeBundleWiring; import org.eclipse.concierge.compat.LegacyBundleProcessing; import org.osgi.framework.Bundle; import org.osgi.framework.BundleActivator; import org.osgi.framework.BundleContext; import org.osgi.framework.BundleEvent; import org.osgi.framework.BundleException; import org.osgi.framework.BundleReference; import org.osgi.framework.Constants; import org.osgi.framework.FrameworkEvent; import org.osgi.framework.InvalidSyntaxException; import org.osgi.framework.ServiceReference; import org.osgi.framework.Version; import org.osgi.framework.VersionRange; import org.osgi.framework.hooks.bundle.CollisionHook; import org.osgi.framework.hooks.weaving.WovenClass; import org.osgi.framework.namespace.BundleNamespace; import org.osgi.framework.namespace.HostNamespace; import org.osgi.framework.namespace.IdentityNamespace; import org.osgi.framework.namespace.PackageNamespace; import org.osgi.framework.startlevel.BundleStartLevel; import org.osgi.framework.wiring.BundleCapability; import org.osgi.framework.wiring.BundleRequirement; import org.osgi.framework.wiring.BundleRevision; import org.osgi.framework.wiring.BundleWire; import org.osgi.framework.wiring.BundleWiring; import org.osgi.resource.Capability; import org.osgi.resource.Namespace; import org.osgi.resource.Requirement; import org.osgi.resource.Wire; import org.osgi.service.log.LogService; import org.osgi.service.resolver.HostedCapability; /** * @author Jan Rellermeyer - Initial Contribution * @author Tim Verbelen - Bugfixing * @author Jochen Hiller - Bugfixing */ public class BundleImpl extends AbstractBundle implements BundleStartLevel { /* * Helper methods for loading classes from a .dex file on Dalvik VM */ protected static final Method dexFileLoader; protected static final Method dexClassLoader; static { Method classloader; Method fileloader; try { final Class<?> dexFileClass = Class .forName("dalvik.system.DexFile"); classloader = dexFileClass.getMethod("loadClass", new Class[] { String.class, ClassLoader.class }); fileloader = dexFileClass.getMethod("loadDex", new Class[] { String.class, String.class, Integer.TYPE }); } catch (final Throwable ignore) { classloader = null; fileloader = null; } dexClassLoader = classloader; dexFileLoader = fileloader; } protected static final Pattern DIRECTIVE_LIST = Pattern .compile("\\s*([^:]*)\\s*:=\\s*\"\\s*(.+)*?\\s*\"\\s*"); private static final int TIMEOUT = 10000; /** * the default name of stored bundles. */ private static final String BUNDLE_FILE_NAME = "bundle"; /** * the default name of the content directory for bundles stored directly in * the file system. */ private static final String CONTENT_DIRECTORY_NAME = "content"; private static final short FRAGMENT_ATTACHMENT_NEVER = -1; private static final short FRAGMENT_ATTACHMENT_RESOLVETIME = 1; private static final short FRAGMENT_ATTACHMENT_ALWAYS = 2; private String symbolicName; private Version version; protected static ThreadLocal<ArrayList<AbstractBundle>> activationChain = new ThreadLocal<ArrayList<AbstractBundle>>(); /** * the host to which this bundle is attached (if any), only valid for * fragment bundles */ protected List<BundleImpl> hostBundles; protected final Concierge framework; /** * is bundle marked to start with lazy activation policy? (otherwise use * eager activation) */ protected boolean lazyActivation; protected boolean beingLazy = false; protected String[] activationIncludes; protected String[] activationExcludes; protected String bundleLocalizationBaseDir; protected String bundleLocalizationBaseFilename; protected HeaderDictionary headers; private Locale lastDefaultLocale; private int currentRevisionNumber = -1; public BundleImpl(final Concierge framework, final BundleContext installingContext, final String location, final long bundleId, final InputStream stream) throws BundleException { this.framework = framework; this.location = location; this.bundleId = bundleId; this.startlevel = framework.initStartlevel; this.lastModified = System.currentTimeMillis(); if (framework.SECURITY_ENABLED) { try { final PermissionCollection permissions = new Permissions(); permissions.add(new FilePermission( framework.STORAGE_LOCATION + bundleId, "read,write,execute,delete")); domain = new ProtectionDomain( new CodeSource( new URL("file:" + framework.STORAGE_LOCATION + bundleId), (java.security.cert.Certificate[]) null), permissions); } catch (final Exception e) { e.printStackTrace(); throw new BundleException("Exception while installing bundle", BundleException.STATECHANGE_ERROR, e); } } this.storageLocation = framework.STORAGE_LOCATION + bundleId + File.separatorChar; currentRevision = readAndProcessInputStream(stream); symbolicName = currentRevision.getSymbolicName(); version = currentRevision.getVersion(); revisions.add(0, currentRevision); // check is same version is already installed framework.checkForCollision(CollisionHook.INSTALLING, installingContext.getBundle(), currentRevision); this.state = INSTALLED; // if we are not during startup or shutdown, update the metadata if (framework.state != Bundle.STARTING && framework.state != Bundle.STOPPING) { updateMetadata(); } } // framework restart case public BundleImpl(final Concierge framework, final File metadata) throws IOException, BundleException { this.framework = framework; // this.content = new JarBundle(new JarFile(file)); final DataInputStream in = new DataInputStream( new FileInputStream(metadata)); // read current revision from metadata this.currentRevisionNumber = in.readInt(); this.bundleId = in.readLong(); this.location = in.readUTF(); this.storageLocation = framework.STORAGE_LOCATION + bundleId + File.separatorChar; // locate current revision final File file = new File(storageLocation, BUNDLE_FILE_NAME + currentRevisionNumber); final File contentDir = new File(storageLocation + CONTENT_DIRECTORY_NAME + currentRevisionNumber); if (file.exists() && file.isFile()) { final JarFile jarFile = new JarFile(file); final Manifest manifest = jarFile.getManifest(); final String[] classpathStrings = readProperties( manifest.getMainAttributes(), Constants.BUNDLE_CLASSPATH, new String[] { "." }); this.currentRevision = new JarBundleRevision(currentRevisionNumber, jarFile, manifest, classpathStrings); } else if (contentDir.exists() && contentDir.isDirectory()) { final Manifest manifest = new Manifest(new FileInputStream( new File(contentDir, JarFile.MANIFEST_NAME))); final String[] classpathStrings = readProperties( manifest.getMainAttributes(), Constants.BUNDLE_CLASSPATH, new String[] { "." }); this.currentRevision = new ExplodedJarBundleRevision( currentRevisionNumber, contentDir.getAbsolutePath(), manifest, classpathStrings); } else { in.close(); throw new BundleException("Bundle revision " + currentRevisionNumber + " does not exist", BundleException.READ_ERROR); } this.symbolicName = currentRevision.getSymbolicName(); this.version = currentRevision.getVersion(); this.revisions.add(0, currentRevision); this.startlevel = in.readInt(); this.state = Bundle.INSTALLED; this.autostart = in.readShort(); this.lazyActivation = in.readBoolean(); this.lastModified = in.readLong(); in.close(); this.context = framework.createBundleContext(this); // System.err.println("RESTORED BUNDLE " + toString() + " WITH SL " + this.startlevel + " and autostart " + this.autostart); if (framework.SECURITY_ENABLED) { domain = new ProtectionDomain(null, null); } } /** * update the bundle's metadata on the storage. */ void updateMetadata() { DataOutputStream out = null; try { out = new DataOutputStream( new FileOutputStream(new File(storageLocation, "meta"))); out.writeInt(currentRevisionNumber); out.writeLong(bundleId); out.writeUTF(location); out.writeInt(startlevel); out.writeShort(autostart); out.writeBoolean(lazyActivation); out.writeLong(lastModified); } catch (final IOException ioe) { ioe.printStackTrace(); } finally { if (out != null) { try { out.close(); } catch (final Exception e) { // ignore } } } } /** * Reads and processes input stream: - writes bundle to storage - processes * manifest * * @param inStream * the input stream of the bundle * @throws BundleException */ private Revision readAndProcessInputStream(final InputStream inStream) throws BundleException { final int revisionNumber = ++currentRevisionNumber; try { // write the JAR file to the storage final File file = new File(storageLocation, BUNDLE_FILE_NAME + revisionNumber); storeFile(file, inStream); // and open a JarFile // TODO: check when verification is really required... final JarFile jar = new JarFile(file, false); // process the manifest final Manifest manifest = jar.getManifest(); // get the classpath final String[] classpathStrings = readProperties( manifest.getMainAttributes(), Constants.BUNDLE_CLASSPATH, new String[] { "." }); if (framework.ALWAYS_DECOMPRESS || framework.DECOMPRESS_EMBEDDED && classpathStrings.length > 1) { final File contentDir = new File(storageLocation + CONTENT_DIRECTORY_NAME + revisionNumber); if (contentDir.exists()) { Concierge.deleteDirectory(contentDir); } // decompress the bundle for (final Enumeration<JarEntry> entries = jar .entries(); entries.hasMoreElements();) { final JarEntry entry = entries.nextElement(); if (entry.isDirectory()) { continue; } final File embeddedJar = new File(contentDir, entry.getName()); storeFile(embeddedJar, jar.getInputStream(entry)); } // delete the bundle jar jar.close(); new File(jar.getName()).delete(); return new ExplodedJarBundleRevision(revisionNumber, contentDir.getAbsolutePath(), manifest, classpathStrings); } else { return new JarBundleRevision(revisionNumber, jar, manifest, classpathStrings); } } catch (final IOException ioe) { ioe.printStackTrace(); Concierge.deleteDirectory(new File(storageLocation)); throw new BundleException( "Not a valid bundle: " + location + " (tried to write to " + new File(storageLocation, BUNDLE_FILE_NAME + revisionNumber) + ")", BundleException.READ_ERROR, ioe); } } // FIXME: can't this be called from constructor??? void install() throws BundleException { // we are just installing the bundle, if it is // possible, resolve it, if not, wait until the // exports are really needed (i.e., they become critical) // if (!currentRevision.isFragment()) { // currentRevision.resolve(false); // } // register bundle with framework: synchronized (framework) { framework.bundles.add(this); framework.bundleID_bundles.put(new Long(getBundleId()), this); framework.symbolicName_bundles .insert(currentRevision.getSymbolicName(), this); framework.location_bundles.put(location, this); } // resolve if it is a framework extension if (currentRevision.isFrameworkExtension()) { currentRevision.resolve(false); } } /** * start the bundle. * * @throws BundleException * if the bundle cannot be resolved or the Activator throws an * exception. * @see org.osgi.framework.Bundle#start() * @category Bundle */ public void start() throws BundleException { start(0); } /** * start the bundle with options. * * @throws BundleException * if the bundle cannot be resolved or the Activator throws an * exception. * @see org.osgi.framework.Bundle#start(int) * @category Bundle */ public void start(final int options) throws BundleException { if (framework.SECURITY_ENABLED) { // TODO: check AdminPermission(this, EXECUTE) } if (state == UNINSTALLED) { throw new IllegalStateException( "Cannot start uninstalled bundle " + toString()); } if (currentRevision.isFragment()) { throw new BundleException( "The fragment bundle " + toString() + " cannot be started", BundleException.INVALID_OPERATION); } if (!lazyActivation && (state == Bundle.STARTING || state == Bundle.STOPPING)) { try { synchronized (this) { wait(TIMEOUT); } } catch (final InterruptedException ie) { // ignore and proceed } if (state == Bundle.STARTING || state == Bundle.STOPPING) { // hit the timeout throw new BundleException( "Timeout occurred. Bundle was unable to start.", BundleException.STATECHANGE_ERROR); } } if ((options & Bundle.START_TRANSIENT) > 0) { if (startlevel > framework.startlevel) { throw new BundleException( "Bundle (with start level " + startlevel + ") cannot be started due to the framework's current start level of " + framework.startlevel, BundleException.START_TRANSIENT_ERROR); } } else { if ((options & Bundle.START_TRANSIENT) == 0) { autostart = options == 0 ? AUTOSTART_STARTED_WITH_EAGER : AUTOSTART_STARTED_WITH_DECLARED; } updateMetadata(); } if (startlevel <= framework.startlevel) { activate(options); } } /** * the actual starting happens here. This method does not modify the * persistent metadata. * * @throws BundleException * if the bundle cannot be resolved or the Activator throws an * exception. */ synchronized void activate(final int options) throws BundleException { if (state == ACTIVE) { return; } if (currentRevision.isFragment()) { return; } // step4 if (state == INSTALLED) { // this time, it is critical to get the bundle resolved // so if we need exports from other unresolved bundles, // we will try to resolve them (recursively) to get the bundle // started currentRevision.resolve(true); } // step5 this.context = framework.createBundleContext(this); if ((options & Bundle.START_ACTIVATION_POLICY) > 0 && lazyActivation) { if (state != STARTING) { beingLazy = true; state = STARTING; framework.notifyBundleListeners(BundleEvent.LAZY_ACTIVATION, this); } synchronized (this) { notify(); } return; } else { beingLazy = false; } activate0(); } private void activate0() throws BundleException { assert state != INSTALLED && state != UNINSTALLED; // step6 state = STARTING; // step7 framework.notifyBundleListeners(BundleEvent.STARTING, this); // step8 (part 1) try { context.isValid = true; final String activatorClassName = currentRevision.activatorClassName; if (activatorClassName != null) { @SuppressWarnings("unchecked") final Class<BundleActivator> activatorClass = (Class<BundleActivator>) currentRevision.classloader .loadClass(activatorClassName); if (activatorClass == null) { throw new ClassNotFoundException(activatorClassName); } currentRevision.activatorInstance = activatorClass .newInstance(); currentRevision.activatorInstance.start(context); // step 9 if (state == UNINSTALLED) { throw new BundleException( "Activator.start uninstalled the bundle!", BundleException.ACTIVATOR_ERROR); } } // step10 state = ACTIVE; // step11 framework.notifyBundleListeners(BundleEvent.STARTED, this); if (framework.DEBUG_BUNDLES) { framework.logger.log(LogService.LOG_INFO, "framework: Bundle " + toString() + " started."); } synchronized (this) { notify(); } } catch (final Throwable t) { // step8 (part2) framework.notifyBundleListeners(BundleEvent.STOPPING, this); framework.clearBundleTrace(this); state = RESOLVED; framework.notifyBundleListeners(BundleEvent.STOPPED, this); throw new BundleException("Error starting bundle " + toString(), BundleException.ACTIVATOR_ERROR, t); } } /** * stop the bundle. * * @throws BundleException * if the bundle has been uninstalled before. * * @see org.osgi.framework.Bundle#stop() * @category Bundle */ public void stop() throws BundleException { stop(0); } /** * stop the bundle. * * @throws BundleException * if the bundle has been uninstalled before. * * @param options * for stopping the bundle. * @see org.osgi.framework.Bundle#stop(int) * @category Bundle */ public void stop(final int options) throws BundleException { if (framework.SECURITY_ENABLED) { // TODO: check AdminPermission(this, EXECUTE); } if (state == UNINSTALLED) { throw new IllegalStateException( "Cannot stop uninstalled bundle " + toString()); } if (currentRevision.isFragment()) { throw new BundleException( "The fragment bundle " + toString() + " cannot be stopped", BundleException.INVALID_OPERATION); } if (state == Bundle.STARTING || state == Bundle.STOPPING) { try { synchronized (this) { wait(TIMEOUT); } } catch (final InterruptedException ie) { // ignore } if (state == UNINSTALLED) { throw new IllegalStateException( "Cannot stop uninstalled bundle " + toString()); } if (state == Bundle.STARTING || state == Bundle.STOPPING) { // timeout occurred! throw new BundleException( "Timeout occurred. Bundle was unable to stop!", BundleException.STATECHANGE_ERROR); } } if (options != Bundle.STOP_TRANSIENT) { // change persistent autostart configuration autostart = AUTOSTART_STOPPED; updateMetadata(); } if (state != ACTIVE && state != STARTING) { return; } stopBundle(); } /** * the actual starting happens here. This method does not modify the * persistent meta data. * * @throws BundleException * if the bundle has been uninstalled before. */ synchronized void stopBundle() throws BundleException { if (state == INSTALLED) { return; } final int oldState = state; // step 5 state = STOPPING; // step 6 framework.notifyBundleListeners(BundleEvent.STOPPING, this); try { if (oldState == ACTIVE) { if (currentRevision.activatorInstance != null) { currentRevision.activatorInstance.stop(context); } if (state == UNINSTALLED) { throw new BundleException( "Activator.stop() uninstalled this bundle!", BundleException.ACTIVATOR_ERROR); } } } catch (final Throwable t) { throw new BundleException("Error stopping bundle " + toString(), BundleException.STATECHANGE_ERROR, t); } finally { if (currentRevision != null && currentRevision.activatorInstance != null) { currentRevision.activatorInstance = null; } framework.clearBundleTrace(this); state = RESOLVED; framework.notifyBundleListeners(BundleEvent.STOPPED, this); if (framework.DEBUG_BUNDLES) { framework.logger.log(LogService.LOG_INFO, "framework: Bundle " + toString() + " stopped."); } if (context != null) { context.isValid = false; } context = null; synchronized (this) { notify(); } } } /** * uninstall the bundle. * * @throws BundleException * if bundle is already uninstalled * @see org.osgi.framework.Bundle#uninstall() * @category Bundle */ public synchronized void uninstall() throws BundleException { if (framework.SECURITY_ENABLED) { // TODO: check AdminPermission(this, LIFECYCLE) } if (state == UNINSTALLED) { throw new IllegalStateException( "Bundle " + toString() + " is already uninstalled."); } if (state == ACTIVE) { try { stopBundle(); } catch (final Throwable t) { framework.notifyFrameworkListeners(FrameworkEvent.ERROR, this, t); } } if (currentRevision.isFragment()) { // fragment becomes unresolved framework.removeFragment(currentRevision); } // reset locale lastDefaultLocale = Locale.getDefault(); headers.headerCache = null; framework.notifyBundleListeners(BundleEvent.UNRESOLVED, this); state = UNINSTALLED; synchronized (framework) { updateLastModified(); new File(storageLocation, "meta").delete(); framework.symbolicName_bundles .remove(currentRevision.getSymbolicName(), this); currentRevision.cleanup(true); currentRevision = null; framework.location_bundles.remove(location); } framework.notifyBundleListeners(BundleEvent.UNINSTALLED, this); if (context != null) { context.isValid = false; context = null; } } /** * update the bundle from its update location or the location from where it * was originally installed. * * @throws BundleException * if something goes wrong. * @see org.osgi.framework.Bundle#update() * @category Bundle */ public synchronized void update() throws BundleException { final String updateLocation = headers .get(Constants.BUNDLE_UPDATELOCATION); try { update(new URL(updateLocation == null ? location : updateLocation) .openConnection().getInputStream()); } catch (final IOException ioe) { throw new BundleException("Could not update " + toString() + " from " + updateLocation, BundleException.READ_ERROR, ioe); } } /** * update the bundle from an input stream. * * @param stream * the stream. * @throws BundleException * if something goes wrong. * @see org.osgi.framework.Bundle#update(java.io.InputStream) * @category Bundle */ public synchronized void update(final InputStream stream) throws BundleException { lastModified = System.currentTimeMillis(); try { if (framework.SECURITY_ENABLED) { // TODO: check AdminPermission(this, LIFECYCLE) } if (state == UNINSTALLED) { throw new IllegalStateException( "Cannot update uninstalled bundle " + toString()); } boolean wasActive = false; if (state == ACTIVE) { // so we have to restart it after update wasActive = true; stop(); } if (currentRevision.isFragment()) { state = INSTALLED; framework.removeFragment(currentRevision); framework.notifyBundleListeners(BundleEvent.UPDATED, this); } else { updateLastModified(); if (currentRevision != null) { // do not close here, e.g. old wirings should still be // available currentRevision.cleanup(false); } } final Revision updatedRevision = readAndProcessInputStream(stream); framework.checkForCollision(CollisionHook.UPDATING, this, updatedRevision); framework.symbolicName_bundles .remove(currentRevision.getSymbolicName(), this); currentRevision = updatedRevision; symbolicName = currentRevision.getSymbolicName(); version = currentRevision.getVersion(); revisions.add(0, updatedRevision); framework.symbolicName_bundles .insert(currentRevision.getSymbolicName(), this); if (!currentRevision.isFragment()) { currentRevision.resolve(false); } framework.notifyBundleListeners(BundleEvent.UPDATED, this); if (wasActive) { try { start(); } catch (final BundleException be) { // TODO: to log } } if (framework.state != Bundle.STARTING && framework.state != Bundle.STOPPING) { updateMetadata(); } } finally { try { stream.close(); } catch (final IOException e) { // ignore } } } void refresh() { // iterate over old and current revisions for (final BundleRevision brev : revisions) { final Revision rev = (Revision) brev; if (rev.wiring != null) { rev.wiring.cleanup(); rev.wiring = null; } } revisions.clear(); if (currentRevision != null) { revisions.add(currentRevision); // detach fragments (if any) and reset classloader currentRevision.refresh(); // remove from framework wirings framework.wirings.remove(currentRevision); // clear and restore dynamic imports currentRevision.dynamicImports.clear(); for (final BundleRequirement req : currentRevision.requirements .lookup(PackageNamespace.PACKAGE_NAMESPACE)) { if (PackageNamespace.RESOLUTION_DYNAMIC .equals(req.getDirectives().get( Namespace.REQUIREMENT_RESOLUTION_DIRECTIVE))) { currentRevision.dynamicImports.add(req); } } } } /** * This is a test to filter out classes which should not trigger bundle * activation in case of a lazy activation policy. * * @param pkgName * Name of the class to test * @return true if the class should trigger activation */ boolean checkActivation(final String pkgName) { if (activationExcludes != null) { for (int i = 0; i < activationExcludes.length; i++) { if (RFC1960Filter.stringCompare( activationExcludes[i].toCharArray(), 0, pkgName.toCharArray(), 0) == 0) { return false; } } } if (activationIncludes != null) { boolean trigger = false; for (int i = 0; i < activationIncludes.length; i++) { if (RFC1960Filter.stringCompare( activationIncludes[i].toCharArray(), 0, pkgName.toCharArray(), 0) == 0) { trigger = true; break; } } if (!trigger) { return false; } } return true; } void triggerActivation() { try { activate0(); } catch (final BundleException be) { // see spec 4.4.6.2 lazy activation policy state = Bundle.STOPPING; framework.notifyBundleListeners(BundleEvent.STOPPING, this); state = Bundle.RESOLVED; framework.notifyBundleListeners(BundleEvent.STOPPED, this); } } /** * get the bundle symbolic name. * * @return the bundle symbolic name. * @see org.osgi.framework.Bundle#getSymbolicName() * @category Bundle */ public final String getSymbolicName() { return symbolicName; } /** * @see org.osgi.framework.Bundle#getLocation() * @category Bundle */ public final Version getVersion() { return version; } /** * @see org.osgi.framework.Bundle#getHeaders() * @category Bundle */ public Dictionary<String, String> getHeaders() { if (framework.SECURITY_ENABLED) { // check AdminPermission(this,METADATA) } return headers.localize(lastDefaultLocale == null ? Locale.getDefault() : lastDefaultLocale); } /** * @see org.osgi.framework.Bundle#getServicesInUse() * @category Bundle */ public ServiceReference<?>[] getServicesInUse() { if (state == UNINSTALLED) { throw new IllegalStateException( "Bundle " + toString() + "has been unregistered."); } if (registeredServices == null) { return null; } final ArrayList<ServiceReference<?>> result = new ArrayList<ServiceReference<?>>(); final ServiceReference<?>[] srefs = registeredServices .toArray(new ServiceReference[registeredServices.size()]); for (int i = 0; i < srefs.length; i++) { synchronized (((ServiceReferenceImpl<?>) srefs[i]).useCounters) { if (((ServiceReferenceImpl<?>) srefs[i]).useCounters .get(this) != null) { result.add(srefs[i]); } } } if (framework.SECURITY_ENABLED) { // permissions for the interfaces have to be checked return checkPermissions( result.toArray(new ServiceReferenceImpl[result.size()])); } else { return result.toArray(new ServiceReference[result.size()]); } } /** * @see org.osgi.framework.Bundle#getResource(java.lang.String) * @category Bundle */ public URL getResource(final String name) { if (state == UNINSTALLED) { throw new IllegalStateException("Bundle is uninstalled"); } if (currentRevision.isFragment()) { // bundle is Fragment, return null return null; } if (state == INSTALLED) { try { if (!currentRevision.resolve(false)) { // search in this bundle: try { if ("/".equals(name)) { return currentRevision.createURL("/", null); } for (int i = 0; i < currentRevision.classpath.length; i++) { final URL url = currentRevision.lookupFile( currentRevision.classpath[i], name); if (url != null) { return url; } } } catch (final IOException e) { // TODO: to log e.printStackTrace(); return null; } return null; } } catch (final BundleException e) { // TODO: to log e.printStackTrace(); return null; } } return currentRevision.classloader.findResource(name); } /** * * @see org.osgi.framework.Bundle#getHeaders(java.lang.String) * @category Bundle */ public Dictionary<String, String> getHeaders(final String locale) { if (locale == null || lastDefaultLocale != null) { return getHeaders(); } if (locale.length() == 0) { return headers; } final String[] vars = locale.split("_"); final Locale loc; if (vars.length > 2) { loc = new Locale(vars[0], vars[1], vars[2]); } else if (vars.length > 1) { loc = new Locale(vars[0], vars[1]); } else { loc = new Locale(vars[0]); } return headers.localize(loc); } /** * @see org.osgi.framework.Bundle#loadClass(java.lang.String) * @category Bundle */ public Class<?> loadClass(final String name) throws ClassNotFoundException { if (state == Bundle.UNINSTALLED) { throw new IllegalStateException("Bundle is uninstalled"); } // is this actually a fragment? if (currentRevision.isFragment()) { throw new ClassNotFoundException( "This bundle is a fragment and cannot load any classes."); } if (state == Bundle.INSTALLED) { try { currentRevision.resolve(true); } catch (final BundleException be) { framework.notifyFrameworkListeners(FrameworkEvent.ERROR, this, be); throw new ClassNotFoundException(name, be); } } return currentRevision.classloader.findClass(name); } /** * @see org.osgi.framework.Bundle#getResources(String) * @category Bundle */ public Enumeration<URL> getResources(final String name) throws IOException { if (state == UNINSTALLED) { throw new IllegalStateException("Bundle is uninstalled"); } if (currentRevision.isFragment()) { // bundle is fragment, return null return null; } if (state == INSTALLED) { try { if (!currentRevision.resolve(false)) { final Vector<URL> result = new Vector<URL>(); for (int i = 0; i < currentRevision.classpath.length; i++) { final URL url = currentRevision .lookupFile(currentRevision.classpath[i], name); if (url != null) { result.add(url); } } return result.isEmpty() ? null : result.elements(); } } catch (final BundleException e) { // TODO: to log e.printStackTrace(); return null; } } return currentRevision.classloader.findResources0(name); } /** * @see org.osgi.framework.Bundle#getEntryPaths(String) * @category Bundle */ public Enumeration<String> getEntryPaths(final String path) throws IllegalStateException { if (state == Bundle.UNINSTALLED) { throw new IllegalStateException( "Bundle has been uninstalled. Cannot retrieve entry."); } if (framework.SECURITY_ENABLED) { // check AdminPermission(this,RESOURCE) } final Vector<URL> urls = currentRevision.searchFiles(null, path, "*", false); return urls.isEmpty() ? null : new Enumeration<String>() { final Enumeration<URL> urlEnumeration = urls.elements(); public boolean hasMoreElements() { return urlEnumeration.hasMoreElements(); } public String nextElement() { return urlEnumeration.nextElement().getFile().substring(1); } }; } /** * @see org.osgi.framework.Bundle#getEntry(String) * @category Bundle */ public URL getEntry(final String path) throws IllegalStateException { if (state == Bundle.UNINSTALLED) { throw new IllegalStateException( "Bundle has been uninstalled. Cannot retrieve entry."); } if (framework.SECURITY_ENABLED) { // TODO: check AdminPermission(this,RESOURCE) } try { if ("/".equals(path)) { return currentRevision.createURL("/", null); } return currentRevision.lookupFile(null, path); } catch (final IOException e) { e.printStackTrace(); return null; } } /** * @see org.osgi.framework.Bundle#findEntries(String, String, boolean) * @category Bundle */ public Enumeration<URL> findEntries(final String path, final String filePattern, final boolean recurse) { // try to resolve bundle if state is installed if (state == Bundle.INSTALLED) { try { currentRevision.resolve(false); } catch (final BundleException ex) { // ignore and search only in this bundles jar file } } final Revision revision = currentRevision == null ? (Revision) revisions.get(0) : currentRevision; return revision.findEntries(path, filePattern, recurse); } /** * TODO: implement * * @see org.osgi.framework.Bundle#getSignerCertificates(int) * @category Bundle */ public Map<X509Certificate, List<X509Certificate>> getSignerCertificates( final int signersType) { throw new UnsupportedOperationException("Not yet implemented."); } @Override protected boolean isSecurityEnabled() { return framework.isSecurityEnabled(); } final InputStream getURLResource(final URL url, final int rev) throws IOException { String frag; try { frag = url.toURI().getFragment(); } catch (final URISyntaxException e) { e.printStackTrace(); frag = null; } for (final BundleRevision brevision : revisions) { final Revision revision = (Revision) brevision; if (revision.revId == rev) { if (frag == null) { return revision.retrieveFile(null, url.getPath()); } else { return revision.retrieveFile(url.getPath(), frag); } } } return null; } final long getResourceLength(final URL url, final int rev) { String frag; try { frag = url.toURI().getFragment(); } catch (final URISyntaxException e) { e.printStackTrace(); frag = null; } try { for (final BundleRevision brevision : revisions) { final Revision revision = (Revision) brevision; if (revision.revId == rev) { if (frag == null) { return revision.retrieveFileLength(null, url.getPath()); } else { return revision.retrieveFileLength(url.getPath(), frag); } } } } catch (final IOException e) { } return -1; } // BundleStartLevel /** * @see org.osgi.framework.startlevel.BundleStartLevel#getStartLevel() * @category BundleStartLevel */ public int getStartLevel() { return startlevel; } /** * @see org.osgi.framework.startlevel.BundleStartLevel#isPersistentlyStarted() * @category BundleStartLevel */ public boolean isPersistentlyStarted() { checkBundleNotUninstalled(); return autostart != AUTOSTART_STOPPED; } /** * @see org.osgi.framework.startlevel.BundleStartLevel#isActivationPolicyUsed() * @category BundleStartLevel */ public boolean isActivationPolicyUsed() { checkBundleNotUninstalled(); return autostart == AUTOSTART_STARTED_WITH_DECLARED; } /** * @see org.osgi.framework.startlevel.BundleStartLevel#setStartLevel(int) * @category BundleStartLevel */ public void setStartLevel(final int targetStartLevel) { checkBundleNotUninstalled(); if (targetStartLevel <= 0) { throw new IllegalArgumentException("Start level " + targetStartLevel + " is not a valid level"); } final int oldStartlevel = startlevel; startlevel = targetStartLevel; updateMetadata(); if (targetStartLevel <= oldStartlevel && state != Bundle.ACTIVE && autostart != AbstractBundle.AUTOSTART_STOPPED) { final int options = isActivationPolicyUsed() ? Bundle.START_ACTIVATION_POLICY & Bundle.START_TRANSIENT : Bundle.START_TRANSIENT; new Thread() { public void run() { try { activate(options); } catch (final BundleException be) { // TODO: remove debug output be.printStackTrace(); framework.notifyFrameworkListeners(FrameworkEvent.ERROR, BundleImpl.this, be); } }; }.start(); } else if (targetStartLevel > oldStartlevel && state != Bundle.RESOLVED && state != Bundle.INSTALLED) { new Thread() { public void run() { try { stopBundle(); } catch (final BundleException be) { framework.notifyFrameworkListeners(FrameworkEvent.ERROR, BundleImpl.this, be); } } }.start(); } } protected static String[] readProperties(final Attributes attrs, final String property, final String[] defaultValue) throws BundleException { final String propString = readProperty(attrs, property); return propString == null ? defaultValue : Utils.splitString(propString, ','); } protected static String readProperty(final Attributes attrs, final String property) throws BundleException { final String value = attrs.getValue(property); if (value != null && value.equals("")) { throw new BundleException( "Broken manifest, " + property + " is empty.", BundleException.MANIFEST_ERROR); } return value; } protected Properties getLocalizationFile(final Locale locale, final String baseDir, final String baseFile) { if (hostBundles != null) { // fragment return hostBundles.get(0).getLocalizationFile(locale, baseDir, baseFile); } final Locale[] locales = new Locale[] { lastDefaultLocale == null ? Locale.getDefault() : lastDefaultLocale, locale }; final Properties props = new Properties(); final String[] choices = new String[7]; int counter = 0; choices[0] = ""; for (int i = 0; i < 2; i++) { choices[++counter] = "_" + locales[i].getLanguage(); if (locales[i].getCountry().length() > 0) { choices[++counter] = choices[counter - 1] + "_" + locales[i].getCountry(); if (locales[i].getVariant().length() > 0) { choices[++counter] = choices[counter - 1] + "_" + locales[i].getVariant(); } } } for (int i = counter; i >= 0; i--) { final Enumeration<URL> urls = findEntries(baseDir, baseFile + choices[i] + ".properties", false); if (urls != null) { while (urls.hasMoreElements()) { try { final URL url = urls.nextElement(); final InputStream stream = url.openStream(); props.load(stream); return props; } catch (final IOException ioe) { // ignore and continue } } } } return null; } @Override public String toString() { return "[" + getSymbolicName() + "-" + getVersion() + "]"; } public abstract class Revision implements BundleRevision, Comparable<Revision> { protected static final int GET_URL = 0; protected static final int RETRIEVE_INPUT_STREAM = 1; protected static final int GET_CONTENT_LENGTH = 2; protected final int revId; protected final MultiMap<String, BundleCapability> capabilities; protected final MultiMap<String, BundleRequirement> requirements; private final List<HostedCapability> hostedCapabilities = new ArrayList<HostedCapability>(); protected final List<BundleRequirement> dynamicImports; private BundleCapability identity; BundleClassLoader classloader; protected final String activatorClassName; private String[] nativeCodeStrings; protected BundleActivator activatorInstance; protected String[] classpath; protected Map<String, String> nativeLibraries; protected List<Revision> fragments; private final String[] classpathStrings; private final short fragmentAttachmentPolicy; protected ConciergeBundleWiring wiring; protected HashMap<String, BundleWire> packageImportWires; protected List<BundleWire> requireBundleWires; protected final HashSet<String> exportIndex; protected Revision(final int revId, final Manifest manifest, final String[] classpathStrings) throws BundleException { this.revId = revId; this.classpathStrings = classpathStrings; this.classloader = new BundleClassLoader(); final Attributes attrs = manifest.getMainAttributes(); // bundle manifest version final String mfVerStr = attrs .getValue(Constants.BUNDLE_MANIFESTVERSION); final int mfVer; try { mfVer = mfVerStr == null ? 1 : Integer.parseInt(mfVerStr.trim()); } catch (final NumberFormatException nfe) { throw new BundleException( "Illegal value for " + Constants.BUNDLE_MANIFESTVERSION + ": `" + mfVerStr + "`", BundleException.MANIFEST_ERROR); } // process generic requirements and capabilities final String reqStr = attrs.getValue(Constants.REQUIRE_CAPABILITY); this.requirements = parseRequirements(reqStr); final String capStr = attrs.getValue(Constants.PROVIDE_CAPABILITY); this.capabilities = parseCapabilities(capStr); this.dynamicImports = new ArrayList<BundleRequirement>(); LegacyBundleProcessing proc; switch (mfVer) { default: proc = framework.getService(LegacyBundleProcessing.class, LegacyBundleProcessing.VERSION_ONE); if (proc == null) { throw new BundleException( "Bundle manifest version 1 is not supported by this deployment", BundleException.UNSUPPORTED_OPERATION); } final Tuple<List<BundleCapability>, List<BundleRequirement>> tuple = proc .processManifest(this, manifest); for (final BundleCapability cap : tuple.getFormer()) { capabilities.insert(cap.getNamespace(), cap); } for (final BundleRequirement req : tuple.getLatter()) { final String namespace = req.getNamespace(); requirements.insert(namespace, req); if (PackageNamespace.PACKAGE_NAMESPACE.equals(namespace) && PackageNamespace.RESOLUTION_DYNAMIC.equals(req .getDirectives() .get(Namespace.REQUIREMENT_RESOLUTION_DIRECTIVE))) { dynamicImports.add(req); } } break; case 2: proc = framework.getService(LegacyBundleProcessing.class, LegacyBundleProcessing.VERSION_TWO); if (proc == null) { throw new BundleException( "Bundle manifest version 2 is not supported by this deployment", BundleException.UNSUPPORTED_OPERATION); } final Tuple<List<BundleCapability>, List<BundleRequirement>> tuple2 = proc .processManifest(this, manifest); for (final BundleCapability cap : tuple2.getFormer()) { capabilities.insert(cap.getNamespace(), cap); } for (final BundleRequirement req : tuple2.getLatter()) { final String namespace = req.getNamespace(); requirements.insert(namespace, req); if (PackageNamespace.PACKAGE_NAMESPACE.equals(namespace) && PackageNamespace.RESOLUTION_DYNAMIC.equals(req .getDirectives() .get(Namespace.REQUIREMENT_RESOLUTION_DIRECTIVE))) { dynamicImports.add(req); } } } // create export index exportIndex = createExportIndex(); // remove dynamic imports for exported packages if (!dynamicImports.isEmpty()) { final Iterator<BundleRequirement> iter = dynamicImports .iterator(); while (iter.hasNext()) { final BundleRequirement req = iter.next(); if (exportIndex.contains( req.getDirectives().get(Concierge.DIR_INTERNAL))) { iter.remove(); } } } // get the native libraries nativeCodeStrings = readProperties(attrs, Constants.BUNDLE_NATIVECODE, null); // get the activator activatorClassName = attrs.getValue(Constants.BUNDLE_ACTIVATOR); // get start_activation_policy, if any final String activationPolicy = readProperty(attrs, Constants.BUNDLE_ACTIVATIONPOLICY); if (activationPolicy != null) { final String[] literals = Utils.splitString(activationPolicy, ';'); if (Constants.ACTIVATION_LAZY.equals(literals[0])) { lazyActivation = true; } for (int i = 1; i < literals.length; i++) { final Matcher matcher = DIRECTIVE_LIST.matcher(literals[i]); if (matcher.matches()) { final String directive = matcher.group(1); final String list = matcher.group(2); final String[] elems = Utils.splitString(list, ','); if (Constants.INCLUDE_DIRECTIVE.equals(directive)) { activationIncludes = elems; } else if (Constants.EXCLUDE_DIRECTIVE.equals(directive)) { activationExcludes = elems; } } } } // set bundle_localization_path String bundleLocalizationBaseName = attrs .getValue(Constants.BUNDLE_LOCALIZATION); if (bundleLocalizationBaseName == null) { bundleLocalizationBaseName = Constants.BUNDLE_LOCALIZATION_DEFAULT_BASENAME; } final int pos = bundleLocalizationBaseName.lastIndexOf("/"); if (pos > -1) { bundleLocalizationBaseDir = bundleLocalizationBaseName .substring(0, pos); bundleLocalizationBaseFilename = bundleLocalizationBaseName .substring(pos + 1); } else { bundleLocalizationBaseDir = "/"; bundleLocalizationBaseFilename = bundleLocalizationBaseName; } // set the bundle headers final HeaderDictionary headers = new HeaderDictionary(attrs.size()); final Object[] entries = attrs.keySet() .toArray(new Object[attrs.keySet().size()]); for (int i = 0; i < entries.length; i++) { headers.put(entries[i].toString(), attrs.get(entries[i]).toString()); } BundleImpl.this.headers = headers; final List<BundleCapability> identities = capabilities .get(IdentityNamespace.IDENTITY_NAMESPACE); if (identities != null) { this.identity = capabilities .get(IdentityNamespace.IDENTITY_NAMESPACE).get(0); } final List<BundleCapability> hosts = capabilities .get(HostNamespace.HOST_NAMESPACE); if (hosts == null) { fragmentAttachmentPolicy = FRAGMENT_ATTACHMENT_NEVER; } else { final String policy = hosts.get(0).getDirectives() .get(Constants.FRAGMENT_ATTACHMENT_DIRECTIVE); if (identity == null || policy == null || Constants.FRAGMENT_ATTACHMENT_ALWAYS .equals(policy)) { fragmentAttachmentPolicy = FRAGMENT_ATTACHMENT_ALWAYS; } else if (Constants.FRAGMENT_ATTACHMENT_RESOLVETIME .equals(policy)) { fragmentAttachmentPolicy = FRAGMENT_ATTACHMENT_RESOLVETIME; } else if (Constants.FRAGMENT_ATTACHMENT_NEVER.equals(policy)) { fragmentAttachmentPolicy = FRAGMENT_ATTACHMENT_NEVER; } else { fragmentAttachmentPolicy = FRAGMENT_ATTACHMENT_ALWAYS; } } if (isFragment()) { framework.addFragment(this); } else { framework.publishCapabilities(capabilities.getAllValues()); } } protected void refresh() { if (fragments != null) { fragments = null; } classloader = new BundleClassLoader(); } private MultiMap<String, BundleRequirement> parseRequirements( final String str) throws BundleException { final MultiMap<String, BundleRequirement> result = new MultiMap<String, BundleRequirement>(); if (str == null) { return result; } final String[] reqStrs = Utils.splitString(str, ','); for (int i = 0; i < reqStrs.length; i++) { final BundleRequirementImpl req = new BundleRequirementImpl( this, reqStrs[i]); final String namespace = req.getNamespace(); result.insert(namespace, req); } return result; } private MultiMap<String, BundleCapability> parseCapabilities( final String str) throws BundleException { final MultiMap<String, BundleCapability> result = new MultiMap<String, BundleCapability>(); if (str == null) { return result; } final String[] reqStrs = Utils.splitString(str, ','); for (int i = 0; i < reqStrs.length; i++) { final BundleCapabilityImpl cap = new BundleCapabilityImpl(this, reqStrs[i]); final String namespace = cap.getNamespace(); result.insert(namespace, cap); } return result; } private HashSet<String> createExportIndex() { final List<BundleCapability> packageReqs = capabilities .get(PackageNamespace.PACKAGE_NAMESPACE); final HashSet<String> index = new HashSet<String>(); if (packageReqs != null) { for (final BundleCapability req : packageReqs) { index.add((String) req.getAttributes() .get(PackageNamespace.PACKAGE_NAMESPACE)); } } return index; } public boolean isFragment() { return requirements.get(HostNamespace.HOST_NAMESPACE) != null; } boolean isExtensionBundle() { final String fragmentHostName = getFragmentHost(); return fragmentHostName.equals(Constants.SYSTEM_BUNDLE_SYMBOLICNAME) || fragmentHostName .equals(Concierge.FRAMEWORK_SYMBOLIC_NAME); } protected boolean isCurrent() { return BundleImpl.this.currentRevision == this; } /** * @see org.osgi.framework.BundleReference#getBundle() * @category BundleReference */ public Bundle getBundle() { return BundleImpl.this; } /** * @see org.osgi.framework.wiring.BundleRevision#getSymbolicName() * @category BundleRevision */ public String getSymbolicName() { return identity == null ? null : (String) identity.getAttributes() .get(IdentityNamespace.IDENTITY_NAMESPACE); } /** * @see org.osgi.framework.wiring.BundleRevision#getVersion() * @category BundleRevision */ public Version getVersion() { return identity == null ? Version.emptyVersion : (Version) identity.getAttributes().get( IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE); } /** * @see org.osgi.framework.wiring.BundleRevision#getDeclaredCapabilities(java.lang.String) * @BundleRevision */ public List<BundleCapability> getDeclaredCapabilities( final String namespace) { return namespace == null ? capabilities.getAllValues() : capabilities.lookup(namespace); } /** * @see org.osgi.framework.wiring.BundleRevision#getDeclaredRequirements(java.lang.String) * @category BundleRevision */ public List<BundleRequirement> getDeclaredRequirements( final String namespace) { return namespace == null ? requirements.getAllValues() : requirements.lookup(namespace); } /** * @see org.osgi.framework.wiring.BundleRevision#getTypes() * @category BundleRevision */ public int getTypes() { return identity == null ? 0 : IdentityNamespace.TYPE_FRAGMENT .equals(identity.getAttributes() .get(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE)) ? BundleRevision.TYPE_FRAGMENT : 0; } /** * @see org.osgi.framework.wiring.BundleRevision#getWiring() * @category BundleRevision */ public BundleWiring getWiring() { return wiring; } /** * @see org.osgi.framework.wiring.BundleRevision#getCapabilities(java.lang.String) * @category BundleRevision */ public List<Capability> getCapabilities(final String namespace) { return Collections.unmodifiableList(new ArrayList<Capability>( getDeclaredCapabilities(namespace))); } /** * @see org.osgi.framework.wiring.BundleRevision#getRequirements(java.lang.String) * @category BundleRevision */ public List<Requirement> getRequirements(final String namespace) { return Collections.unmodifiableList(new ArrayList<Requirement>( getDeclaredRequirements(namespace))); } protected boolean resolve(final boolean critical) throws BundleException { if (!resolveMetadata(critical)) { return false; } if (!framework.resolve( Collections.<BundleRevision> singletonList(this), critical)) { return false; } if (state == Bundle.INSTALLED) { state = Bundle.RESOLVED; framework.notifyBundleListeners(BundleEvent.RESOLVED, BundleImpl.this); } return true; } protected boolean resolveMetadata(final boolean critical) throws BundleException { try { /* * resolve the bundle's internal classpath. <specs>The framework * must ignore missing files in the Bundle-Classpath headers. * However, a Framework should publish a Framework Event of type * ERROR for each file that is not found in the bundle's JAR * with an appropriate message</specs> */ if (classpath == null) { for (int i = 0; i < classpathStrings.length; i++) { if (classpathStrings[i].equals(".")) { // '.' is always fine continue; } try { if (null == retrieveFile(null, classpathStrings[i])) { framework.notifyFrameworkListeners( FrameworkEvent.ERROR, BundleImpl.this, new BundleException( "Missing file in bundle classpath " + classpathStrings[i], BundleException.RESOLVE_ERROR)); } } catch (final IOException ioe) { framework.notifyFrameworkListeners( FrameworkEvent.ERROR, BundleImpl.this, ioe); } } classpath = classpathStrings; } // resolve native code dependencies if (nativeCodeStrings != null) { nativeLibraries = new HashMap<String, String>( nativeCodeStrings.length); if (!processNativeLibraries(nativeCodeStrings)) { if (critical) { throw new BundleException( "No matching native clause"); } else { return false; } } } return true; } catch (final IllegalArgumentException iae) { throw new BundleException( "Error while resolving bundle " + currentRevision.getSymbolicName(), BundleException.RESOLVE_ERROR, iae); } } protected boolean isFrameworkExtension() { final List<BundleRequirement> hostReqs = requirements .get(HostNamespace.HOST_NAMESPACE); if (hostReqs == null) { return false; } return HostNamespace.EXTENSION_FRAMEWORK .equals(hostReqs.get(0).getDirectives().get( HostNamespace.REQUIREMENT_EXTENSION_DIRECTIVE)); } /** * process the native libraries declarations from the manifest. Only * register natives that comply with stated OS/version/languages * constraints. * * @param nativeStrings * the native library declarations and constraints. * @param nativeLibraries * the map. * @throws BundleException */ private boolean processNativeLibraries(final String[] nativeStrings) throws BundleException { int pos = -1; boolean n = false; boolean no_n = true; boolean l = false; boolean no_l = true; boolean v = false; boolean no_v = true; boolean p = false; boolean no_p = false; boolean s = false; boolean no_s = true; boolean hasOptional = false; boolean hasMatch = false; final List<String> libs = new ArrayList<String>(); for (int i = 0; i < nativeStrings.length; i++) { if (nativeStrings[i].indexOf(";") == -1) { if (nativeStrings[i].equals("*")) { hasOptional = true; } else { nativeLibraries.put( (pos = nativeStrings[i].lastIndexOf("/")) > -1 ? nativeStrings[i].substring(pos + 1) : nativeStrings[i], stripTrailing(nativeStrings[i])); } } else { final StringTokenizer tokenizer = new StringTokenizer( nativeStrings[i], ";"); while (tokenizer.hasMoreTokens()) { final String token = tokenizer.nextToken(); final int a = token.indexOf("="); if (a > -1) { final String criterium = token.substring(0, a) .trim().intern(); final String value = token.substring(a + 1).trim(); if (criterium == Constants.BUNDLE_NATIVECODE_OSNAME) { if (framework.osname.startsWith("Windows")) { n |= value.toLowerCase().startsWith("win"); } else { n |= value .equalsIgnoreCase(framework.osname); // support aliases for "Mac OS" and // "Mac OS X" if (framework.osname.equals("MacOSX")) { n |= value.equalsIgnoreCase("Mac OS X"); } else if (framework.osname.equals("MacOS")) { n |= value.equalsIgnoreCase("Mac OS"); } // TODO add other alias missing in OSGi R5 // spec Table 4.4 } no_n = false; } else if (criterium == Constants.BUNDLE_NATIVECODE_OSVERSION) { v |= new VersionRange(Utils.unQuote(value)) .includes(framework.osversion); no_v = false; } else if (criterium == Constants.BUNDLE_NATIVECODE_LANGUAGE) { l |= new Locale(value, "").getLanguage() .equals(framework.language); no_l = false; } else if (criterium == Constants.BUNDLE_NATIVECODE_PROCESSOR) { if (framework.processor.equals("x86")) { p |= value.equals("x86") || value.equals("pentium") || value.equals("i386") || value.equals("i486") || value.equals("i586") || value.equals("i686"); } else if (framework.processor.equals("x86-64")) { p |= value.equals("amd64") || value.equals("em64t") || value.equals("x86_64") || value.equals("x86-64"); } else if (framework.processor.equals("ppc")) { p |= value.equals("ppc"); } else { p |= value.equalsIgnoreCase( framework.processor); } no_p = false; } else if (criterium == Constants.SELECTION_FILTER_ATTRIBUTE) { try { s |= RFC1960Filter .fromString(Utils.unQuote(value)) .match(Concierge.props2Dict( framework.properties)); no_s = false; } catch (final InvalidSyntaxException e) { e.printStackTrace(); } } } else if ("*".equals(token.trim()) && !tokenizer.hasMoreTokens()) { // wildcard hasMatch = true; } else { libs.add(token.trim()); } } if (!libs.isEmpty() && (no_p || p) && (no_n || n) && (no_v || v) && (no_l || l) && (no_s || s)) { final String[] libraries = libs .toArray(new String[libs.size()]); for (int c = 0; c < libraries.length; c++) { nativeLibraries.put( (pos = libraries[c].lastIndexOf("/")) > -1 ? libraries[c].substring(pos + 1) : libraries[c], stripTrailing(libraries[c])); } hasMatch = true; } p = n = v = l = s = false; no_p = no_n = no_v = no_l = no_s = true; libs.clear(); } } return hasMatch || hasOptional; } /** * @param uninstall * if false, the bundle is only prepared for an update or * refresh. If true, it is prepared for the uninstalled * state. */ void cleanup(final boolean uninstall) { framework.removeCapabilities(this); // if this is the final cleanup, remove this resource from all other // inUse lists if (currentRevision == null) { if (wiring != null) { wiring.cleanup(); framework.wirings.remove(this); } wiring = null; packageImportWires = null; requireBundleWires = null; fragments = null; } if (!uninstall) { currentRevision = (Revision) revisions.get(0); } } void markResolved() { state = Bundle.RESOLVED; framework.notifyBundleListeners(BundleEvent.RESOLVED, BundleImpl.this); } void setWiring(final ConciergeBundleWiring wiring) { this.wiring = wiring; packageImportWires = wiring.getPackageImportWires(); requireBundleWires = wiring.getRequireBundleWires(); } ConciergeBundleWiring addAdditionalWires(final List<Wire> wires) { for (final Wire wire : wires) { wiring.addWire((BundleWire) wire); } packageImportWires = wiring.getPackageImportWires(); requireBundleWires = wiring.getRequireBundleWires(); return wiring; } String getFragmentHost() { final List<BundleRequirement> hostReqs = requirements .get(HostNamespace.HOST_NAMESPACE); if (hostReqs == null) { return null; } return hostReqs.get(0).getDirectives() .get(HostNamespace.HOST_NAMESPACE); } final boolean allowsFragmentAttachment() { return fragmentAttachmentPolicy != FRAGMENT_ATTACHMENT_NEVER; } List<Revision> getAttachedFragments() { return fragments; } /** * * @param fragment * @return false if the fragment was already attached. * @throws BundleException */ boolean attachFragment(final Revision fragment) throws BundleException { if (fragments != null) { if (fragments.contains(fragment)) { return false; } } if (state == Bundle.ACTIVE || state == Bundle.STARTING) { // attaching fragment at runtime // test if host allows attaching at runtime: if (fragmentAttachmentPolicy == FRAGMENT_ATTACHMENT_RESOLVETIME) { throw new BundleException( "Host bundle does not allow to attach fragment at runtime", BundleException.RESOLVE_ERROR); } final List<Requirement> imports = fragment .getRequirements(PackageNamespace.PACKAGE_NAMESPACE); final Set<String> importPkgs = new HashSet<String>(); for (final Requirement pkgImport : imports) { importPkgs.add(pkgImport.getDirectives() .get(Concierge.DIR_INTERNAL)); } importPkgs.remove("org.osgi.framework"); if (!importPkgs.isEmpty()) { final List<BundleRequirement> wiredImports = wiring .getRequirements( PackageNamespace.PACKAGE_NAMESPACE); for (final Requirement wiredImport : wiredImports) { importPkgs.remove(wiredImport.getDirectives() .get(Concierge.DIR_INTERNAL)); } if (!importPkgs.isEmpty()) { // bundle needs to be resolved anew if this fragment // would be attached throw new BundleException( "Imports of this Fragment are not satisfiable without restart of the host bundle", BundleException.RESOLVE_ERROR); } } // test for require bundles if (!fragment.getRequirements(BundleNamespace.BUNDLE_NAMESPACE) .isEmpty()) { throw new BundleException( "Fragment must not add new require bundle entries", BundleException.RESOLVE_ERROR); } } final List<String> newClasspaths = new ArrayList<String>( classpath != null ? classpath.length : 0); if (classpath != null) { for (int k = 0; k < classpath.length; k++) { newClasspaths.add(classpath[k]); } } // prepare the attachment // imports final List<BundleRequirement> newImports = fragment.requirements .get(PackageNamespace.PACKAGE_NAMESPACE); if (newImports != null) { checkConflicts(newImports, requirements.get(PackageNamespace.PACKAGE_NAMESPACE), PackageNamespace.PACKAGE_NAMESPACE, "import"); } // require bundles final List<BundleRequirement> newRequiredBundle = fragment.requirements .get(BundleNamespace.BUNDLE_NAMESPACE); if (newRequiredBundle != null) { checkConflicts(newRequiredBundle, requirements.get(BundleNamespace.BUNDLE_NAMESPACE), BundleNamespace.BUNDLE_NAMESPACE, "requireBundle"); } // native code final String[] newNativeStrings; if (fragment.nativeCodeStrings == null) { newNativeStrings = nativeCodeStrings; } else { final ArrayList<String> temp = new ArrayList<String>(); if (nativeCodeStrings != null) { temp.addAll(Arrays.asList(nativeCodeStrings)); } temp.addAll(Arrays.asList(fragment.nativeCodeStrings)); newNativeStrings = temp.toArray(new String[temp.size()]); } // commit the changes final BundleImpl fragmentBundle = (BundleImpl) fragment.getBundle(); if (newImports != null) { requirements.insertAll(PackageNamespace.PACKAGE_NAMESPACE, newImports); } if (newRequiredBundle != null) { requirements.insertAll(BundleNamespace.BUNDLE_NAMESPACE, newRequiredBundle); } final List<Capability> newExports = fragment .getCapabilities(PackageNamespace.PACKAGE_NAMESPACE); if (newExports != null) { for (final Capability cap : newExports) { exportIndex.add((String) cap.getAttributes() .get(PackageNamespace.PACKAGE_NAMESPACE)); } } // add fragment to fragments-array if (fragments == null) { fragments = new ArrayList<Revision>(); } fragments.add(fragment); Collections.sort(fragments); // register the host if (fragmentBundle.hostBundles == null) { fragmentBundle.hostBundles = new ArrayList<BundleImpl>(); } fragmentBundle.hostBundles.add(BundleImpl.this); if (fragmentBundle.state != RESOLVED) { fragment.wiring = new ConciergeBundleWiring(fragment, null); fragmentBundle.state = RESOLVED; framework.notifyBundleListeners(BundleEvent.RESOLVED, fragmentBundle); } // add classpath for (int n = 0; n < fragment.classpathStrings.length; n++) { if (!newClasspaths.contains(fragment.classpathStrings[n])) { newClasspaths.add(fragment.classpathStrings[n]); } } if (newClasspaths.size() > 0) { classpath = newClasspaths .toArray(new String[newClasspaths.size()]); } // add native code nativeCodeStrings = newNativeStrings; if (nativeCodeStrings != null) { nativeLibraries = new HashMap<String, String>( nativeCodeStrings.length); processNativeLibraries(newNativeStrings); } return true; } private <T extends Requirement> void checkConflicts(final List<T> list1, final List<T> list2, final String namespace, final String s) throws BundleException { if (list1 == null) { throw new IllegalArgumentException("list1 == null"); } if (list2 == null) { return; } final int list1size = list1.size(); final int list2size = list2.size(); if (list1size == 0 || list2size == 0) { return; } final List<T> shorter; final List<T> longer; if (list1size > list2size) { shorter = list2; longer = list1; } else { shorter = list1; longer = list2; } // remember element and not only attribute as string final Map<String, T> index = new HashMap<String, T>(); for (final T element : longer) { final String attr = element.getDirectives() .get(Concierge.DIR_INTERNAL); if (attr != null) { index.put(attr, element); } } for (final T element : shorter) { final String attr = element.getDirectives() .get(Concierge.DIR_INTERNAL); if (attr != null && index.containsKey(attr)) { // JR: temporarily disabled due to TCK regression // // T element2 = index.get(attr); // check if directives are equals // TODO must compare version statements including semantics // if (!element.getDirectives().equals( // element2.getDirectives())) { throw new BundleException( "Conflicting " + s + " statement " + element.getAttributes().get(namespace) + " from " + element, BundleException.RESOLVE_ERROR); // } } } } protected Object checkActivationChain(final Object result) { final ArrayList<AbstractBundle> activationList = activationChain .get(); if (activationList != null && activationList.size() > 0 && activationList.get(0) == BundleImpl.this) { activationChain.set(new ArrayList<AbstractBundle>()); for (int i = activationList.size() - 1; i >= 0; i--) { final BundleImpl toActivate = (BundleImpl) activationList .get(i); if (toActivate.beingLazy) { toActivate.triggerActivation(); } } activationChain.set(null); } return result; } /** * get a string representation of the object. * * @return a string. * @see java.lang.Object#toString() * @category Object */ public String toString() { return "[Revision " + revId + " of " + BundleImpl.this + "]"; } protected Enumeration<URL> findEntries(final String path, final String filePattern, final boolean recurse) { final Vector<URL> result = searchFiles(null, path, filePattern, recurse); // get results from fragments: if (fragments != null) { for (final Revision fragment : fragments) { final Vector<URL> fragResult = fragment.searchFiles(null, path, filePattern, recurse); result.addAll(fragResult); } } return result.isEmpty() ? null : result.elements(); } protected abstract URL lookupFile(final String classpath, final String filename) throws IOException; protected abstract URL lookupFile(final String classpath, final String filename, final HashSet<String> visited) throws IOException; protected abstract Vector<URL> searchFiles(final String classpath, final String path, final String filePattern, boolean recurse); protected abstract InputStream retrieveFile(final String classpath, final String filename) throws IOException; protected abstract long retrieveFileLength(final String classpath, final String filename) throws IOException; protected abstract void close() throws IOException; URL createURL(final String name1, final String fragment) throws MalformedURLException { final String name = name1.replace('\\', '/'); return new URL("bundle", bundleId + "." + revId, (name.charAt(0) == '/' ? name : "/" + name) + (fragment == null ? "" : "#" + fragment)); } class BundleClassLoader extends ClassLoader implements BundleReference { public BundleClassLoader() { // set Concierge Classloader as parent of BundleClassLoader // super(Concierge.class.getClassLoader()); super(framework.parentClassLoader); } /** * * @see java.lang.ClassLoader#loadClass(java.lang.String) * @category ClassLoader */ public final Class<?> loadClass(final String name) throws ClassNotFoundException { return findClass(name); } /** * find a class. * * @param classname * the name of the class. * @return the <code>Class</code> object, if the class could be * found. * @throws ClassNotFoundException * if the class could not be found. * @see java.lang.ClassLoader#findClass(java.lang.String) * @category ClassLoader * */ protected final Class<?> findClass(final String name) throws ClassNotFoundException { final Class<?> result = (Class<?>) findResource0( packageOf(name), name, true, false); if (result == null) { throw new ClassNotFoundException(name); } return result; } @Override public Enumeration<URL> getResources(final String name) { return findResources(name); } /** * * @see java.lang.ClassLoader#getResource(java.lang.String) * @category ClassLoader */ public URL getResource(final String name) { return findResource(name); } /** * @see org.osgi.framework.BundleReference#getBundle() * @category BundleReference */ public Bundle getBundle() { return BundleImpl.this; } /** * find a single resource. * * @param filename * the name of the resource. * @return the URL to the resource. * @see java.lang.ClassLoader#findResource(java.lang.String) * @category ClassLoader * */ protected URL findResource(final String name) { final String strippedName = stripTrailing(name); try { return (URL) findResource0( packageOf(pseudoClassname(strippedName)), strippedName, false, false); } catch (final ClassNotFoundException e) { // does not happen e.printStackTrace(); return null; } } /** * find multiple resources. * * @param filename * the name of the resource. * @return an <code>Enumeration</code> over <code>URL</code> * objects. * @see java.lang.ClassLoader#findResources(java.lang.String) * @category ClassLoader */ protected Enumeration<URL> findResources(final String name) { final Enumeration<URL> result = findResources0(name); return result == null ? Collections.enumeration(Collections.<URL> emptyList()) : result; } protected Enumeration<URL> findResources0(final String name) { final String strippedName = stripTrailing(name); try { @SuppressWarnings("unchecked") final Vector<URL> results = (Vector<URL>) findResource0( packageOf(pseudoClassname(strippedName)), strippedName, false, true); return results == null || results.isEmpty() ? null : results.elements(); } catch (final ClassNotFoundException e) { // does not happen e.printStackTrace(); return null; } } /** * * @param pkg * @param name * @param isClass * @param multiple * @return * @throws ClassNotFoundException */ @SuppressWarnings("null") private synchronized Object findResource0(final String pkg, final String name, final boolean isClass, final boolean multiple) throws ClassNotFoundException { final Vector<URL> resources = multiple ? new Vector<URL>() : null; // is the bundle uninstalled? if (state == Bundle.UNINSTALLED) { throw new IllegalStateException("Cannot " + (isClass ? "load class" : "find resource") + ", bundle " + getSymbolicName() + " has been uninstalled."); } // try to resolve bundle if state is installed if (state == Bundle.INSTALLED) { try { resolve(true); } catch (final BundleException be) { if (isClass) { framework.notifyFrameworkListeners( FrameworkEvent.ERROR, BundleImpl.this, be); throw new ClassNotFoundException(name, be); } } } // Step 1: delegate java.* to the parent class loader // Step 2: delegate org.osgi.framework.bootdelegation to the // parent class loader if (pkg.startsWith("java.") || framework.bootdelegation(pkg)) { if (isClass) { return getParent().loadClass(name); } else { if (multiple) { try { final Enumeration<URL> e = getParent() .getResources(name); while (e.hasMoreElements()) { resources.add(e.nextElement()); } } catch (final IOException ioe) { // nothing we can do about it } } else { return getParent().getResource(name); } } } // Step 3: if wires exist, check if the resource is imported if (wiring != null) { final BundleWire delegation = packageImportWires.get(pkg); if (delegation != null) { final BundleCapabilityImpl cap = (BundleCapabilityImpl) delegation .getCapability(); if (!cap.hasExcludes() || cap.filter(classOf(name))) { if (delegation.getProvider().getBundle() .getBundleId() == 0) { // system bundle if (isClass) { return framework.systemBundleClassLoader .loadClass(name); } else { if (multiple) { try { final Enumeration<URL> e = framework.systemBundleClassLoader .getResources(name); while (e.hasMoreElements()) { resources.add(e.nextElement()); } } catch (final IOException ioe) { // nothing we can do about it // FIXME: to log } } else { return framework.systemBundleClassLoader .getResource(name); } } } else { return ((Revision) delegation .getProvider()).classloader .findResource1(pkg, name, isClass, multiple, resources); } } } } return findResource1(pkg, name, isClass, multiple, resources); } /** * * @param pkg * @param name * @param isClass * @param multiple * @param resources * @return * @throws ClassNotFoundException */ private synchronized Object findResource1(final String pkg, final String name, final boolean isClass, final boolean multiple, final Vector<URL> resources) throws ClassNotFoundException { // trigger lazy activation if required if (isClass && lazyActivation && getState() == Bundle.STARTING && checkActivation(pkg)) { ArrayList<AbstractBundle> activationList = activationChain .get(); if (activationList == null) { activationList = new ArrayList<AbstractBundle>(); activationList.add(BundleImpl.this); activationChain.set(activationList); } else if (!activationList.isEmpty() && !activationList.contains(BundleImpl.this)) { activationList.add(BundleImpl.this); } } // Step 4: check required bundles, depth first if (requireBundleWires != null) { final HashSet<Bundle> visited = new HashSet<Bundle>(); visited.add(BundleImpl.this); for (final BundleWire wire : requireBundleWires) { if (wire.getProvider().getBundle().getBundleId() == 0) { // if provider is system bundle: nothing // to do as system bundle is already loaded } else { final Object result = ((Revision) wire .getProvider()).classloader .requireBundleLookup(pkg, name, isClass, multiple, resources, visited); if (!multiple && result != null) { return isClass ? checkActivationChain(result) : result; } } } } // Step 5: search the bundle class path // Step 6: search fragments bundle class path if (isClass) { final Class<?> clazz = findOwnClass(name); if (clazz != null) { return checkActivationChain(clazz); } } else { final Object result = findOwnResources(stripTrailing(name), true, multiple, resources); if (!multiple && result != null) { return result; } } // Step 7: if the package is exported, fail if (exportIndex.contains(pkg)) { return null; } // Step 8: check dynamic imports if (!dynamicImports.isEmpty()) { for (final Iterator<BundleRequirement> iter = dynamicImports .iterator(); iter.hasNext();) { final BundleRequirement dynImport = iter.next(); // TODO: think of something better final String dynImportPackage = dynImport .getDirectives().get(Concierge.DIR_INTERNAL); // TODO: first check if dynImport could apply to the // requested package!!! if (RFC1960Filter.stringCompare( dynImportPackage.toCharArray(), 0, pkg.toCharArray(), 0) != 0) { continue; } final boolean wildcard = Namespace.CARDINALITY_MULTIPLE .equals(dynImport.getDirectives().get( Namespace.REQUIREMENT_CARDINALITY_DIRECTIVE)); List<BundleCapability> matches; matches = framework.resolveDynamic(Revision.this, pkg, dynImportPackage, dynImport, wildcard); if (matches != null && matches.size() > 0) { final BundleCapability bundleCap = matches.get(0); final BundleWire wire = new ConciergeBundleWire( bundleCap, dynImport); if (wiring == null) { setWiring(new ConciergeBundleWiring( Revision.this, null)); } wiring.addWire(wire); ((ConciergeBundleWiring) bundleCap.getRevision() .getWiring()).addWire(wire); packageImportWires.put( (String) bundleCap.getAttributes() .get(PackageNamespace.PACKAGE_NAMESPACE), wire); if (!wildcard) { // FIXME: iter.remove(); } final BundleRevision rev = bundleCap.getRevision(); if (!(rev instanceof Revision)) { if (isClass) { return framework.systemBundleClassLoader .loadClass(name); } else { if (multiple) { try { final Enumeration<URL> e = framework.systemBundleClassLoader .getResources(name); while (e.hasMoreElements()) { resources.add(e.nextElement()); } } catch (final IOException ioe) { // nothing we can do about it // FIXME: to log } } else { return framework.systemBundleClassLoader .getResource(name); } } } else { return ((Revision) bundleCap .getRevision()).classloader .findResource1(pkg, name, isClass, multiple, resources); } } } } // convenience for resources: delegate to boot class path as // final fallback if ("".equals(pkg) && !isClass && !multiple) { return getParent().getResource(name); } return resources; } Set<String> listResources(final String path, final String filePattern, final int options, final HashSet<String> visited) { final String pkg = pseudoClassname( stripTrailing(path.endsWith("/") ? path.substring(0, path.length() - 1) : path)); final HashSet<String> result = new HashSet<String>(); if (wiring != null) { if ((options & BundleWiring.LISTRESOURCES_RECURSE) != 0) { for (final Map.Entry<String, BundleWire> entry : packageImportWires .entrySet()) { final String importPackage = entry.getKey(); if (importPackage.startsWith(pkg)) { final BundleWire delegation = entry.getValue(); final BundleCapabilityImpl cap = (BundleCapabilityImpl) delegation .getCapability(); if (!cap.hasExcludes() || cap.filter(classOf(filePattern))) { // if LISTRESOURCES_LOCAL not set : add to // results if ((options & BundleWiring.LISTRESOURCES_LOCAL) == 0) { result.addAll(((Revision) delegation .getProvider()).classloader .listResources( importPackage .replace( '.', '/'), filePattern, options, visited)); } // always search imports to ignore // overridden packages from imports visited.add(importPackage); } } } } else { final BundleWire delegation = packageImportWires .get(pkg); if (delegation != null) { final BundleCapabilityImpl cap = (BundleCapabilityImpl) delegation .getCapability(); if (!cap.hasExcludes() || cap.filter(classOf(filePattern))) { if ((options & BundleWiring.LISTRESOURCES_LOCAL) == 0) { result.addAll(((Revision) delegation .getProvider()).classloader .listResources(path, filePattern, options, visited)); } visited.add(pkg); } } } if (requireBundleWires != null && (options & BundleWiring.LISTRESOURCES_LOCAL) == 0) { for (final BundleWire wire : requireBundleWires) { result.addAll( ((Revision) wire.getProvider()).classloader .listResources(path, filePattern, options, new HashSet<String>())); } } // Step 5: search the bundle class path // Step 6: search fragments bundle class path // if (!visited.isEmpty()) { result.addAll(listOwnResources(path, filePattern, options, visited)); } return new HashSet<String>(result); } /** * find a native code library. * * @param libname * the name of the library. * @return the String of a path name to the library or * <code>null</code> . * @see java.lang.ClassLoader#findLibrary(java.lang.String) * @category ClassLoader */ protected String findLibrary(final String libname) { if (nativeLibraries == null) { throw new UnsatisfiedLinkError(libname); } String lib = null; final String[] libnames = framework.getLibraryName(libname); for (int i = 0; i < libnames.length; i++) { lib = nativeLibraries.get(libnames[i]); if (lib != null) { break; } } if (framework.DEBUG_CLASSLOADING) { framework.logger.log(LogService.LOG_DEBUG, "Requested " + libname); framework.logger.log(LogService.LOG_INFO, "Native libraries " + nativeLibraries); } if (lib == null) { throw new UnsatisfiedLinkError(libname); } try { final File libfile = new File(storageLocation + "lib", lib); /* * If a native library already exists by that name the newer * library in the bundle will not be stored on disc */ // if (!libfile.exists()) { final URL url = (URL) findOwnResources(lib, true, false, null); storeFile(libfile, url.openStream()); framework.execPermission(libfile); // } return libfile.getAbsolutePath(); } catch (final IOException ioe) { ioe.printStackTrace(); } return null; } /** * Find a class in the bundle scope. * * @param classname * the name of the class. * @return the <code>Class</code> object if the class could be * found. <code>null</code> otherwise. */ private synchronized Class<?> findOwnClass(final String classname) { final Class<?> clazz; if (dexClassLoader != null) { clazz = findDexClass(classname); } else { clazz = findLoadedClass(classname); } if (clazz != null) { definePackage(packageOf(classname)); return clazz; } try { final String filename = classToFile(classname); for (int i = 0; i < classpath.length; i++) { final InputStream input = retrieveFile(classpath[i], filename); if (input == null) { continue; } try { int len; final ByteArrayOutputStream out = new ByteArrayOutputStream(); final BufferedInputStream bis = new BufferedInputStream( input); final byte[] chunk = new byte[Concierge.CLASSLOADER_BUFFER_SIZE]; while ((len = bis.read(chunk, 0, Concierge.CLASSLOADER_BUFFER_SIZE)) > 0) { out.write(chunk, 0, len); } byte[] bytes = out.toByteArray(); // call weaving hooks here if (framework.hasWeavingHooks()) { final WovenClassImpl wovenClass = new WovenClassImpl( classname, bytes, Revision.this, domain); framework.callWeavingHooks(wovenClass); bytes = wovenClass.getBytes(); requirements.insertAll( PackageNamespace.PACKAGE_NAMESPACE, wovenClass.dynamicImportRequirements); dynamicImports.addAll( wovenClass.dynamicImportRequirements); final Class<?> ownClazz = defineClass(classname, bytes, 0, bytes.length, domain); wovenClass.setDefinedClass(ownClazz); wovenClass.setProtectionDomain( ownClazz.getProtectionDomain()); return ownClazz; } // define package definePackage(packageOf(classname)); return defineClass(classname, bytes, 0, bytes.length, domain); } catch (final IOException ioe) { ioe.printStackTrace(); return null; } catch (final LinkageError le) { if (framework.DEBUG_CLASSLOADING) { framework.logger .log(LogService.LOG_DEBUG, "Error during loading class=" + classname + " from bundle=" + this.getBundle() .getSymbolicName(), le); } throw le; } } if (fragments != null) { for (final Revision fragment : fragments) { for (int i = 0; i < classpath.length; i++) { final InputStream input = fragment .retrieveFile(classpath[i], filename); if (input == null) { continue; } try { int len; final ByteArrayOutputStream out = new ByteArrayOutputStream(); final BufferedInputStream bis = new BufferedInputStream( input); final byte[] chunk = new byte[Concierge.CLASSLOADER_BUFFER_SIZE]; while ((len = bis.read(chunk, 0, Concierge.CLASSLOADER_BUFFER_SIZE)) > 0) { out.write(chunk, 0, len); } return defineClass(classname, out.toByteArray(), 0, out.size(), ((AbstractBundle) fragment .getBundle()).domain); } catch (final IOException ioe) { ioe.printStackTrace(); return null; } catch (final LinkageError le) { if (framework.DEBUG_CLASSLOADING) { framework.logger.log( LogService.LOG_DEBUG, "Error during loading class=" + classname + " from bundle=" + this.getBundle() .getSymbolicName(), le); } throw le; } } } } } catch (final IOException e) { e.printStackTrace(); } return null; } /** * find a class from .dex embedded in the bundle when running on * Android */ private Object dexFile = null; private Class<?> findDexClass(final String classname) { try { if (dexFile == null) { final String fileName = storageLocation + BUNDLE_FILE_NAME + revId; dexFile = dexFileLoader.invoke(null, new Object[] { fileName, storageLocation + "classes.dex", new Integer(0) }); } if (dexFile != null) { return (Class<?>) dexClassLoader .invoke(dexFile, new Object[] { classname.replace('.', '/'), this }); } } catch (final Exception e) { return null; } return null; } List<String> listOwnResources(final String path, final String filePattern, final int options, final HashSet<String> visited) { final List<String> result = new ArrayList<String>(); for (int i = 0; i < classpath.length; i++) { final Vector<URL> urls = searchFiles(classpath[i], path, filePattern, (options & BundleWiring.LISTRESOURCES_RECURSE) != 0); if (urls != null) { for (final URL url : urls) { final int pos = url.getPath().lastIndexOf('/'); final String pkg = pseudoClassname( url.getPath().substring(1, pos)); if (!visited.contains(pkg)) { result.add(url.getPath().substring(1)); } } } } if (fragments != null) { for (final Revision fragment : fragments) { for (int i = 0; i < classpath.length; i++) { final Vector<URL> urls = fragment.searchFiles( classpath[i], path, filePattern, (options & BundleWiring.LISTRESOURCES_RECURSE) != 0); if (urls != null) { for (final URL url : urls) { final int pos = url.getPath() .lastIndexOf('/'); final String pkg = pseudoClassname( url.getPath().substring(1, pos)); if (!visited.contains(pkg)) { result.add(url.getPath().substring(1)); } } } } } } return result; } /** * find one or more resources in the scope of the own class loader. * * @param name * the name of the resource * @param multiple * if false, the search terminates if the first result * has been found. * @return a <code>Vector</code> of <code>URL</code> elements. */ Object findOwnResources(final String name, final boolean useFragments, final boolean multiple, final Vector<URL> resources) { if ("".equals(name)) { return resources; } final Vector<URL> results = resources == null ? new Vector<URL>() : resources; try { for (int i = 0; i < classpath.length; i++) { final URL url = lookupFile(classpath[i], name); if (url != null) { if (!multiple) { return url; } else { results.add(url); } } } if (useFragments && fragments != null) { // look in fragments for (final Revision fragment : fragments) { if (fragment == null) { throw new IllegalStateException( "REVISION IS NULL"); } for (int i = 0; i < classpath.length; i++) { final URL url = fragment .lookupFile(classpath[i], name); if (url != null) { if (!multiple) { return url; } else { results.add(url); } } } } } } catch (final IOException ioe) { ioe.printStackTrace(); } return results.isEmpty() ? resources : results; } /** * * @param pkg * @param name * @param isClass * @param multiple * @param resources * @param visited * @return */ @SuppressWarnings("unchecked") private Object requireBundleLookup(final String pkg, final String name, final boolean isClass, final boolean multiple, final Vector<URL> resources, final Set<Bundle> visited) throws ClassNotFoundException { if (visited.contains(BundleImpl.this)) { return null; } // depth-first: descent into re-exports visited.add(BundleImpl.this); if (requireBundleWires != null) { for (final BundleWire wire : requireBundleWires) { if (BundleNamespace.VISIBILITY_REEXPORT.equals(wire .getRequirement().getDirectives() .get(BundleNamespace.REQUIREMENT_VISIBILITY_DIRECTIVE))) { final Object result = ((Revision) wire .getProvider()).classloader .requireBundleLookup(pkg, name, isClass, multiple, resources, visited); if (!multiple && result != null) { return result; } else if (result != null) { resources.addAll((Vector<URL>) result); } } } } if (exportIndex.contains(pkg)) { // could be delegated when the export was imported as well, // so check packageImportWires first BundleClassLoader exportLoader; final BundleWire delegation = packageImportWires.get(pkg); if (delegation != null) { exportLoader = ((Revision) delegation .getProvider()).classloader; } else { exportLoader = this; } if (isClass) { return exportLoader.findOwnClass(name); } else { final Object result = exportLoader.findOwnResources( name, true, multiple, resources); if (!multiple) { return result; } else if (result != null) { resources.addAll((Vector<URL>) result); } } } // instead of inspecting exportIndex and imports, just delegate // findResources? // return findResource0(pkg, name, isClass, multiple); return null; } private void definePackage(final String pkg) { // TODO fill in version/spec/vendor attributes according to // bundle manifest headers? try { definePackage(pkg, null, null, null, null, null, null, null); } catch (final IllegalArgumentException e) { // ignore } } } class WovenClassImpl implements WovenClass { private final String clazzName; private byte[] bytes; private boolean weavingComplete; private Class<?> clazz; private List<String> dynamicImports; protected List<BundleRequirement> dynamicImportRequirements; private ProtectionDomain domain; WovenClassImpl(final String clazzName, final byte[] bytes, final Revision revision, final ProtectionDomain domain) { this.bytes = bytes; this.clazzName = clazzName; this.dynamicImportRequirements = new ArrayList<BundleRequirement>(); this.dynamicImports = new ArrayList<String>() { /** * */ private static final long serialVersionUID = 975783807443126126L; @Override public boolean add(final String dynImport) { checkDynamicImport(dynImport); return super.add(dynImport); } @Override public boolean addAll( final Collection<? extends String> c) { for (final String dynImport : c) { checkDynamicImport(dynImport); } return super.addAll(c); } private void checkDynamicImport(final String dynImport) throws IllegalArgumentException { try { final String[] literals = Utils .splitString(dynImport, ';'); if (literals[0].contains(";")) { throw new IllegalArgumentException(dynImport); } final ParseResult parseResult = Utils .parseLiterals(literals, 1); final HashMap<String, String> dirs = parseResult .getDirectives(); dirs.put(Namespace.REQUIREMENT_FILTER_DIRECTIVE, Utils.createFilter( PackageNamespace.PACKAGE_NAMESPACE, literals[0], parseResult.getLatter())); dirs.put(Namespace.REQUIREMENT_RESOLUTION_DIRECTIVE, PackageNamespace.RESOLUTION_DYNAMIC); dirs.put(Concierge.DIR_INTERNAL, literals[0]); final BundleRequirement req = new BundleRequirementImpl( revision, PackageNamespace.PACKAGE_NAMESPACE, dirs, null, Constants.DYNAMICIMPORT_PACKAGE + ' ' + dynImport); dynamicImportRequirements.add(req); } catch (final BundleException be) { throw new IllegalArgumentException( "Unvalid dynamic import " + dynImport); } } }; this.domain = domain; } public byte[] getBytes() { return bytes; } public void setBytes(final byte[] newBytes) { if (newBytes == null) { throw new NullPointerException("newBytes"); } if (weavingComplete) { throw new IllegalStateException("Weaving is complete"); } bytes = newBytes; } public List<String> getDynamicImports() { return dynamicImports; } public boolean isWeavingComplete() { return weavingComplete; } public String getClassName() { return clazzName; } public ProtectionDomain getProtectionDomain() { return domain; } public Class<?> getDefinedClass() { return clazz; } public BundleWiring getBundleWiring() { return getWiring(); } void setDefinedClass(final Class<?> clazz) { this.clazz = clazz; } void setProtectionDomain(final ProtectionDomain protectionDomain) { this.domain = protectionDomain; } void setComplete() { weavingComplete = true; bytes = bytes.clone(); dynamicImports = Collections.unmodifiableList(dynamicImports); } } void addHostedCapability(final HostedCapability hostedCap) { hostedCapabilities.add(hostedCap); } List<HostedCapability> getHostedCapabilities() { return hostedCapabilities; } public int compareTo(final Revision other) { final int ids = (int) (bundleId - other.getBundle().getBundleId()); return ids != 0 ? ids : revId - other.revId; } } class HeaderDictionary extends Hashtable<String, String> { private static final long serialVersionUID = 6688251578575649710L; // lazily initialized protected WeakHashMap<Locale, HeaderDictionary> headerCache; private final HashMap<String, String> index = new HashMap<String, String>(); private boolean hasLocalizedValues; public HeaderDictionary(final int size) { super(size); } HeaderDictionary localize(final Locale locale) { if (!hasLocalizedValues) { return this; } if (headerCache != null) { final HeaderDictionary cached = headerCache.get(locale); if (cached != null) { return cached; } } final Properties props = getLocalizationFile(locale, bundleLocalizationBaseDir, bundleLocalizationBaseFilename); final HeaderDictionary localized = (HeaderDictionary) clone(); final Enumeration<String> keys = localized.keys(); while (keys.hasMoreElements()) { final String key = keys.nextElement(); final String value = localized.get(key); if (value != null && value.charAt(0) == '%') { final String rawValue = value.substring(1).trim(); final String localizedValue = props == null ? null : (String) props.get(rawValue); localized.put(key, localizedValue == null ? rawValue : localizedValue); } } if (headerCache == null) { headerCache = new WeakHashMap<Locale, HeaderDictionary>(); headerCache.put(locale, localized); } return localized; } @Override public String put(final String key, final String value) { if (value.length() > 0 && value.charAt(0) == '%') { hasLocalizedValues = true; } index.put(key.toLowerCase(), key); return super.put(key, value); } @Override public String get(final Object key) { final String result = super.get(key); if (result != null) { return result; } final String indexedKey = index.get(((String) key).toLowerCase()); return indexedKey == null ? null : super.get(indexedKey); } } class JarBundleRevision extends Revision { private final JarFile jarFile; protected JarBundleRevision(final int revId, final JarFile jar, final Manifest manifest, final String[] classpathStrings) throws BundleException { super(revId, manifest, classpathStrings); this.jarFile = jar; } protected URL lookupFile(final String classpath, final String filename) throws IOException { return (URL) findFile(classpath, filename, GET_URL); } protected URL lookupFile(final String classpath, final String filename, final HashSet<String> visited) throws IOException { return (URL) findFile(classpath, filename, GET_URL); } public InputStream retrieveFile(final String classpath, final String filename) throws IOException { return (InputStream) findFile(classpath, filename, RETRIEVE_INPUT_STREAM); } public long retrieveFileLength(final String classpath, final String filename) throws IOException { final Object res = findFile(classpath, filename, GET_CONTENT_LENGTH); if (res == null) { if (framework.DEBUG_CLASSLOADING) { framework.logger.log(LogService.LOG_DEBUG, "Could not retrieveFileLength for filename=" + filename + " from bundle=" + this.toString()); } return -1; } else { return (Long) res; } } private Object findFile(final String classpath, String filename, final int mode) throws IOException { // strip trailing separator if (filename.charAt(0) == '/') { filename = filename.substring(1); } if (classpath == null || classpath.equals(".")) { final ZipEntry entry = jarFile.getEntry(filename); if (entry == null) { return null; } switch (mode) { case GET_URL: return createURL(entry.getName(), null); case RETRIEVE_INPUT_STREAM: return jarFile.getInputStream(entry); case GET_CONTENT_LENGTH: return entry.getSize(); } } else { final ZipEntry entry = jarFile.getEntry(classpath); if (entry == null) { return null; } final InputStream in = jarFile.getInputStream(entry); if (in == null) { // classpath is a directory final ZipEntry entry2 = jarFile .getEntry(classpath + "/" + filename); if (entry2 == null) { return null; } switch (mode) { case GET_URL: return createURL(entry2.getName(), null); case RETRIEVE_INPUT_STREAM: return jarFile.getInputStream(entry2); case GET_CONTENT_LENGTH: return entry2.getSize(); } } final JarInputStream embeddedJar = new JarInputStream( jarFile.getInputStream(entry)); JarEntry embeddedEntry; while ((embeddedEntry = embeddedJar .getNextJarEntry()) != null) { if (embeddedEntry.getName().equals(filename)) { switch (mode) { case GET_URL: return createURL(entry.getName(), embeddedEntry.getName()); case RETRIEVE_INPUT_STREAM: return embeddedJar; case GET_CONTENT_LENGTH: return embeddedEntry.getSize(); } } } } return null; } protected Vector<URL> searchFiles(final String classpath, final String path, final String filePattern, final boolean recurse) { final Vector<URL> results = new Vector<URL>(); String pathString = path.length() > 0 && path.charAt(0) == '/' ? path.substring(1) : path; pathString = path.length() == 0 || path.charAt(path.length() - 1) == '/' ? pathString : pathString + "/"; final int cpOffset; final String comp = pathString; if (classpath != null && !".".equals(classpath)) { pathString = classpath + "/" + pathString; cpOffset = classpath.length() + 1; } else { cpOffset = 0; } final Enumeration<JarEntry> enums = jarFile.entries(); while (enums.hasMoreElements()) { final JarEntry ze = enums.nextElement(); final String name; if (cpOffset == 0 || cpOffset >= ze.getName().length()) { name = ze.getName().replace('\\', '/'); } else if (ze.getName().length() < cpOffset) { continue; } else { name = ze.getName().substring(cpOffset).replace('\\', '/'); } if (name.startsWith(comp)) { final String rest = name.substring(comp.length(), name.length()); if (rest.length() > 0) { final File file = new File(rest); if (file.getParent() != null & !recurse) { continue; } if (filePattern == null || RFC1960Filter.stringCompare( filePattern.toCharArray(), 0, file.getName().toCharArray(), 0) == 0) { try { results.add(createURL( name.charAt(0) == '/' ? name.substring(1) : name, null)); } catch (final IOException ex) { // do nothing, URL will not be added to // results } } } } } return results; } protected void close() throws IOException { jarFile.close(); } public String toString() { return "JarBundleResource {" + jarFile.getName() + " of bundle " + BundleImpl.this.toString() + "}"; } } class ExplodedJarBundleRevision extends Revision { private final String storageLocation; ExplodedJarBundleRevision(final int revId, final String location, final Manifest manifest, final String[] classpathStrings) throws BundleException { super(revId, manifest, classpathStrings); this.storageLocation = location; } protected URL lookupFile(final String classpath, final String filename, final HashSet<String> visited) throws IOException { return (URL) findFile(classpath, filename, 0); } @Override protected InputStream retrieveFile(final String classpath, final String filename) throws IOException { return (InputStream) findFile(classpath, filename, 1); } @Override protected long retrieveFileLength(final String classpath, final String filename) throws IOException { return (Long) findFile(classpath, filename, 2); } @Override protected URL lookupFile(final String classpath, final String filename) throws IOException { return (URL) findFile(classpath, filename, 0); } private Object findFile(final String classpath, String filename, final int mode) throws IOException { // strip trailing separator if (filename.charAt(0) == '/') { filename = filename.substring(1); } if (classpath == null || classpath.equals(".")) { final File file = new File(storageLocation, filename); try { if (file.exists()) { switch (mode) { case GET_URL: return createURL(filename, null); case RETRIEVE_INPUT_STREAM: return new FileInputStream(file); case GET_CONTENT_LENGTH: return file.length(); } } else { return null; } } catch (final FileNotFoundException ex) { return null; } } else { final File file = new File(storageLocation, classpath); if (file.exists()) { if (!file.isDirectory()) { // TODO check when security check must be done final ZipFile jar = new ZipFile(file); try { final ZipEntry entry = jar.getEntry(filename); if (entry == null) { return null; } switch (mode) { case GET_URL: return createURL(classpath, filename); case RETRIEVE_INPUT_STREAM: return jar.getInputStream(entry); case GET_CONTENT_LENGTH: return entry.getSize(); } } finally { if (mode != RETRIEVE_INPUT_STREAM) { jar.close(); } } } else { // file is a directory try { final File source = new File(file, filename); if (file.exists()) { switch (mode) { case GET_URL: return createURL(filename, null); case RETRIEVE_INPUT_STREAM: return new FileInputStream(source); case GET_CONTENT_LENGTH: return source.length(); } } else { return null; } } catch (final FileNotFoundException e) { return null; } } } } return null; } // TODO: fix and use classpath... public Vector<URL> searchFiles(final String classpath, final String path, final String filePattern, final boolean recurse) { final Vector<URL> result = new Vector<URL>(); String pathString = path; if (pathString.length() > 0) { if (pathString.charAt(0) == '/') { pathString = pathString.substring(0); } if (pathString.charAt(pathString.length() - 1) != '/') { pathString = pathString + '/'; } } testFiles(new File(storageLocation, pathString), result, recurse, filePattern); return result; } private void testFiles(final File directory, final Vector<URL> results, final boolean recurse, final String filePattern) { if (directory.isDirectory()) { final File[] files = directory.listFiles(); for (int i = 0; i < files.length; i++) { final File toTest = files[i]; if (framework.DEBUG_CLASSLOADING) { framework.logger.log(LogService.LOG_DEBUG, "testing " + toTest.getAbsolutePath() + (toTest.isDirectory() ? "/" : "")); } // get basename final String basename = toTest.getName(); if (filePattern == null || RFC1960Filter.stringCompare( filePattern.toCharArray(), 0, basename.toCharArray(), 0) == 0) { try { final String absPath = toTest.getAbsolutePath(); results.add(createURL(absPath .substring(absPath.indexOf(storageLocation) + storageLocation.length() + 1) // TODO File.separatorChar instead of "/" ? + (toTest.isDirectory() ? "/" : ""), null)); } catch (final IOException ex) { // do nothing, URL will not be added to // results } } if (recurse && toTest.isDirectory()) { testFiles(toTest, results, recurse, filePattern); } } } } protected void close() throws IOException { // nop } } /* * static methods */ /** * get the package of a class. * * @param classname * @return the package. */ static String packageOf(final String classname) { final int pos = classname.lastIndexOf('.'); return pos > -1 ? classname.substring(0, pos) : ""; } protected static String classOf(final String classname) { final int pos = classname.lastIndexOf('.'); return pos > -1 ? classname.substring(pos + 1, classname.length()) : classname; } /** * create a pseudo classname from a file. * * @param filename * the filename. * @return the pseudo classname. */ protected static String pseudoClassname(final String filename) { return filename.replace('.', '-').replace('/', '.').replace('\\', '.'); } protected static String stripTrailing(final String filename) { return filename.startsWith("/") || filename.startsWith("\\") ? filename.substring(1) : filename; } /** * get a file from a class name. * * @param fqc * the fully qualified class name. * @return the file name. */ protected static String classToFile(final String fqc) { return fqc.replace('.', '/') + ".class"; } /** * store a file on the storage. * * @param file * the file. * @param input * the input stream. */ static void storeFile(final File file, final InputStream input) { try { file.getParentFile().mkdirs(); final FileOutputStream fos = new FileOutputStream(file); final byte[] buffer = new byte[Concierge.CLASSLOADER_BUFFER_SIZE]; int read; while ((read = input.read(buffer, 0, Concierge.CLASSLOADER_BUFFER_SIZE)) > -1) { fos.write(buffer, 0, read); } input.close(); fos.close(); } catch (final IOException ioe) { ioe.printStackTrace(); } } }