Commit 0879759f authored by Matt Tucker's avatar Matt Tucker Committed by matt

Added support for child plugins (JM-336).


git-svn-id: http://svn.igniterealtime.org/svn/repos/messenger/trunk@1713 b35dd754-fafc-0310-a699-88a17e54d16e
parent 832a1048
...@@ -91,6 +91,10 @@ file might look like the following: ...@@ -91,6 +91,10 @@ file might look like the following:
<li>minServerVersion -- the minimum version of Jive Messenger required <li>minServerVersion -- the minimum version of Jive Messenger required
to run the plugin (supported by Jive Messenger 2.1.2 and later). If the to run the plugin (supported by Jive Messenger 2.1.2 and later). If the
server version is less than the required value, the plugin will not be started.</li> server version is less than the required value, the plugin will not be started.</li>
<li>parentPlugin -- the name of the parent plugin (given as "foo" for the "foo.jar" plugin).
When a plugin has a parent plugin, the parent plugin's classloader will be used instead
of creating a new classloader. This lets plugins work together more closely. A
child plugin will not function without its parent present.</li>
</ul></p> </ul></p>
Several additional files can be present in the plugin to provide additional information to Several additional files can be present in the plugin to provide additional information to
......
...@@ -11,6 +11,8 @@ ...@@ -11,6 +11,8 @@
package org.jivesoftware.messenger.container; package org.jivesoftware.messenger.container;
import org.jivesoftware.util.Log;
import java.io.File; import java.io.File;
import java.io.FilenameFilter; import java.io.FilenameFilter;
import java.net.MalformedURLException; import java.net.MalformedURLException;
...@@ -34,7 +36,7 @@ import java.util.List; ...@@ -34,7 +36,7 @@ import java.util.List;
class PluginClassLoader { class PluginClassLoader {
private URLClassLoader classLoader; private URLClassLoader classLoader;
private final List list = new ArrayList(); private final List<URL> list = new ArrayList<URL>();
/** /**
...@@ -43,15 +45,24 @@ class PluginClassLoader { ...@@ -43,15 +45,24 @@ class PluginClassLoader {
* @param pluginDir the plugin directory. * @param pluginDir the plugin directory.
* @throws java.lang.SecurityException if the created class loader violates * @throws java.lang.SecurityException if the created class loader violates
* existing security constraints. * existing security constraints.
* @throws java.net.MalformedURLException if a located resource name cannot be
* properly converted to a URL.
*/ */
public PluginClassLoader(File pluginDir) throws MalformedURLException, SecurityException { public PluginClassLoader(File pluginDir) throws SecurityException {
File classesDir = new File(pluginDir, "classes"); addDirectory(pluginDir);
}
/**
* Adds a directory to the class loader. The {@link #initialize()} method should be called
* after adding the directory to make the change take effect.
*
* @param directory the directory.
*/
public void addDirectory(File directory) {
try {
File classesDir = new File(directory, "classes");
if (classesDir.exists()) { if (classesDir.exists()) {
list.add(classesDir.toURL()); list.add(classesDir.toURL());
} }
File libDir = new File(pluginDir, "lib"); File libDir = new File(directory, "lib");
File[] jars = libDir.listFiles(new FilenameFilter() { File[] jars = libDir.listFiles(new FilenameFilter() {
public boolean accept(File dir, String name) { public boolean accept(File dir, String name) {
return name.endsWith(".jar") || name.endsWith(".zip"); return name.endsWith(".jar") || name.endsWith(".zip");
...@@ -65,11 +76,25 @@ class PluginClassLoader { ...@@ -65,11 +76,25 @@ class PluginClassLoader {
} }
} }
} }
catch (MalformedURLException mue) {
Log.error(mue);
}
}
/**
* Adds a URL to the class loader. The {@link #initialize()} method should be called
* after adding the URL to make the change take effect.
*
* @param url the url.
*/
public void addURL(URL url) { public void addURL(URL url) {
list.add(url); list.add(url);
} }
/**
* Initializes the class loader with all configured classpath URLs. This method
* can be called multiple times if the list of URLs changes.
*/
public void initialize(){ public void initialize(){
Iterator urls = list.iterator(); Iterator urls = list.iterator();
URL[] urlArray = new URL[list.size()]; URL[] urlArray = new URL[list.size()];
......
...@@ -37,6 +37,7 @@ import java.util.Map; ...@@ -37,6 +37,7 @@ import java.util.Map;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.ConcurrentHashMap;
import java.util.jar.JarEntry; import java.util.jar.JarEntry;
import java.util.jar.JarFile; import java.util.jar.JarFile;
import java.util.zip.ZipFile; import java.util.zip.ZipFile;
...@@ -62,6 +63,8 @@ public class PluginManager { ...@@ -62,6 +63,8 @@ public class PluginManager {
private boolean setupMode = !(Boolean.valueOf(JiveGlobals.getXMLProperty("setup")).booleanValue()); private boolean setupMode = !(Boolean.valueOf(JiveGlobals.getXMLProperty("setup")).booleanValue());
private ScheduledExecutorService executor = null; private ScheduledExecutorService executor = null;
private Map<Plugin, PluginDevEnvironment> pluginDevelopment; private Map<Plugin, PluginDevEnvironment> pluginDevelopment;
private Map<Plugin, List<String>> parentPluginMap;
private Map<Plugin, String> childPluginMap;
/** /**
* Constructs a new plugin manager. * Constructs a new plugin manager.
...@@ -70,10 +73,12 @@ public class PluginManager { ...@@ -70,10 +73,12 @@ public class PluginManager {
*/ */
public PluginManager(File pluginDir) { public PluginManager(File pluginDir) {
this.pluginDirectory = pluginDir; this.pluginDirectory = pluginDir;
plugins = new HashMap<String, Plugin>(); plugins = new ConcurrentHashMap<String, Plugin>();
pluginDirs = new HashMap<Plugin, File>(); pluginDirs = new HashMap<Plugin, File>();
classloaders = new HashMap<Plugin, PluginClassLoader>(); classloaders = new HashMap<Plugin, PluginClassLoader>();
pluginDevelopment = new HashMap<Plugin, PluginDevEnvironment>(); pluginDevelopment = new HashMap<Plugin, PluginDevEnvironment>();
parentPluginMap = new HashMap<Plugin, List<String>>();
childPluginMap = new HashMap<Plugin, String>();
} }
/** /**
...@@ -81,7 +86,7 @@ public class PluginManager { ...@@ -81,7 +86,7 @@ public class PluginManager {
*/ */
public void start() { public void start() {
executor = new ScheduledThreadPoolExecutor(1); executor = new ScheduledThreadPoolExecutor(1);
executor.scheduleWithFixedDelay(new PluginMonitor(), 0, 10, TimeUnit.SECONDS); executor.scheduleWithFixedDelay(new PluginMonitor(), 0, 15, TimeUnit.SECONDS);
} }
/** /**
...@@ -100,6 +105,7 @@ public class PluginManager { ...@@ -100,6 +105,7 @@ public class PluginManager {
pluginDirs.clear(); pluginDirs.clear();
classloaders.clear(); classloaders.clear();
pluginDevelopment.clear(); pluginDevelopment.clear();
childPluginMap.clear();
} }
/** /**
...@@ -158,11 +164,10 @@ public class PluginManager { ...@@ -158,11 +164,10 @@ public class PluginManager {
if (pluginConfig.exists()) { if (pluginConfig.exists()) {
SAXReader saxReader = new SAXReader(); SAXReader saxReader = new SAXReader();
Document pluginXML = saxReader.read(pluginConfig); Document pluginXML = saxReader.read(pluginConfig);
// See if the plugin specifies a version of Jive Messenger // See if the plugin specifies a version of Jive Messenger
// required to run. // required to run.
Element minServerVersion = (Element)pluginXML.selectSingleNode("/plugin/minServerVersion"); Element minServerVersion = (Element)pluginXML.selectSingleNode("/plugin/minServerVersion");
if (minServerVersion != null) { if (minServerVersion != null) {
String requiredVersion = minServerVersion.getTextTrim(); String requiredVersion = minServerVersion.getTextTrim();
Version version = XMPPServer.getInstance().getServerInfo().getVersion(); Version version = XMPPServer.getInstance().getServerInfo().getVersion();
...@@ -176,7 +181,60 @@ public class PluginManager { ...@@ -176,7 +181,60 @@ public class PluginManager {
return; return;
} }
} }
PluginClassLoader pluginLoader = new PluginClassLoader(pluginDir);
PluginClassLoader pluginLoader;
// Check to see if this is a child plugin of another plugin. If it is, we
// re-use the parent plugin's class loader so that the plugins can interact.
Element parentPluginNode = (Element)pluginXML.selectSingleNode("/plugin/parentPlugin");
if (parentPluginNode != null) {
String parentPlugin = parentPluginNode.getTextTrim();
// See if the parent is already loaded.
if (plugins.containsKey(parentPlugin)) {
pluginLoader = classloaders.get(getPlugin(parentPlugin));
pluginLoader.addDirectory(pluginDir);
}
else {
// See if the parent plugin exists but just hasn't been loaded yet.
// This can only be the case if this plugin name is alphabetically before
// the parent.
if (pluginDir.getName().compareTo(parentPlugin) < 0) {
// See if the parent exists.
File file = new File(pluginDir.getParentFile(), parentPlugin + ".jar");
if (file.exists()) {
// Silently return. The child plugin will get loaded up on the next
// plugin load run after the parent.
return;
}
else {
file = new File(pluginDir.getParentFile(), parentPlugin + ".war");
if (file.exists()) {
// Silently return. The child plugin will get loaded up on the next
// plugin load run after the parent.
return;
}
else {
String msg = "Ignoring plugin " + pluginDir.getName() + ": parent plugin " +
parentPlugin + " not present.";
Log.warn(msg);
System.out.println(msg);
return;
}
}
}
else {
String msg = "Ignoring plugin " + pluginDir.getName() + ": parent plugin " +
parentPlugin + " not present.";
Log.warn(msg);
System.out.println(msg);
return;
}
}
}
// This is not a child plugin, so create a new class loader.
else {
pluginLoader = new PluginClassLoader(pluginDir);
}
// Check to see if development mode is turned on for the plugin. If it is, // Check to see if development mode is turned on for the plugin. If it is,
// configure dev mode. // configure dev mode.
...@@ -211,7 +269,25 @@ public class PluginManager { ...@@ -211,7 +269,25 @@ public class PluginManager {
plugin.initializePlugin(this, pluginDir); plugin.initializePlugin(this, pluginDir);
plugins.put(pluginDir.getName(), plugin); plugins.put(pluginDir.getName(), plugin);
pluginDirs.put(plugin, pluginDir); pluginDirs.put(plugin, pluginDir);
// If this is a child plugin, register it as such.
if (parentPluginNode != null) {
String parentPlugin = parentPluginNode.getTextTrim();
List<String> childrenPlugins = parentPluginMap.get(plugins.get(parentPlugin));
if (childrenPlugins == null) {
childrenPlugins = new ArrayList<String>();
parentPluginMap.put(plugins.get(parentPlugin), childrenPlugins);
}
childrenPlugins.add(pluginDir.getName());
// Also register child to parent relationship.
childPluginMap.put(plugin, parentPlugin);
}
else {
// Only register the class loader in the case of this not being
// a child plugin.
classloaders.put(plugin, pluginLoader); classloaders.put(plugin, pluginLoader);
}
// Load any JSP's defined by the plugin. // Load any JSP's defined by the plugin.
File webXML = new File(pluginDir, "web" + File.separator + "WEB-INF" + File webXML = new File(pluginDir, "web" + File.separator + "WEB-INF" +
File.separator + "web.xml"); File.separator + "web.xml");
...@@ -276,10 +352,21 @@ public class PluginManager { ...@@ -276,10 +352,21 @@ public class PluginManager {
*/ */
public void unloadPlugin(String pluginName) { public void unloadPlugin(String pluginName) {
Log.debug("Unloading plugin " + pluginName); Log.debug("Unloading plugin " + pluginName);
Plugin plugin = plugins.get(pluginName); Plugin plugin = plugins.get(pluginName);
if (plugin == null) { if (plugin == null) {
return; return;
} }
// See if any child plugins are defined.
if (parentPluginMap.containsKey(plugin)) {
for (String childPlugin : parentPluginMap.get(plugin)) {
Log.debug("Unloading child plugin: " + childPlugin);
unloadPlugin(childPlugin);
}
parentPluginMap.remove(plugin);
}
File webXML = new File(pluginDirectory, pluginName + File.separator + "web" + File.separator + "WEB-INF" + File webXML = new File(pluginDirectory, pluginName + File.separator + "web" + File.separator + "WEB-INF" +
File.separator + "web.xml"); File.separator + "web.xml");
if (webXML.exists()) { if (webXML.exists()) {
...@@ -292,12 +379,22 @@ public class PluginManager { ...@@ -292,12 +379,22 @@ public class PluginManager {
PluginServlet.unregisterServlets(customWebXML); PluginServlet.unregisterServlets(customWebXML);
} }
PluginClassLoader classLoader = classloaders.get(plugin);
plugin.destroyPlugin(); plugin.destroyPlugin();
PluginClassLoader classLoader = classloaders.get(plugin);
// Destroy class loader if defined, which it won't be if this is a child plugin.
if (classLoader != null) {
classLoader.destroy(); classLoader.destroy();
}
plugins.remove(pluginName); plugins.remove(pluginName);
pluginDirs.remove(plugin); pluginDirs.remove(plugin);
classloaders.remove(plugin); classloaders.remove(plugin);
// See if this is a child plugin. If it is, we should unload
// the parent plugin as well.
if (childPluginMap.containsKey(plugin)) {
unloadPlugin(childPluginMap.get(plugin));
}
childPluginMap.remove(plugin);
} }
/** /**
...@@ -442,10 +539,12 @@ public class PluginManager { ...@@ -442,10 +539,12 @@ public class PluginManager {
unloadPlugin(pluginName); unloadPlugin(pluginName);
// Ask the system to clean up references. // Ask the system to clean up references.
System.gc(); System.gc();
while (!deleteDir(dir)) { int count = 0;
while (!deleteDir(dir) && count < 5) {
Log.error("Error unloading plugin " + pluginName + ". " + Log.error("Error unloading plugin " + pluginName + ". " +
"Will attempt again momentarily."); "Will attempt again momentarily.");
Thread.sleep(5000); Thread.sleep(5000);
count++;
} }
// Now unzip the plugin. // Now unzip the plugin.
unzipPlugin(pluginName, jarFile, dir); unzipPlugin(pluginName, jarFile, dir);
...@@ -499,10 +598,13 @@ public class PluginManager { ...@@ -499,10 +598,13 @@ public class PluginManager {
for (String pluginName : toDelete) { for (String pluginName : toDelete) {
unloadPlugin(pluginName); unloadPlugin(pluginName);
System.gc(); System.gc();
while (!deleteDir(new File(pluginDirectory, pluginName))) { int count = 0;
File dir = new File(pluginDirectory, pluginName);
while (!deleteDir(dir) && count < 5) {
Log.error("Error unloading plugin " + pluginName + ". " + Log.error("Error unloading plugin " + pluginName + ". " +
"Will attempt again momentarily."); "Will attempt again momentarily.");
Thread.sleep(5000); Thread.sleep(5000);
count++;
} }
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment