view rt/net/sourceforge/jnlp/runtime/JNLPClassLoader.java @ 1319:d95ddc227d01

2009-01-20 Lillian Angel <langel@redhat.com> * rt/net/sourceforge/jnlp/DefaultLaunchHandler.java: Removed debug lines. * rt/net/sourceforge/jnlp/runtime/JNLPClassLoader.java: Likewise. * rt/net/sourceforge/jnlp/security/AccessWarningPane.java: Updated imports. * rt/net/sourceforge/jnlp/security/AppletWarningPane.java: Updated imports. * rt/net/sourceforge/jnlp/security/CertWarningPane.java: Updated imports, added certVerifier global variable. (CertWarningPane): Initialized certVerifier. (installComponents): Added checks to determine if certificate is for an https site, and set the name/publisher/from variables appropriately. Also, customized warning pane label for https site. * rt/net/sourceforge/jnlp/security/HttpsCertVerifier.java: (getDetails): Implemented. (addToDetails): Likewise. (R): Likewise. (getPublisher): Likewise. (getRoot): Likewise. (getRootInCacerts): Likewise. (hasSigningIssues): Likewise. (noSigningIssues): Likewise. * rt/net/sourceforge/jnlp/security/MoreInfoPane.java: Fixed imports. * rt/net/sourceforge/jnlp/security/SecurityDialogUI.java: Fixed imports. * rt/net/sourceforge/jnlp/security/SecurityWarningDialog.java: Fixed imports. * rt/net/sourceforge/jnlp/security/SingleCertInfoPane.java: Fixed imports. * rt/net/sourceforge/jnlp/security/viewer/CertificatePane.java: Fixed imports. * rt/net/sourceforge/jnlp/tools/KeyTool.java: Removed debug lines. * rt/net/sourceforge/jnlp/security/CertVerifier.java: Moved file below, here. * rt/net/sourceforge/jnlp/tools/CertVerifier.java: Removed. * rt/net/sourceforge/jnlp/security/VariableX509TrustManager.java: Removed debug lines.
author Lillian Angel <langel@redhat.com>
date Tue, 20 Jan 2009 18:37:40 -0500
parents c63cc0eff68b
children 15437352b69c
line wrap: on
line source


// 
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
// 
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.


package net.sourceforge.jnlp.runtime;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.CodeSource;
import java.security.Permission;
import java.security.PermissionCollection;
import java.security.Permissions;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.TreeSet;
import java.util.Vector;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import net.sourceforge.jnlp.ExtensionDesc;
import net.sourceforge.jnlp.JARDesc;
import net.sourceforge.jnlp.JNLPFile;
import net.sourceforge.jnlp.LaunchException;
import net.sourceforge.jnlp.ParseException;
import net.sourceforge.jnlp.PluginBridge;
import net.sourceforge.jnlp.ResourcesDesc;
import net.sourceforge.jnlp.SecurityDesc;
import net.sourceforge.jnlp.cache.CacheUtil;
import net.sourceforge.jnlp.cache.ResourceTracker;
import net.sourceforge.jnlp.cache.UpdatePolicy;
import net.sourceforge.jnlp.security.SecurityWarningDialog;
import net.sourceforge.jnlp.tools.JarSigner;
import sun.misc.JarIndex;

/**
 * Classloader that takes it's resources from a JNLP file.  If the
 * JNLP file defines extensions, separate classloaders for these
 * will be created automatically.  Classes are loaded with the
 * security context when the classloader was created.
 *
 * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author
 * @version $Revision: 1.20 $ 
 */
public class JNLPClassLoader extends URLClassLoader {

    // todo: initializePermissions should get the permissions from
    // extension classes too so that main file classes can load
    // resources in an extension.

    /** shortcut for resources */
    private static String R(String key) { return JNLPRuntime.getMessage(key); }

    /** map from JNLPFile url to shared classloader */
    private static Map urlToLoader = new HashMap(); // never garbage collected!

    /** number of times a classloader with native code is created */
    private static int nativeCounter = 0;

    /** the directory for native code */
    private File nativeDir = null; // if set, some native code exists

    /** security context */
    private AccessControlContext acc = AccessController.getContext();

    /** the permissions for the cached jar files */
    private List resourcePermissions;

    /** the app */
    private ApplicationInstance app = null; // here for faster lookup in security manager

    /** list of this, local and global loaders this loader uses */
    private JNLPClassLoader loaders[] = null; // ..[0]==this

    /** whether to strictly adhere to the spec or not */
    private boolean strict = true;

    /** loads the resources */
    private ResourceTracker tracker = new ResourceTracker(true); // prefetch

    /** the update policy for resources */
    private UpdatePolicy updatePolicy;

    /** the JNLP file */
    private JNLPFile file;

    /** the resources section */
    private ResourcesDesc resources;

    /** the security section */
    private SecurityDesc security;
    
    /** Permissions granted by the user during runtime. */
    private ArrayList<Permission> runtimePermissions = new ArrayList<Permission>();

    /** all jars not yet part of classloader or active */
    private List available = new ArrayList();

	/** all of the jar files that were verified */
	private ArrayList<String> verifiedJars = null;

	/** all of the jar files that were not verified */
	private ArrayList<String> unverifiedJars = null;

	/** the jarsigner tool to verify our jars */
	private JarSigner js = null;

	private boolean signing = false;
	
	/** ArrayList containing jar indexes for various jars available to this classloader */
	private ArrayList<JarIndex> jarIndexes = new ArrayList<JarIndex>();
	
	/** File entries in the jar files available to this classloader */
	private TreeSet jarEntries = new TreeSet();

    /**
     * Create a new JNLPClassLoader from the specified file.
     *
     * @param file the JNLP file
     */
    protected JNLPClassLoader(JNLPFile file, UpdatePolicy policy) throws LaunchException {
        super(new URL[0], JNLPClassLoader.class.getClassLoader());

        if (JNLPRuntime.isDebug())
            System.out.println("New classloader: "+file.getFileLocation());

        this.file = file;
        this.updatePolicy = policy;
        this.resources = file.getResources();

        // initialize extensions
        initializeExtensions();

        // initialize permissions
        initializePermissions();

        initializeResources();

        setSecurity();

    }

    private void setSecurity() {
		/**
		 * When we're trying to load an applet, file.getSecurity() will return
		 * null since there is no jnlp file to specify permissions. We
		 * determine security settings here, after trying to verify jars.
		 */
		if (file instanceof PluginBridge) {
			if (signing == true) {
				this.security = new SecurityDesc(file, 
					SecurityDesc.ALL_PERMISSIONS,
					file.getCodeBase().getHost());
			} else {
				this.security = new SecurityDesc(file, 
					SecurityDesc.SANDBOX_PERMISSIONS, 
					file.getCodeBase().getHost());
			}
		} else { //regular jnlp file
			
			/**
			 * If the application is signed, then we set the SecurityDesc to the
			 * <security> tag in the jnlp file. Note that if an application is
			 * signed, but there is no <security> tag in the jnlp file, the
			 * application will get sandbox permissions.
			 * If the application is unsigned, we ignore the <security> tag and 
			 * use a sandbox instead. 
			 */
			if (signing == true) {
				this.security = file.getSecurity();
			} else {
				this.security = new SecurityDesc(file, 
						SecurityDesc.SANDBOX_PERMISSIONS, 
						file.getCodeBase().getHost());
			}
		}
    }
    
    /**
     * Returns a JNLP classloader for the specified JNLP file.
     *
     * @param file the file to load classes for
     * @param policy the update policy to use when downloading resources
     */
    public static JNLPClassLoader getInstance(JNLPFile file, UpdatePolicy policy) throws LaunchException {
        JNLPClassLoader loader = null;
        URL location = file.getFileLocation();

        if (location != null)
            loader = (JNLPClassLoader) urlToLoader.get(location);

		try {
        	if (loader == null)
            	loader = new JNLPClassLoader(file, policy);
		} catch (LaunchException e) {
			throw e;
		}

        if (file.getInformation().isSharingAllowed())
            urlToLoader.put(location, loader);

        return loader;
    }

    /**
     * Returns a JNLP classloader for the JNLP file at the specified
     * location. 
     *
     * @param location the file's location
     * @param policy the update policy to use when downloading resources
     */
    public static JNLPClassLoader getInstance(URL location, UpdatePolicy policy) throws IOException, ParseException, LaunchException {
        JNLPClassLoader loader = (JNLPClassLoader) urlToLoader.get(location);

        if (loader == null)
            loader = getInstance(new JNLPFile(location, false, policy), policy);

        return loader;
    }

    /**
     * Load the extensions specified in the JNLP file.
     */
    void initializeExtensions() {
        ExtensionDesc[] ext = resources.getExtensions();

        List loaderList = new ArrayList();

        loaderList.add(this);

		//if (ext != null) {
        	for (int i=0; i < ext.length; i++) {
            	try {
               		JNLPClassLoader loader = getInstance(ext[i].getLocation(), updatePolicy);
                	loaderList.add(loader);
            	}
            	catch (Exception ex) {
                	ex.printStackTrace();
            	}
        	}
		//}

        loaders = (JNLPClassLoader[]) loaderList.toArray(new JNLPClassLoader[ loaderList.size()]);
    }

    /**
     * Make permission objects for the classpath.
     */
    void initializePermissions() {
        resourcePermissions = new ArrayList();

        JARDesc jars[] = resources.getJARs();
        for (int i=0; i < jars.length; i++) {
            Permission p = CacheUtil.getReadPermission(jars[i].getLocation(),
                                                       jars[i].getVersion());

            if (JNLPRuntime.isDebug()) {
            	if (p == null)
            		System.out.println("Unable to add permission for " + jars[i].getLocation());
            	else
            		System.out.println("Permission added: " + p.toString());
            }
            if (p != null)
                resourcePermissions.add(p);
        }
    }

    /**
     * Load all of the JARs used in this JNLP file into the
     * ResourceTracker for downloading.
     */
    void initializeResources() throws LaunchException {
        JARDesc jars[] = resources.getJARs();
		if (jars == null || jars.length == 0)
			return;
		/*
		if (jars == null || jars.length == 0) {
			throw new LaunchException(null, null, R("LSFatal"),
			                    R("LCInit"), R("LFatalVerification"), "No jars!");
		}
		*/
        List initialJars = new ArrayList();

        for (int i=0; i < jars.length; i++) {

            available.add(jars[i]);

            if (jars[i].isEager())
                initialJars.add(jars[i]); // regardless of part

            tracker.addResource(jars[i].getLocation(), 
                                jars[i].getVersion(), 
                                jars[i].isCacheable() ? JNLPRuntime.getDefaultUpdatePolicy() : UpdatePolicy.FORCE
                               );
        }

        if (strict)
            fillInPartJars(initialJars); // add in each initial part's lazy jars

		if (JNLPRuntime.isVerifying()) {

			JarSigner js;
			waitForJars(initialJars); //download the jars first.

			try {
				js = verifyJars(initialJars);
			} catch (Exception e) {
				//we caught an Exception from the JarSigner class.
				//Note: one of these exceptions could be from not being able
				//to read the cacerts or trusted.certs files.
				e.printStackTrace();
				throw new LaunchException(null, null, R("LSFatal"),
					R("LCInit"), R("LFatalVerification"), R("LFatalVerificationInfo"));
			}

			//Case when at least one jar has some signing
			if (js.anyJarsSigned()){
				signing = true;

				//user does not trust this publisher
				if (!js.getAlreadyTrustPublisher()) {
					if (!js.getRootInCacerts()) { //root cert is not in cacerts
						boolean b = SecurityWarningDialog.showCertWarningDialog(
							SecurityWarningDialog.AccessType.UNVERIFIED, file, js);
						if (!b)
							throw new LaunchException(null, null, R("LSFatal"), 
								R("LCLaunching"), R("LNotVerified"), "");
					} else if (js.getRootInCacerts()) { //root cert is in cacerts
						boolean b = false;
						if (js.noSigningIssues())
							b = SecurityWarningDialog.showCertWarningDialog(
									SecurityWarningDialog.AccessType.VERIFIED, file, js);
						else if (!js.noSigningIssues())
							b = SecurityWarningDialog.showCertWarningDialog(
									SecurityWarningDialog.AccessType.SIGNING_ERROR, file, js);
						if (!b)
							throw new LaunchException(null, null, R("LSFatal"),
								R("LCLaunching"), R("LCancelOnUserRequest"), "");
					}
				} else {
					/**
					 * If the user trusts this publisher (i.e. the publisher's certificate
					 * is in the user's trusted.certs file), we do not show any dialogs.
					 */
				}
			} else {

				signing = false;
				//otherwise this jar is simply unsigned -- make sure to ask
				//for permission on certain actions
			}
		}

        activateJars(initialJars);
    }

    /**
     * Add applet's codebase URL.  This allows compatibility with
     * applets that load resources from their codebase instead of
     * through JARs, but can slow down resource loading.  Resources
     * loaded from the codebase are not cached.
     */
    public void enableCodeBase() {
        addURL( file.getCodeBase() ); // nothing happens if called more that once?
    }

    /**
     * Sets the JNLP app this group is for; can only be called once.
     */
    public void setApplication(ApplicationInstance app) {
        if (this.app != null) {
            if (JNLPRuntime.isDebug()) {
                Exception ex = new IllegalStateException("Application can only be set once");
                ex.printStackTrace();
            }
            return;
        }

        this.app = app;
    }

    /**
     * Returns the JNLP app for this classloader
     */
    public ApplicationInstance getApplication() {
        return app;
    }

    /**
     * Returns the JNLP file the classloader was created from.
     */
    public JNLPFile getJNLPFile() {
        return file;
    }

    /**
     * Returns the permissions for the CodeSource.
     */
    protected PermissionCollection getPermissions(CodeSource cs) {
        Permissions result = new Permissions();

        // should check for extensions or boot, automatically give all
        // access w/o security dialog once we actually check certificates.

        // copy security permissions from SecurityDesc element
		if (security != null) {
        	Enumeration e = security.getPermissions().elements();
        	while (e.hasMoreElements())
            	result.add((Permission) e.nextElement());
		}

        // add in permission to read the cached JAR files
        for (int i=0; i < resourcePermissions.size(); i++)
            result.add((Permission) resourcePermissions.get(i));

        // add in the permissions that the user granted.
        for (int i=0; i < runtimePermissions.size(); i++)
        	result.add(runtimePermissions.get(i));

        return result;
    }

    protected void addPermission(Permission p) {
    	runtimePermissions.add(p);
    }
    
    /**
     * Adds to the specified list of JARS any other JARs that need
     * to be loaded at the same time as the JARs specified (ie, are
     * in the same part).
     */
    protected void fillInPartJars(List jars) {
        for (int i=0; i < jars.size(); i++) {
            String part = ((JARDesc) jars.get(i)).getPart();

            for (int a=0; a < available.size(); a++) {
                JARDesc jar = (JARDesc) available.get(a);

                if (part != null && part.equals(jar.getPart()))
                    if (!jars.contains(jar))
                        jars.add(jar);
            }
        }
    }

    /**
     * Ensures that the list of jars have all been transferred, and
     * makes them available to the classloader.  If a jar contains
     * native code, the libraries will be extracted and placed in
     * the path.
     *
     * @param jars the list of jars to load
     */
    protected void activateJars(final List jars) {
        PrivilegedAction activate = new PrivilegedAction() {

            public Object run() {
                // transfer the Jars
                waitForJars(jars);

                for (int i=0; i < jars.size(); i++) {
                    JARDesc jar = (JARDesc) jars.get(i);

                    available.remove(jar);

                    // add jar
                    File localFile = tracker.getCacheFile(jar.getLocation());
                    try {
                        URL location = jar.getLocation(); // non-cacheable, use source location
                        if (localFile != null) {
                            location = localFile.toURL(); // cached file
                            
                            // This is really not the best way.. but we need some way for 
                            // PluginAppletViewer::getCachedImageRef() to check if the image 
                            // is available locally, and it cannot use getResources() because 
                            // that prefetches the resource, which confuses MediaTracker.waitForAll() 
                            // which does a wait(), waiting for notification (presumably 
                            // thrown after a resource is fetched). This bug manifests itself
                            // particularly when using The FileManager applet from Webmin.
                            
                            JarFile jarFile = new JarFile(localFile);
                            Enumeration e = jarFile.entries();
                            while (e.hasMoreElements())
                                jarEntries.add(((JarEntry) e.nextElement()).getName());

                        }

                        addURL(location);

                        // there is currently no mechanism to cache files per 
                        // instance.. so only index cached files
                        if (localFile != null) {
                            JarIndex index = JarIndex.getJarIndex(new JarFile(localFile.getAbsolutePath()), null);

                            if (index != null)
                                jarIndexes.add(index);
                        }

                        if (JNLPRuntime.isDebug())
                            System.err.println("Activate jar: "+location);
                    }
                    catch (Exception ex) {
                        if (JNLPRuntime.isDebug())
                            ex.printStackTrace();
                    }

                    if (jar.isNative())
                        activateNative(jar);
                }

                return null;
            }
        };

        AccessController.doPrivileged(activate, acc);
    }

    /**
     * Enable the native code contained in a JAR by copying the
     * native files into the filesystem.  Called in the security
     * context of the classloader.
     */
    protected void activateNative(JARDesc jar) {
        if (JNLPRuntime.isDebug())
            System.out.println("Activate native: "+jar.getLocation());

        File localFile = tracker.getCacheFile(jar.getLocation());
        if (localFile == null)
            return;

        if (nativeDir == null)
            nativeDir = getNativeDir();

        try {
            JarFile jarFile = new JarFile(localFile, false);
            Enumeration entries = jarFile.entries();

            while (entries.hasMoreElements()) {
                JarEntry e = (JarEntry) entries.nextElement();

                if (e.isDirectory() || e.getName().indexOf('/') != -1)
                    continue;

                File outFile = new File(nativeDir, e.getName());

                CacheUtil.streamCopy(jarFile.getInputStream(e),
                                     new FileOutputStream(outFile));
            }
        }
        catch (IOException ex) {
            if (JNLPRuntime.isDebug())
                ex.printStackTrace();
        }
    }

    /**
     * Return the base directory to store native code files in.
     * This method does not need to return the same directory across
     * calls.
     */
    protected File getNativeDir() {
        nativeDir = new File(System.getProperty("java.io.tmpdir") 
                             + File.separator + "netx-native-" 
                             + (new Random().nextInt() & 0xFFFF));

        if (!nativeDir.mkdirs()) 
            return null;
        else
            return nativeDir;
    }

    /**
     * Return the absolute path to the native library.
     */
    protected String findLibrary(String lib) {
        if (nativeDir == null)
            return null;

        String syslib = System.mapLibraryName(lib);

        File target = new File(nativeDir, syslib);
        if (target.exists())
            return target.toString();
        else {
            String result = super.findLibrary(lib);
            if (result != null)
                return result;

            return findLibraryExt(lib);
        }
    }

    /**
     * Try to find the library path from another peer classloader.
     */
    protected String findLibraryExt(String lib) {
        for (int i=0; i < loaders.length; i++) {
            String result = null;

            if (loaders[i] != this)
                result = loaders[i].findLibrary(lib);

            if (result != null)
                return result;
        }

        return null;
    }

    /**
     * Wait for a group of JARs, and send download events if there
     * is a download listener or display a progress window otherwise.
     *
     * @param jars the jars
     */
    private void waitForJars(List jars) {
        URL urls[] = new URL[jars.size()];

        for (int i=0; i < jars.size(); i++) {
            JARDesc jar = (JARDesc) jars.get(i);

            urls[i] = jar.getLocation();
        }

        CacheUtil.waitForResources(app, tracker, urls, file.getTitle());
    }

    /**
	 * Verifies code signing of jars to be used.
	 *
	 * @param jars the jars to be verified.
	 */
	private JarSigner verifyJars(List<JARDesc> jars) throws Exception {
	
		js = new JarSigner();
		js.verifyJars(jars, tracker);
		return js;
	}

    /**
     * Find the loaded class in this loader or any of its extension loaders.
     */
    protected Class findLoadedClassAll(String name) {
        for (int i=0; i < loaders.length; i++) {
            Class result = null;

            if (loaders[i] == this)
                result = super.findLoadedClass(name);
            else
                result = loaders[i].findLoadedClassAll(name);

            if (result != null)
                return result;
        }

        return null;
    }

    /**
     * Find a JAR in the shared 'extension' classloaders, this
     * classloader, or one of the classloaders for the JNLP file's
     * extensions.
     */
    public Class loadClass(String name) throws ClassNotFoundException {

        Class result = findLoadedClassAll(name);

        // try parent classloader
        if (result == null) {
            try {
                ClassLoader parent = getParent();
                if (parent == null)
                    parent = ClassLoader.getSystemClassLoader();

                return parent.loadClass(name);
            }
            catch (ClassNotFoundException ex) { }
        }

        // filter out 'bad' package names like java, javax
        // validPackage(name);

        // search this and the extension loaders
        if (result == null)
            try {
                result = loadClassExt(name);
            } catch (ClassNotFoundException cnfe) {

                // Not found in external loader either. As a last resort, look in any available indexes

                // Currently this loads jars directly from the site. We cannot cache it because this 
                // call is initiated from within the applet, which does not have disk read/write permissions
                for (JarIndex index: jarIndexes) {
                    LinkedList<String> jarList = index.get(name.replace('.', '/'));

                    if (jarList != null) {
                        for (String jarName: jarList) {
                            JARDesc desc;
                            try {
                                desc = new JARDesc(new URL(file.getCodeBase(), jarName),
                                        null, null, false, true, false, true);
                            } catch (MalformedURLException mfe) {
                                throw new ClassNotFoundException(name);
                            }

                            available.add(desc);

                            tracker.addResource(desc.getLocation(), 
                                    desc.getVersion(), 
                                    JNLPRuntime.getDefaultUpdatePolicy()
                            );

                            URL remoteURL;
                            try {
                                remoteURL = new URL(file.getCodeBase() + jarName);
                            } catch (MalformedURLException mfe) {
                                throw new ClassNotFoundException(name);
                            }

                            URL u;

                            try {
                                u = tracker.getCacheURL(remoteURL);
                            } catch (Exception e) {
                                throw new ClassNotFoundException(name);
                            }

                            if (u != null)
                                addURL(u);

                        }

                        // If it still fails, let it error out                        
                        result = loadClassExt(name);
                    }
                }
            }

        return result;
    }

    /**
     * Find the class in this loader or any of its extension loaders.
     */
    protected Class findClass(String name) throws ClassNotFoundException {
        for (int i=0; i < loaders.length; i++) {
            try {
                if (loaders[i] == this)
                    return super.findClass(name);
                else
                    return loaders[i].findClass(name);
            }
            catch(ClassNotFoundException ex) { }
        }

        throw new ClassNotFoundException(name);
    }

    /**
     * Search for the class by incrementally adding resources to the
     * classloader and its extension classloaders until the resource
     * is found.
     */
    private Class loadClassExt(String name) throws ClassNotFoundException {
        // make recursive
        addAvailable();

        // find it
        try {
            return findClass(name);
        }
        catch(ClassNotFoundException ex) {
        }

        // add resources until found
        while (true) {
            JNLPClassLoader addedTo = addNextResource();

            if (addedTo == null)
                throw new ClassNotFoundException(name);

            try {
                return addedTo.findClass(name);
            }
            catch(ClassNotFoundException ex) {
            }
        }
    }

    /**
     * Finds the resource in this, the parent, or the extension
     * class loaders.
     */
    public URL getResource(String name) {
        URL result = super.getResource(name);

        for (int i=1; i < loaders.length; i++)
            if (result == null)
                result = loaders[i].getResource(name);

        return result;
    }

    /**
     * Finds the resource in this, the parent, or the extension
     * class loaders.
     */
    public Enumeration findResources(String name) throws IOException {
        Vector resources = new Vector();

        for (int i=0; i < loaders.length; i++) {
            Enumeration e;

            if (loaders[i] == this)
                e = super.findResources(name);
            else 
                e = loaders[i].findResources(name);

            while (e.hasMoreElements())
                resources.add(e.nextElement());
        }

        return resources.elements();
    }
    
    /**
     * Returns if the specified resource is available locally from a cached jar
     * 
     * @param s The name of the resource
     * @return Whether or not the resource is available locally
     */
    public boolean resourceAvailableLocally(String s) {
        return jarEntries.contains(s);
    }

    /**
     * Adds whatever resources have already been downloaded in the
     * background.
     */
    protected void addAvailable() {
        // go through available, check tracker for it and all of its
        // part brothers being available immediately, add them.

        for (int i=1; i < loaders.length; i++) {
            loaders[i].addAvailable();
        }
    }

    /**
     * Adds the next unused resource to the classloader.  That
     * resource and all those in the same part will be downloaded
     * and added to the classloader before returning.  If there are
     * no more resources to add, the method returns immediately.
     *
     * @return the classloader that resources were added to, or null
     */
    protected JNLPClassLoader addNextResource() {
        if (available.size() == 0) {
            for (int i=1; i < loaders.length; i++) {
                JNLPClassLoader result = loaders[i].addNextResource();

                if (result != null)
                    return result;
            }
            return null;
        }

        // add jar
        List jars = new ArrayList();
        jars.add(available.get(0));

        fillInPartJars(jars);

		
		activateJars(jars);

        return this;
    }

    // this part compatibility with previous classloader
    /**
     * @deprecated
     */
    public String getExtensionName() {
        String result = file.getInformation().getTitle();

        if (result == null)
            result = file.getInformation().getDescription();
        if (result == null && file.getFileLocation() != null)
            result = file.getFileLocation().toString();
        if (result == null && file.getCodeBase() != null)
            result = file.getCodeBase().toString();

        return result;
    }

    /**
     * @deprecated
     */
    public String getExtensionHREF() {
        return file.getFileLocation().toString();
    }

	public boolean getSigning() {
		return signing;
	}

	protected SecurityDesc getSecurity() {
		return security;
	}
}