blob: fe83979a7486850304ad95615424232da0c1123d [file] [log] [blame]
/*******************************************************************************
* 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.
* <p>
* Clients may extend this class.
* </p>
* @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 <code>null</code> 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);
}
}
}