/******************************************************************************* * Copyright (c) 2004, 2005 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.osgi.framework.adaptor.core; import java.io.*; import java.net.MalformedURLException; import java.net.URL; import java.security.*; import java.security.cert.Certificate; import java.util.*; import org.eclipse.osgi.framework.adaptor.ClassLoaderDelegate; import org.eclipse.osgi.framework.debug.Debug; import org.eclipse.osgi.util.NLS; import org.osgi.framework.BundleException; import org.osgi.framework.FrameworkEvent; /** * A concrete implementation of BundleClassLoader. This implementation * consolidates all Bundle-ClassPath entries into a single ClassLoader. *

* Clients may extend this class. *

* @since 3.1 */ public class DefaultClassLoader extends AbstractClassLoader { /** * A PermissionCollection for AllPermissions; shared across all ProtectionDomains when security is disabled */ static final PermissionCollection ALLPERMISSIONS; static { AllPermission allPerm = new AllPermission(); ALLPERMISSIONS = allPerm.newPermissionCollection(); if (ALLPERMISSIONS != null) ALLPERMISSIONS.add(allPerm); } /** * The BundleData object for this BundleClassLoader */ protected AbstractBundleData hostdata; /** * The ClasspathEntries for this BundleClassLoader. Each ClasspathEntry object * represents on Bundle-ClassPath entry. */ protected ClasspathEntry[] classpathEntries; /** * A list of fragment classpaths for this classloader */ protected Vector fragClasspaths; //TODO This should be an array or an arraylist if the synchronization is not required /** * The buffer size to use when loading classes. This value is used * only if we cannot determine the size of the class we are loading. */ protected int buffersize = 8 * 1024; //TODO Could not that be a constant? /** * BundleClassLoader constructor. * @param delegate The ClassLoaderDelegate for this ClassLoader. * @param domain The ProtectionDomain for this ClassLoader. * @param classpath An array of Bundle-ClassPath entries to * use for loading classes and resources. This is specified by the * Bundle-ClassPath manifest entry. * @param parent The parent ClassLoader. * @param bundledata The BundleData for this ClassLoader */ public DefaultClassLoader(ClassLoaderDelegate delegate, ProtectionDomain domain, String[] classpath, ClassLoader parent, AbstractBundleData bundledata) { super(delegate, domain, classpath, parent); this.hostdata = bundledata; try { hostdata.open(); /* make sure the BundleData is open */ } catch (IOException e) { hostdata.getAdaptor().getEventPublisher().publishFrameworkEvent(FrameworkEvent.ERROR, hostdata.getBundle(), e); } } /** * @see org.eclipse.osgi.framework.adaptor.BundleClassLoader#initialize() */ public void initialize() { classpathEntries = buildClasspath(hostclasspath, hostdata, hostdomain); } /** * @see org.eclipse.osgi.framework.adaptor.BundleClassLoader#attachFragment(BundleData, ProtectionDomain, String[]) */ public void attachFragment(org.eclipse.osgi.framework.adaptor.BundleData bundledata, ProtectionDomain domain, String[] classpath) { AbstractBundleData abstractbundledata = (AbstractBundleData) bundledata; try { bundledata.open(); /* make sure the BundleData is open */ } catch (IOException e) { abstractbundledata.getAdaptor().getEventPublisher().publishFrameworkEvent(FrameworkEvent.ERROR, abstractbundledata.getBundle(), e); } ClasspathEntry[] fragEntries = buildClasspath(classpath, abstractbundledata, domain); FragmentClasspath fragClasspath = new FragmentClasspath(fragEntries, abstractbundledata, domain); insertFragment(fragClasspath); } /** * Inserts a fragment classpath to into the list of fragments for this host. * Fragments are inserted into the list according to the fragment's * Bundle ID. * @param fragClasspath The FragmentClasspath to insert. */ protected synchronized void insertFragment(FragmentClasspath fragClasspath) { if (fragClasspaths == null) { // First fragment to attach. Simply create the list and add the fragment. fragClasspaths = new Vector(10); fragClasspaths.addElement(fragClasspath); return; } // Find a place in the fragment list to insert this fragment. int size = fragClasspaths.size(); long fragID = fragClasspath.bundledata.getBundleID(); for (int i = 0; i < size; i++) { long otherID = ((FragmentClasspath) fragClasspaths.elementAt(i)).bundledata.getBundleID(); if (fragID < otherID) { fragClasspaths.insertElementAt(fragClasspath, i); return; } } // This fragment has the highest ID; put it at the end of the list. fragClasspaths.addElement(fragClasspath); } /** * Returns a string of the symbolic name and version * @return a string of the symbolic name and version */ protected String getBundleSymbolicName() { return hostdata.getSymbolicName() + "_" + hostdata.getVersion(); //$NON-NLS-1$ } /** * Returns the host BundleData for this classloader * @return the host BundleData for this classloader */ public AbstractBundleData getHostData() { return hostdata; } /** * Returns a list of FragmentClasspath objects for the currently attached fragments * @return a list of FragmentClasspath objects for the currently attached fragments */ public FragmentClasspath[] getFragClasspaths() { if (fragClasspaths == null) return null; return (FragmentClasspath[]) fragClasspaths.toArray(new FragmentClasspath[fragClasspaths.size()]); } /** * Gets a ClasspathEntry object for the specified ClassPath entry. * @param cp The ClassPath entry to get the ClasspathEntry for. * @param bundledata The BundleData that the ClassPath entry is for. * @param domain The ProtectionDomain for the ClassPath entry. * @return The ClasspathEntry object for the ClassPath entry. */ protected ClasspathEntry getClasspath(String cp, AbstractBundleData bundledata, ProtectionDomain domain) { BundleFile bundlefile = null; File file; // check for internal library jars if ((file = bundledata.getBaseBundleFile().getFile(cp)) != null) bundlefile = createBundleFile(file, bundledata); // check for intenral library directories in a bundle jar file if (bundlefile == null && bundledata.getBaseBundleFile().containsDir(cp)) bundlefile = new BundleFile.NestedDirBundleFile(bundledata.getBaseBundleFile(), cp); // if in dev mode, try using the cp as an absolute path if (bundlefile == null && DevClassPathHelper.inDevelopmentMode()) return getExternalClassPath(cp, bundledata, domain); if (bundlefile != null) return createClassPathEntry(bundlefile, domain); return null; } /** * Gets a ClasspathEntry object for the specified classpath entry which is external to the * bundledata. * @param cp The ClassPath entry to get the ClasspathEntry for. * @param bundledata The BundleData that the ClassPath entry is for. * @param domain The ProtectionDomain for the ClassPath entry. * @return The ClasspathEntry object for the ClassPath entry. */ protected ClasspathEntry getExternalClassPath(String cp, AbstractBundleData bundledata, ProtectionDomain domain) { File file = new File(cp); if (!file.isAbsolute()) return null; BundleFile bundlefile = createBundleFile(file, bundledata); if (bundlefile != null) return createClassPathEntry(bundlefile, domain); return null; } /** * Creates a BundleFile object for a classpath entry * @param file the file object used to create a BundleFile * @param bundledata the bundle data * @return a BundleFile object for a classpath entry */ protected BundleFile createBundleFile(File file, AbstractBundleData bundledata) { if (file == null || !file.exists()) return null; try { return hostdata.getAdaptor().createBundleFile(file, bundledata); } catch (IOException e) { bundledata.getAdaptor().getEventPublisher().publishFrameworkEvent(FrameworkEvent.ERROR, bundledata.getBundle(), e); } return null; } /** * @see ClassLoader#findClass(java.lang.String) */ protected synchronized Class findClass(String name) throws ClassNotFoundException { // must call findLoadedClass here even if it was called earlier, // the findLoadedClass and defineClass calls must be atomic Class result = findLoadedClass(name); if (result != null) return result; for (int i = 0; i < classpathEntries.length; i++) { if (classpathEntries[i] != null) { result = findClassImpl(name, classpathEntries[i]); if (result != null) return result; } } // look in fragments. if (fragClasspaths != null) { int size = fragClasspaths.size(); for (int i = 0; i < size; i++) { FragmentClasspath fragCP = (FragmentClasspath) fragClasspaths.elementAt(i); for (int j = 0; j < fragCP.classpathEntries.length; j++) { result = findClassImpl(name, fragCP.classpathEntries[j]); if (result != null) return result; } } } throw new ClassNotFoundException(name); } /** * Finds a class in the BundleFile. If a class is found then the class * is defined using the ProtectionDomain bundledomain. * @param name The name of the class to find. * @param classpathEntry The ClasspathEntry to find the class in. * @return The loaded class object or null if the class is not found. */ protected Class findClassImpl(String name, ClasspathEntry classpathEntry) { if (Debug.DEBUG && Debug.DEBUG_LOADER) { Debug.println("BundleClassLoader[" + hostdata + "].findClass(" + name + ")"); //$NON-NLS-1$ //$NON-NLS-2$//$NON-NLS-3$ } String filename = name.replace('.', '/').concat(".class"); //$NON-NLS-1$ BundleEntry entry = classpathEntry.getBundleFile().getEntry(filename); if (entry == null) { return null; } InputStream in; try { in = entry.getInputStream(); } catch (IOException e) { return null; } int length = (int) entry.getSize(); byte[] classbytes; int bytesread = 0; int readcount; if (Debug.DEBUG && Debug.DEBUG_LOADER) { Debug.println(" about to read " + length + " bytes from " + filename); //$NON-NLS-1$ //$NON-NLS-2$ } try { try { if (length > 0) { classbytes = new byte[length]; readloop: for (; bytesread < length; bytesread += readcount) { readcount = in.read(classbytes, bytesread, length - bytesread); if (readcount <= 0) /* if we didn't read anything */{ break readloop; /* leave the loop */ } } } else /* BundleEntry does not know its own length! */{ length = buffersize; classbytes = new byte[length]; readloop: while (true) { for (; bytesread < length; bytesread += readcount) { readcount = in.read(classbytes, bytesread, length - bytesread); if (readcount <= 0) /* if we didn't read anything */{ break readloop; /* leave the loop */ } } byte[] oldbytes = classbytes; length += buffersize; classbytes = new byte[length]; System.arraycopy(oldbytes, 0, classbytes, 0, bytesread); } } } catch (IOException e) { if (Debug.DEBUG && Debug.DEBUG_LOADER) { Debug.println(" IOException reading " + filename + " from " + hostdata); //$NON-NLS-1$ //$NON-NLS-2$ } return null; } } finally { try { in.close(); } catch (IOException ee) { // nothing to do here } } if (Debug.DEBUG && Debug.DEBUG_LOADER) { Debug.println(" read " + bytesread + " bytes from " + filename); //$NON-NLS-1$ //$NON-NLS-2$ Debug.println(" defining class " + name); //$NON-NLS-1$ } try { return (defineClass(name, classbytes, 0, bytesread, classpathEntry)); } catch (Error e) { if (Debug.DEBUG && Debug.DEBUG_LOADER) { Debug.println(" error defining class " + name); //$NON-NLS-1$ } throw e; } } /** * Defines a class for this classloader * @param name the name of the class * @param classbytes the class bytes * @param off the offset in the class bytes array * @param len the legth of the class bytes * @param classpathEntry the classpath entry used for the class * @return a loaded Class object * @throws ClassFormatError if the class has a format error */ protected Class defineClass(String name, byte[] classbytes, int off, int len, ClasspathEntry classpathEntry) throws ClassFormatError { if (name != null && name.startsWith("java.")) { //$NON-NLS-1$ // To work around the security issue that prevents any // other classloader except for the bootstrap classloader // from loading packages that start with java. name = null; } return defineClass(name, classbytes, off, len, classpathEntry.getProtectionDomain()); } /** * @see ClassLoader#findResource(java.lang.String) */ protected URL findResource(String name) { URL result = null; for (int i = 0; i < classpathEntries.length; i++) { if (classpathEntries[i] != null) { result = findResourceImpl(name, classpathEntries[i].getBundleFile()); if (result != null) return result; } } // look in fragments if (fragClasspaths != null) { int size = fragClasspaths.size(); for (int i = 0; i < size; i++) { FragmentClasspath fragCP = (FragmentClasspath) fragClasspaths.elementAt(i); for (int j = 0; j < fragCP.classpathEntries.length; j++) { result = findResourceImpl(name, fragCP.classpathEntries[j].getBundleFile()); if (result != null) return result; } } } return null; } /** * Looks in the specified BundleFile for the resource. * @param name The name of the resource to find. * @param bundlefile The BundleFile to look in. * @return A URL to the resource or null if the resource does not exist. */ protected URL findResourceImpl(String name, BundleFile bundlefile) { return findResourceImpl(name, bundlefile, 0); } /** * Looks in the specified BundleFile for the resource. * @param name The name of the resource to find. * @param bundlefile The BundleFile to look in. * @param index the index of the resource. * @return A URL to the resource or null if the resource does not exist. */ protected URL findResourceImpl(String name, BundleFile bundlefile, int index) { return bundlefile.getResourceURL(name, hostdata.getBundleID(), index); } /** * @see org.eclipse.osgi.framework.adaptor.BundleClassLoader#findLocalResources(String) */ public Enumeration findLocalResources(String resource) { Vector resources = new Vector(6); // use a Vector instead of ArrayList because we need an enumeration for (int i = 0; i < classpathEntries.length; i++) { if (classpathEntries[i] != null) { URL url = findResourceImpl(resource, classpathEntries[i].getBundleFile(), resources.size()); if (url != null) resources.addElement(url); } } // look in fragments if (fragClasspaths != null) { int size = fragClasspaths.size(); for (int i = 0; i < size; i++) { FragmentClasspath fragCP = (FragmentClasspath) fragClasspaths.elementAt(i); for (int j = 0; j < fragCP.classpathEntries.length; j++) { URL url = findResourceImpl(resource, fragCP.classpathEntries[j].getBundleFile(), resources.size()); if (url != null) resources.addElement(url); } } } if (resources.size() > 0) return resources.elements(); return null; } /** * @see AbstractClassLoader#findLocalObject(String) */ public Object findLocalObject(String object) { BundleEntry result = null; for (int i = 0; i < classpathEntries.length; i++) { if (classpathEntries[i] != null) { result = findObjectImpl(object, classpathEntries[i].getBundleFile()); if (result != null) { return result; } } } // look in fragments if (fragClasspaths != null) { int size = fragClasspaths.size(); for (int i = 0; i < size; i++) { FragmentClasspath fragCP = (FragmentClasspath) fragClasspaths.elementAt(i); for (int j = 0; j < fragCP.classpathEntries.length; j++) { result = findObjectImpl(object, fragCP.classpathEntries[j].getBundleFile()); if (result != null) { return result; } } } } return null; } /** * @see AbstractClassLoader#findLocalObjects(String) */ public Enumeration findLocalObjects(String object) { Vector objects = new Vector(6); // use a Vector instead of ArrayList because we need an enumeration for (int i = 0; i < classpathEntries.length; i++) { if (classpathEntries[i] != null) { Object result = findObjectImpl(object, classpathEntries[i].getBundleFile()); if (result != null) objects.addElement(result); } } // look in fragments if (fragClasspaths != null) { int size = fragClasspaths.size(); for (int i = 0; i < size; i++) { FragmentClasspath fragCP = (FragmentClasspath) fragClasspaths.elementAt(i); for (int j = 0; j < fragCP.classpathEntries.length; j++) { Object result = findObjectImpl(object, fragCP.classpathEntries[j].getBundleFile()); if (result != null) objects.addElement(result); } } } if (objects.size() > 0) return objects.elements(); return null; } /** * Looks in the specified BundleFile for the entry. * @param object The name of the entry to find. * @param bundleFile The BundleFile to look in. * @return a bundle entry for the specified entry or null if the * entry does not exist */ protected BundleEntry findObjectImpl(String object, BundleFile bundleFile) { return bundleFile.getEntry(object); } /** * @see org.eclipse.osgi.framework.adaptor.BundleClassLoader#close() */ public void close() { super.close(); if (classpathEntries != null) { for (int i = 0; i < classpathEntries.length; i++) { if (classpathEntries[i] != null) { try { classpathEntries[i].getBundleFile().close(); } catch (IOException e) { hostdata.getAdaptor().getEventPublisher().publishFrameworkEvent(FrameworkEvent.ERROR, hostdata.getBundle(), e); } } } } if (fragClasspaths != null) { int size = fragClasspaths.size(); for (int i = 0; i < size; i++) { FragmentClasspath fragCP = (FragmentClasspath) fragClasspaths.elementAt(i); fragCP.close(); } } } /** * Builds the classpath entry objects for this classloader * @param classpath a list of classpath entries to build * @param bundledata the bundle data * @param domain the ProtectionDomain for the classpath entry objects * @return */ protected ClasspathEntry[] buildClasspath(String[] classpath, AbstractBundleData bundledata, ProtectionDomain domain) { ArrayList result = new ArrayList(classpath.length); // if in dev mode add the dev entries addDefaultDevEntries(result, bundledata, domain); // add the regular classpath entries. for (int i = 0; i < classpath.length; i++) findClassPathEntry(result, classpath[i], bundledata, domain); return (ClasspathEntry[]) result.toArray(new ClasspathEntry[result.size()]); } /** * Adds the default development classpath entries * @param result a list of current classpath entries. This list is modified by this method to add * a new classpath entry. * @param bundledata the bundle data * @param domain the ProtectionDomain for the classpath entry */ protected void addDefaultDevEntries(ArrayList result, AbstractBundleData bundledata, ProtectionDomain domain) { String[] devClassPath = !DevClassPathHelper.inDevelopmentMode() ? null : DevClassPathHelper.getDevClassPath(bundledata.getSymbolicName()); if (devClassPath == null) return; // not in dev mode return for (int i = 0; i < devClassPath.length; i++) findClassPathEntry(result, devClassPath[i], bundledata, domain); } /** * Finds a classpath entry for this classloader * @param result a list of current classpath entries. This list is modified by this method to add * a new classpath entry. * @param entry the path to the entry to find * @param bundledata the bundle data * @param domain the ProtectionDomain for the classpath entry */ protected void findClassPathEntry(ArrayList result, String entry, AbstractBundleData bundledata, ProtectionDomain domain) { if (!addClassPathEntry(result, entry, bundledata, domain)) { String[] devCP = !DevClassPathHelper.inDevelopmentMode() ? null : DevClassPathHelper.getDevClassPath(bundledata.getSymbolicName()); if (devCP == null || devCP.length == 0) { BundleException be = new BundleException(NLS.bind(AdaptorMsg.BUNDLE_CLASSPATH_ENTRY_NOT_FOUND_EXCEPTION, entry, bundledata.getLocation())); bundledata.getAdaptor().getEventPublisher().publishFrameworkEvent(FrameworkEvent.INFO, bundledata.getBundle(), be); } } } /** * Adds a classpath entry to this classloader * @param result a list of current classpath entries. This list is modified by this method to add * a new classpath entry. * @param entry the path to the entry to add * @param bundledata the bundle data * @param domain the ProtectionDomain for the classpath entry * @return true if a classpath entry was added to the result; false if the classpath entry could * not be found */ protected boolean addClassPathEntry(ArrayList result, String entry, AbstractBundleData bundledata, ProtectionDomain domain) { if (entry.equals(".")) { //$NON-NLS-1$ result.add(createClassPathEntry(bundledata.getBaseBundleFile(), domain)); return true; } Object element = getClasspath(entry, bundledata, domain); if (element != null) { result.add(element); return true; } // need to check in fragments for the classpath entry. // only check for fragments if the bundledata is the hostdata. if (fragClasspaths != null && hostdata == bundledata) { int size = fragClasspaths.size(); for (int i = 0; i < size; i++) { FragmentClasspath fragCP = (FragmentClasspath) fragClasspaths.elementAt(i); element = getClasspath(entry, fragCP.bundledata, fragCP.domain); if (element != null) { result.add(element); return true; } } } return false; } /** * Creates a ClasspathEntry from a BundleFile and ProtectionDomain. * @param bundlefile the BundleFile. * @param domain the ProtectionDomain * @return the ClasspathEntry */ protected ClasspathEntry createClassPathEntry(BundleFile bundlefile, ProtectionDomain domain) { return new ClasspathEntry(bundlefile, domain); } /** * A data structure to hold information about a fragment classpath. */ protected class FragmentClasspath { /** The ClasspathEntries of the fragments Bundle-Classpath */ protected ClasspathEntry[] classpathEntries; /** The BundleData of the fragment */ protected AbstractBundleData bundledata; /** The ProtectionDomain of the fragment */ protected ProtectionDomain domain; protected FragmentClasspath(ClasspathEntry[] classpathEntries, AbstractBundleData bundledata, ProtectionDomain domain) { this.classpathEntries = classpathEntries; this.bundledata = bundledata; this.domain = domain; } protected void close() { for (int i = 0; i < classpathEntries.length; i++) { try { classpathEntries[i].getBundleFile().close(); } catch (IOException e) { bundledata.getAdaptor().getEventPublisher().publishFrameworkEvent(FrameworkEvent.ERROR, bundledata.getBundle(), e); } } } public AbstractBundleData getBundleData() { return bundledata; } } /** * A data structure to hold information about a classpath entry. */ protected class ClasspathEntry { protected BundleFile bundlefile; protected ProtectionDomain domain; protected ClasspathEntry(BundleFile bundlefile, ProtectionDomain domain) { this.bundlefile = bundlefile; this.domain = createProtectionDomain(domain); } public BundleFile getBundleFile() { return bundlefile; } public ProtectionDomain getProtectionDomain() { return domain; } /* * Creates a ProtectionDomain using the permissions of the specified baseDomain */ protected ProtectionDomain createProtectionDomain(ProtectionDomain baseDomain) { // create a protection domain which knows about the codesource for this classpath entry (bug 89904) try { // use the permissions supplied by the domain passed in from the framework PermissionCollection permissions; if (baseDomain != null) permissions = baseDomain.getPermissions(); else // no domain specified. Better use a collection that has all permissions // this is done just incase someone sets the security manager later permissions = ALLPERMISSIONS; return new ClasspathDomain(bundlefile.getBaseFile().toURL(), permissions); } catch (MalformedURLException e) { // Failed to create our own domain; just return the baseDomain return baseDomain; } } } /* * Very simple protection domain that uses a URL to create a CodeSource for a ProtectionDomain */ protected class ClasspathDomain extends ProtectionDomain { public ClasspathDomain(URL codeLocation, PermissionCollection permissions) { super(new CodeSource(codeLocation, (Certificate[]) null), permissions); } } }