org.java.plugin.standard.StandardPluginClassLoader.java Source code

Java tutorial

Introduction

Here is the source code for org.java.plugin.standard.StandardPluginClassLoader.java

Source

/*****************************************************************************
 * Java Plug-in Framework (JPF)
 * Copyright (C) 2004-2007 Dmitry Olshansky
 * 
 * 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 org.java.plugin.standard;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.AccessController;
import java.security.CodeSource;
import java.security.PrivilegedAction;
import java.security.ProtectionDomain;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.java.plugin.PathResolver;
import org.java.plugin.Plugin;
import org.java.plugin.PluginClassLoader;
import org.java.plugin.PluginManager;
import org.java.plugin.registry.Library;
import org.java.plugin.registry.PluginDescriptor;
import org.java.plugin.registry.PluginPrerequisite;
import org.java.plugin.registry.PluginRegistry;
import org.java.plugin.util.IoUtil;

/**
 * Standard implementation of plug-in class loader.
 * @version $Id: StandardPluginClassLoader.java,v 1.25 2007/04/07 12:43:21 ddimon Exp $
 */
public class StandardPluginClassLoader extends PluginClassLoader {
    static Log log = LogFactory.getLog(StandardPluginClassLoader.class);

    private static File libCacheFolder;
    private static boolean libCacheFolderInitialized = false;

    private static URL getClassBaseUrl(final Class cls) {
        ProtectionDomain pd = cls.getProtectionDomain();
        if (pd != null) {
            CodeSource cs = pd.getCodeSource();
            if (cs != null) {
                return cs.getLocation();
            }
        }
        return null;
    }

    private static URL[] getUrls(final PluginManager manager, final PluginDescriptor descr) {
        List result = new LinkedList();
        for (Iterator it = descr.getLibraries().iterator(); it.hasNext();) {
            Library lib = (Library) it.next();
            if (!lib.isCodeLibrary()) {
                continue;
            }
            result.add(manager.getPathResolver().resolvePath(lib, lib.getPath()));
        }
        if (log.isDebugEnabled()) {
            StringBuffer buf = new StringBuffer();
            buf.append("Code URL's populated for plug-in " //$NON-NLS-1$
                    + descr + ":\r\n"); //$NON-NLS-1$
            for (Iterator it = result.iterator(); it.hasNext();) {
                buf.append("\t"); //$NON-NLS-1$
                buf.append(it.next());
                buf.append("\r\n"); //$NON-NLS-1$
            }
            log.debug(buf.toString());
        }
        return (URL[]) result.toArray(new URL[result.size()]);
    }

    private static URL[] getUrls(final PluginManager manager, final PluginDescriptor descr,
            final URL[] existingUrls) {
        List urls = Arrays.asList(existingUrls);
        List result = new LinkedList();
        for (Iterator it = descr.getLibraries().iterator(); it.hasNext();) {
            Library lib = (Library) it.next();
            if (!lib.isCodeLibrary()) {
                continue;
            }
            URL url = manager.getPathResolver().resolvePath(lib, lib.getPath());
            if (!urls.contains(url)) {
                result.add(url);
            }
        }
        return (URL[]) result.toArray(new URL[result.size()]);
    }

    private static File getLibCacheFolder() {
        if (libCacheFolder != null) {
            return libCacheFolderInitialized ? libCacheFolder : null;
        }
        synchronized (StandardPluginClassLoader.class) {
            libCacheFolder = new File(System.getProperty("java.io.tmpdir"), //$NON-NLS-1$
                    System.currentTimeMillis() + ".jpf-lib-cache"); //$NON-NLS-1$
            log.debug("libraries cache folder is " + libCacheFolder); //$NON-NLS-1$
            File lockFile = new File(libCacheFolder, "lock"); //$NON-NLS-1$
            if (lockFile.exists()) {
                log.error("can't initialize libraries cache folder " //$NON-NLS-1$
                        + libCacheFolder + " as lock file indicates that it" //$NON-NLS-1$
                        + " is owned by another JPF instance"); //$NON-NLS-1$
                return null;
            }
            if (libCacheFolder.exists()) {
                // clean up folder
                IoUtil.emptyFolder(libCacheFolder);
            } else {
                libCacheFolder.mkdirs();
            }
            try {
                if (!lockFile.createNewFile()) {
                    log.error("can\'t create lock file in JPF libraries cache" //$NON-NLS-1$
                            + " folder " + libCacheFolder); //$NON-NLS-1$
                    return null;
                }
            } catch (IOException ioe) {
                log.error("can\'t create lock file in JPF libraries cache" //$NON-NLS-1$
                        + " folder " + libCacheFolder, ioe); //$NON-NLS-1$
                return null;
            }
            lockFile.deleteOnExit();
            libCacheFolder.deleteOnExit();
            libCacheFolderInitialized = true;
        }
        return libCacheFolder;
    }

    private PluginDescriptor[] publicImports;
    private PluginDescriptor[] privateImports;
    private PluginDescriptor[] reverseLookups;
    private PluginResourceLoader resourceLoader;
    private Map resourceFilters; // <lib URL, ResourceFilter>
    private Map libraryCache; // <lib URL, File>
    private boolean probeParentLoaderLast;
    private boolean stickySynchronizing;
    private boolean localClassLoadingOptimization = true;
    private boolean foreignClassLoadingOptimization = true;
    private final Set localPackages = new HashSet(); //<String>
    private final Map pluginPackages = new HashMap(); //<String, PluginDescriptor>

    /**
     * Creates class instance configured to load classes and resources for
     * given plug-in.
     * @param aManager plug-in manager instance
     * @param descr plug-in descriptor
     * @param parent parent class loader, usually this is JPF "host"
     *        application class loader
     */
    public StandardPluginClassLoader(final PluginManager aManager, final PluginDescriptor descr,
            final ClassLoader parent) {
        super(aManager, descr, getUrls(aManager, descr), parent);
        collectImports();
        resourceLoader = PluginResourceLoader.get(aManager, descr);
        collectFilters();
        libraryCache = new HashMap();
    }

    protected void collectImports() {
        // collect imported plug-ins (exclude duplicates)
        Map publicImportsMap = new HashMap(); //<plug-in ID, PluginDescriptor>
        Map privateImportsMap = new HashMap(); //<plug-in ID, PluginDescriptor>
        PluginRegistry registry = getPluginDescriptor().getRegistry();
        for (Iterator it = getPluginDescriptor().getPrerequisites().iterator(); it.hasNext();) {
            PluginPrerequisite pre = (PluginPrerequisite) it.next();
            if (!pre.matches()) {
                continue;
            }
            PluginDescriptor preDescr = registry.getPluginDescriptor(pre.getPluginId());
            if (pre.isExported()) {
                publicImportsMap.put(preDescr.getId(), preDescr);
            } else {
                privateImportsMap.put(preDescr.getId(), preDescr);
            }
        }
        publicImports = (PluginDescriptor[]) publicImportsMap.values()
                .toArray(new PluginDescriptor[publicImportsMap.size()]);
        privateImports = (PluginDescriptor[]) privateImportsMap.values()
                .toArray(new PluginDescriptor[privateImportsMap.size()]);
        // collect reverse look up plug-ins (exclude duplicates)
        Map reverseLookupsMap = new HashMap(); //<plug-in ID, PluginDescriptor>
        for (Iterator it = registry.getPluginDescriptors().iterator(); it.hasNext();) {
            PluginDescriptor descr = (PluginDescriptor) it.next();
            if (descr.equals(getPluginDescriptor()) || publicImportsMap.containsKey(descr.getId())
                    || privateImportsMap.containsKey(descr.getId())) {
                continue;
            }
            for (Iterator it2 = descr.getPrerequisites().iterator(); it2.hasNext();) {
                PluginPrerequisite pre = (PluginPrerequisite) it2.next();
                if (!pre.getPluginId().equals(getPluginDescriptor().getId()) || !pre.isReverseLookup()) {
                    continue;
                }
                if (!pre.matches()) {
                    continue;
                }
                reverseLookupsMap.put(descr.getId(), descr);
                break;
            }
        }
        reverseLookups = (PluginDescriptor[]) reverseLookupsMap.values()
                .toArray(new PluginDescriptor[reverseLookupsMap.size()]);
    }

    protected void collectFilters() {
        if (resourceFilters == null) {
            resourceFilters = new HashMap();
        } else {
            resourceFilters.clear();
        }
        for (Iterator it = getPluginDescriptor().getLibraries().iterator(); it.hasNext();) {
            Library lib = (Library) it.next();
            resourceFilters.put(
                    getPluginManager().getPathResolver().resolvePath(lib, lib.getPath()).toExternalForm(),
                    new ResourceFilter(lib));
        }
    }

    /**
     * @see org.java.plugin.PluginClassLoader#pluginsSetChanged()
     */
    protected void pluginsSetChanged() {
        URL[] newUrls = getUrls(getPluginManager(), getPluginDescriptor(), getURLs());
        for (int i = 0; i < newUrls.length; i++) {
            addURL(newUrls[i]);
        }
        if (log.isDebugEnabled()) {
            StringBuffer buf = new StringBuffer();
            buf.append("New code URL's populated for plug-in " //$NON-NLS-1$
                    + getPluginDescriptor() + ":\r\n"); //$NON-NLS-1$
            for (int i = 0; i < newUrls.length; i++) {
                buf.append("\t"); //$NON-NLS-1$
                buf.append(newUrls[i]);
                buf.append("\r\n"); //$NON-NLS-1$
            }
            log.debug(buf.toString());
        }
        collectImports();
        // repopulate resource URLs
        resourceLoader = PluginResourceLoader.get(getPluginManager(), getPluginDescriptor());
        collectFilters();
        for (Iterator it = libraryCache.entrySet().iterator(); it.hasNext();) {
            if (((Map.Entry) it.next()).getValue() == null) {
                it.remove();
            }
        }
        synchronized (localPackages) {
            localPackages.clear();
        }
        synchronized (pluginPackages) {
            pluginPackages.clear();
        }
    }

    /**
     * @see org.java.plugin.PluginClassLoader#dispose()
     */
    protected void dispose() {
        for (Iterator it = libraryCache.values().iterator(); it.hasNext();) {
            ((File) it.next()).delete();
        }
        libraryCache.clear();
        resourceFilters.clear();
        privateImports = null;
        publicImports = null;
        resourceLoader = null;
        synchronized (localPackages) {
            localPackages.clear();
        }
        synchronized (pluginPackages) {
            pluginPackages.clear();
        }
    }

    protected void setProbeParentLoaderLast(final boolean value) {
        probeParentLoaderLast = value;
    }

    protected void setStickySynchronizing(final boolean value) {
        stickySynchronizing = value;
    }

    protected void setLocalClassLoadingOptimization(final boolean value) {
        localClassLoadingOptimization = value;
    }

    protected void setForeignClassLoadingOptimization(final boolean value) {
        foreignClassLoadingOptimization = value;
    }

    /**
     * @see java.lang.ClassLoader#loadClass(java.lang.String, boolean)
     */
    protected Class loadClass(final String name, final boolean resolve) throws ClassNotFoundException {
        Class result;
        boolean tryLocal = true;
        if (isLocalClass(name)) {
            if (log.isDebugEnabled()) {
                log.debug("loadClass: trying local class guess, name=" //$NON-NLS-1$
                        + name + ", this=" + this); //$NON-NLS-1$
            }
            result = loadLocalClass(name, resolve, this);
            if (result != null) {
                if (log.isDebugEnabled()) {
                    log.debug("loadClass: local class guess succeeds, name=" //$NON-NLS-1$
                            + name + ", this=" + this); //$NON-NLS-1$
                }
                checkClassVisibility(result, this);
                return result;
            }
            tryLocal = false;
        }
        if (probeParentLoaderLast) {
            try {
                result = loadPluginClass(name, resolve, tryLocal, this, null);
            } catch (ClassNotFoundException cnfe) {
                result = getParent().loadClass(name);
            }
            if (result == null) {
                result = getParent().loadClass(name);
            }
        } else {
            try {
                result = getParent().loadClass(name);
            } catch (ClassNotFoundException cnfe) {
                result = loadPluginClass(name, resolve, tryLocal, this, null);
            }
        }
        if (result != null) {
            return result;
        }
        throw new ClassNotFoundException(name);
    }

    private Class loadLocalClass(final String name, final boolean resolve,
            final StandardPluginClassLoader requestor) {
        boolean debugEnabled = log.isDebugEnabled();
        synchronized (stickySynchronizing ? requestor : this) {
            Class result = findLoadedClass(name);
            if (result != null) {
                if (debugEnabled) {
                    log.debug("loadLocalClass: found loaded class, class=" //$NON-NLS-1$
                            + result + ", this=" //$NON-NLS-1$
                            + this + ", requestor=" + requestor); //$NON-NLS-1$
                }
                return result; // found already loaded class in this plug-in
            }
            try {
                result = findClass(name);
            } catch (LinkageError le) {
                if (debugEnabled) {
                    log.debug("loadLocalClass: class loading failed," //$NON-NLS-1$
                            + " name=" + name + ", this=" //$NON-NLS-1$ //$NON-NLS-2$
                            + this + ", requestor=" + requestor, le); //$NON-NLS-1$
                }
                throw le;
            } catch (ClassNotFoundException cnfe) {
                // ignore
            }
            if (result != null) {
                if (debugEnabled) {
                    log.debug("loadLocalClass: found class, class=" //$NON-NLS-1$
                            + result + ", this=" //$NON-NLS-1$
                            + this + ", requestor=" + requestor); //$NON-NLS-1$
                }
                if (resolve) {
                    resolveClass(result);
                }
                registerLocalPackage(result);
                return result; // found class in this plug-in
            }
        }
        return null;
    }

    protected Class loadPluginClass(final String name, final boolean resolve, final boolean tryLocal,
            final StandardPluginClassLoader requestor, final Set seenPlugins) throws ClassNotFoundException {
        Set seen = seenPlugins;
        if ((seen != null) && seen.contains(getPluginDescriptor().getId())) {
            return null;
        }
        if (seen == null) {
            seen = new HashSet();
        }
        seen.add(getPluginDescriptor().getId());
        if ((this != requestor) && !getPluginManager().isPluginActivated(getPluginDescriptor())
                && !getPluginManager().isPluginActivating(getPluginDescriptor())) {
            String msg = "can't load class " + name + ", plug-in " //$NON-NLS-1$ //$NON-NLS-2$
                    + getPluginDescriptor() + " is not activated yet"; //$NON-NLS-1$
            log.warn(msg);
            throw new ClassNotFoundException(msg);
        }
        Class result = null;
        boolean debugEnabled = log.isDebugEnabled();
        PluginDescriptor descr = guessPlugin(name);
        if ((descr != null) && !seen.contains(descr.getId())) {
            if (debugEnabled) {
                log.debug("loadPluginClass: trying plug-in guess, name=" //$NON-NLS-1$
                        + name + ", this=" //$NON-NLS-1$
                        + this + ", requestor=" + requestor); //$NON-NLS-1$
            }
            result = ((StandardPluginClassLoader) getPluginManager().getPluginClassLoader(descr))
                    .loadPluginClass(name, resolve, true, requestor, seen);
            if (result != null) {
                if (debugEnabled) {
                    log.debug("loadPluginClass: plug-in guess succeeds, name=" //$NON-NLS-1$
                            + name + ", this=" //$NON-NLS-1$
                            + this + ", requestor=" + requestor); //$NON-NLS-1$
                }
                return result;
            }
        }
        if (tryLocal) {
            result = loadLocalClass(name, resolve, requestor);
            if (result != null) {
                checkClassVisibility(result, requestor);
                return result;
            }
        }
        for (int i = 0; i < publicImports.length; i++) {
            if (seen.contains(publicImports[i].getId())) {
                continue;
            }
            result = ((StandardPluginClassLoader) getPluginManager().getPluginClassLoader(publicImports[i]))
                    .loadPluginClass(name, resolve, true, requestor, seen);
            if (result != null) {
                break; // found class in publicly imported plug-in
            }
        }
        if ((this == requestor) && (result == null)) {
            for (int i = 0; i < privateImports.length; i++) {
                if (seen.contains(privateImports[i].getId())) {
                    continue;
                }
                result = ((StandardPluginClassLoader) getPluginManager().getPluginClassLoader(privateImports[i]))
                        .loadPluginClass(name, resolve, true, requestor, seen);
                if (result != null) {
                    break; // found class in privately imported plug-in
                }
            }
        }
        if (result == null) {
            for (int i = 0; i < reverseLookups.length; i++) {
                if (seen.contains(reverseLookups[i].getId())) {
                    continue;
                }
                if (!getPluginManager().isPluginActivated(reverseLookups[i])
                        && !getPluginManager().isPluginActivating(reverseLookups[i])) {
                    continue;
                }
                result = ((StandardPluginClassLoader) getPluginManager().getPluginClassLoader(reverseLookups[i]))
                        .loadPluginClass(name, resolve, true, requestor, seen);
                if (result != null) {
                    break; // found class in plug-in that marks itself as
                           // allowed reverse look up
                }
            }
        }
        registerPluginPackage(result);
        return result;
    }

    private boolean isLocalClass(final String className) {
        if (!localClassLoadingOptimization) {
            return false;
        }
        String pkgName = getPackageName(className);
        if (pkgName == null) {
            return false;
        }
        return localPackages.contains(pkgName);
    }

    private void registerLocalPackage(final Class cls) {
        if (!localClassLoadingOptimization) {
            return;
        }
        String pkgName = getPackageName(cls.getName());
        if ((pkgName == null) || localPackages.contains(pkgName)) {
            return;
        }
        synchronized (localPackages) {
            localPackages.add(pkgName);
        }
        if (log.isDebugEnabled()) {
            log.debug("registered local package: name=" + pkgName); //$NON-NLS-1$
        }
    }

    private PluginDescriptor guessPlugin(final String className) {
        if (!foreignClassLoadingOptimization) {
            return null;
        }
        String pkgName = getPackageName(className);
        if (pkgName == null) {
            return null;
        }
        return (PluginDescriptor) pluginPackages.get(pkgName);
    }

    private void registerPluginPackage(final Class cls) {
        if (!foreignClassLoadingOptimization) {
            return;
        }
        Plugin plugin = getPluginManager().getPluginFor(cls);
        if (plugin == null) {
            return;
        }
        String pkgName = getPackageName(cls.getName());
        if ((pkgName == null) || pluginPackages.containsKey(pkgName)) {
            return;
        }
        synchronized (pluginPackages) {
            pluginPackages.put(pkgName, plugin.getDescriptor());
        }
        if (log.isDebugEnabled()) {
            log.debug("registered plug-in package: name=" + pkgName //$NON-NLS-1$
                    + ", plugin=" + plugin.getDescriptor()); //$NON-NLS-1$
        }
    }

    private String getPackageName(final String className) {
        int p = className.lastIndexOf('.');
        if (p == -1) {
            return null;
        }
        return className.substring(0, p);
    }

    protected void checkClassVisibility(final Class cls, final StandardPluginClassLoader requestor)
            throws ClassNotFoundException {
        if (this == requestor) {
            return;
        }
        URL lib = getClassBaseUrl(cls);
        if (lib == null) {
            return; // cls is a system class
        }
        ClassLoader loader = cls.getClassLoader();
        if (!(loader instanceof StandardPluginClassLoader)) {
            return;
        }
        if (loader != this) {
            ((StandardPluginClassLoader) loader).checkClassVisibility(cls, requestor);
        } else {
            ResourceFilter filter = (ResourceFilter) resourceFilters.get(lib.toExternalForm());
            if (filter == null) {
                log.warn("class not visible, no class filter found, lib=" + lib //$NON-NLS-1$
                        + ", class=" + cls + ", this=" + this //$NON-NLS-1$ //$NON-NLS-2$
                        + ", requestor=" + requestor); //$NON-NLS-1$
                throw new ClassNotFoundException("class " //$NON-NLS-1$
                        + cls.getName() + " is not visible for plug-in " //$NON-NLS-1$
                        + requestor.getPluginDescriptor().getId() + ", no filter found for library " + lib); //$NON-NLS-1$
            }
            if (!filter.isClassVisible(cls.getName())) {
                log.warn("class not visible, lib=" + lib //$NON-NLS-1$
                        + ", class=" + cls + ", this=" + this //$NON-NLS-1$ //$NON-NLS-2$
                        + ", requestor=" + requestor); //$NON-NLS-1$
                throw new ClassNotFoundException("class " //$NON-NLS-1$
                        + cls.getName() + " is not visible for plug-in " //$NON-NLS-1$
                        + requestor.getPluginDescriptor().getId());
            }
        }
    }

    /**
     * @see java.lang.ClassLoader#findLibrary(java.lang.String)
     */
    protected String findLibrary(final String name) {
        if ((name == null) || "".equals(name.trim())) { //$NON-NLS-1$
            return null;
        }
        if (log.isDebugEnabled()) {
            log.debug("findLibrary(String): name=" + name //$NON-NLS-1$
                    + ", this=" + this); //$NON-NLS-1$
        }
        String libname = System.mapLibraryName(name);
        String result = null;
        PathResolver pathResolver = getPluginManager().getPathResolver();
        for (Iterator it = getPluginDescriptor().getLibraries().iterator(); it.hasNext();) {
            Library lib = (Library) it.next();
            if (lib.isCodeLibrary()) {
                continue;
            }
            URL libUrl = pathResolver.resolvePath(lib, lib.getPath() + libname);
            if (log.isDebugEnabled()) {
                log.debug("findLibrary(String): trying URL " + libUrl); //$NON-NLS-1$
            }
            File libFile = IoUtil.url2file(libUrl);
            if (libFile != null) {
                if (log.isDebugEnabled()) {
                    log.debug("findLibrary(String): URL " + libUrl //$NON-NLS-1$
                            + " resolved as local file " + libFile); //$NON-NLS-1$
                }
                if (libFile.isFile()) {
                    result = libFile.getAbsolutePath();
                    break;
                }
                continue;
            }
            // we have some kind of non-local URL
            // try to copy it to local temporary file
            libFile = (File) libraryCache.get(libUrl.toExternalForm());
            if (libFile != null) {
                if (libFile.isFile()) {
                    result = libFile.getAbsolutePath();
                    break;
                }
                libraryCache.remove(libUrl.toExternalForm());
            }
            if (libraryCache.containsKey(libUrl.toExternalForm())) {
                // already tried to cache this library
                break;
            }
            libFile = cacheLibrary(libUrl, libname);
            if (libFile != null) {
                result = libFile.getAbsolutePath();
                break;
            }
        }
        if (log.isDebugEnabled()) {
            log.debug("findLibrary(String): name=" + name //$NON-NLS-1$
                    + ", libname=" + libname //$NON-NLS-1$
                    + ", result=" + result //$NON-NLS-1$
                    + ", this=" + this); //$NON-NLS-1$
        }
        return result;
    }

    protected synchronized File cacheLibrary(final URL libUrl, final String libname) {
        File cacheFolder = getLibCacheFolder();
        String libUrlStr = libUrl.toExternalForm();
        if (libraryCache.containsKey(libUrlStr)) {
            return (File) libraryCache.get(libUrlStr);
        }
        File result = null;
        try {
            if (cacheFolder == null) {
                throw new IOException("can't initialize libraries cache folder"); //$NON-NLS-1$
            }
            File libCachePluginFolder = new File(cacheFolder, getPluginDescriptor().getUniqueId());
            if (!libCachePluginFolder.exists() && !libCachePluginFolder.mkdirs()) {
                throw new IOException("can't create cache folder " //$NON-NLS-1$
                        + libCachePluginFolder);
            }
            result = new File(libCachePluginFolder, libname);
            InputStream in = IoUtil.getResourceInputStream(libUrl);
            try {
                OutputStream out = new BufferedOutputStream(new FileOutputStream(result));
                try {
                    IoUtil.copyStream(in, out, 512);
                } finally {
                    out.close();
                }
            } finally {
                in.close();
            }
            libraryCache.put(libUrlStr, result);
            if (log.isDebugEnabled()) {
                log.debug("library " + libname //$NON-NLS-1$
                        + " successfully cached from URL " + libUrl //$NON-NLS-1$
                        + " and saved to local file " + result); //$NON-NLS-1$
            }
        } catch (IOException ioe) {
            log.error("can't cache library " + libname //$NON-NLS-1$
                    + " from URL " + libUrl, ioe); //$NON-NLS-1$
            libraryCache.put(libUrlStr, null);
            result = null;
        }
        return result;
    }

    /**
     * @see java.lang.ClassLoader#findResource(java.lang.String)
     */
    public URL findResource(final String name) {
        URL result = findResource(name, this, null);
        return result;
    }

    /**
     * @see java.lang.ClassLoader#findResources(java.lang.String)
     */
    public Enumeration findResources(final String name) throws IOException {
        List result = new LinkedList();
        findResources(result, name, this, null);
        return Collections.enumeration(result);
    }

    protected URL findResource(final String name, final StandardPluginClassLoader requestor,
            final Set seenPlugins) {
        Set seen = seenPlugins;
        if ((seen != null) && seen.contains(getPluginDescriptor().getId())) {
            return null;
        }
        URL result = super.findResource(name);
        if (result != null) { // found resource in this plug-in class path
            if (log.isDebugEnabled()) {
                log.debug("findResource(...): resource found in classpath, name=" //$NON-NLS-1$
                        + name + " URL=" + result + ", this=" //$NON-NLS-1$ //$NON-NLS-2$
                        + this + ", requestor=" + requestor); //$NON-NLS-1$
            }
            if (isResourceVisible(name, result, requestor)) {
                return result;
            }
            return null;
        }
        if (resourceLoader != null) {
            result = resourceLoader.findResource(name);
            if (result != null) { // found resource in this plug-in resource libraries
                if (log.isDebugEnabled()) {
                    log.debug("findResource(...): resource found in libraries, name=" //$NON-NLS-1$
                            + name + ", URL=" + result + ", this=" //$NON-NLS-1$ //$NON-NLS-2$
                            + this + ", requestor=" + requestor); //$NON-NLS-1$
                }
                if (isResourceVisible(name, result, requestor)) {
                    return result;
                }
                return null;
            }
        }
        if (seen == null) {
            seen = new HashSet();
        }
        if (log.isDebugEnabled()) {
            log.debug("findResource(...): resource not found, name=" //$NON-NLS-1$
                    + name + ", this=" //$NON-NLS-1$
                    + this + ", requestor=" + requestor); //$NON-NLS-1$
        }
        seen.add(getPluginDescriptor().getId());
        for (int i = 0; i < publicImports.length; i++) {
            if (seen.contains(publicImports[i].getId())) {
                continue;
            }
            result = ((StandardPluginClassLoader) getPluginManager().getPluginClassLoader(publicImports[i]))
                    .findResource(name, requestor, seen);
            if (result != null) {
                break; // found resource in publicly imported plug-in
            }
        }
        if ((this == requestor) && (result == null)) {
            for (int i = 0; i < privateImports.length; i++) {
                if (seen.contains(privateImports[i].getId())) {
                    continue;
                }
                result = ((StandardPluginClassLoader) getPluginManager().getPluginClassLoader(privateImports[i]))
                        .findResource(name, requestor, seen);
                if (result != null) {
                    break; // found resource in privately imported plug-in
                }
            }
        }
        if (result == null) {
            for (int i = 0; i < reverseLookups.length; i++) {
                if (seen.contains(reverseLookups[i].getId())) {
                    continue;
                }
                result = ((StandardPluginClassLoader) getPluginManager().getPluginClassLoader(reverseLookups[i]))
                        .findResource(name, requestor, seen);
                if (result != null) {
                    break; // found resource in plug-in that marks itself as
                           // allowed reverse look up
                }
            }
        }
        return result;
    }

    protected void findResources(final List result, final String name, final StandardPluginClassLoader requestor,
            final Set seenPlugins) throws IOException {
        Set seen = seenPlugins;
        if ((seen != null) && seen.contains(getPluginDescriptor().getId())) {
            return;
        }
        for (Enumeration enm = super.findResources(name); enm.hasMoreElements();) {
            URL url = (URL) enm.nextElement();
            if (isResourceVisible(name, url, requestor)) {
                result.add(url);
            }
        }
        if (resourceLoader != null) {
            for (Enumeration enm = resourceLoader.findResources(name); enm.hasMoreElements();) {
                URL url = (URL) enm.nextElement();
                if (isResourceVisible(name, url, requestor)) {
                    result.add(url);
                }
            }
        }
        if (seen == null) {
            seen = new HashSet();
        }
        seen.add(getPluginDescriptor().getId());
        for (int i = 0; i < publicImports.length; i++) {
            if (seen.contains(publicImports[i].getId())) {
                continue;
            }
            ((StandardPluginClassLoader) getPluginManager().getPluginClassLoader(publicImports[i]))
                    .findResources(result, name, requestor, seen);
        }
        if (this == requestor) {
            for (int i = 0; i < privateImports.length; i++) {
                if (seen.contains(privateImports[i].getId())) {
                    continue;
                }
                ((StandardPluginClassLoader) getPluginManager().getPluginClassLoader(privateImports[i]))
                        .findResources(result, name, requestor, seen);
            }
        }
        for (int i = 0; i < reverseLookups.length; i++) {
            if (seen.contains(reverseLookups[i].getId())) {
                continue;
            }
            ((StandardPluginClassLoader) getPluginManager().getPluginClassLoader(reverseLookups[i]))
                    .findResources(result, name, requestor, seen);
        }
    }

    protected boolean isResourceVisible(final String name, final URL url,
            final StandardPluginClassLoader requestor) {
        if (this == requestor) {
            return true;
        }
        URL lib;
        try {
            String file = url.getFile();
            lib = new URL(url.getProtocol(), url.getHost(), file.substring(0, file.length() - name.length()));
        } catch (MalformedURLException mue) {
            log.error("can't get resource library URL", mue); //$NON-NLS-1$
            return false;
        }
        ResourceFilter filter = (ResourceFilter) resourceFilters.get(lib.toExternalForm());
        if (filter == null) {
            log.warn("no resource filter found for library " //$NON-NLS-1$
                    + lib + ", name=" + name //$NON-NLS-1$
                    + ", URL=" + url + ", this=" + this //$NON-NLS-1$ //$NON-NLS-2$
                    + ", requestor=" + requestor); //$NON-NLS-1$
            return false;
        }
        if (!filter.isResourceVisible(name)) {
            log.warn("resource not visible, name=" + name //$NON-NLS-1$
                    + ", URL=" + url + ", this=" + this //$NON-NLS-1$ //$NON-NLS-2$
                    + ", requestor=" + requestor); //$NON-NLS-1$
            return false;
        }
        return true;
    }

    protected static final class ResourceFilter {
        private boolean isPublic;
        private Set entries;

        protected ResourceFilter(final Library lib) {
            entries = new HashSet();
            for (Iterator it = lib.getExports().iterator(); it.hasNext();) {
                String exportPrefix = (String) it.next();
                if ("*".equals(exportPrefix)) { //$NON-NLS-1$
                    isPublic = true;
                    entries.clear();
                    break;
                }
                if (!lib.isCodeLibrary()) {
                    exportPrefix = exportPrefix.replace('\\', '.').replace('/', '.');
                    if (exportPrefix.startsWith(".")) { //$NON-NLS-1$
                        exportPrefix = exportPrefix.substring(1);
                    }
                }
                entries.add(exportPrefix);
            }
        }

        protected boolean isClassVisible(final String className) {
            if (isPublic) {
                return true;
            }
            if (entries.isEmpty()) {
                return false;
            }
            if (entries.contains(className)) {
                return true;
            }
            int p = className.lastIndexOf('.');
            if (p == -1) {
                return false;
            }
            return entries.contains(className.substring(0, p) + ".*"); //$NON-NLS-1$
        }

        protected boolean isResourceVisible(final String resPath) {
            // quick check
            if (isPublic) {
                return true;
            }
            if (entries.isEmpty()) {
                return false;
            }
            // translate "path spec" -> "full class name"
            String str = resPath.replace('\\', '.').replace('/', '.');
            if (str.startsWith(".")) { //$NON-NLS-1$
                str = str.substring(1);
            }
            if (str.endsWith(".")) { //$NON-NLS-1$
                str = str.substring(0, str.length() - 1);
            }
            return isClassVisible(str);
        }
    }

    protected static class PluginResourceLoader extends URLClassLoader {
        private static Log logger = LogFactory.getLog(PluginResourceLoader.class);

        static PluginResourceLoader get(final PluginManager manager, final PluginDescriptor descr) {
            final List urls = new LinkedList();
            for (Iterator it = descr.getLibraries().iterator(); it.hasNext();) {
                Library lib = (Library) it.next();
                if (lib.isCodeLibrary()) {
                    continue;
                }
                urls.add(manager.getPathResolver().resolvePath(lib, lib.getPath()));
            }
            if (logger.isDebugEnabled()) {
                StringBuffer buf = new StringBuffer();
                buf.append("Resource URL's populated for plug-in " + descr //$NON-NLS-1$
                        + ":\r\n"); //$NON-NLS-1$
                for (Iterator it = urls.iterator(); it.hasNext();) {
                    buf.append("\t"); //$NON-NLS-1$
                    buf.append(it.next());
                    buf.append("\r\n"); //$NON-NLS-1$
                }
                logger.trace(buf.toString());
            }
            if (urls.isEmpty()) {
                return null;
            }
            return (PluginResourceLoader) AccessController.doPrivileged(new PrivilegedAction() {
                public Object run() {
                    return new PluginResourceLoader((URL[]) urls.toArray(new URL[urls.size()]));
                }
            });
        }

        /**
         * Creates loader instance configured to load resources only from given
         * URLs.
         * @param urls array of resource URLs
         */
        PluginResourceLoader(final URL[] urls) {
            super(urls);
        }

        /**
         * @see java.lang.ClassLoader#findClass(java.lang.String)
         */
        protected Class findClass(final String name) throws ClassNotFoundException {
            throw new ClassNotFoundException(name);
        }

        /**
         * @see java.lang.ClassLoader#loadClass(java.lang.String, boolean)
         */
        protected Class loadClass(final String name, final boolean resolve) throws ClassNotFoundException {
            throw new ClassNotFoundException(name);
        }
    }
}