/* * $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 java.io.IOException; import java.net.URL; import java.nio.ByteBuffer; import java.security.AccessController; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.jnode.plugin.Extension; import org.jnode.plugin.ExtensionPoint; import org.jnode.plugin.PluginDescriptor; import org.jnode.plugin.PluginException; import org.jnode.plugin.PluginLoader; import org.jnode.plugin.PluginPrerequisite; import org.jnode.plugin.PluginReference; import org.jnode.plugin.PluginRegistry; import org.jnode.plugin.PluginSecurityConstants; import org.jnode.vm.isolate.VmIsolateLocal; import org.jnode.vm.objects.BootableHashMap; import org.jnode.vm.objects.VmSystemObject; import org.xml.sax.EntityResolver; import org.xml.sax.InputSource; import org.xml.sax.SAXException; /** * @author epr */ public final class PluginRegistryModel extends VmSystemObject implements PluginRegistry { /** * A map of all descriptors (id, descriptor) */ private final BootableHashMap<String, PluginDescriptor> descriptorMap; /** * A map off all extensionpoints (id, ep) */ private final BootableHashMap<String, ExtensionPoint> extensionPoints; private transient VmIsolateLocal<PluginsClassLoader> classLoaderHolder; /** * Initialize this instance. * * @param pluginFiles */ PluginRegistryModel(URL[] pluginFiles) throws PluginException { this.extensionPoints = new BootableHashMap<String, ExtensionPoint>(); this.descriptorMap = new BootableHashMap<String, PluginDescriptor>(); final List<PluginDescriptorModel> descriptors = loadDescriptors(pluginFiles); resolveDescriptors(descriptors); } /** * Gets the descriptor of the plugin with the given id. * * @param pluginId * @return The plugin descriptor found, or null if not found */ public PluginDescriptor getPluginDescriptor(String pluginId) { return (PluginDescriptor) descriptorMap.get(pluginId); } /** * Gets the extension point with the given id. * * @param id * @return The extension point found, or null if not found */ public ExtensionPoint getExtensionPoint(String id) { return (ExtensionPoint) extensionPoints.get(id); } /** * Returns an iterator to iterate over all PluginDescriptor's. * * @return Iterator<PluginDescriptor> */ public Iterator<PluginDescriptor> iterator() { return descriptorMap.values().iterator(); } /** * Load all plugin descriptors. * * @param pluginUrls */ private List<PluginDescriptorModel> loadDescriptors(URL[] pluginUrls) throws PluginException { final int max = pluginUrls.length; final ArrayList<PluginDescriptorModel> descriptors = new ArrayList<PluginDescriptorModel>(max); for (int i = 0; i < max; i++) { //System.out.println(pluginUrls[i]); descriptors.add(loadPlugin(pluginUrls[i], false)); } return descriptors; } /** * Resolve all plugin descriptors. */ public void resolveDescriptors() throws PluginException { for (PluginDescriptor pluginDescriptor : descriptorMap.values()) { final PluginDescriptorModel descr = (PluginDescriptorModel) pluginDescriptor; descr.resolve(this); } } /** * Resolve the plugins corresponding to a collection of supplied descriptors. * * @param descriptors the collection of descriptors to be resolved. */ public void resolveDescriptors(Collection<PluginDescriptorModel> descriptors) throws PluginException { // This method uses the brute-force approach of repeatedly checking the descriptors // in order until if finds one that will resolve. This gives O(N**2) calls to // canResolve unless the descriptor collection is suitably ordered. while (!descriptors.isEmpty()) { boolean change = false; for (Iterator<PluginDescriptorModel> i = descriptors.iterator(); i.hasNext();) { final PluginDescriptorModel descr = i.next(); if (canResolve(descr)) { descr.resolve(this); i.remove(); change = true; } } if (!change) { throw new PluginException("Failed to resolve all descriptors: " + descriptors); } } } /** * Check to see if a plugin is resolvable given the current (simulated) registry state. * * @param descr the descriptor for the plugin at issue. * @return <code>true</code> if the plugin is resolvable. */ private final boolean canResolve(PluginDescriptor descr) { final PluginPrerequisite reqs[] = descr.getPrerequisites(); final int length = reqs.length; for (int i = 0; i < length; i++) { // We cannot (yet) resolve a plugin if it imports an as-yet unresolved plugin. if (getPluginDescriptor(reqs[i].getPluginReference().getId()) == null) { return false; } } final Extension[] exts = descr.getExtensions(); final ExtensionPoint[] extPoints = descr.getExtensionPoints(); final int extsLength = exts.length; final int extPointsLength = extPoints.length; for (int i = 0; i < extsLength; i++) { // We cannot resolve a plugin has an extension whose extension point defined by an // as-yet unresolved plugin. However, if an extension and its matching extension point // are defined in the same plugin, this will not prevent plugin resolution. if (getPluginDescriptor(exts[i].getExtensionPointPluginId()) == null) { boolean oneOfMine = false; String epId = exts[i].getExtensionPointUniqueIdentifier(); for (int j = 0; j < extPointsLength; j++) { if (epId.equals(extPoints[j].getUniqueIdentifier())) { oneOfMine = true; break; } } if (!oneOfMine) { return false; } } } return true; } /** * Register a plugin descriptor. * * @param descr */ protected synchronized void registerPlugin(PluginDescriptorModel descr) throws PluginException { final String id = descr.getId(); if (descriptorMap.containsKey(id)) { throw new PluginException("Duplicate plugin " + id); } descriptorMap.put(id, descr); } /** * Register a plugin descriptor. * * @param descr */ protected synchronized void unregisterPlugin(PluginDescriptorModel descr) throws PluginException { final String id = descr.getId(); descriptorMap.remove(id); } /** * Register a known extension point. * * @param ep */ protected synchronized void registerExtensionPoint(ExtensionPoint ep) throws PluginException { final BootableHashMap<String, ExtensionPoint> epMap = this.extensionPoints; if (epMap.containsKey(ep.getUniqueIdentifier())) { throw new PluginException( "Duplicate extension point " + ep.getUniqueIdentifier()); } epMap.put(ep.getUniqueIdentifier(), ep); } /** * Unregister a known extension point. * * @param ep */ protected synchronized void unregisterExtensionPoint(ExtensionPoint ep) throws PluginException { final BootableHashMap<String, ExtensionPoint> epMap = this.extensionPoints; epMap.remove(ep.getUniqueIdentifier()); } /** * Load a plugin from a given URL. This will not activate the plugin. * * @param pluginUrl * @return The descriptor of the loaded plugin. * @throws PluginException */ public PluginDescriptorModel loadPlugin(final URL pluginUrl, boolean resolve) throws PluginException { final PluginRegistryModel registry = this; final PluginJar pluginJar; try { pluginJar = AccessController .doPrivileged(new PrivilegedExceptionAction<PluginJar>() { public PluginJar run() throws PluginException, IOException { return new PluginJar(registry, pluginUrl); } }); } catch (PrivilegedActionException pax) { final Throwable ex = pax.getException(); if (ex instanceof PluginException) { throw (PluginException) ex; } else { throw new PluginException(ex); } } final PluginDescriptorModel descr = pluginJar.getDescriptorModel(); if (resolve) { descr.resolve(this); } return descr; } /** * {@inheritDoc} */ public PluginDescriptor loadPlugin(final PluginLoader loader, final PluginReference pluginReference, boolean resolve) throws PluginException { final SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(PluginSecurityConstants.LOAD_PERM); } // Load the requested plugin final PluginDescriptorModel descr = loadPluginImpl(loader, pluginReference); if (resolve) { final HashMap<String, PluginDescriptorModel> descriptors = new HashMap<String, PluginDescriptorModel>(); descriptors.put(descr.getId(), descr); // Load the dependent plugins loadDependencies(loader, descr, descriptors); // Resolve the loaded descriptors. resolveDescriptors(descriptors.values()); } return descr; } private final void loadDependencies(PluginLoader loader, PluginDescriptorModel descr, Map<String, PluginDescriptorModel> descriptors) throws PluginException { // Prerequisites final PluginPrerequisite reqs[] = descr.getPrerequisites(); final int reqLength = reqs.length; for (int i = 0; i < reqLength; i++) { final PluginPrerequisite req = reqs[i]; loadDependency(loader, req.getPluginReference(), descriptors); } // Extensions final Extension[] exts = descr.getExtensions(); final int extLength = exts.length; for (int i = 0; i < extLength; i++) { final Extension ext = exts[i]; final String id = ext.getExtensionPointPluginId(); loadDependency(loader, new PluginReference(id, descr.getVersion()), descriptors); } } private final void loadDependency(PluginLoader loader, PluginReference dependency, Map<String, PluginDescriptorModel> descriptors) throws PluginException { if (getPluginDescriptor(dependency.getId()) != null) { return; } if (descriptors.containsKey(dependency.getId())) { return; } final PluginDescriptorModel descr = loadPluginImpl(loader, dependency); descriptors.put(descr.getId(), descr); loadDependencies(loader, descr, descriptors); } /** * Load a plugin from a given loader but doesn't resolve its dependencies. * * @param loader * @param pluginReference * @return The descriptor of the loaded plugin. * @throws PluginException */ private PluginDescriptorModel loadPluginImpl(final PluginLoader loader, final PluginReference pluginReference) throws PluginException { final PluginRegistryModel registry = this; final PluginJar pluginJar; try { pluginJar = AccessController .doPrivileged(new PrivilegedExceptionAction<PluginJar>() { public PluginJar run() throws PluginException, IOException { final ByteBuffer buf = loader.getPluginBuffer(pluginReference); if (buf == null) { throw new PluginException( "Plugin " + pluginReference + " not found"); } return new PluginJar(registry, buf, null); } }); } catch (PrivilegedActionException pax) { final Throwable ex = pax.getException(); if (ex instanceof PluginException) { throw (PluginException) ex; } else { throw new PluginException(ex); } } return pluginJar.getDescriptorModel(); } /** * Remove the plugin with the given id from this registry. * * @param pluginId * @throws PluginException */ public synchronized List<PluginReference> unloadPlugin(String pluginId) throws PluginException { final SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(PluginSecurityConstants.UNLOAD_PERM); } final List<PluginReference> unloadedIds = new ArrayList<PluginReference>(); doUnloadPlugin(pluginId, unloadedIds); Collections.reverse(unloadedIds); return unloadedIds; } /** * Unload the given plugin and all plugins that depends on it. * * @param pluginId * @param unloadedIds * @throws PluginException */ private final void doUnloadPlugin(String pluginId, List<PluginReference> unloadedIds) throws PluginException { final PluginDescriptorModel descr = (PluginDescriptorModel) getPluginDescriptor(pluginId); if (descr != null) { if (descr.isSystemPlugin()) { throw new PluginException( "Cannot unload a system plugin: " + pluginId); } // Unload all plugins that depend on this plugin final ArrayList<PluginDescriptor> descriptors = new ArrayList<PluginDescriptor>(descriptorMap.values()); for (PluginDescriptor dep : descriptors) { if (dep.depends(pluginId)) { doUnloadPlugin(dep.getId(), unloadedIds); } } // Now remove it unloadedIds.add(descr.getPluginReference()); descr.unresolve(this); } } /** * Gets the classloader that loads classes from all loaded plugins. * * @return ClassLoader */ public ClassLoader getPluginsClassLoader() { if (classLoaderHolder == null) { classLoaderHolder = new VmIsolateLocal<PluginsClassLoader>(); } if (classLoaderHolder.get() == null) { classLoaderHolder.set(new PluginsClassLoader(this)); } return classLoaderHolder.get(); } static class DTDResolver implements EntityResolver { /** * @see org.xml.sax.EntityResolver#resolveEntity(java.lang.String, * java.lang.String) */ public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException { if ((systemId != null) && systemId.endsWith("jnode.dtd")) { return new InputSource(getClass().getResourceAsStream( "/jnode.dtd")); } else { return null; } } } }