Commit 7a211259 authored by Matt Tucker's avatar Matt Tucker Committed by matt

Updated plugin interface (JM-176).


git-svn-id: http://svn.igniterealtime.org/svn/repos/messenger/trunk@999 b35dd754-fafc-0310-a699-88a17e54d16e
parent d8c71948
...@@ -22,9 +22,9 @@ developer's guide for creating plugins. ...@@ -22,9 +22,9 @@ developer's guide for creating plugins.
<h2>Structure of a Plugin</h2> <h2>Structure of a Plugin</h2>
<p> <p>
Plugins live in the <tt>plugins</tt> directory of <tt>messengerHome</tt>. If Plugins live in the <tt>plugins</tt> directory of <tt>messengerHome</tt>. When a plugin
a plugin is deployed as a JAR file, it will be automatically expanded into is deployed as a JAR file, it is automatically expanded into a directory. The files in a
a directory. The files in a plugin directory are as follows: plugin directory are as follows:
</p> </p>
<fieldset> <fieldset>
...@@ -54,7 +54,14 @@ file might look like the following: ...@@ -54,7 +54,14 @@ file might look like the following:
&lt;plugin&gt; &lt;plugin&gt;
<span class="comment">&lt;!-- Main plugin class --&gt;</span> <span class="comment">&lt;!-- Main plugin class --&gt;</span>
&lt;class&gt;org.example.ExamplePlugin&lt;/class&gt; &lt;class&gt;org.example.ExamplePlugin&lt;/class&gt;
<span class="comment">&lt;!-- Plugin meta-data --&gt;</span>
&lt;name&gt;Example Plugin&lt;/name&gt;
&lt;description&gt;This is an example plugin.&lt;/description&gt;
&lt;author&gt;Jive Software&lt;/author&gt;
&lt;version&gt;1.0&lt;/version&gt;
&lt;minServerVersion&gt;2.1.2&lt;/minServerVersion&gt;
<span class="comment">&lt;!-- Admin console entries --&gt;</span> <span class="comment">&lt;!-- Admin console entries --&gt;</span>
&lt;adminconsole&gt; &lt;adminconsole&gt;
<span class="comment">&lt;!-- More on this below --&gt;</span> <span class="comment">&lt;!-- More on this below --&gt;</span>
...@@ -63,12 +70,23 @@ file might look like the following: ...@@ -63,12 +70,23 @@ file might look like the following:
</pre> </pre>
</fieldset> </fieldset>
<p>The meta-data fields that can be set in the plugin.xml file:
<ul>
<li>name -- the name of the plugin.</li>
<li>description -- the description of the plugin.</li>
<li>author -- the author of the plugin.</li>
<li>version -- the version of the pluginn.</li>
<li>minServerVersion -- the minimum version of Jive Messenger required
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>
</ul></p>
<p>Your plugin class must be implement the <p>Your plugin class must be implement the
<tt><a href="javadoc/org/jivesoftware/messenger/container/Plugin.html">Plugin</a></tt> <tt><a href="javadoc/org/jivesoftware/messenger/container/Plugin.html">Plugin</a></tt>
interface from the <a href="javadoc/index.html">Jive Messenger API</a> as interface from the <a href="javadoc/index.html">Jive Messenger API</a> as
well as have a default (no argument) contructor. The Plugin interface has well as have a default (no argument) contructor. The Plugin interface has
methods for initializing and destroying the plugin, as well as methods for retrieving meta-data methods for initializing and destroying the plugin.
such as the plugin name, description, version, and author.
</p> </p>
<fieldset> <fieldset>
...@@ -86,22 +104,6 @@ import java.io.File; ...@@ -86,22 +104,6 @@ import java.io.File;
*/ */
public class ExamplePlugin implements Plugin { public class ExamplePlugin implements Plugin {
public String getName() {
return "My Plugin";
}
public String getDescription() {
return "A simple plugin";
}
public String getAuthor() {
return "Johnny Java Coder";
}
public String getVersion() {
return "1.0";
}
public void initialize(PluginManager manager, File pluginDirectory) { public void initialize(PluginManager manager, File pluginDirectory) {
<span class="comment">// Your code goes here</span> <span class="comment">// Your code goes here</span>
} }
......
...@@ -41,6 +41,11 @@ import java.io.File; ...@@ -41,6 +41,11 @@ import java.io.File;
* &lt;?xml version="1.0" encoding="UTF-8"?&gt; * &lt;?xml version="1.0" encoding="UTF-8"?&gt;
* &lt;plugin&gt; * &lt;plugin&gt;
* &lt;class&gt;org.example.YourPlugin&lt;/class&gt; * &lt;class&gt;org.example.YourPlugin&lt;/class&gt;
* &lt;name&gt;Example Plugin&lt;/name&gt;
* &lt;description&gt;This is an example plugin.&lt;/description&gt;
* &lt;author&gt;Foo Inc.&lt;/author&gt;
* &lt;version&gt;1.0&lt;/version&gt;
* &lt;minServerVersion&gt;2.1.2&lt;/minServerVersion&gt;
* &lt;/plugin&gt;</pre> * &lt;/plugin&gt;</pre>
* *
* Each plugin will be loaded in its own class loader. * Each plugin will be loaded in its own class loader.
...@@ -49,45 +54,23 @@ import java.io.File; ...@@ -49,45 +54,23 @@ import java.io.File;
*/ */
public interface Plugin { public interface Plugin {
/**
* Returns the name of this plugin.
*
* @return the plugin's name.
*/
public String getName();
/**
* Returns the description of the plugin or <tt>null</tt> if there is no description.
*
* @return this plugin's description.
*/
public String getDescription();
/**
* Returns the author of this plugin.
*
* @return the plugin's author.
*/
public String getAuthor();
/**
* The plugin's version.
*
* @return the version of the plugin.
*/
public String getVersion();
/** /**
* Initializes the plugin. * Initializes the plugin.
* *
* @param manager the plugin manager. * @param manager the plugin manager.
* @param pluginDirectory the directory where the plugin is located. * @param pluginDirectory the directory where the plugin is located.
*/ */
public void initialize(PluginManager manager, File pluginDirectory); public void initializePlugin(PluginManager manager, File pluginDirectory);
/** /**
* Destroys the plugin. * Destroys the plugin.<p>
*
* Implementations of this method must release all resources held
* by the plugin such as file handles, database or network connections,
* and references to core Jive Messenger classes. In other words, a
* garbage collection executed after this method is called must be able
* to clean up all plugin classes.
*/ */
public void destroy(); public void destroyPlugin();
} }
\ No newline at end of file
/** /**
* $RCSfile$ * $RCSfile$
* $Revision$ * $Revision$
* $Date$ * $Date$
* *
* Copyright (C) 2004 Jive Software. All rights reserved. * Copyright (C) 2004 Jive Software. All rights reserved.
* *
* This software is published under the terms of the GNU Public License (GPL), * This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution. * a copy of which is included in this distribution.
*/ */
package org.jivesoftware.messenger.container; package org.jivesoftware.messenger.container;
import org.jivesoftware.util.Log; import org.jivesoftware.util.Log;
import org.jivesoftware.util.Version;
import org.jivesoftware.messenger.JiveGlobals; import org.jivesoftware.messenger.JiveGlobals;
import org.jivesoftware.messenger.XMPPServer;
import org.jivesoftware.admin.AdminConsole; import org.jivesoftware.admin.AdminConsole;
import org.dom4j.Document; import org.dom4j.Document;
import org.dom4j.Element; import org.dom4j.Element;
import org.dom4j.Attribute; import org.dom4j.Attribute;
import org.dom4j.io.SAXReader; import org.dom4j.io.SAXReader;
import java.io.*; import java.io.*;
import java.util.*; import java.util.*;
import java.util.jar.JarFile; import java.util.jar.JarFile;
import java.util.jar.JarEntry; import java.util.jar.JarEntry;
import java.util.zip.ZipFile; import java.util.zip.ZipFile;
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;
/** /**
...@@ -35,11 +61,13 @@ import java.util.concurrent.TimeUnit; ...@@ -35,11 +61,13 @@ import java.util.concurrent.TimeUnit;
* @see Plugin * @see Plugin
* @author Matt Tucker * @author Matt Tucker
*/ */
public class PluginManager { public class PluginManager {
private File pluginDirectory; private File pluginDirectory;
private Map<String,Plugin> plugins; private Map<String,Plugin> plugins;
private Map<Plugin,PluginClassLoader> classloaders; private Map<Plugin,PluginClassLoader> classloaders;
private Map<Plugin,File> pluginDirs;
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;
...@@ -51,6 +79,7 @@ public class PluginManager { ...@@ -51,6 +79,7 @@ public class PluginManager {
public PluginManager(File pluginDir) { public PluginManager(File pluginDir) {
this.pluginDirectory = pluginDir; this.pluginDirectory = pluginDir;
plugins = new HashMap<String,Plugin>(); plugins = new HashMap<String,Plugin>();
pluginDirs = new HashMap<Plugin,File>();
classloaders = new HashMap<Plugin,PluginClassLoader>(); classloaders = new HashMap<Plugin,PluginClassLoader>();
} }
...@@ -72,9 +101,10 @@ public class PluginManager { ...@@ -72,9 +101,10 @@ public class PluginManager {
} }
// Shutdown all installed plugins. // Shutdown all installed plugins.
for (Plugin plugin : plugins.values()) { for (Plugin plugin : plugins.values()) {
plugin.destroy(); plugin.destroyPlugin();
} }
plugins.clear(); plugins.clear();
pluginDirs.clear();
classloaders.clear(); classloaders.clear();
} }
...@@ -112,11 +142,29 @@ public class PluginManager { ...@@ -112,11 +142,29 @@ 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
// required to run.
Element minServerVersion = (Element)pluginXML.selectSingleNode(
"/plugin/minServerVersion");
if (minServerVersion != null) {
String requiredVersion = minServerVersion.getTextTrim();
Version version = XMPPServer.getInstance().getServerInfo().getVersion();
String hasVersion = version.getMajor() + "." + version.getMinor() + "." +
version.getMicro();
if (hasVersion.compareTo(requiredVersion) < 0) {
String msg = "Ignoring plugin " + pluginDir.getName() + ": requires " +
"server version " + requiredVersion;
Log.warn(msg);
System.out.println(msg);
return;
}
}
PluginClassLoader pluginLoader = new PluginClassLoader(pluginDir); PluginClassLoader pluginLoader = new PluginClassLoader(pluginDir);
String className = pluginXML.selectSingleNode("/plugin/class").getText(); String className = pluginXML.selectSingleNode("/plugin/class").getText();
plugin = (Plugin)pluginLoader.loadClass(className).newInstance(); plugin = (Plugin)pluginLoader.loadClass(className).newInstance();
plugin.initialize(this, pluginDir); plugin.initializePlugin(this, pluginDir);
plugins.put(pluginDir.getName(), plugin); plugins.put(pluginDir.getName(), plugin);
pluginDirs.put(plugin, pluginDir);
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.xml"); File webXML = new File(pluginDir, "web" + File.separator + "web.xml");
...@@ -157,7 +205,7 @@ public class PluginManager { ...@@ -157,7 +205,7 @@ public class PluginManager {
} }
/** /**
* Unloads a plugin. The {@link Plugin#destroy()} method will be called and then * Unloads a plugin. The {@link Plugin#destroyPlugin()} method will be called and then
* any resources will be released. * any resources will be released.
* *
* @param pluginName the name of the plugin to unload. * @param pluginName the name of the plugin to unload.
...@@ -176,9 +224,10 @@ public class PluginManager { ...@@ -176,9 +224,10 @@ public class PluginManager {
} }
PluginClassLoader classLoader = classloaders.get(plugin); PluginClassLoader classLoader = classloaders.get(plugin);
plugin.destroy(); plugin.destroyPlugin();
classLoader.destroy(); classLoader.destroy();
plugins.remove(pluginName); plugins.remove(pluginName);
pluginDirs.remove(plugin);
classloaders.remove(plugin); classloaders.remove(plugin);
} }
...@@ -189,6 +238,80 @@ public class PluginManager { ...@@ -189,6 +238,80 @@ public class PluginManager {
return loader.loadClass(className); return loader.loadClass(className);
} }
/**
* Returns the name of a plugin. The value is retrieved from the plugin.xml file
* of the plugin. If the value could not be found, <tt>null</tt> will be returned.
*
* @param plugin the plugin.
* @return the plugin's name.
*/
public String getName(Plugin plugin) {
return getElementValue(plugin, "/plugin/name");
}
/**
* Returns the description of a plugin. The value is retrieved from the plugin.xml file
* of the plugin. If the value could not be found, <tt>null</tt> will be returned.
*
* @param plugin the plugin.
* @return the plugin's description.
*/
public String getDescription(Plugin plugin) {
return getElementValue(plugin, "/plugin/description");
}
/**
* Returns the author of a plugin. The value is retrieved from the plugin.xml file
* of the plugin. If the value could not be found, <tt>null</tt> will be returned.
*
* @param plugin the plugin.
* @return the plugin's author.
*/
public String getAuthor(Plugin plugin) {
return getElementValue(plugin, "/plugin/author");
}
/**
* Returns the version of a plugin. The value is retrieved from the plugin.xml file
* of the plugin. If the value could not be found, <tt>null</tt> will be returned.
*
* @param plugin the plugin.
* @return the plugin's version.
*/
public String getVersion(Plugin plugin) {
return getElementValue(plugin, "/plugin/version");
}
/**
* Returns the value of an element selected via an xpath expression from
* a Plugin's plugin.xml file.
*
* @param plugin the plugin.
* @param xpath the xpath expression.
* @return the value of the element selected by the xpath expression.
*/
private String getElementValue(Plugin plugin, String xpath) {
File pluginDir = pluginDirs.get(plugin);
if (pluginDir == null) {
return null;
}
try {
File pluginConfig = new File(pluginDir, "plugin.xml");
if (pluginConfig.exists()) {
SAXReader saxReader = new SAXReader();
Document pluginXML = saxReader.read(pluginConfig);
Element element = (Element)pluginXML.selectSingleNode(xpath);
if (element != null) {
return element.getTextTrim();
}
}
}
catch (Exception e) {
Log.error(e);
}
return null;
}
/** /**
* A service that monitors the plugin directory for plugins. It periodically * A service that monitors the plugin directory for plugins. It periodically
* checks for new plugin JAR files and extracts them if they haven't already * checks for new plugin JAR files and extracts them if they haven't already
...@@ -296,7 +419,7 @@ public class PluginManager { ...@@ -296,7 +419,7 @@ public class PluginManager {
* *
* @param pluginName the name of the plugin. * @param pluginName the name of the plugin.
* @param file the JAR file * @param file the JAR file
* @param dir the directory to extract the plugin to. * @param dir the directory to extract the plugin to.
*/ */
private void unzipPlugin(String pluginName, File file, File dir) { private void unzipPlugin(String pluginName, File file, File dir) {
try { try {
......
...@@ -5,4 +5,9 @@ ...@@ -5,4 +5,9 @@
--> -->
<plugin> <plugin>
<class>org.jivesoftware.messenger.plugin.BroadcastPlugin</class> <class>org.jivesoftware.messenger.plugin.BroadcastPlugin</class>
<name>Broadcast Plugin</name>
<description>Broadcasts messages to users.</description>
<author>Jive Software</author>
<version>1.1</version>
<minMessengerVersion>3.1.0</minMessengerVersion>
</plugin> </plugin>
\ No newline at end of file
...@@ -50,6 +50,7 @@ public class BroadcastPlugin implements Plugin, Component { ...@@ -50,6 +50,7 @@ public class BroadcastPlugin implements Plugin, Component {
private List<JID> allowedUsers; private List<JID> allowedUsers;
private boolean groupMembersAllowed; private boolean groupMembersAllowed;
private ComponentManager componentManager; private ComponentManager componentManager;
private PluginManager pluginManager;
/** /**
* Constructs a new broadcast plugin. * Constructs a new broadcast plugin.
...@@ -63,23 +64,8 @@ public class BroadcastPlugin implements Plugin, Component { ...@@ -63,23 +64,8 @@ public class BroadcastPlugin implements Plugin, Component {
// Plugin Interface // Plugin Interface
public String getName() { public void initializePlugin(PluginManager manager, File pluginDirectory) {
return "Broadcast Plugin"; pluginManager = manager;
}
public String getDescription() {
return "Broadcasts messages to users.";
}
public String getAuthor() {
return "Jive Software";
}
public String getVersion() {
return "1.0";
}
public void initialize(PluginManager manager, File pluginDirectory) {
sessionManager = SessionManager.getInstance(); sessionManager = SessionManager.getInstance();
groupManager = GroupManager.getInstance(); groupManager = GroupManager.getInstance();
...@@ -93,7 +79,7 @@ public class BroadcastPlugin implements Plugin, Component { ...@@ -93,7 +79,7 @@ public class BroadcastPlugin implements Plugin, Component {
} }
} }
public void destroy() { public void destroyPlugin() {
// Unregister component. // Unregister component.
try { try {
componentManager.removeComponent(serviceName); componentManager.removeComponent(serviceName);
...@@ -101,6 +87,8 @@ public class BroadcastPlugin implements Plugin, Component { ...@@ -101,6 +87,8 @@ public class BroadcastPlugin implements Plugin, Component {
catch (Exception e) { catch (Exception e) {
componentManager.getLog().error(e); componentManager.getLog().error(e);
} }
componentManager = null;
pluginManager = null;
sessionManager = null; sessionManager = null;
groupManager = null; groupManager = null;
allowedUsers.clear(); allowedUsers.clear();
...@@ -116,6 +104,16 @@ public class BroadcastPlugin implements Plugin, Component { ...@@ -116,6 +104,16 @@ public class BroadcastPlugin implements Plugin, Component {
// Component Interface // Component Interface
public String getName() {
// Get the name from the plugin.xml file.
return pluginManager.getName(this);
}
public String getDescription() {
// Get the description from the plugin.xml file.
return pluginManager.getDescription(this);
}
public void processPacket(Packet packet) { public void processPacket(Packet packet) {
// Only respond to incoming messages. TODO: handle disco, presence, etc. // Only respond to incoming messages. TODO: handle disco, presence, etc.
if (packet instanceof Message) { if (packet instanceof Message) {
......
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