/* * $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.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.security.AccessController; import java.security.PrivilegedAction; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; import java.util.ArrayList; import java.util.List; import org.jnode.bootlog.BootLogInstance; import org.jnode.nanoxml.XMLElement; import org.jnode.plugin.Extension; import org.jnode.plugin.ExtensionPoint; import org.jnode.plugin.Plugin; import org.jnode.plugin.PluginDescriptor; import org.jnode.plugin.PluginDescriptorListener; import org.jnode.plugin.PluginException; import org.jnode.plugin.PluginPrerequisite; import org.jnode.plugin.PluginReference; import org.jnode.plugin.Runtime; import org.jnode.util.Version; import org.jnode.vm.VmSystem; import org.jnode.vm.classmgr.VmClassLoader; import org.jnode.vm.isolate.VmIsolateLocal; import org.jnode.vm.objects.BootableArrayList; /** * Implementation of {@link org.jnode.plugin.PluginDescriptor}. * * @author epr */ public class PluginDescriptorModel extends AbstractModelObject implements PluginDescriptor { private final boolean autoStart; private transient VmIsolateLocal<ClassLoader> classLoaderHolder; private transient VmClassLoader vmClassLoader; private final String className; private final ExtensionPointModel[] extensionPoints; private final ExtensionModel[] extensions; private final List<FragmentDescriptorModel> fragments; private final String id; private final PluginJar jarFile; private final String licenseName; private final String licenseUrl; private List<PluginDescriptorListener> listeners; private final Object listenerLock = new Object(); private final String name; private Plugin plugin; private final String providerName; private final String providerUrl; private PluginRegistryModel registry; private final PluginPrerequisiteModel[] requires; private boolean resolved; private final RuntimeModel runtime; private boolean started = false; private boolean starting = false; private final Object startLock = new Object(); private final boolean system; private final Version version; private final int priority; private PluginReference reference; /** * Create a new instance * * @param rootElement the root XMLElement for the XML plugin descriptor * @param jarFile the PluginJar object to associate with the descriptor. */ PluginDescriptorModel(PluginJar jarFile, XMLElement rootElement) throws PluginException { this.jarFile = jarFile; this.fragments = new BootableArrayList<FragmentDescriptorModel>(); id = getAttribute(rootElement, "id", true); name = getAttribute(rootElement, "name", true); providerName = getAttribute(rootElement, "provider-name", false); providerUrl = getAttribute(rootElement, "provider-url", false); licenseName = getAttribute(rootElement, "license-name", true); licenseUrl = getAttribute(rootElement, "license-url", false); version = new Version(getAttribute(rootElement, "version", true)); className = getAttribute(rootElement, "class", false); system = getBooleanAttribute(rootElement, "system", false); autoStart = getBooleanAttribute(rootElement, "auto-start", false); priority = Math.min(MAX_PRIORITY, Math.max(MIN_PRIORITY, getIntAttribute(rootElement, "priority", DEFAULT_PRIORITY))); // if (registry != null) { // registry.registerPlugin(this); // } final ArrayList<ExtensionPointModel> epList = new ArrayList<ExtensionPointModel>(); final ArrayList<ExtensionModel> exList = new ArrayList<ExtensionModel>(); final ArrayList<PluginPrerequisiteModel> reqList = new ArrayList<PluginPrerequisiteModel>(); RuntimeModel runtime = null; initializeRequiresList(reqList, rootElement); for (final XMLElement childE : rootElement.getChildren()) { final String tag = childE.getName(); if (tag.equals("extension-point")) { final ExtensionPointModel ep = new ExtensionPointModel(this, childE); epList.add(ep); // if (registry != null) { // registry.registerExtensionPoint(ep); // } } else if (tag.equals("requires")) { for (final XMLElement impE : childE.getChildren()) { if (impE.getName().equals("import")) { reqList.add(new PluginPrerequisiteModel(this, impE)); } else { throw new PluginException("Unknown element " + impE.getName()); } } } else if (tag.equals("extension")) { exList.add(new ExtensionModel(this, childE)); } else if (tag.equals("runtime")) { if (runtime == null) { runtime = new RuntimeModel(this, childE); } else { throw new PluginException("duplicate runtime element"); } } else { throw new PluginException("Unknown element " + tag); } } if (!epList.isEmpty()) { extensionPoints = (ExtensionPointModel[]) epList .toArray(new ExtensionPointModel[epList.size()]); } else { extensionPoints = new ExtensionPointModel[0]; } if (!reqList.isEmpty()) { requires = (PluginPrerequisiteModel[]) reqList .toArray(new PluginPrerequisiteModel[reqList.size()]); } else { requires = new PluginPrerequisiteModel[0]; } if (!exList.isEmpty()) { extensions = (ExtensionModel[]) exList .toArray(new ExtensionModel[exList.size()]); } else { extensions = new ExtensionModel[0]; } this.runtime = runtime; } /** * Create a plugin descriptor without a PluginJar object * * @param e the root XMLElement for the XML plugin descriptor */ PluginDescriptorModel(XMLElement e) throws PluginException { this(null, e); } /** * Add a fragment to this plugin. This method is called only by * {@link FragmentDescriptorModel#resolve(PluginRegistryModel) }. * * @param fragment */ final void add(FragmentDescriptorModel fragment) { fragments.add(fragment); if (isSystemPlugin()) { VmSystem.getSystemClassLoader().add(fragment); } } /** * Add a listener to this descriptor. * * @param listener */ public final void addListener(PluginDescriptorListener listener) { synchronized (listenerLock) { if (listeners == null) { listeners = new ArrayList<PluginDescriptorListener>(); } listeners.add(listener); } } /** * Create the plugin described by this descriptor * @return the Plugin so created. */ private Plugin createPlugin() throws PluginException { if (className == null) { return new EmptyPlugin(this); } else { try { // final Class cls = // Thread.currentThread().getContextClassLoader().loadClass(className); final ClassLoader cl = getPluginClassLoader(); // System.out.println("cl=" + cl.getClass().getName()); final Class<?> cls = cl.loadClass(className); // Loading the class may have triggered the creation of the // plugin already. if (plugin != null) { return plugin; } else { final Constructor<?> cons = cls .getConstructor(new Class[]{PluginDescriptor.class}); return (Plugin) cons.newInstance(new Object[]{this}); } } catch (ClassNotFoundException ex) { throw new PluginException(ex); } catch (IllegalAccessException ex) { throw new PluginException(ex); } catch (InstantiationException ex) { throw new PluginException(ex); } catch (InvocationTargetException ex) { throw new PluginException(ex.getTargetException()); } catch (NoSuchMethodException ex) { throw new PluginException(ex); } } } /** * Does the plugin described by this descriptor directly depends on the * given plugin id. * * @param id * @return True if id is in the list of required plugins of this descriptor, * false otherwise. */ public boolean depends(String id) { final PluginPrerequisite[] req = this.requires; final int max = req.length; for (int i = 0; i < max; i++) { if (req[i].getPluginReference().getId().equals(id)) { return true; } } return false; } /** * {@inheritDoc} */ public final void firePluginStarted() { final List<PluginDescriptorListener> listeners; synchronized (listenerLock) { if (this.listeners != null) { listeners = new ArrayList<PluginDescriptorListener>( this.listeners); } else { return; } } for (PluginDescriptorListener l : listeners) { l.pluginStarted(this); } } /** * {@inheritDoc} */ public final void firePluginStopped() { final List<PluginDescriptorListener> listeners; synchronized (listenerLock) { if (this.listeners != null) { listeners = new ArrayList<PluginDescriptorListener>( this.listeners); } else { return; } } for (PluginDescriptorListener l : listeners) { l.pluginStop(this); } } /** * Gets all fragments attached to this plugin. * * @return the fragments. */ public final List<FragmentDescriptorModel> fragments() { return fragments; } /** * Gets the name of the custom plugin class of this plugin. * * @return the custom class name or {@code null} */ public String getCustomPluginClassName() { return className; } /** * Returns the extension point with the given simple identifier declared in * this plug-in, or null if there is no such extension point. * * @param extensionPointId the simple identifier of the extension point (e.g. "wizard"). * @return the extension point, or null */ public ExtensionPoint getExtensionPoint(String extensionPointId) { final int max = extensionPoints.length; for (int i = 0; i < max; i++) { final ExtensionPoint ep = extensionPoints[i]; if (ep.getSimpleIdentifier().equals(extensionPointId)) { return ep; } } return null; } /** * Gets all extension-points provided by this plugin * * @return the extension points as an array. */ public ExtensionPoint[] getExtensionPoints() { return extensionPoints; } /** * Gets all extensions provided by this plugin * * @return the estensions as an array. */ public Extension[] getExtensions() { return extensions; } /** * Gets the registry this plugin is declared in. */ /* * public PluginRegistry getPluginRegistry() { return registry; } */ /** * Gets the unique identifier of this plugin. * @return the plugin identifier. */ public String getId() { return id; } /** * Get the plugin's JAR file as a PluginJar object. * @return the plugin's JAR file or {@code null} */ public final PluginJar getJarFile() { return this.jarFile; } public String getLicenseName() { return licenseName; } public String getLicenseUrl() { return licenseUrl; } /** * Gets the human readable name for this plugin */ public String getName() { return name; } /** * Gets the plugin that is described by this descriptor. If no plugin class * is given in the descriptor, an empty plugin is returned. This method will * always returns the same plugin instance for a given descriptor. */ public Plugin getPlugin() throws PluginException { if (plugin == null) { plugin = createPlugin(); } return plugin; } /** * Gets the classloader of this plugin descriptor. * * @return ClassLoader */ public ClassLoader getPluginClassLoader() { if (classLoaderHolder == null) { classLoaderHolder = new VmIsolateLocal<ClassLoader>(); } if (classLoaderHolder.get() == null) { if (system) { classLoaderHolder.set(ClassLoader.getSystemClassLoader()); } else { classLoaderHolder.set(createClassLoader()); } } return classLoaderHolder.get(); } private final PluginClassLoaderImpl createClassLoader() { if (registry == null) { throw new RuntimeException("Plugin is not resolved yet"); } if (jarFile == null) { throw new RuntimeException( "Cannot create classloader without a jarfile"); } final int reqMax = requires.length; final PluginClassLoaderImpl[] preLoaders = new PluginClassLoaderImpl[reqMax]; for (int i = 0; i < reqMax; i++) { final String reqId = requires[i].getPluginReference().getId(); final PluginDescriptor reqDescr = registry.getPluginDescriptor(reqId); final ClassLoader cl = reqDescr.getPluginClassLoader(); if (cl instanceof PluginClassLoaderImpl) { preLoaders[i] = (PluginClassLoaderImpl) cl; } } final VmClassLoader currentVmClassLoader = this.vmClassLoader; final PrivilegedAction<Object[]> a = new PrivilegedAction<Object[]>() { public Object[] run() { if (currentVmClassLoader != null) { PluginClassLoaderImpl cl = new PluginClassLoaderImpl( currentVmClassLoader, registry, PluginDescriptorModel.this, jarFile, preLoaders); return new Object[]{cl, currentVmClassLoader}; } else { PluginClassLoaderImpl cl = new PluginClassLoaderImpl( registry, PluginDescriptorModel.this, jarFile, preLoaders); return new Object[]{cl, cl.getVmClassLoader()}; } } }; final Object[] result = AccessController.doPrivileged(a); this.vmClassLoader = (VmClassLoader) result[1]; return (PluginClassLoaderImpl) result[0]; } /** * Gets the plugin's prerequisites. * * @return the prerequisites as an array. */ public PluginPrerequisite[] getPrerequisites() { return requires; } /** * Gets the name of the provider of this plugin */ public String getProviderName() { return providerName; } public String getProviderUrl() { return providerUrl; } /** * Gets the runtime information of this descriptor. * * @return The runtime, or null if no runtime information is provided. */ public Runtime getRuntime() { return runtime; } /** * Gets the version of this plugin */ public Version getVersion() { return version; } public PluginReference getPluginReference() { if (reference == null) { // lazy creation reference = new PluginReference(id, version); } return reference; } /** * Does this plugin have a custom plugin class specified? * * @return {@code true} if this plugin has a custom plugin class, {@code false} otherwise. */ public boolean hasCustomPluginClass() { return (className != null); } /** * Initialize the list of plugin requirements. */ protected void initializeRequiresList(List<PluginPrerequisiteModel> list, XMLElement e) throws PluginException { // Nothing here } /** * Has this plugin the auto-start flag set. If true, the plugin will be * started automatically at boot/load time. * * @return the plugin's autostart flag. */ public boolean isAutoStart() { return autoStart; } /** * Gets the priority of this plugin. Plugins are loaded by increasing * priority. * * @return the plugin's priority. */ public int getPriority() { return priority; } /** * Is this a descriptor of a fragment. * * @return {@code true} for a fragment, {@code false} for a plugin. */ public boolean isFragment() { return false; } /** * Is this a descriptor of a system plugin. System plugins are not * reloadable. * * @return {@code true} if the plugin is reloadable. */ public boolean isSystemPlugin() { return system; } /** * Remove a fragment from this plugin. This method is called only by * {@link FragmentDescriptorModel#unresolve(PluginRegistryModel)}. * * @param fragment the fagment to be removed */ final void remove(FragmentDescriptorModel fragment) { if (isSystemPlugin()) { VmSystem.getSystemClassLoader().remove(fragment); } fragments.remove(fragment); } /** * Remove a listener from this descriptor. * * @param listener the listener to be removed. */ public final void removeListener(PluginDescriptorListener listener) { synchronized (listenerLock) { if (listeners != null) { listeners.remove(listener); } } } /** * Resolve all references to (elements of) other plugins * * @param registry the registry that will be used to resolve references. * @throws PluginException */ public void resolve(PluginRegistryModel registry) throws PluginException { if ((this.registry != null) && (this.registry != registry)) { throw new SecurityException("Cannot overwrite the registry"); } if (!resolved) { // BootLogInstance.get().info("Resolve " + id); this.registry = registry; registry.registerPlugin(this); for (ExtensionPointModel extensionPoint : extensionPoints) { extensionPoint.resolve(registry); } for (PluginPrerequisiteModel require : requires) { require.resolve(registry); } if (runtime != null) { runtime.resolve(registry); } resolved = true; for (ExtensionModel extension : extensions) { extension.resolve(registry); } } } /** * Start this plugin. This descriptor is resolved. All plugins that this * plugin depends on, are started first. * @param registry the registry that will be used to resolve references. */ final void startPlugin(final PluginRegistryModel registry) throws PluginException { if (started || starting) { return; } synchronized (startLock) { if (started || starting) { return; } starting = true; } // BootLogInstance.get().info("Resolve on plugin " + getId()); try { AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() { public Object run() throws PluginException { resolve(registry); final int reqMax = requires.length; for (int i = 0; i < reqMax; i++) { final String reqId = requires[i].getPluginReference().getId(); // BootLogInstance.get().info("Start dependency " + reqId); final PluginDescriptorModel reqDescr = (PluginDescriptorModel) registry .getPluginDescriptor(reqId); reqDescr.startPlugin(registry); // Make sure that it is really started reqDescr.waitUntilStarted(); } // BootLogInstance.get().info("Start myself " + getId()); getPlugin().start(); return null; } }); } catch (PrivilegedActionException ex) { BootLogInstance.get().error("Error starting plugin", ex); /*try { Thread.sleep(10000); } catch (InterruptedException ex1) { // Ignore }*/ } finally { synchronized (startLock) { started = true; startLock.notifyAll(); } } } /** * Block the current thread until this plugin is started. * If the current thread is starting this plugin, then the thread * will not be blocked. * * @throws PluginException if the blocked thread was interrupted. */ private void waitUntilStarted() throws PluginException { if (!started) { synchronized (startLock) { if (starting) { // I'm the thread doing the start, otherwise // we would have been blocked here. return; } while (!started) { try { startLock.wait(); } catch (InterruptedException ex) { throw new PluginException(ex); } } } } } public String toString() { return getId(); } /** * Remove all references to (elements of) other plugin descriptors * * @param registry the registry that will be used to unresolve references. * @throws PluginException */ protected void unresolve(PluginRegistryModel registry) throws PluginException { if (plugin != null) { plugin.stop(); } if (runtime != null) { runtime.unresolve(registry); } for (PluginPrerequisiteModel require : requires) { require.unresolve(registry); } for (ExtensionPointModel extensionPoint : extensionPoints) { extensionPoint.unresolve(registry); } for (ExtensionModel extension : extensions) { extension.unresolve(registry); } registry.unregisterPlugin(this); resolved = false; } }