Commit 9220f3f9 authored by Matt Tucker's avatar Matt Tucker Committed by matt

Plugin work.


git-svn-id: http://svn.igniterealtime.org/svn/repos/messenger/trunk@400 b35dd754-fafc-0310-a699-88a17e54d16e
parent e44b9a77
......@@ -23,10 +23,9 @@ import org.jivesoftware.messenger.transport.TransportHandler;
import org.jivesoftware.messenger.user.spi.*;
/**
* <p>A bootstrap container to launch the Messenger XMPP server.</p>
* <p/>
* <p>This container knows what classes must be loaded to create a
* functional Messenger deployment.</p>
* A bootstrap container to launch the Messenger XMPP server. This
* container knows what classes must be loaded to create a functional
* Jive Messenger deployment.
*
* @author Iain Shigeoka
*/
......
/**
* $RCSfile$
* $Revision$
* $Date$
*
* Copyright 2004 Jive Software.
*
* All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.messenger.container;
import org.jivesoftware.util.Log;
import org.jivesoftware.util.XMLProperties;
import org.jivesoftware.messenger.container.spi.JiveModuleLoader;
import org.jivesoftware.messenger.JiveGlobals;
import java.io.*;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* Manages plugins.
*
* @author Matt Tucker
*/
public class PluginManager {
private Container container;
private File pluginDirectory;
private Map<String,Module> plugins;
private boolean setupMode = !(Boolean.valueOf(JiveGlobals.getXMLProperty("setup")).booleanValue());
private ScheduledExecutorService executor = null;
public PluginManager(File pluginDir, Container container) {
this.pluginDirectory = pluginDir;
this.container = container;
plugins = new HashMap<String,Module>();
}
/**
* Starts plugins and the plugin monitoring service.
*/
public void start() {
executor = new ScheduledThreadPoolExecutor(2);
executor.scheduleWithFixedDelay(new PluginLoader(), 0, 5, TimeUnit.SECONDS);
}
/**
* Shuts down all running plugins.
*/
public void shutdown() {
// Stop the plugin monitoring service.
if (executor != null) {
executor.shutdown();
}
// Shutdown all installed plugins.
for (Module module : plugins.values()) {
module.stop();
module.destroy();
}
}
/**
* Loads a plug-in module into the container. Loading consists of the
* following steps:<ul>
*
* <li>Add all jars in the <tt>lib</tt> dir (if it exists) to the class loader</li>
* <li>Add all files in <tt>classes</tt> dir (if it exists) to the class loader</li>
* <li>Locate and load <tt>module.xml</tt> into the context</li>
* <li>For each jive.module entry, load the given class as a module and start it</li>
*
* </ul>
*
* @param pluginDir the plugin directory.
*/
private void loadPlugin(File pluginDir) {
// Only load the admin plugin during setup mode.
if (setupMode && !(pluginDir.getName().equals("admin"))) {
return;
}
// ClassLoader
Log.debug("Loading plugin " + pluginDir.getName());
Module mod = null;
try {
File moduleConfig = new File(pluginDir, "module.xml");
if (moduleConfig.exists()) {
XMLProperties moduleProps = new XMLProperties(moduleConfig);
JiveModuleLoader modLoader = new JiveModuleLoader(pluginDir.toString());
mod = modLoader.loadModule(moduleProps.getProperty("module"));
mod.initialize(container);
mod.start();
plugins.put(pluginDir.getName(), mod);
}
else {
Log.warn("Plugin " + pluginDir +
" not loaded: no module.xml configuration file not found");
}
}
catch (Exception e) {
Log.error("Error loading plugin", e);
}
}
/**
* 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
* been extracted. Then, any new plugin directories are loaded.
*/
private class PluginLoader implements Runnable {
public void run() {
try {
File [] jars = pluginDirectory.listFiles(new FileFilter() {
public boolean accept(File pathname) {
return pathname.getName().toLowerCase().endsWith(".jar");
}
});
for (int i=0; i<jars.length; i++) {
File jarFile = jars[i];
String jarName = jarFile.getName().substring(
0, jarFile.getName().length()-4).toLowerCase();
// See if the JAR has already been exploded.
File dir = new File(pluginDirectory, jarName);
// If the JAR hasn't been exploded, do so.
if (!dir.exists()) {
try {
ZipFile zipFile = new ZipFile(jarFile);
// Ensure that this JAR is a plugin.
if (zipFile.getEntry("module.xml") == null) {
continue;
}
dir.mkdir();
Log.debug("Extracting plugin: " + jarName);
for (Enumeration e=zipFile.entries(); e.hasMoreElements(); ) {
ZipEntry entry = (ZipEntry)e.nextElement();
File entryFile = new File(dir, entry.getName());
if (!entryFile.isDirectory()) {
entryFile.getParentFile().mkdirs();
FileOutputStream out = new FileOutputStream(entryFile);
InputStream zin = zipFile.getInputStream(entry);
byte [] b = new byte[512];
int len = 0;
while ( (len=zin.read(b))!= -1 ) {
out.write(b,0,len);
}
out.flush();
out.close();
zin.close();
}
}
}
catch (Exception e) {
Log.error(e);
}
}
}
File [] dirs = pluginDirectory.listFiles(new FileFilter() {
public boolean accept(File pathname) {
return pathname.isDirectory();
}
});
// Sort the list of directories so that the "admin" plugin is always
// first in the list.
Arrays.sort(dirs, new Comparator<File>() {
public int compare(File file1, File file2) {
if (file1.getName().equals("admin")) {
return -1;
}
else if (file2.getName().equals("admin")) {
return 1;
}
else return file1.compareTo(file2);
}
});
for (int i=0; i<dirs.length; i++) {
File dirFile = dirs[i];
// If the plugin hasn't already been started, start it.
if (!plugins.containsKey(dirFile.getName())) {
loadPlugin(dirFile);
}
}
}
catch (Exception e) {
Log.error(e);
}
}
}
}
\ No newline at end of file
......@@ -125,6 +125,8 @@ public abstract class BootstrapContainer implements Container, ServiceLookupProv
private File messengerHome;
private ClassLoader loader;
private PluginManager pluginManager;
/**
* True if in setup mode
*/
......@@ -136,9 +138,7 @@ public abstract class BootstrapContainer implements Container, ServiceLookupProv
"org.tanukisoftware.wrapper.WrapperManager";
/**
* Construct the server bootstrap server.
* <p/>
* the server could not be started
* Constructs the bootstrap server.
*/
public BootstrapContainer() {
ServiceLookupFactory.setLookupProvider(this);
......@@ -166,16 +166,19 @@ public abstract class BootstrapContainer implements Container, ServiceLookupProv
loader = Thread.currentThread().getContextClassLoader();
if (setupMode) {
loadCorePlugins(getSetupModuleNames());
loadCoreModules(getSetupModuleNames());
}
else {
verifyDataSource();
loadCorePlugins(getBootModuleNames());
loadCorePlugins(getCoreModuleNames());
loadCorePlugins(getStandardModuleNames());
loadCoreModules(getBootModuleNames());
loadCoreModules(getCoreModuleNames());
loadCoreModules(getStandardModuleNames());
}
startCorePlugins();
loadPlugins(setupMode);
startCoreModules();
// Load plugins.
File pluginDir = new File(messengerHome + "/plugins");
pluginManager = new PluginManager(pluginDir, this);
pluginManager.start();
}
catch (Exception e) {
e.printStackTrace();
......@@ -196,7 +199,6 @@ public abstract class BootstrapContainer implements Container, ServiceLookupProv
return restartable;
}
public boolean isStandAlone() {
boolean standalone = false;
try {
......@@ -215,7 +217,8 @@ public abstract class BootstrapContainer implements Container, ServiceLookupProv
java.sql.Connection conn = null;
try {
conn = DbConnectionManager.getConnection();
PreparedStatement stmt = conn.prepareStatement("SELECT count(*) FROM jiveID");
PreparedStatement stmt = conn.prepareStatement(
"SELECT count(*) FROM jiveID");
ResultSet rs = stmt.executeQuery();
rs.next();
rs.close();
......@@ -241,7 +244,7 @@ public abstract class BootstrapContainer implements Container, ServiceLookupProv
* <p/>
*
*/
private void loadCorePlugins(String[] modules) {
private void loadCoreModules(String[] modules) {
for (int i = 0; i < modules.length; i++) {
Module mod = null;
boolean isInitialized = false;
......@@ -268,7 +271,7 @@ public abstract class BootstrapContainer implements Container, ServiceLookupProv
* this method is called to iterate through the known modules and
* start them.</p>
*/
private void startCorePlugins() {
private void startCoreModules() {
for (Module module : modules) {
boolean started = false;
try {
......@@ -301,73 +304,12 @@ public abstract class BootstrapContainer implements Container, ServiceLookupProv
mod.destroy();
}
modules.clear();
// Stop all plugins
pluginManager.shutdown();
// TODO: hack to allow safe stopping
Log.info("Jive Messenger stopped");
}
/**
* Loads the plugins for the container.
*
* @param setupMode True if starting in setup mode.
*/
private void loadPlugins(boolean setupMode) {
File pluginDir = new File(messengerHome + "/plugins");
if (pluginDir.exists()) {
File[] plugins = pluginDir.listFiles();
for (int i = 0; i < plugins.length; i++) {
if (plugins[i].isDirectory()) {
// Only load admin plugin if in setup mode.
if (setupMode) {
if ("admin".equals(plugins[i].getName())) {
loadPlugin(plugins[i]);
}
}
else {
loadPlugin(plugins[i]);
}
}
}
}
else {
Log.info("startup.missing-plugins");
}
}
/**
* Loads a plug-in module into the container. Loading consists of the
* following steps:
* <p/>
* <ul>
* <li>Add all jars in the <tt>lib</tt> dir (if it exists) to the class loader</li>
* <li>Add all files in <tt>classes</tt> dir (if it exists) to the class loader</li>
* <li>Locate and load <tt>module.xml</tt> into the context</li>
* <li>For each jive.module entry, load the given class as a module and start it</li>
* </ul>
*
* @param pluginDir The root directory for the plug-in
*/
private void loadPlugin(File pluginDir) {
Module mod = null;
try {
File moduleConfig = new File(pluginDir, "module.xml");
if (moduleConfig.exists()) {
XMLProperties moduleProps = new XMLProperties(moduleConfig);
JiveModuleLoader modLoader = new JiveModuleLoader(pluginDir.toString());
mod = modLoader.loadModule(moduleProps.getProperty("module"));
mod.initialize(this);
mod.start();
this.modules.add(mod);
}
else {
Log.warn("Plugin " + pluginDir +
" not loaded: no module.xml configuration file found");
}
}
catch (Exception e) {
Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
}
}
public boolean isSetupMode() {
return setupMode;
}
......@@ -380,7 +322,7 @@ public abstract class BootstrapContainer implements Container, ServiceLookupProv
Object instance = lookup.lookup(service);
if (instance == null) {
if (service.getName().equals("org.jivesoftware.messenger.user.UserManager")) {
loadCorePlugins(new String[]{
loadCoreModules(new String[]{
"org.jivesoftware.messenger.user.spi.UserManagerImpl"});
instance = lookup.lookup(service);
}
......
......@@ -39,6 +39,8 @@ public class BasicServer extends BasicModule implements XMPPServer, BasicServerM
private ConnectionManager connectionManager;
private boolean initialized = false;
private boolean setupMode = !(Boolean.valueOf(JiveGlobals.getXMLProperty("setup")).booleanValue());
/**
* Create a default loopback test server.
*/
......@@ -90,7 +92,9 @@ public class BasicServer extends BasicModule implements XMPPServer, BasicServerM
super.initialize(container);
try {
lookup = container.getServiceLookup();
if (!setupMode) {
name = JiveGlobals.getProperty("xmpp.domain");
}
if (name == null) {
name = "127.0.0.1";
}
......
......@@ -17,26 +17,26 @@ import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
/**
* <p>A simple classloader to extend the classpath to
* include all jars in a lib directory.</p>
* <p/>
* <p>The new classpath includes all <tt>*.jar</tt> and <tt>*.zip</tt>
* A simple classloader to extend the classpath to
* include all jars in a lib directory.<p>
*
* The new classpath includes all <tt>*.jar</tt> and <tt>*.zip</tt>
* archives (zip is commonly used in packaging JDBC drivers). The extended
* classpath is used for both the initial server startup, as well as loading
* plug-in support jars.</p>
* plug-in support jars.
*
* @author Derek DeMoro
* @author Iain Shigeoka
*/
class JiveClassLoader extends URLClassLoader {
/**
* <p>Create the classloader.</p>
* Constructs the classloader.
*
* @param parent The parent class loader (or null for none)
* @param libDir The directory to load jar files from
* @throws java.net.MalformedURLException If the libDir path is not valid
* @param parent the parent class loader (or null for none).
* @param libDir the directory to load jar files from.
* @throws java.net.MalformedURLException if the libDir path is not valid.
*/
JiveClassLoader(ClassLoader parent, File libDir) throws MalformedURLException {
super(new URL[]{libDir.toURL()}, parent);
......
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