forked from I2P_Developers/i2p.i2p
230 lines
7.2 KiB
Java
230 lines
7.2 KiB
Java
package net.i2p.router.startup;
|
|
|
|
import java.io.IOException;
|
|
import java.io.Writer;
|
|
import java.util.Arrays;
|
|
import java.util.ArrayList;
|
|
import java.util.Collections;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
|
|
import net.i2p.app.*;
|
|
import static net.i2p.app.ClientAppState.*;
|
|
import net.i2p.router.RouterContext;
|
|
import net.i2p.util.Log;
|
|
|
|
/**
|
|
* Notify the router of events, and provide methods for
|
|
* client apps to find each other.
|
|
*
|
|
* @since 0.9.4
|
|
*/
|
|
public class RouterAppManager extends ClientAppManagerImpl {
|
|
|
|
private final RouterContext _context;
|
|
private final Log _log;
|
|
// client to args
|
|
// this assumes clients do not override equals()
|
|
private final ConcurrentHashMap<ClientApp, String[]> _clients;
|
|
|
|
public RouterAppManager(RouterContext ctx) {
|
|
super(ctx);
|
|
_context = ctx;
|
|
_log = ctx.logManager().getLog(RouterAppManager.class);
|
|
_clients = new ConcurrentHashMap<ClientApp, String[]>(16);
|
|
ctx.addShutdownTask(new Shutdown());
|
|
}
|
|
|
|
/**
|
|
* @param args the args that were used to instantiate the app, non-null, may be zero-length
|
|
* @return success
|
|
* @throws IllegalArgumentException if already added
|
|
*/
|
|
public boolean addAndStart(ClientApp app, String[] args) {
|
|
if (_log.shouldLog(Log.INFO))
|
|
_log.info("Client " + app.getDisplayName() + " ADDED");
|
|
String[] old = _clients.putIfAbsent(app, args);
|
|
if (old != null)
|
|
throw new IllegalArgumentException("already added");
|
|
try {
|
|
app.startup();
|
|
return true;
|
|
} catch (Throwable t) {
|
|
_clients.remove(app);
|
|
_log.error("Client " + app + " failed to start", t);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the first known ClientApp with this class name and exact arguments.
|
|
* Caller may then retrieve or control the state of the returned client.
|
|
* A client will generally be found only if it is running or transitioning;
|
|
* after it is stopped it will not be tracked by the manager.
|
|
*
|
|
* @param args non-null, may be zero-length
|
|
* @return client app or null
|
|
* @since 0.9.6
|
|
*/
|
|
public ClientApp getClientApp(String className, String[] args) {
|
|
for (Map.Entry<ClientApp, String[]> e : _clients.entrySet()) {
|
|
if (e.getKey().getClass().getName().equals(className) &&
|
|
Arrays.equals(e.getValue(), args))
|
|
return e.getKey();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// ClientAppManager methods
|
|
|
|
/**
|
|
* Must be called on all state transitions except
|
|
* from UNINITIALIZED to INITIALIZED.
|
|
*
|
|
* @param app non-null
|
|
* @param state non-null
|
|
* @param message may be null
|
|
* @param e may be null
|
|
*/
|
|
@Override
|
|
public void notify(ClientApp app, ClientAppState state, String message, Exception e) {
|
|
switch(state) {
|
|
case UNINITIALIZED:
|
|
case INITIALIZED:
|
|
if (_log.shouldLog(Log.WARN))
|
|
_log.warn("Client " + app.getDisplayName() + " is now " + state);
|
|
break;
|
|
|
|
case STARTING:
|
|
case RUNNING:
|
|
if (_log.shouldLog(Log.INFO))
|
|
_log.info("Client " + app.getDisplayName() + " is now " + state);
|
|
break;
|
|
|
|
case FORKED:
|
|
case STOPPING:
|
|
case STOPPED:
|
|
_clients.remove(app);
|
|
_registered.remove(app.getName(), app);
|
|
if (message == null)
|
|
message = "";
|
|
if (_log.shouldLog(Log.INFO))
|
|
_log.info("Client " + app.getDisplayName() + " is now " + state +
|
|
' ' + message, e);
|
|
break;
|
|
|
|
case CRASHED:
|
|
case START_FAILED:
|
|
_clients.remove(app);
|
|
_registered.remove(app.getName(), app);
|
|
if (message == null)
|
|
message = "";
|
|
_log.log(Log.CRIT, "Client " + app.getDisplayName() + ' ' + state +
|
|
' ' + message, e);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Register with the manager under the given name,
|
|
* so that other clients may find it.
|
|
* Only required for apps used by other apps.
|
|
*
|
|
* @param app non-null
|
|
* @return true if successful, false if duplicate name
|
|
*/
|
|
@Override
|
|
public boolean register(ClientApp app) {
|
|
if (!_clients.containsKey(app)) {
|
|
// Allow registration even if we didn't start it,
|
|
// useful for plugins
|
|
if (_log.shouldLog(Log.INFO))
|
|
_log.info("Registering untracked client " + app.getName());
|
|
//return false;
|
|
}
|
|
if (_log.shouldLog(Log.INFO))
|
|
_log.info("Client " + app.getDisplayName() + " REGISTERED AS " + app.getName());
|
|
// TODO if old app in there is not running and != this app, allow replacement
|
|
return super.register(app);
|
|
}
|
|
|
|
/// end ClientAppManager interface
|
|
|
|
/**
|
|
* @since 0.9.6
|
|
*/
|
|
public synchronized void shutdown() {
|
|
Set<ClientApp> apps = new HashSet<ClientApp>(_clients.keySet());
|
|
for (ClientApp app : apps) {
|
|
ClientAppState state = app.getState();
|
|
if (state == RUNNING || state == STARTING) {
|
|
try {
|
|
if (_log.shouldWarn())
|
|
_log.warn("Shutting down client " + app.getDisplayName());
|
|
app.shutdown(null);
|
|
} catch (Throwable t) {}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @since 0.9.6
|
|
*/
|
|
public class Shutdown implements Runnable {
|
|
public void run() {
|
|
shutdown();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* debug
|
|
* @since 0.9.6
|
|
*/
|
|
public void renderStatusHTML(Writer out) throws IOException {
|
|
StringBuilder buf = new StringBuilder(1024);
|
|
buf.append("<h2>App Manager</h2>");
|
|
buf.append("<h3>Tracked</h3>");
|
|
toString1(buf);
|
|
buf.append("<h3>Registered</h3>");
|
|
toString2(buf);
|
|
out.write(buf.toString());
|
|
}
|
|
|
|
/**
|
|
* debug
|
|
* @since 0.9.6
|
|
*/
|
|
private void toString1(StringBuilder buf) {
|
|
List<String> list = new ArrayList<String>(_clients.size());
|
|
for (Map.Entry<ClientApp, String[]> entry : _clients.entrySet()) {
|
|
ClientApp key = entry.getKey();
|
|
String[] val = entry.getValue();
|
|
list.add("[" + key.getName() + "] = [" + key.getClass().getName() + ' ' + Arrays.toString(val) + "] " + key.getState() + "<br>");
|
|
}
|
|
Collections.sort(list);
|
|
for (String e : list) {
|
|
buf.append(e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* debug
|
|
* @since 0.9.6
|
|
*/
|
|
private void toString2(StringBuilder buf) {
|
|
List<String> list = new ArrayList<String>(_registered.size());
|
|
for (Map.Entry<String, ClientApp> entry : _registered.entrySet()) {
|
|
String key = entry.getKey();
|
|
ClientApp val = entry.getValue();
|
|
list.add("[" + key + "] = [" + val.getClass().getName() + "]<br>");
|
|
}
|
|
Collections.sort(list);
|
|
for (String e : list) {
|
|
buf.append(e);
|
|
}
|
|
}
|
|
}
|