package net.i2p.router.startup;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.List;
import net.i2p.I2PAppContext;
import net.i2p.app.ClientApp;
import net.i2p.app.ClientAppManager;
import net.i2p.router.JobImpl;
import net.i2p.router.RouterContext;
import net.i2p.router.app.RouterApp;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
import net.i2p.util.SimpleTimer2;
/**
* Run any client applications specified in clients.config. If any clientApp
* contains the config property ".onBoot=true" it'll be launched immediately, otherwise
* it'll get queued up for starting 2 minutes later.
*
*/
public class LoadClientAppsJob extends JobImpl {
private final Log _log;
private static boolean _loaded = false;
public LoadClientAppsJob(RouterContext ctx) {
super(ctx);
_log = ctx.logManager().getLog(LoadClientAppsJob.class);
}
public void runJob() {
synchronized (LoadClientAppsJob.class) {
if (_loaded) return;
_loaded = true;
}
List<ClientAppConfig> apps = ClientAppConfig.getClientApps(getContext());
if (apps.isEmpty()) {
_log.logAlways(Log.WARN, "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;
}
for(int i = 0; i < apps.size(); i++) {
ClientAppConfig app = apps.get(i);
if (app.disabled) {
if ("net.i2p.router.web.RouterConsoleRunner".equals(app.className)) {
String s = "Warning - Router console is disabled. To enable,\n edit the file " +
ClientAppConfig.configFile(getContext()) +
",\n change the line \"clientApp." + i + ".startOnLoad=false\"" +
" to \"clientApp." + i + ".startOnLoad=true\",\n and restart.";
_log.logAlways(Log.WARN, s);
System.err.println(s);
}
continue;
}
String argVal[] = parseArgs(app.args);
if (app.delay <= 0) {
// run this guy now
runClient(app.className, app.clientName, argVal, getContext(), _log);
} else {
// wait before firing it up
DelayedRunClient drc = new DelayedRunClient(getContext().simpleTimer2(), getContext(), app.className,
app.clientName, argVal);
drc.schedule(app.delay);
}
}
}
/**
* Public for router console only, not for use by others, subject to change
*/
public static class DelayedRunClient extends SimpleTimer2.TimedEvent {
private final RouterContext _ctx;
private final String _className;
private final String _clientName;
private final String _args[];
private final Log _log;
private final ThreadGroup _threadGroup;
private final ClassLoader _cl;
/** caller MUST call schedule() */
public DelayedRunClient(SimpleTimer2 pool, RouterContext enclosingContext, String className,
String clientName, String args[]) {
this(pool, enclosingContext, className, clientName, args, null, null);
}
/** caller MUST call schedule() */
public DelayedRunClient(SimpleTimer2 pool, RouterContext enclosingContext, String className, String clientName,
String args[], ThreadGroup threadGroup, ClassLoader cl) {
super(pool);
_ctx = enclosingContext;
_className = className;
_clientName = clientName;
_args = args;
_log = enclosingContext.logManager().getLog(LoadClientAppsJob.class);
_threadGroup = threadGroup;
_cl = cl;
}
public void timeReached() {
runClient(_className, _clientName, _args, _ctx, _log, _threadGroup, _cl);
}
}
/**
* Parse arg string into an array of args.
* Spaces or tabs separate args.
* Args may be single- or double-quoted if they contain spaces or tabs.
* There is no provision for escaping quotes.
* A quoted string may not contain a quote of any kind.
*
* @param args may be null
* @return non-null, 0-length if args is null
*/
public static String[] parseArgs(String args) {
List<String> argList = new ArrayList<String>(4);
if (args != null) {
char data[] = args.toCharArray();
StringBuilder buf = new StringBuilder(32);
boolean isQuoted = false;
for (int i = 0; i < data.length; i++) {
switch (data[i]) {
case '\'':
case '"':
if (isQuoted) {
String str = buf.toString().trim();
if (str.length() > 0)
argList.add(str);
buf = new StringBuilder(32);
}
isQuoted = !isQuoted;
break;
case ' ':
case '\t':
// whitespace - if we're in a quoted section, keep this as part of the quote,
// otherwise use it as a delim
if (isQuoted) {
buf.append(data[i]);
} else {
String str = buf.toString().trim();
if (str.length() > 0)
argList.add(str);
buf = new StringBuilder(32);
}
break;
default:
buf.append(data[i]);
break;
}
}
if (buf.length() > 0) {
String str = buf.toString().trim();
if (str.length() > 0)
argList.add(str);
}
}
String rv[] = new String[argList.size()];
for (int i = 0; i < argList.size(); i++) {
rv[i] = argList.get(i);
}
return rv;
}
/**
* Use to test if the class is present,
* 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, ClassLoader cl) throws ClassNotFoundException {
if (cl == null)
cl = ClassLoader.getSystemClassLoader();
Class.forName(className, false, cl);
}
/**
* Run client in this thread.
* Used for plugin sub-clients only. Does not register with the ClientAppManager.
*
* @param clientName can be null
* @param args can be null
* @throws Exception 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.
* Used for plugin sub-clients only. Does not register with the ClientAppManager.
*
* @param clientName can be null
* @param args can be null
* @param cl can be null
* @throws Exception 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, true, cl);
Method method = cls.getMethod("main", 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[], RouterContext ctx, Log log) {
runClient(className, clientName, args, ctx, 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[], RouterContext ctx, 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, ctx, log, cl));
else
t = new I2PThread(new RunApp(className, clientName, args, ctx, log, cl));
if (clientName == null)
clientName = className + " client";
t.setName(clientName);
t.setDaemon(true);
if (cl != null)
t.setContextClassLoader(cl);
t.start();
}
private final static class RunApp implements Runnable {
private final String _className;
private final String _appName;
private final String _args[];
private final RouterContext _ctx;
private final Log _log;
private final ClassLoader _cl;
public RunApp(String className, String appName, String args[], RouterContext ctx, Log log, ClassLoader cl) {
_className = className;
_appName = appName;
if (args == null)
_args = new String[0];
else
_args = args;
_ctx = ctx;
_log = log;
if (cl == null)
_cl = ClassLoader.getSystemClassLoader();
else
_cl = cl;
}
public void run() {
try {
Class<?> cls = Class.forName(_className, true, _cl);
if (isRouterApp(cls)) {
Constructor<?> con = cls.getConstructor(RouterContext.class, ClientAppManager.class, String[].class);
RouterAppManager mgr = _ctx.routerAppManager();
Object[] conArgs = new Object[] {_ctx, _ctx.clientAppManager(), _args};
RouterApp app = (RouterApp) con.newInstance(conArgs);
mgr.addAndStart(app, _args);
} else if (isClientApp(cls)) {
Constructor<?> con = cls.getConstructor(I2PAppContext.class, ClientAppManager.class, String[].class);
RouterAppManager mgr = _ctx.routerAppManager();
Object[] conArgs = new Object[] {_ctx, _ctx.clientAppManager(), _args};
ClientApp app = (ClientApp) con.newInstance(conArgs);
mgr.addAndStart(app, _args);
} else {
Method method = cls.getMethod("main", String[].class);
method.invoke(cls, new Object[] { _args });
}
} catch (Throwable t) {
_log.log(Log.CRIT, "Error starting up the client class " + _className, t);
}
if (_log.shouldLog(Log.INFO))
_log.info("Done running client application " + _appName);
}
private static boolean isRouterApp(Class<?> cls) {
return isInterface(cls, RouterApp.class);
}
private static boolean isClientApp(Class<?> cls) {
return isInterface(cls, ClientApp.class);
}
private static boolean isInterface(Class<?> cls, Class<?> intfc) {
try {
Class<?>[] intfcs = cls.getInterfaces();
for (int i = 0; i < intfcs.length; i++) {
if (intfcs[i] == intfc)
return true;
}
} catch (Throwable t) {}
return false;
}
}
public String getName() { return "Load up any client applications"; }
/****
public static void main(String args[]) {
test(null);
test("hi how are you?");
test("hi how are you? ");
test(" hi how are you? ");
test(" hi how are \"y\"ou? ");
test("-nogui -e \"config localhost 17654\" -e \"httpclient 4544\"");
test("-nogui -e 'config localhost 17654' -e 'httpclient 4544'");
}
private static void test(String args) {
String parsed[] = parseArgs(args);
System.out.print("Parsed [" + args + "] into " + parsed.length + " elements: ");
for (int i = 0; i < parsed.length; i++)
System.out.print("[" + parsed[i] + "] ");
System.out.println();
}
****/
}