| /******************************************************************************* |
| * Copyright (c) 2003 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Common Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/cpl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| |
| package org.eclipse.osgi.framework.internal.defaultadaptor; |
| |
| import java.io.*; |
| import java.net.URL; |
| import java.security.ProtectionDomain; |
| import java.util.*; |
| import org.eclipse.osgi.framework.adaptor.ClassLoaderDelegate; |
| import org.eclipse.osgi.framework.debug.Debug; |
| import org.eclipse.osgi.framework.internal.core.Msg; |
| 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. |
| */ |
| public class DefaultClassLoader |
| extends org.eclipse.osgi.framework.adaptor.BundleClassLoader { |
| |
| /** |
| * The BundleData object for this BundleClassLoader |
| */ |
| protected DefaultBundleData hostdata; |
| |
| /** |
| * The ClasspathEntries for this BundleClassLoader. Each ClasspathEntry object |
| * represents on Bundle-ClassPath entry. |
| */ |
| protected ClasspathEntry[] classpathEntries; |
| |
| protected Vector fragClasspaths; |
| |
| /** |
| * 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; |
| |
| /** |
| * BundleClassLoader constructor. |
| * @param delegate The ClassLoaderDelegate for this ClassLoader. |
| * @param domain The ProtectionDomain for this ClassLoader. |
| * @param bundleclasspath An array of Bundle-ClassPath entries to |
| * use for loading classes and resources. This is specified by the |
| * Bundle-ClassPath manifest entry. |
| * @param bundledata The BundleData for this ClassLoader |
| */ |
| public DefaultClassLoader(ClassLoaderDelegate delegate, ProtectionDomain domain, String[] classpath, DefaultBundleData bundledata) |
| { |
| this(delegate,domain,classpath,null,bundledata); |
| } |
| |
| /** |
| * BundleClassLoader constructor. |
| * @param delegate The ClassLoaderDelegate for this ClassLoader. |
| * @param domain The ProtectionDomain for this ClassLoader. |
| * @param bundleclasspath 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, DefaultBundleData bundledata) |
| { |
| super(delegate,domain,classpath,parent); |
| this.hostdata = bundledata; |
| |
| try |
| { |
| hostdata.open(); /* make sure the BundleData is open */ |
| } catch (IOException e) |
| { |
| hostdata.adaptor.getEventPublisher().publishFrameworkEvent( |
| FrameworkEvent.ERROR,hostdata.getBundle(),e); |
| } |
| } |
| |
| public void initialize() { |
| classpathEntries = buildClasspath(hostclasspath, hostdata, hostdomain); |
| } |
| |
| /** |
| * Attaches the BundleData for a fragment to this BundleClassLoader. |
| * The Fragment BundleData resources must be appended to the end of |
| * this BundleClassLoader's classpath. Fragment BundleData resources |
| * must be searched ordered by Bundle ID's. |
| * @param bundledata The BundleData of the fragment. |
| * @param domain The ProtectionDomain of the resources of the fragment. |
| * Any classes loaded from the fragment's BundleData must belong to this |
| * ProtectionDomain. |
| * @param classpath An array of Bundle-ClassPath entries to |
| * use for loading classes and resources. This is specified by the |
| * Bundle-ClassPath manifest entry of the fragment. |
| */ |
| public void attachFragment(org.eclipse.osgi.framework.adaptor.BundleData bundledata,ProtectionDomain domain,String[] classpath) { |
| DefaultBundleData defaultBundledata = (DefaultBundleData) bundledata; |
| try |
| { |
| bundledata.open(); /* make sure the BundleData is open */ |
| } catch (IOException e) |
| { |
| |
| defaultBundledata.adaptor.getEventPublisher().publishFrameworkEvent( |
| FrameworkEvent.ERROR,defaultBundledata.getBundle(),e); |
| } |
| ClasspathEntry[] fragEntries = buildClasspath(classpath,defaultBundledata,domain); |
| FragmentClasspath fragClasspath = new FragmentClasspath(fragEntries,defaultBundledata,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.id; |
| for (int i=0; i<size; i++) { |
| long otherID = ((FragmentClasspath)fragClasspaths.elementAt(i)).bundledata.id; |
| 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); |
| } |
| |
| /** |
| * 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, DefaultBundleData bundledata, ProtectionDomain domain){ |
| BundleFile bundlefile = null; |
| File file = bundledata.bundleFile.getFile(cp); |
| if (file != null && file.exists()) { |
| try |
| { |
| bundlefile = BundleFile.createBundleFile(file, bundledata); |
| } |
| catch (IOException e) |
| { |
| bundledata.adaptor.getEventPublisher().publishFrameworkEvent( |
| FrameworkEvent.ERROR,bundledata.getBundle(),e); |
| } |
| } |
| else { |
| if (bundledata.bundleFile instanceof BundleFile.ZipBundleFile) |
| { |
| // the classpath entry may be a directory in the bundle jar file. |
| Enumeration entries = bundledata.bundleFile.getEntryPaths(cp); |
| if (entries.hasMoreElements()) |
| { |
| bundlefile = BundleFile.createBundleFile( |
| (BundleFile.ZipBundleFile)bundledata.bundleFile, cp); |
| } |
| } |
| } |
| if (bundlefile != null) |
| return new ClasspathEntry(bundlefile,domain); |
| else |
| return null; |
| } |
| |
| protected synchronized Class findClass(String name) throws ClassNotFoundException { |
| 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].bundlefile,classpathEntries[i].domain); |
| 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].bundlefile,fragCP.classpathEntries[j].domain); |
| 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 bundleFile The BundleFile to find the class in. |
| * @param bundledomain The ProtectionDomain to use to defind the class if |
| * it is found. |
| * @return The loaded class object or null if the class is not found. |
| */ |
| protected Class findClassImpl(String name,BundleFile bundleFile,ProtectionDomain bundledomain){ |
| if (Debug.DEBUG && Debug.DEBUG_LOADER) |
| { |
| Debug.println("BundleClassLoader["+hostdata+"].findClass("+name+")"); |
| } |
| |
| String filename = name.replace('.', '/').concat(".class"); |
| |
| BundleEntry entry = bundleFile.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); |
| } |
| |
| 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); |
| } |
| |
| return null; |
| } |
| } |
| finally |
| { |
| try |
| { |
| in.close(); |
| } |
| catch (IOException ee) |
| { |
| } |
| } |
| |
| if (Debug.DEBUG && Debug.DEBUG_LOADER) |
| { |
| Debug.println(" read "+bytesread+" bytes from "+filename); |
| Debug.println(" defining class "+name); |
| } |
| |
| try |
| { |
| return(defineClass(name, classbytes, 0, bytesread, bundledomain)); |
| } |
| catch (Error e) |
| { |
| if (Debug.DEBUG && Debug.DEBUG_LOADER) |
| { |
| Debug.println(" error defining class "+name); |
| } |
| |
| throw e; |
| } |
| } |
| |
| /** |
| * @see org.eclipse.osgi.framework.adaptor.BundleClassLoader#findResource(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].bundlefile,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 = findResourceImpl(name,fragCP.classpathEntries[j].bundlefile,j); |
| 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. |
| * @param cpEntry The ClassPath entry index of the BundleFile. |
| * @return A URL to the resource or null if the resource does not exist. |
| */ |
| protected URL findResourceImpl(String name,BundleFile bundlefile,int cpEntry) { |
| return bundlefile.getURL(name,cpEntry); |
| } |
| |
| /** |
| * @see org.eclipse.osgi.framework.adaptor.BundleClassLoader#findLocalResources(String) |
| */ |
| public Enumeration findLocalResources(String resource) { |
| Vector resources = new Vector(6); |
| for(int i=0; i<classpathEntries.length; i++) { |
| if (classpathEntries[i] != null){ |
| URL url = findResourceImpl(resource,classpathEntries[i].bundlefile,i); |
| 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].bundlefile,j); |
| if (url != null) { |
| resources.addElement(url); |
| } |
| } |
| } |
| } |
| if (resources.size()>0){ |
| return resources.elements(); |
| } |
| return null; |
| } |
| |
| /** |
| * Closes all the BundleFile objects for this BundleClassLoader. |
| */ |
| public void close(){ |
| if (!closed) { |
| super.close(); |
| for (int i=0; i<classpathEntries.length; i++) { |
| if (classpathEntries[i] != null) { |
| try |
| { |
| if (classpathEntries[i].bundlefile!=hostdata.bundleFile) { |
| classpathEntries[i].bundlefile.close(); |
| } |
| } |
| catch (IOException e) |
| { |
| hostdata.adaptor.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(); |
| } |
| } |
| } |
| } |
| |
| protected ClasspathEntry[] buildClasspath(String[] classpath, DefaultBundleData bundledata, ProtectionDomain domain) { |
| ArrayList result = new ArrayList(10); |
| |
| // If not in dev mode then just add the regular classpath entries and return |
| if (System.getProperty("osgi.dev") == null) { |
| for (int i = 0; i < classpath.length; i++) |
| findClassPathEntry(result, classpath[i],bundledata,domain); |
| return (ClasspathEntry[])result.toArray(new ClasspathEntry[result.size()]); |
| } |
| |
| // Otherwise, add the legacy entries for backwards compatibility and |
| // then for each classpath entry add the dev entries as spec'd in the |
| // corresponding properties file. If none are spec'd, add the |
| // classpath entry itself |
| addDefaultDevEntries(result, bundledata, domain); |
| for (int i = 0; i < classpath.length; i++) { |
| String[] devEntries = getDevEntries(classpath[i], bundledata); |
| if (devEntries != null && devEntries.length > 0) { |
| for (int j = 0; j < devEntries.length; j++) |
| findClassPathEntry(result, devEntries[j], bundledata, domain); |
| } else |
| findClassPathEntry(result, classpath[i], bundledata, domain); |
| } |
| return (ClasspathEntry[])result.toArray(new ClasspathEntry[result.size()]); |
| } |
| |
| protected void addDefaultDevEntries(ArrayList result, DefaultBundleData bundledata, ProtectionDomain domain) { |
| if (System.getProperty("osgi.dev") == null) |
| return; |
| String[] defaultDevEntries = bundledata.adaptor.devCP; |
| if (defaultDevEntries != null) |
| for (int i = 0; i < defaultDevEntries.length; i++) |
| findClassPathEntry(result, defaultDevEntries[i], bundledata, domain); |
| } |
| |
| protected void findClassPathEntry(ArrayList result, String entry, DefaultBundleData bundledata, ProtectionDomain domain) { |
| if (!addClassPathEntry(result,entry,bundledata,domain)) { |
| BundleException be = new BundleException(Msg.formatter.getString("BUNDLE_CLASSPATH_ENTRY_NOT_FOUND_EXCEPTION", entry)); |
| bundledata.adaptor.getEventPublisher().publishFrameworkEvent( |
| FrameworkEvent.ERROR,bundledata.getBundle(),be); |
| } |
| } |
| |
| protected boolean addClassPathEntry(ArrayList result, String entry, DefaultBundleData bundledata, ProtectionDomain domain) { |
| if (entry.equals(".")) { |
| result.add(new ClasspathEntry(bundledata.bundleFile,domain)); |
| return true; |
| } |
| else { |
| Object element = getClasspath(entry, bundledata, domain); |
| if (element != null){ |
| result.add(element); |
| return true; |
| } |
| else { |
| // 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; |
| } |
| |
| protected String[] getDevEntries(String classpathEntry, DefaultBundleData bundledata) { |
| Properties devProps = null; |
| File propLocation = bundledata.bundleFile.getFile(classpathEntry + ".properties"); |
| if (propLocation == null) |
| return null; |
| try { |
| InputStream in = new FileInputStream(propLocation); |
| try { |
| devProps = new Properties(); |
| devProps.load(in); |
| return getArrayFromList(devProps.getProperty("bin")); |
| } finally { |
| in.close(); |
| } |
| } catch (IOException e) { |
| // TODO log the failures but ignore and try to keep going |
| } |
| return null; |
| } |
| |
| /** |
| * Returns the result of converting a list of comma-separated tokens into an array |
| * |
| * @return the array of string tokens |
| * @param prop the initial comma-separated string |
| */ |
| protected String[] getArrayFromList(String prop) { |
| if (prop == null || prop.trim().equals("")) //$NON-NLS-1$ |
| return new String[0]; |
| Vector list = new Vector(); |
| StringTokenizer tokens = new StringTokenizer(prop, ","); //$NON-NLS-1$ |
| while (tokens.hasMoreTokens()) { |
| String token = tokens.nextToken().trim(); |
| if (!token.equals("")) //$NON-NLS-1$ |
| list.addElement(token); |
| } |
| return list.isEmpty() ? new String[0] : (String[]) list.toArray(new String[list.size()]); |
| } |
| |
| /** |
| * 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 DefaultBundleData bundledata; |
| /** The ProtectionDomain of the fragment */ |
| protected ProtectionDomain domain; |
| |
| protected FragmentClasspath(ClasspathEntry[] classpathEntries, DefaultBundleData bundledata, ProtectionDomain domain){ |
| this.classpathEntries = classpathEntries; |
| this.bundledata = bundledata; |
| this.domain = domain; |
| } |
| |
| protected void close(){ |
| for (int i=0; i<classpathEntries.length; i++) { |
| try { |
| if (classpathEntries[i].bundlefile != bundledata.bundleFile) { |
| classpathEntries[i].bundlefile.close(); |
| } |
| } |
| catch (IOException e) |
| { |
| bundledata.adaptor.getEventPublisher().publishFrameworkEvent( |
| FrameworkEvent.ERROR,bundledata.getBundle(),e); |
| } |
| } |
| } |
| } |
| |
| protected class ClasspathEntry { |
| protected BundleFile bundlefile; |
| protected ProtectionDomain domain; |
| protected ClasspathEntry(BundleFile bundlefile, ProtectionDomain domain){ |
| this.bundlefile = bundlefile; |
| this.domain = domain; |
| } |
| } |
| } |