* Plugins:
- Set classpath for specific client only, not for the whole JVM - Use ConfigDir() not AppDir()
This commit is contained in:
@ -2,6 +2,7 @@ package net.i2p.router.web;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.ClassLoader;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
@ -48,6 +49,7 @@ public class PluginStarter implements Runnable {
|
||||
"midnight" };
|
||||
private static Map<String, ThreadGroup> pluginThreadGroups = new ConcurrentHashMap<String, ThreadGroup>(); // one thread group per plugin (map key=plugin name)
|
||||
private static Map<String, Collection<Job>> pluginJobs = new ConcurrentHashMap<String, Collection<Job>>();
|
||||
private static Map<String, ClassLoader> _clCache = new ConcurrentHashMap();
|
||||
|
||||
public PluginStarter(RouterContext ctx) {
|
||||
_context = ctx;
|
||||
@ -87,7 +89,7 @@ public class PluginStarter implements Runnable {
|
||||
*/
|
||||
static boolean startPlugin(RouterContext ctx, String appName) throws Exception {
|
||||
Log log = ctx.logManager().getLog(PluginStarter.class);
|
||||
File pluginDir = new File(ctx.getAppDir(), PluginUpdateHandler.PLUGIN_DIR + '/' + appName);
|
||||
File pluginDir = new File(ctx.getConfigDir(), PluginUpdateHandler.PLUGIN_DIR + '/' + appName);
|
||||
if ((!pluginDir.exists()) || (!pluginDir.isDirectory())) {
|
||||
log.error("Cannot start nonexistent plugin: " + appName);
|
||||
return false;
|
||||
@ -195,7 +197,7 @@ public class PluginStarter implements Runnable {
|
||||
*/
|
||||
static boolean stopPlugin(RouterContext ctx, String appName) throws Exception {
|
||||
Log log = ctx.logManager().getLog(PluginStarter.class);
|
||||
File pluginDir = new File(ctx.getAppDir(), PluginUpdateHandler.PLUGIN_DIR + '/' + appName);
|
||||
File pluginDir = new File(ctx.getConfigDir(), PluginUpdateHandler.PLUGIN_DIR + '/' + appName);
|
||||
if ((!pluginDir.exists()) || (!pluginDir.isDirectory())) {
|
||||
log.error("Cannot stop nonexistent plugin: " + appName);
|
||||
return false;
|
||||
@ -244,7 +246,7 @@ public class PluginStarter implements Runnable {
|
||||
/** @return true on success - caller should call stopPlugin() first */
|
||||
static boolean deletePlugin(RouterContext ctx, String appName) throws Exception {
|
||||
Log log = ctx.logManager().getLog(PluginStarter.class);
|
||||
File pluginDir = new File(ctx.getAppDir(), PluginUpdateHandler.PLUGIN_DIR + '/' + appName);
|
||||
File pluginDir = new File(ctx.getConfigDir(), PluginUpdateHandler.PLUGIN_DIR + '/' + appName);
|
||||
if ((!pluginDir.exists()) || (!pluginDir.isDirectory())) {
|
||||
log.error("Cannot delete nonexistent plugin: " + appName);
|
||||
return false;
|
||||
@ -287,7 +289,7 @@ public class PluginStarter implements Runnable {
|
||||
|
||||
/** plugin.config */
|
||||
public static Properties pluginProperties(I2PAppContext ctx, String appName) {
|
||||
File cfgFile = new File(ctx.getAppDir(), PluginUpdateHandler.PLUGIN_DIR + '/' + appName + '/' + "plugin.config");
|
||||
File cfgFile = new File(ctx.getConfigDir(), PluginUpdateHandler.PLUGIN_DIR + '/' + appName + '/' + "plugin.config");
|
||||
Properties rv = new Properties();
|
||||
try {
|
||||
DataHelper.loadProps(rv, cfgFile);
|
||||
@ -322,7 +324,7 @@ public class PluginStarter implements Runnable {
|
||||
*/
|
||||
public static List<String> getPlugins() {
|
||||
List<String> rv = new ArrayList();
|
||||
File pluginDir = new File(I2PAppContext.getGlobalContext().getAppDir(), PluginUpdateHandler.PLUGIN_DIR);
|
||||
File pluginDir = new File(I2PAppContext.getGlobalContext().getConfigDir(), PluginUpdateHandler.PLUGIN_DIR);
|
||||
File[] files = pluginDir.listFiles();
|
||||
if (files == null)
|
||||
return rv;
|
||||
@ -405,6 +407,8 @@ public class PluginStarter implements Runnable {
|
||||
argVal[i] = argVal[i].replace("$PLUGIN", pluginDir.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
|
||||
ClassLoader cl = null;
|
||||
if (app.classpath != null) {
|
||||
String cp = new String(app.classpath);
|
||||
if (cp.indexOf("$") >= 0) {
|
||||
@ -412,22 +416,41 @@ public class PluginStarter implements Runnable {
|
||||
cp = cp.replace("$CONFIG", ctx.getConfigDir().getAbsolutePath());
|
||||
cp = cp.replace("$PLUGIN", pluginDir.getAbsolutePath());
|
||||
}
|
||||
addToClasspath(cp, app.clientName, log);
|
||||
|
||||
// Old way - add for the whole JVM
|
||||
//addToClasspath(cp, app.clientName, log);
|
||||
|
||||
// New way - add only for this client
|
||||
// We cache the ClassLoader we start the client with, so
|
||||
// we can reuse it for stopping and uninstalling.
|
||||
// If we don't, the client won't be able to find its
|
||||
// static members.
|
||||
String clCacheKey = pluginName + app.className + app.args;
|
||||
if (!action.equals("start"))
|
||||
cl = _clCache.get(clCacheKey);
|
||||
if (cl == null) {
|
||||
URL[] urls = classpathToURLArray(cp, app.clientName, log);
|
||||
if (urls != null) {
|
||||
cl = new URLClassLoader(urls, ClassLoader.getSystemClassLoader());
|
||||
if (action.equals("start"))
|
||||
_clCache.put(clCacheKey, cl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (app.delay < 0 && action.equals("start")) {
|
||||
// this will throw exceptions
|
||||
LoadClientAppsJob.runClientInline(app.className, app.clientName, argVal, log);
|
||||
LoadClientAppsJob.runClientInline(app.className, app.clientName, argVal, log, cl);
|
||||
} else if (app.delay == 0 || !action.equals("start")) {
|
||||
// quick check, will throw ClassNotFoundException on error
|
||||
LoadClientAppsJob.testClient(app.className);
|
||||
LoadClientAppsJob.testClient(app.className, cl);
|
||||
// run this guy now
|
||||
LoadClientAppsJob.runClient(app.className, app.clientName, argVal, log, pluginThreadGroup);
|
||||
LoadClientAppsJob.runClient(app.className, app.clientName, argVal, log, pluginThreadGroup, cl);
|
||||
} else {
|
||||
// quick check, will throw ClassNotFoundException on error
|
||||
LoadClientAppsJob.testClient(app.className);
|
||||
LoadClientAppsJob.testClient(app.className, cl);
|
||||
// wait before firing it up
|
||||
Job job = new LoadClientAppsJob.DelayedRunClient(ctx, app.className, app.clientName, argVal, app.delay, pluginThreadGroup);
|
||||
Job job = new LoadClientAppsJob.DelayedRunClient(ctx, app.className, app.clientName, argVal, app.delay, pluginThreadGroup, cl);
|
||||
ctx.jobQueue().addJob(job);
|
||||
pluginJobs.get(pluginName).add(job);
|
||||
}
|
||||
@ -470,6 +493,7 @@ public class PluginStarter implements Runnable {
|
||||
* but I don't see how to make it magically get used for everything.
|
||||
* So add this to the whole JVM's classpath.
|
||||
*/
|
||||
/******
|
||||
private static void addToClasspath(String classpath, String clientName, Log log) {
|
||||
StringTokenizer tok = new StringTokenizer(classpath, ",");
|
||||
while (tok.hasMoreTokens()) {
|
||||
@ -488,6 +512,33 @@ public class PluginStarter implements Runnable {
|
||||
}
|
||||
}
|
||||
}
|
||||
*****/
|
||||
|
||||
/**
|
||||
* @return null if no valid elements
|
||||
*/
|
||||
private static URL[] classpathToURLArray(String classpath, String clientName, Log log) {
|
||||
StringTokenizer tok = new StringTokenizer(classpath, ",");
|
||||
List<URL> urls = new ArrayList();
|
||||
while (tok.hasMoreTokens()) {
|
||||
String elem = tok.nextToken().trim();
|
||||
File f = new File(elem);
|
||||
if (!f.isAbsolute()) {
|
||||
log.error("Plugin client " + clientName + " classpath element is not absolute: " + f);
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
urls.add(f.toURI().toURL());
|
||||
if (log.shouldLog(Log.WARN))
|
||||
log.warn("INFO: Adding plugin to classpath: " + f);
|
||||
} catch (Exception e) {
|
||||
log.error("Plugin client " + clientName + " bad classpath element: " + f, e);
|
||||
}
|
||||
}
|
||||
if (urls.isEmpty())
|
||||
return null;
|
||||
return urls.toArray(new URL[urls.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* http://jimlife.wordpress.com/2007/12/19/java-adding-new-classpath-at-runtime/
|
||||
|
@ -150,7 +150,7 @@ public class PluginUpdateHandler extends UpdateHandler {
|
||||
public void transferComplete(long alreadyTransferred, long bytesTransferred, long bytesRemaining, String url, String outputFile, boolean notModified) {
|
||||
updateStatus("<b>" + _("Plugin downloaded") + "</b>");
|
||||
File f = new File(_updateFile);
|
||||
File appDir = new File(_context.getAppDir(), PLUGIN_DIR);
|
||||
File appDir = new File(_context.getConfigDir(), PLUGIN_DIR);
|
||||
if ((!appDir.exists()) && (!appDir.mkdir())) {
|
||||
f.delete();
|
||||
statusDone("<b>" + _("Cannot create plugin directory {0}", appDir.getAbsolutePath()) + "</b>");
|
||||
|
@ -54,7 +54,7 @@ public class WebAppConfiguration implements WebApplicationContext.Configuration
|
||||
I2PAppContext i2pContext = I2PAppContext.getGlobalContext();
|
||||
File libDir = new File(i2pContext.getBaseDir(), "lib");
|
||||
// FIXME this only works if war is the same name as the plugin
|
||||
File pluginDir = new File(i2pContext.getAppDir(),
|
||||
File pluginDir = new File(i2pContext.getConfigDir(),
|
||||
PluginUpdateHandler.PLUGIN_DIR + ctxPath);
|
||||
|
||||
File dir = libDir;
|
||||
|
14
history.txt
14
history.txt
@ -1,3 +1,17 @@
|
||||
2010-05-05 zzz
|
||||
* build.xml: Create packed sud in release
|
||||
* Console:
|
||||
- Print stack trace if exception on startup
|
||||
- IllegalStateException rather than NPE if no context
|
||||
* EepGet: Limit max times to fail completely even if numRetries is higher
|
||||
* i2psnark: Skip 'the' when sorting snarks
|
||||
* I2PTunnelHTTPClient: Reject 192.168.*
|
||||
* Plugins:
|
||||
- Set classpath for specific client only, not for the whole JVM
|
||||
- Use ConfigDir() not AppDir()
|
||||
* Replace size() <= 0 with isEmpty() everywhere, ditto > 0 -> !isEmpty()
|
||||
* RouterInfo: Clean up use of sortStructures()
|
||||
|
||||
2010-05-02 zzz
|
||||
* ByteCache:
|
||||
- Add a per-cache stat
|
||||
|
@ -18,7 +18,7 @@ public class RouterVersion {
|
||||
/** deprecated */
|
||||
public final static String ID = "Monotone";
|
||||
public final static String VERSION = CoreVersion.VERSION;
|
||||
public final static long BUILD = 2;
|
||||
public final static long BUILD = 3;
|
||||
|
||||
/** for example "-test" */
|
||||
public final static String EXTRA = "";
|
||||
|
@ -30,7 +30,7 @@ public class LoadClientAppsJob extends JobImpl {
|
||||
_loaded = true;
|
||||
}
|
||||
List apps = ClientAppConfig.getClientApps(getContext());
|
||||
if (apps.size() <= 0) {
|
||||
if (apps.isEmpty()) {
|
||||
_log.error("Warning - No client apps or router console configured - we are just a router");
|
||||
System.err.println("Warning - No client apps or router console configured - we are just a router");
|
||||
return;
|
||||
@ -56,23 +56,26 @@ public class LoadClientAppsJob extends JobImpl {
|
||||
private String _args[];
|
||||
private Log _log;
|
||||
private ThreadGroup _threadGroup;
|
||||
private ClassLoader _cl;
|
||||
|
||||
public DelayedRunClient(RouterContext enclosingContext, String className, String clientName, String args[], long delay) {
|
||||
this(enclosingContext, className, clientName, args, delay, null);
|
||||
this(enclosingContext, className, clientName, args, delay, null, null);
|
||||
}
|
||||
|
||||
public DelayedRunClient(RouterContext enclosingContext, String className, String clientName, String args[], long delay, ThreadGroup threadGroup) {
|
||||
public DelayedRunClient(RouterContext enclosingContext, String className, String clientName, String args[],
|
||||
long delay, ThreadGroup threadGroup, ClassLoader cl) {
|
||||
super(enclosingContext);
|
||||
_className = className;
|
||||
_clientName = clientName;
|
||||
_args = args;
|
||||
_log = enclosingContext.logManager().getLog(LoadClientAppsJob.class);
|
||||
_threadGroup = threadGroup;
|
||||
_cl = cl;
|
||||
getTiming().setStartAfter(getContext().clock().now() + delay);
|
||||
}
|
||||
public String getName() { return "Delayed client job"; }
|
||||
public void runJob() {
|
||||
runClient(_className, _clientName, _args, _log, _threadGroup);
|
||||
runClient(_className, _clientName, _args, _log, _threadGroup, _cl);
|
||||
}
|
||||
}
|
||||
|
||||
@ -129,50 +132,81 @@ public class LoadClientAppsJob extends JobImpl {
|
||||
* to propagate an error back to the user,
|
||||
* since runClient() runs in a separate thread.
|
||||
*
|
||||
* @param cl can be null
|
||||
* @since 0.7.13
|
||||
*/
|
||||
public static void testClient(String className) throws ClassNotFoundException {
|
||||
Class.forName(className);
|
||||
public static void testClient(String className, ClassLoader cl) throws ClassNotFoundException {
|
||||
if (cl == null)
|
||||
cl = ClassLoader.getSystemClassLoader();
|
||||
Class.forName(className, false, cl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run client in this thread.
|
||||
*
|
||||
* @param clientName can be null
|
||||
* @param args can be null
|
||||
* @throws just about anything, caller would be wise to catch Throwable
|
||||
* @since 0.7.13
|
||||
*/
|
||||
public static void runClientInline(String className, String clientName, String args[], Log log) throws Exception {
|
||||
runClientInline(className, clientName, args, log, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run client in this thread.
|
||||
*
|
||||
* @param clientName can be null
|
||||
* @param args can be null
|
||||
* @param cl can be null
|
||||
* @throws just about anything, caller would be wise to catch Throwable
|
||||
* @since 0.7.14
|
||||
*/
|
||||
public static void runClientInline(String className, String clientName, String args[],
|
||||
Log log, ClassLoader cl) throws Exception {
|
||||
if (log.shouldLog(Log.INFO))
|
||||
log.info("Loading up the client application " + clientName + ": " + className + " " + Arrays.toString(args));
|
||||
if (args == null)
|
||||
args = new String[0];
|
||||
Class cls = Class.forName(className);
|
||||
Class cls = Class.forName(className, true, cl);
|
||||
Method method = cls.getMethod("main", new Class[] { String[].class });
|
||||
method.invoke(cls, new Object[] { args });
|
||||
}
|
||||
|
||||
/**
|
||||
* Run client in a new thread.
|
||||
*
|
||||
* @param clientName can be null
|
||||
* @param args can be null
|
||||
*/
|
||||
public static void runClient(String className, String clientName, String args[], Log log) {
|
||||
runClient(className, clientName, args, log, null);
|
||||
runClient(className, clientName, args, log, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run client in a new thread.
|
||||
*
|
||||
* @param clientName can be null
|
||||
* @param args can be null
|
||||
* @param threadGroup can be null
|
||||
* @param cl can be null
|
||||
* @since 0.7.13
|
||||
*/
|
||||
public static void runClient(String className, String clientName, String args[], Log log, ThreadGroup threadGroup) {
|
||||
public static void runClient(String className, String clientName, String args[], Log log,
|
||||
ThreadGroup threadGroup, ClassLoader cl) {
|
||||
if (log.shouldLog(Log.INFO))
|
||||
log.info("Loading up the client application " + clientName + ": " + className + " " + Arrays.toString(args));
|
||||
I2PThread t;
|
||||
if (threadGroup != null)
|
||||
t = new I2PThread(threadGroup, new RunApp(className, clientName, args, log));
|
||||
t = new I2PThread(threadGroup, new RunApp(className, clientName, args, log, cl));
|
||||
else
|
||||
t = new I2PThread(new RunApp(className, clientName, args, log));
|
||||
t = new I2PThread(new RunApp(className, clientName, args, log, cl));
|
||||
if (clientName == null)
|
||||
clientName = className + " client";
|
||||
t.setName(clientName);
|
||||
t.setDaemon(true);
|
||||
if (cl != null)
|
||||
t.setContextClassLoader(cl);
|
||||
t.start();
|
||||
}
|
||||
|
||||
@ -181,7 +215,9 @@ public class LoadClientAppsJob extends JobImpl {
|
||||
private String _appName;
|
||||
private String _args[];
|
||||
private Log _log;
|
||||
public RunApp(String className, String appName, String args[], Log log) {
|
||||
private ClassLoader _cl;
|
||||
|
||||
public RunApp(String className, String appName, String args[], Log log, ClassLoader cl) {
|
||||
_className = className;
|
||||
_appName = appName;
|
||||
if (args == null)
|
||||
@ -189,10 +225,15 @@ public class LoadClientAppsJob extends JobImpl {
|
||||
else
|
||||
_args = args;
|
||||
_log = log;
|
||||
if (cl == null)
|
||||
_cl = ClassLoader.getSystemClassLoader();
|
||||
else
|
||||
_cl = cl;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
Class cls = Class.forName(_className);
|
||||
Class cls = Class.forName(_className, true, _cl);
|
||||
Method method = cls.getMethod("main", new Class[] { String[].class });
|
||||
method.invoke(cls, new Object[] { _args });
|
||||
} catch (Throwable t) {
|
||||
|
Reference in New Issue
Block a user