I2PAppContext.java 31.29 KiB
package net.i2p;
import java.io.File;
import java.util.Collections;
import java.util.HashSet;
import java.util.Properties;
import java.util.Random;
import java.util.Set;
import net.i2p.client.naming.NamingService;
import net.i2p.crypto.AESEngine;
import net.i2p.crypto.CryptixAESEngine;
import net.i2p.crypto.DSAEngine;
import net.i2p.crypto.DummyDSAEngine;
import net.i2p.crypto.DummyElGamalEngine;
//import net.i2p.crypto.DummyPooledRandomSource;
import net.i2p.crypto.ElGamalAESEngine;
import net.i2p.crypto.ElGamalEngine;
import net.i2p.crypto.HMAC256Generator;
import net.i2p.crypto.HMACGenerator;
import net.i2p.crypto.KeyGenerator;
import net.i2p.crypto.SHA256Generator;
import net.i2p.crypto.SessionKeyManager;
import net.i2p.crypto.TransientSessionKeyManager;
import net.i2p.data.Base64;
import net.i2p.data.RoutingKeyGenerator;
import net.i2p.internal.InternalClientManager;
import net.i2p.stat.StatManager;
import net.i2p.util.Clock;
import net.i2p.util.ConcurrentHashSet;
import net.i2p.util.FileUtil;
import net.i2p.util.FortunaRandomSource;
import net.i2p.util.I2PProperties;
import net.i2p.util.KeyRing;
import net.i2p.util.LogManager;
//import net.i2p.util.PooledRandomSource;
import net.i2p.util.RandomSource;
import net.i2p.util.SecureDirectory;
import net.i2p.util.I2PProperties.I2PPropertyCallback;
/**
* <p>Provide a base scope for accessing singletons that I2P exposes. Rather than
* using the traditional singleton, where any component can access the component
* in question directly, all of those I2P related singletons are exposed through
* a particular I2PAppContext. This helps not only with understanding their use
* and the components I2P exposes, but it also allows multiple isolated
* environments to operate concurrently within the same JVM - particularly useful
* for stubbing out implementations of the rooted components and simulating the
* software's interaction between multiple instances.</p>
*
* As a simplification, there is also a global context - if some component needs
* access to one of the singletons but doesn't have its own context from which
* to root itself, it binds to the I2PAppContext's globalAppContext(), which is
* the first context that was created within the JVM, or a new one if no context
* existed already. This functionality is often used within the I2P core for
* logging - e.g. <pre>
* private static final Log _log = new Log(someClass.class);
* </pre>
* It is for this reason that applications that care about working with multiple
* contexts should build their own context as soon as possible (within the main(..))
* so that any referenced components will latch on to that context instead of
* instantiating a new one. However, there are situations in which both can be
* relevent.
*
*/
public class I2PAppContext {
/** the context that components without explicit root are bound */
protected static volatile I2PAppContext _globalAppContext;
protected I2PProperties _overrideProps;
private StatManager _statManager;
private SessionKeyManager _sessionKeyManager;
private NamingService _namingService;
private ElGamalEngine _elGamalEngine;
private ElGamalAESEngine _elGamalAESEngine;
private AESEngine _AESEngine;
private LogManager _logManager;
private HMACGenerator _hmac;
private HMAC256Generator _hmac256;
private SHA256Generator _sha;
protected Clock _clock; // overridden in RouterContext
private DSAEngine _dsa;
private RoutingKeyGenerator _routingKeyGenerator;
private RandomSource _random;
private KeyGenerator _keyGenerator;
protected KeyRing _keyRing; // overridden in RouterContext
private volatile boolean _statManagerInitialized;
private volatile boolean _sessionKeyManagerInitialized;
private volatile boolean _namingServiceInitialized;
private volatile boolean _elGamalEngineInitialized;
private volatile boolean _elGamalAESEngineInitialized;
private volatile boolean _AESEngineInitialized;
private volatile boolean _logManagerInitialized;
private volatile boolean _hmacInitialized;
private volatile boolean _hmac256Initialized;
private volatile boolean _shaInitialized;
protected volatile boolean _clockInitialized; // used in RouterContext
private volatile boolean _dsaInitialized;
private volatile boolean _routingKeyGeneratorInitialized;
private volatile boolean _randomInitialized;
private volatile boolean _keyGeneratorInitialized;
protected volatile boolean _keyRingInitialized; // used in RouterContext
protected final Set<Runnable> _shutdownTasks;
private File _baseDir;
private File _configDir;
private File _routerDir;
private File _pidDir;
private File _logDir;
private File _appDir;
private File _tmpDir;
// split up big lock on this to avoid deadlocks
private final Object _lock1 = new Object(), _lock2 = new Object(), _lock3 = new Object(), _lock4 = new Object(),
_lock5 = new Object(), _lock6 = new Object(), _lock7 = new Object(), _lock8 = new Object(),
_lock9 = new Object(), _lock10 = new Object(), _lock11 = new Object(), _lock12 = new Object(),
_lock13 = new Object(), _lock14 = new Object(), _lock15 = new Object(), _lock16 = new Object(),
_lock17 = new Object();
/**
* Pull the default context, creating a new one if necessary, else using
* the first one created.
*
* Warning - do not save the returned value, or the value of any methods below,
* in a static field, or you will get the old context if a new router is
* started in the same JVM after the first is shut down,
* e.g. on Android.
*/
public static I2PAppContext getGlobalContext() {
// skip the global lock - _gAC must be volatile
// http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
I2PAppContext rv = _globalAppContext;
if (rv != null)
return rv;
synchronized (I2PAppContext.class) {
if (_globalAppContext == null) {
_globalAppContext = new I2PAppContext(false, null);
}
}
return _globalAppContext;
}
/**
* Pull the default context, WITHOUT creating a new one.
* Use this in static methods used early in router initialization,
* where creating a context messes things up.
*
* @return context or null
* @since 0.8.2
*/
public static I2PAppContext getCurrentContext() {
return _globalAppContext;
}
/**
* Create a brand new context.
* WARNING: In almost all cases, you should use getGlobalContext() instead,
* to avoid creating additional contexts, which may spawn numerous
* additional resources and threads, and may be the cause of logging
* problems or hard-to-isolate bugs.
*/
public I2PAppContext() {
this(true, null);
}
/**
* Create a brand new context.
* WARNING: In almost all cases, you should use getGlobalContext() instead,
* to avoid creating additional contexts, which may spawn numerous
* additional resources and threads, and may be the cause of logging
* problems or hard-to-isolate bugs.
*/
public I2PAppContext(Properties envProps) {
this(true, envProps);
}
/**
* Create a brand new context.
* WARNING: In almost all cases, you should use getGlobalContext() instead,
* to avoid creating additional contexts, which may spawn numerous
* additional resources and threads, and may be the cause of logging
* problems or hard-to-isolate bugs.
*
* @param doInit should this context be used as the global one (if necessary)?
* Will only apply if there is no global context now.
*/
private I2PAppContext(boolean doInit, Properties envProps) {
if (doInit) {
synchronized (I2PAppContext.class) {
if (_globalAppContext == null) {
_globalAppContext = this;
} else {
System.out.println("Warning - New context not replacing old one, you now have a second one");
(new Exception("I did it")).printStackTrace();
}
}
}
_overrideProps = new I2PProperties();
if (envProps != null)
_overrideProps.putAll(envProps);
_shutdownTasks = new ConcurrentHashSet(32);
initializeDirs();
}
/**
* Directories. These are all set at instantiation and will not be changed by
* subsequent property changes.
* All properties, if set, should be absolute paths.
*
* Name Property Method Files
* ----- -------- ----- -----
* Base i2p.dir.base getBaseDir() lib/, webapps/, docs/, geoip/, licenses/, ...
* Temp i2p.dir.temp getTempDir() Temporary files
* Config i2p.dir.config getConfigDir() *.config, hosts.txt, addressbook/, ...
* PID i2p.dir.pid getPIDDir() router.ping
*
* (the following all default to the same as Config)
*
* Router i2p.dir.router getRouterDir() netDb/, peerProfiles/, router.*, keyBackup/, ...
* Log i2p.dir.log getLogDir() logs/
* App i2p.dir.app getAppDir() eepsite/, ...
*
* Note that we can't control where the wrapper puts its files.
*
* The app dir is where all data files should be. Apps should always read and write files here,
* using a constructor such as:
*
* String path = mypath;
* File f = new File(path);
* if (!f.isAbsolute())
* f = new File(_context.geAppDir(), path);
*
* and never attempt to access files in the CWD using
*
* File f = new File("foo");
*
* An app should assume the CWD is not writable.
*
* Here in I2PAppContext, all the dirs default to CWD.
* However these will be different in RouterContext, as Router.java will set
* the properties in the RouterContext constructor.
*
* Apps should never need to access the base dir, which is the location of the base I2P install.
* However this is provided for the router's use, and for backward compatibility should an app
* need to look there as well.
*
* All dirs except the base are created if they don't exist, but the creation will fail silently.
* @since 0.7.6
*/
private void initializeDirs() {
String s = getProperty("i2p.dir.base", System.getProperty("user.dir"));
_baseDir = new File(s);
// config defaults to base
s = getProperty("i2p.dir.config");
if (s != null) {
_configDir = new SecureDirectory(s);
if (!_configDir.exists())
_configDir.mkdir();
} else {
_configDir = _baseDir;
}
// router defaults to config
s = getProperty("i2p.dir.router");
if (s != null) {
_routerDir = new SecureDirectory(s);
if (!_routerDir.exists())
_routerDir.mkdir();
} else {
_routerDir = _configDir;
}
// pid defaults to system temp directory
s = getProperty("i2p.dir.pid", System.getProperty("java.io.tmpdir"));
_pidDir = new File(s);
if (!_pidDir.exists())
_pidDir.mkdir();
// these all default to router
s = getProperty("i2p.dir.log");
if (s != null) {
_logDir = new SecureDirectory(s);
if (!_logDir.exists())
_logDir.mkdir();
} else {
_logDir = _routerDir;
}
s = getProperty("i2p.dir.app");
if (s != null) {
_appDir = new SecureDirectory(s);
if (!_appDir.exists())
_appDir.mkdir();
} else {
_appDir = _routerDir;
}
/******
(new Exception("Initialized by")).printStackTrace();
System.err.println("Base directory: " + _baseDir.getAbsolutePath());
System.err.println("Config directory: " + _configDir.getAbsolutePath());
System.err.println("Router directory: " + _routerDir.getAbsolutePath());
System.err.println("App directory: " + _appDir.getAbsolutePath());
System.err.println("Log directory: " + _logDir.getAbsolutePath());
System.err.println("PID directory: " + _pidDir.getAbsolutePath());
System.err.println("Temp directory: " + getTempDir().getAbsolutePath());
******/
}
/**
* This is the installation dir, often referred to as $I2P.
* Applilcations should consider this directory read-only and never
* attempt to write to it.
* It may actually be read-only on a multi-user installation.
* The config files in this directory are templates for user
* installations and should not be accessed by applications.
* The only thing that may be useful in here is the lib/ dir
* containing the .jars.
* @since 0.7.6
* @return dir constant for the life of the context
*/
public File getBaseDir() { return _baseDir; }
/**
* The base dir for config files.
* Applications may use this to access router configuration files if necessary.
* Usually ~/.i2p on Linux and %APPDIR%\I2P on Windows.
* In installations originally installed with 0.7.5 or earlier, and in
* "portable" installations, this will be the same as the base dir.
* @since 0.7.6
* @return dir constant for the life of the context
*/
public File getConfigDir() { return _configDir; }
/**
* Where the router keeps its files.
* Applications should not use this.
* The same as the config dir for now.
* @since 0.7.6
* @return dir constant for the life of the context
*/
public File getRouterDir() { return _routerDir; }
/**
* Where router.ping goes.
* Applications should not use this.
* The same as the system temp dir for now.
* Which is a problem for multi-user installations.
* @since 0.7.6
* @return dir constant for the life of the context
*/
public File getPIDDir() { return _pidDir; }
/**
* Where the router keeps its log directory.
* Applications should not use this.
* The same as the config dir for now.
* (i.e. ~/.i2p, NOT ~/.i2p/logs)
* @since 0.7.6
* @return dir constant for the life of the context
*/
public File getLogDir() { return _logDir; }
/**
* Where applications may store data.
* The same as the config dir for now, but may change in the future.
* Apps should be careful not to overwrite router files.
* @since 0.7.6
* @return dir constant for the life of the context
*/
public File getAppDir() { return _appDir; }
/**
* Where anybody may store temporary data.
* This is a directory created in the system temp dir on the
* first call in this context, and is deleted on JVM exit.
* Applications should create their own directory inside this directory
* to avoid collisions with other apps.
* @since 0.7.6
* @return dir constant for the life of the context
*/
public File getTempDir() {
// fixme don't synchronize every time
synchronized (_lock1) {
if (_tmpDir == null) {
String d = getProperty("i2p.dir.temp", System.getProperty("java.io.tmpdir"));
// our random() probably isn't warmed up yet
byte[] rand = new byte[6];
(new Random()).nextBytes(rand);
String f = "i2p-" + Base64.encode(rand) + ".tmp";
_tmpDir = new SecureDirectory(d, f);
if (_tmpDir.exists()) {
// good or bad ? loop and try again?
} else if (_tmpDir.mkdir()) {
_tmpDir.deleteOnExit();
} else {
System.err.println("Could not create temp dir " + _tmpDir.getAbsolutePath());
_tmpDir = new SecureDirectory(_routerDir, "tmp");
_tmpDir.mkdir();
}
}
}
return _tmpDir;
}
/** don't rely on deleteOnExit() */
public void deleteTempDir() {
synchronized (_lock1) {
if (_tmpDir != null) {
FileUtil.rmdir(_tmpDir, false);
_tmpDir = null;
}
}
}
/**
* Access the configuration attributes of this context, using properties
* provided during the context construction, or falling back on
* System.getProperty if no properties were provided during construction
* (or the specified prop wasn't included).
*
*/
public String getProperty(String propName) {
if (_overrideProps != null) {
if (_overrideProps.containsKey(propName))
return _overrideProps.getProperty(propName);
}
return System.getProperty(propName);
}
/**
* Access the configuration attributes of this context, using properties
* provided during the context construction, or falling back on
* System.getProperty if no properties were provided during construction
* (or the specified prop wasn't included).
*
*/
public String getProperty(String propName, String defaultValue) {
if (_overrideProps != null) {
if (_overrideProps.containsKey(propName))
return _overrideProps.getProperty(propName, defaultValue);
}
return System.getProperty(propName, defaultValue);
}
/**
* Return an int with an int default
*/
public int getProperty(String propName, int defaultVal) {
String val = null;
if (_overrideProps != null) {
val = _overrideProps.getProperty(propName);
if (val == null)
val = System.getProperty(propName);
}
int ival = defaultVal;
if (val != null) {
try {
ival = Integer.parseInt(val);
} catch (NumberFormatException nfe) {}
}
return ival;
}
/**
* Return a boolean with a boolean default
* @since 0.7.12
*/
public boolean getProperty(String propName, boolean defaultVal) {
String val = getProperty(propName);
if (val == null)
return defaultVal;
return Boolean.valueOf(val).booleanValue();
}
/**
* Default false
* @since 0.7.12
*/
public boolean getBooleanProperty(String propName) {
return Boolean.valueOf(getProperty(propName)).booleanValue();
}
/**
* @since 0.7.12
*/
public boolean getBooleanPropertyDefaultTrue(String propName) {
return getProperty(propName, true);
}
/**
* Access the configuration attributes of this context, listing the properties
* provided during the context construction, as well as the ones included in
* System.getProperties.
*
* WARNING - not overridden in RouterContext, doesn't contain router config settings,
* use getProperties() instead.
*
* @return set of Strings containing the names of defined system properties
*/
public Set getPropertyNames() {
Set names = new HashSet(System.getProperties().keySet());
if (_overrideProps != null)
names.addAll(_overrideProps.keySet());
return names;
}
/**
* Access the configuration attributes of this context, listing the properties
* provided during the context construction, as well as the ones included in
* System.getProperties.
*
* @return new Properties with system and context properties
* @since 0.8.4
*/
public Properties getProperties() {
Properties rv = new Properties();
rv.putAll(System.getProperties());
rv.putAll(_overrideProps);
return rv;
}
/**
* Add a callback, which will fire upon changes in the property
* given in the specific callback.
* Unimplemented in I2PAppContext: this only makes sense in a router context.
* @param callback The implementation of the callback.
*/
public void addPropertyCallback(I2PPropertyCallback callback) {}
/**
* The statistics component with which we can track various events
* over time.
*/
public StatManager statManager() {
if (!_statManagerInitialized)
initializeStatManager();
return _statManager;
}
private void initializeStatManager() {
synchronized (_lock2) {
if (_statManager == null)
_statManager = new StatManager(this);
_statManagerInitialized = true;
}
}
/**
* The session key manager which coordinates the sessionKey / sessionTag
* data. This component allows transparent operation of the
* ElGamal/AES+SessionTag algorithm, and contains all of the session tags
* for one particular application.
*
* This is deprecated for client use, it should be used only by the router
* as its own key manager. Not that clients are doing end-to-end crypto anyway.
*
* For client crypto within the router,
* use RouterContext.clientManager.getClientSessionKeyManager(dest)
*
*/
public SessionKeyManager sessionKeyManager() {
if (!_sessionKeyManagerInitialized)
initializeSessionKeyManager();
return _sessionKeyManager;
}
private void initializeSessionKeyManager() {
synchronized (_lock3) {
if (_sessionKeyManager == null)
//_sessionKeyManager = new PersistentSessionKeyManager(this);
_sessionKeyManager = new TransientSessionKeyManager(this);
_sessionKeyManagerInitialized = true;
}
}
/**
* Pull up the naming service used in this context. The naming service itself
* works by querying the context's properties, so those props should be
* specified to customize the naming service exposed.
*/
public NamingService namingService() {
if (!_namingServiceInitialized)
initializeNamingService();
return _namingService;
}
private void initializeNamingService() {
synchronized (_lock4) {
if (_namingService == null) {
_namingService = NamingService.createInstance(this);
}
_namingServiceInitialized = true;
}
}
/**
* This is the ElGamal engine used within this context. While it doesn't
* really have anything substantial that is context specific (the algorithm
* just does the algorithm), it does transparently use the context for logging
* its performance and activity. In addition, the engine can be swapped with
* the context's properties (though only someone really crazy should mess with
* it ;)
*/
public ElGamalEngine elGamalEngine() {
if (!_elGamalEngineInitialized)
initializeElGamalEngine();
return _elGamalEngine;
}
private void initializeElGamalEngine() {
synchronized (_lock5) {
if (_elGamalEngine == null) {
if ("off".equals(getProperty("i2p.encryption", "on")))
_elGamalEngine = new DummyElGamalEngine(this);
else
_elGamalEngine = new ElGamalEngine(this);
}
_elGamalEngineInitialized = true;
}
}
/**
* Access the ElGamal/AES+SessionTag engine for this context. The algorithm
* makes use of the context's sessionKeyManager to coordinate transparent
* access to the sessionKeys and sessionTags, as well as the context's elGamal
* engine (which in turn keeps stats, etc).
*
*/
public ElGamalAESEngine elGamalAESEngine() {
if (!_elGamalAESEngineInitialized)
initializeElGamalAESEngine();
return _elGamalAESEngine;
}
private void initializeElGamalAESEngine() {
synchronized (_lock6) {
if (_elGamalAESEngine == null)
_elGamalAESEngine = new ElGamalAESEngine(this);
_elGamalAESEngineInitialized = true;
}
}
/**
* Ok, I'll admit it. there is no good reason for having a context specific
* AES engine. We dont really keep stats on it, since its just too fast to
* matter. Though for the crazy people out there, we do expose a way to
* disable it.
*/
public AESEngine aes() {
if (!_AESEngineInitialized)
initializeAESEngine();
return _AESEngine;
}
private void initializeAESEngine() {
synchronized (_lock7) {
if (_AESEngine == null) {
if ("off".equals(getProperty("i2p.encryption", "on")))
_AESEngine = new AESEngine(this);
else
_AESEngine = new CryptixAESEngine(this);
}
_AESEngineInitialized = true;
}
}
/**
* Query the log manager for this context, which may in turn have its own
* set of configuration settings (loaded from the context's properties).
* Each context's logManager keeps its own isolated set of Log instances with
* their own log levels, output locations, and rotation configuration.
*/
public LogManager logManager() {
if (!_logManagerInitialized)
initializeLogManager();
return _logManager;
}
private void initializeLogManager() {
synchronized (_lock8) {
if (_logManager == null)
_logManager = new LogManager(this);
_logManagerInitialized = true;
}
}
/**
* There is absolutely no good reason to make this context specific,
* other than for consistency, and perhaps later we'll want to
* include some stats.
*/
public HMACGenerator hmac() {
if (!_hmacInitialized)
initializeHMAC();
return _hmac;
}
private void initializeHMAC() {
synchronized (_lock9) {
if (_hmac == null) {
_hmac= new HMACGenerator(this);
}
_hmacInitialized = true;
}
}
/** @deprecated used only by syndie */
public HMAC256Generator hmac256() {
if (!_hmac256Initialized)
initializeHMAC256();
return _hmac256;
}
/** @deprecated used only by syndie */
private void initializeHMAC256() {
synchronized (_lock10) {
if (_hmac256 == null) {
_hmac256 = new HMAC256Generator(this);
}
_hmac256Initialized = true;
}
}
/**
* Our SHA256 instance (see the hmac discussion for why its context specific)
*
*/
public SHA256Generator sha() {
if (!_shaInitialized)
initializeSHA();
return _sha;
}
private void initializeSHA() {
synchronized (_lock11) {
if (_sha == null)
_sha= new SHA256Generator(this);
_shaInitialized = true;
}
}
/**
* Our DSA engine (see HMAC and SHA above)
*
*/
public DSAEngine dsa() {
if (!_dsaInitialized)
initializeDSA();
return _dsa;
}
private void initializeDSA() {
synchronized (_lock12) {
if (_dsa == null) {
if ("off".equals(getProperty("i2p.encryption", "on")))
_dsa = new DummyDSAEngine(this);
else
_dsa = new DSAEngine(this);
}
_dsaInitialized = true;
}
}
/**
* Component to generate ElGamal, DSA, and Session keys. For why it is in
* the appContext, see the DSA, HMAC, and SHA comments above.
*/
public KeyGenerator keyGenerator() {
if (!_keyGeneratorInitialized)
initializeKeyGenerator();
return _keyGenerator;
}
private void initializeKeyGenerator() {
synchronized (_lock13) {
if (_keyGenerator == null)
_keyGenerator = new KeyGenerator(this);
_keyGeneratorInitialized = true;
}
}
/**
* The context's synchronized clock, which is kept context specific only to
* enable simulators to play with clock skew among different instances.
*
*/
public Clock clock() {
if (!_clockInitialized)
initializeClock();
return _clock;
}
protected void initializeClock() { // overridden in RouterContext
synchronized (_lock14) {
if (_clock == null)
_clock = new Clock(this);
_clockInitialized = true;
}
}
/**
* Determine how much do we want to mess with the keys to turn them
* into something we can route. This is context specific because we
* may want to test out how things react when peers don't agree on
* how to skew.
*
*/
public RoutingKeyGenerator routingKeyGenerator() {
if (!_routingKeyGeneratorInitialized)
initializeRoutingKeyGenerator();
return _routingKeyGenerator;
}
private void initializeRoutingKeyGenerator() {
synchronized (_lock15) {
if (_routingKeyGenerator == null)
_routingKeyGenerator = new RoutingKeyGenerator(this);
_routingKeyGeneratorInitialized = true;
}
}
/**
* Basic hash map
*/
public KeyRing keyRing() {
if (!_keyRingInitialized)
initializeKeyRing();
return _keyRing;
}
protected void initializeKeyRing() {
synchronized (_lock16) {
if (_keyRing == null)
_keyRing = new KeyRing();
_keyRingInitialized = true;
}
}
/**
* [insert snarky comment here]
*
*/
public RandomSource random() {
if (!_randomInitialized)
initializeRandom();
return _random;
}
private void initializeRandom() {
synchronized (_lock17) {
if (_random == null) {
//if (true)
_random = new FortunaRandomSource(this);
//else if ("true".equals(getProperty("i2p.weakPRNG", "false")))
// _random = new DummyPooledRandomSource(this);
//else
// _random = new PooledRandomSource(this);
}
_randomInitialized = true;
}
}
/**
* WARNING - Shutdown tasks are not executed in an I2PAppContext.
* You must be in a RouterContext for the tasks to be executed
* at shutdown.
* This method moved from Router in 0.7.1 so that clients
* may use it without depending on router.jar.
* @since 0.7.1
*/
public void addShutdownTask(Runnable task) {
_shutdownTasks.add(task);
}
/**
* @return an unmodifiable Set
* @since 0.7.1
*/
public Set<Runnable> getShutdownTasks() {
return Collections.unmodifiableSet(_shutdownTasks);
}
/**
* Use this instead of context instanceof RouterContext
* @since 0.7.9
*/
public boolean isRouterContext() {
return false;
}
/**
* Use this to connect to the router in the same JVM.
* @return always null in I2PAppContext, the client manager if in RouterContext
* @since 0.8.3
*/
public InternalClientManager internalClientManager() {
return null;
}
/**
* Is the wrapper present?
* @since 0.8.8
*/
public boolean hasWrapper() {
return System.getProperty("wrapper.version") != null;
}
}