/* * $Id$ * * Copyright (C) 2003-2015 JNode.org * * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ package org.jnode.plugin.model; import gnu.java.security.action.GetPolicyAction; import java.net.URL; import java.nio.ByteBuffer; import java.security.AccessController; import java.security.CodeSource; import java.security.Policy; import java.security.ProtectionDomain; import java.security.cert.Certificate; import java.util.ArrayList; import java.util.Collection; import java.util.Enumeration; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import org.jnode.bootlog.BootLogInstance; import org.jnode.plugin.PluginClassLoader; import org.jnode.plugin.PluginDescriptor; import org.jnode.plugin.PluginException; import org.jnode.vm.ResourceLoader; import org.jnode.vm.classmgr.VmClassLoader; /** * @author Ewout Prangsma (epr@users.sourceforge.net) */ final class PluginClassLoaderImpl extends ClassLoader implements PluginClassLoader { /** * The registry */ private final PluginRegistryModel registry; /** * The descriptor */ private final PluginDescriptorModel descriptor; /** * The plugin jar file */ private final PluginJar jar; /** * The classloaders of the prerequisite plugins */ private final PluginClassLoaderImpl[] prerequisiteLoaders; /** * Initialize this instance. * * @param jar */ public PluginClassLoaderImpl(PluginRegistryModel registry, PluginDescriptorModel descr, PluginJar jar, PluginClassLoaderImpl[] prerequisiteLoaders) { this.registry = registry; this.descriptor = descr; this.jar = jar; this.prerequisiteLoaders = prerequisiteLoaders; } /** * Wrap this {@link ClassLoader} around the given vmClassLoader. * Requires special permission. * * @param vmClassLoader * @param registry * @param descr * @param jar * @param prerequisiteLoaders */ protected PluginClassLoaderImpl(VmClassLoader vmClassLoader, PluginRegistryModel registry, PluginDescriptorModel descr, PluginJar jar, PluginClassLoaderImpl[] prerequisiteLoaders) { super(ClassLoader.getSystemClassLoader(), vmClassLoader); this.registry = registry; this.descriptor = descr; this.jar = jar; this.prerequisiteLoaders = prerequisiteLoaders; } /** * Gets the names of the classes contained in this plugin. * * @return */ public Set<String> getClassNames() { HashSet<String> classNames = new HashSet<String>(); for (String name : jar.resourceNames()) { if (name.endsWith(".class")) { name = name.substring(0, name.length() - 6); classNames.add(name.replace('/', '.')); } } return classNames; } /** * Gets the names of the resources contained in this plugin. * * @return the set of contained resources */ public Collection<String> getResources() { return jar.resourceNames(); } /** * Finds the specified class. This method should be overridden by class * loader implementations that follow the new delegation model for loading * classes, and will be called by the loadClass method after checking the * parent class loader for the requested class. The default implementation * throws ClassNotFoundException. * * @param name * @return Class * @throws ClassNotFoundException * @see java.lang.ClassLoader#findClass(java.lang.String) */ protected final Class<?> findClass(String name) throws ClassNotFoundException { final Class<?> cls = findPluginClass(name); if (cls != null) { return cls; } else { // Not found throw new ClassNotFoundException(name); } } /** * Finds the specified class. This method should be overridden by class * loader implementations that follow the new delegation model for loading * classes, and will be called by the loadClass method after checking the * parent class loader for the requested class. The default implementation * throws ClassNotFoundException. * * @param name * @return Class The class, or null if not found. * @see java.lang.ClassLoader#findClass(java.lang.String) */ private final Class<?> findPluginClass(String name) { // Try the prerequisite loaders first final int max = prerequisiteLoaders.length; for (int i = 0; i < max; i++) { final PluginClassLoaderImpl cl = prerequisiteLoaders[i]; if (cl != null) { final Class<?> cls = cl.findPluginClass(name); if (cls != null) { return cls; } } } // Try the loaded classes first final Class<?> loadedCls = findLoadedClass(name); if (loadedCls != null) { return loadedCls; } // Look for it in the fragments ByteBuffer b = null; FragmentDescriptorModel fragment = null; for (FragmentDescriptorModel l : descriptor.fragments()) { b = loadClassData(l, name); if (b != null) { fragment = l; break; } } // Look for it in our own jar if (b == null) { b = loadClassData(jar, name); } if (b != null) { // We're are now going to use one of my classes, // so make sure that my plugin has been started. try { startPlugin(); if (fragment != null) { fragment.startPlugin(registry); } } catch (PluginException ex) { BootLogInstance.get().error("Error starting plugin", ex); } // Define package (if needed) final int lastDotIndex = name.lastIndexOf('.'); if (lastDotIndex > 0) { String packageName = name.substring(0, lastDotIndex); if (getPackage(packageName) == null) { String specTitle = null; String specVendor = null; String specVersion = null; String implTitle = descriptor.getName(); String implVendor = descriptor.getProviderName(); String implVersion = descriptor.getVersion().toString(); URL sealed = null; definePackage(packageName, specTitle, specVendor, specVersion, implTitle, implVendor, implVersion, sealed); } } final URL sourceUrl = jar.getResource(name.replace('.', '/') + ".class"); final CodeSource cs = new CodeSource(sourceUrl, (Certificate[]) null); final Policy policy = (Policy) AccessController .doPrivileged(GetPolicyAction.getInstance()); final ProtectionDomain pd = new ProtectionDomain(cs, policy .getPermissions(cs)); final Class<?> cls = defineClass(name, b, pd); resolveClass(cls); return cls; } else { return null; } } /** * Does this classloader contain the specified class. * * @return boolean */ protected final boolean containsClass(String name) { final String resName = name.replace('.', '/') + ".class"; if (jar.containsResource(resName)) { return true; } for (ResourceLoader l : descriptor.fragments()) { if (l.containsResource(resName)) { return true; } } return false; } /** * Finds the resource with the given name. Class loader implementations * should override this method to specify where to find resources. * * @param name * @return URL * @see java.lang.ClassLoader#findResource(java.lang.String) */ protected final URL findResource(String name) { // Try the prerequisite loaders first final int max = prerequisiteLoaders.length; for (int i = 0; i < max; i++) { final PluginClassLoaderImpl cl = prerequisiteLoaders[i]; if (cl != null) { final URL url = cl.findResource(name); if (url != null) { return url; } } } // Try the fragments URL url = null; FragmentDescriptorModel fragment = null; for (FragmentDescriptorModel f : descriptor.fragments()) { url = f.getResource(name); if (url != null) { fragment = f; break; } } // Not found, try my own plugin // System.out.println("Try resource " + name + " on " + // jar.getDescriptor().getId()); if (url == null) { url = jar.getResource(name); } if (url != null) { try { startPlugin(); if (fragment != null) { fragment.startPlugin(registry); } } catch (PluginException ex) { BootLogInstance.get().error("Cannot start plugin", ex); } } return url; } public Enumeration<?> getResources(String name) { System.err.println("getResources " + name); final List<URL> urls = new ArrayList<URL>(); // // Try the prerequisite loaders first final int max = prerequisiteLoaders.length; for (int i = 0; i < max; i++) { final PluginClassLoaderImpl cl = prerequisiteLoaders[i]; if (cl != null) { final URL url = cl.findResource(name); if (url != null) { System.err.println("adding " + url); if (!urls.contains(url)) urls.add(url); } } } // Try the fragments URL url = null; for (FragmentDescriptorModel fragment : descriptor.fragments()) { url = fragment.getResource(name); if (url != null) { try { startPlugin(); fragment.startPlugin(registry); } catch (PluginException ex) { BootLogInstance.get().error("Cannot start plugin", ex); } System.err.println("adding " + url); if (!urls.contains(url)) urls.add(url); } } // Not found, try my own plugin // System.out.println("Try resource " + name + " on " + // jar.getDescriptor().getId()); url = jar.getResource(name); if (url != null) { try { startPlugin(); } catch (PluginException ex) { BootLogInstance.get().error("Cannot start plugin", ex); } System.err.println("adding " + url); if (!urls.contains(url)) urls.add(url); } // return new Enumeration<URL>() { private Iterator<URL> it = urls.iterator(); public boolean hasMoreElements() { return it.hasNext(); } public URL nextElement() { return it.next(); } }; } /** * Try to load the data of a class with a given name. * * @param name * @return The loaded class data or null if not found. */ private final ByteBuffer loadClassData(ResourceLoader loader, String name) { return loader.getResourceAsBuffer(name.replace('.', '/') + ".class"); } /** * Make sure that the plugin gets started. This method ensures that this * classloader can be used to start the plugin. */ private final void startPlugin() throws PluginException { descriptor.startPlugin(registry); } /** * @see org.jnode.plugin.PluginClassLoader#getDeclaringPluginDescriptor() */ public PluginDescriptor getDeclaringPluginDescriptor() { return descriptor; } public String toString() { return getClass().getName() + '(' + getDeclaringPluginDescriptor().getId() + ')'; } }