From c269546c087437ccc2148aceb8a4cbe5eec2d025 Mon Sep 17 00:00:00 2001 From: zzz Date: Tue, 22 Feb 2011 23:39:51 +0000 Subject: [PATCH] stub out new API, needs testing --- .../client/naming/BlockfileNamingService.java | 3 +- .../i2p/client/naming/DummyNamingService.java | 104 ++++- .../naming/EepGetAndAddNamingService.java | 1 + .../client/naming/EepGetNamingService.java | 22 +- .../i2p/client/naming/ExecNamingService.java | 22 +- .../client/naming/HostsTxtNamingService.java | 151 ++----- .../i2p/client/naming/MetaNamingService.java | 137 +++++- .../net/i2p/client/naming/NamingService.java | 413 ++++++++++++++---- 8 files changed, 581 insertions(+), 272 deletions(-) diff --git a/core/java/src/net/i2p/client/naming/BlockfileNamingService.java b/core/java/src/net/i2p/client/naming/BlockfileNamingService.java index 712b29082..b9be1257a 100644 --- a/core/java/src/net/i2p/client/naming/BlockfileNamingService.java +++ b/core/java/src/net/i2p/client/naming/BlockfileNamingService.java @@ -335,8 +335,7 @@ public class BlockfileNamingService extends DummyNamingService { return rv; } - @Override - public Destination lookup(String hostname) { + public Destination lookup(String hostname, Properties lookupOptions, Properties storedOptions) { Destination d = super.lookup(hostname); if (d != null) return d; diff --git a/core/java/src/net/i2p/client/naming/DummyNamingService.java b/core/java/src/net/i2p/client/naming/DummyNamingService.java index 012fe196d..1f8d96799 100644 --- a/core/java/src/net/i2p/client/naming/DummyNamingService.java +++ b/core/java/src/net/i2p/client/naming/DummyNamingService.java @@ -7,6 +7,12 @@ */ package net.i2p.client.naming; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; + import net.i2p.I2PAppContext; import net.i2p.data.Destination; @@ -14,8 +20,12 @@ import net.i2p.data.Destination; * A Dummy naming service that can only handle base64 and b32 destinations. */ class DummyNamingService extends NamingService { + private final Map _cache; + private static final int BASE32_HASH_LENGTH = 52; // 1 + Hash.HASH_LENGTH * 8 / 5 public final static String PROP_B32 = "i2p.naming.hostsTxt.useB32"; + protected static final int CACHE_MAX_SIZE = 16; + public static final int DEST_SIZE = 516; // Std. Base64 length (no certificate) /** * The naming service should only be constructed and accessed through the @@ -23,11 +33,13 @@ class DummyNamingService extends NamingService { * appropriate application context itself. * */ - protected DummyNamingService(I2PAppContext context) { super(context); } - private DummyNamingService() { super(null); } + protected DummyNamingService(I2PAppContext context) { + super(context); + _cache = new HashMap(CACHE_MAX_SIZE); + } @Override - public Destination lookup(String hostname) { + public Destination lookup(String hostname, Properties lookupOptions, Properties storedOptions) { Destination d = getCache(hostname); if (d != null) return d; @@ -52,4 +64,90 @@ class DummyNamingService extends NamingService { return null; } + + /** + * Provide basic caching for the service + * The service may override the age and/or size limit + */ + /** Don't know why a dest would ever change but keep this short anyway */ + protected static final long CACHE_MAX_AGE = 7*60*1000; + + private class CacheEntry { + public Destination dest; + public long exp; + public CacheEntry(Destination d) { + dest = d; + exp = _context.clock().now() + CACHE_MAX_AGE; + } + public boolean isExpired() { + return exp < _context.clock().now(); + } + } + + /** + * Clean up when full. + * Don't bother removing old entries unless full. + * Caller must synchronize on _cache. + */ + private void cacheClean() { + if (_cache.size() < CACHE_MAX_SIZE) + return; + boolean full = true; + String oldestKey = null; + long oldestExp = Long.MAX_VALUE; + List deleteList = new ArrayList(CACHE_MAX_SIZE); + for (Map.Entry entry : _cache.entrySet()) { + CacheEntry ce = entry.getValue(); + if (ce.isExpired()) { + deleteList.add(entry.getKey()); + full = false; + continue; + } + if (oldestKey == null || ce.exp < oldestExp) { + oldestKey = entry.getKey(); + oldestExp = ce.exp; + } + } + if (full && oldestKey != null) + deleteList.add(oldestKey); + for (String s : deleteList) { + _cache.remove(s); + } + } + + protected void putCache(String s, Destination d) { + if (d == null) + return; + synchronized (_cache) { + _cache.put(s, new CacheEntry(d)); + cacheClean(); + } + } + + protected Destination getCache(String s) { + synchronized (_cache) { + CacheEntry ce = _cache.get(s); + if (ce == null) + return null; + if (ce.isExpired()) { + _cache.remove(s); + return null; + } + return ce.dest; + } + } + + /** @since 0.8.5 */ + protected void removeCache(String s) { + synchronized (_cache) { + _cache.remove(s); + } + } + + /** @since 0.8.1 */ + public void clearCache() { + synchronized (_cache) { + _cache.clear(); + } + } } diff --git a/core/java/src/net/i2p/client/naming/EepGetAndAddNamingService.java b/core/java/src/net/i2p/client/naming/EepGetAndAddNamingService.java index 085fe1729..83baf8a43 100644 --- a/core/java/src/net/i2p/client/naming/EepGetAndAddNamingService.java +++ b/core/java/src/net/i2p/client/naming/EepGetAndAddNamingService.java @@ -30,6 +30,7 @@ import net.i2p.data.Destination; * i2p.naming.eepget.list=http://stats.i2p/cgi-bin/hostquery.cgi?a=,http://i2host.i2p/cgi-bin/i2hostquery? * * @author zzz + * @deprecated use HostsTxtNamingService.put() * @since 0.7.9 */ public class EepGetAndAddNamingService extends EepGetNamingService { diff --git a/core/java/src/net/i2p/client/naming/EepGetNamingService.java b/core/java/src/net/i2p/client/naming/EepGetNamingService.java index 737e33423..baf47da86 100644 --- a/core/java/src/net/i2p/client/naming/EepGetNamingService.java +++ b/core/java/src/net/i2p/client/naming/EepGetNamingService.java @@ -7,6 +7,7 @@ package net.i2p.client.naming; import java.io.ByteArrayOutputStream; import java.util.ArrayList; import java.util.List; +import java.util.Properties; import java.util.StringTokenizer; import net.i2p.I2PAppContext; @@ -25,6 +26,7 @@ import net.i2p.util.Log; * Should be used from MetaNamingService, after HostsTxtNamingService. * Cannot be used as the only NamingService! Be sure any naming service hosts * are in hosts.txt. + * Supports caching, b32, and b64. * * Sample config to put in configadvanced.jsp (restart required): * @@ -33,7 +35,7 @@ import net.i2p.util.Log; * i2p.naming.eepget.list=http://namingservice.i2p/cgi-bin/lkup.cgi?host=,http://i2host.i2p/cgi-bin/i2hostquery? * */ -public class EepGetNamingService extends NamingService { +public class EepGetNamingService extends DummyNamingService { private final static String PROP_EEPGET_LIST = "i2p.naming.eepget.list"; private final static String DEFAULT_EEPGET_LIST = "http://i2host.i2p/cgi-bin/i2hostquery?"; @@ -59,22 +61,13 @@ public class EepGetNamingService extends NamingService { } @Override - public Destination lookup(String hostname) { - // If it's long, assume it's a key. - if (hostname.length() >= DEST_SIZE) - return lookupBase64(hostname); - - hostname = hostname.toLowerCase(); - - // If you want b32, chain with HostsTxtNamingService - if (hostname.length() == 60 && hostname.endsWith(".b32.i2p")) - return null; - - // check the cache - Destination d = getCache(hostname); + public Destination lookup(String hostname, Properties lookupOptions, Properties storedOptions) { + Destination d = super.lookup(hostname, null, null); if (d != null) return d; + hostname = hostname.toLowerCase(); + List URLs = getURLs(); if (URLs.isEmpty()) return null; @@ -103,7 +96,6 @@ public class EepGetNamingService extends NamingService { } // FIXME allow larger Dests for non-null Certs - private static final int DEST_SIZE = 516; // Std. Base64 length (no certificate) private static final int MAX_RESPONSE = DEST_SIZE + 68 + 10; // allow for hostname= and some trailing stuff private String fetchAddr(String url, String hostname) { ByteArrayOutputStream baos = new ByteArrayOutputStream(MAX_RESPONSE); diff --git a/core/java/src/net/i2p/client/naming/ExecNamingService.java b/core/java/src/net/i2p/client/naming/ExecNamingService.java index 118f06eac..27aa916af 100644 --- a/core/java/src/net/i2p/client/naming/ExecNamingService.java +++ b/core/java/src/net/i2p/client/naming/ExecNamingService.java @@ -5,6 +5,7 @@ package net.i2p.client.naming; import java.io.InputStream; +import java.util.Properties; import net.i2p.I2PAppContext; import net.i2p.data.Destination; @@ -27,6 +28,7 @@ import net.i2p.util.Log; * * Can be used from MetaNamingService, (e.g. after HostsTxtNamingService), * or as the sole naming service. + * Supports caching, b32, and b64. * * Sample chained config to put in configadvanced.jsp (restart required): * @@ -40,7 +42,7 @@ import net.i2p.util.Log; * i2p.naming.exec.command=/usr/local/bin/i2presolve * */ -public class ExecNamingService extends NamingService { +public class ExecNamingService extends DummyNamingService { private final static String PROP_EXEC_CMD = "i2p.naming.exec.command"; private final static String DEFAULT_EXEC_CMD = "/usr/local/bin/i2presolve"; @@ -59,22 +61,13 @@ public class ExecNamingService extends NamingService { } @Override - public Destination lookup(String hostname) { - // If it's long, assume it's a key. - if (hostname.length() >= DEST_SIZE) - return lookupBase64(hostname); - - hostname = hostname.toLowerCase(); - - // If you want b32, chain with HostsTxtNamingService - if (hostname.length() == 60 && hostname.endsWith(".b32.i2p")) - return null; - - // check the cache - Destination d = getCache(hostname); + public Destination lookup(String hostname, Properties lookupOptions, Properties storedOptions) { + Destination d = super.lookup(hostname, null, null); if (d != null) return d; + hostname = hostname.toLowerCase(); + // lookup String key = fetchAddr(hostname); if (key != null) { @@ -87,7 +80,6 @@ public class ExecNamingService extends NamingService { } // FIXME allow larger Dests for non-null Certs - private static final int DEST_SIZE = 516; // Std. Base64 length (no certificate) private static final int MAX_RESPONSE = DEST_SIZE + 68 + 10; // allow for hostname= and some trailing stuff private String fetchAddr(String hostname) { String[] commandArr = new String[3]; diff --git a/core/java/src/net/i2p/client/naming/HostsTxtNamingService.java b/core/java/src/net/i2p/client/naming/HostsTxtNamingService.java index 4a899977a..44b47848b 100644 --- a/core/java/src/net/i2p/client/naming/HostsTxtNamingService.java +++ b/core/java/src/net/i2p/client/naming/HostsTxtNamingService.java @@ -7,29 +7,21 @@ */ package net.i2p.client.naming; -import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.InputStreamReader; -import java.io.IOException; import java.util.ArrayList; -import java.util.Iterator; import java.util.List; import java.util.Properties; -import java.util.Set; import java.util.StringTokenizer; import net.i2p.I2PAppContext; -import net.i2p.data.DataFormatException; -import net.i2p.data.DataHelper; import net.i2p.data.Destination; -import net.i2p.data.Hash; -import net.i2p.util.Log; /** - * A naming service based on the "hosts.txt" file. + * A naming service based on multiple "hosts.txt" files. + * Supports .b32.i2p and {b64} lookups. + * Supports caching. + * All host names are converted to lower case. */ -public class HostsTxtNamingService extends DummyNamingService { +public class HostsTxtNamingService extends MetaNamingService { /** * The naming service should only be constructed and accessed through the @@ -37,8 +29,12 @@ public class HostsTxtNamingService extends DummyNamingService { * appropriate application context itself. * */ - public HostsTxtNamingService(I2PAppContext context) { super(context); } - private HostsTxtNamingService() { super(null); } + public HostsTxtNamingService(I2PAppContext context) { + super(context, null); + for (String name : getFilenames()) { + addNamingService(new SingleFileNamingService(context, name), false); + } + } /** * If this system property is specified, the tunnel will read the @@ -46,132 +42,39 @@ public class HostsTxtNamingService extends DummyNamingService { */ public final static String PROP_HOSTS_FILE = "i2p.hostsfilelist"; - /** default hosts.txt filename */ + /** default hosts.txt filenames */ public final static String DEFAULT_HOSTS_FILE = "privatehosts.txt,userhosts.txt,hosts.txt"; - private final static Log _log = new Log(HostsTxtNamingService.class); - - private List getFilenames() { + private List getFilenames() { String list = _context.getProperty(PROP_HOSTS_FILE, DEFAULT_HOSTS_FILE); StringTokenizer tok = new StringTokenizer(list, ","); - List rv = new ArrayList(tok.countTokens()); + List rv = new ArrayList(tok.countTokens()); while (tok.hasMoreTokens()) rv.add(tok.nextToken()); return rv; } @Override - public Destination lookup(String hostname) { - Destination d = super.lookup(hostname); - if (d != null) - return d; - - List filenames = getFilenames(); - for (int i = 0; i < filenames.size(); i++) { - String hostsfile = (String)filenames.get(i); - try { - File f = new File(_context.getRouterDir(), hostsfile); - if ( (f.exists()) && (f.canRead()) ) { - String key = getKey(f, hostname.toLowerCase()); - if ( (key != null) && (key.trim().length() > 0) ) { - d = lookupBase64(key); - putCache(hostname, d); - return d; - } - - } else { - _log.warn("Hosts file " + hostsfile + " does not exist."); - } - } catch (Exception ioe) { - _log.error("Error loading hosts file " + hostsfile, ioe); - } - // not found, continue to the next file - } - return null; + public Destination lookup(String hostname, Properties lookupOptions, Properties storedOptions) { + // If it's long, assume it's a key. + if (hostname.length() >= DEST_SIZE) + return lookupBase64(hostname); + return super.lookup(hostname.toLowerCase(), lookupOptions, storedOptions); } - + @Override - public String reverseLookup(Destination dest) { - String destkey = dest.toBase64(); - List filenames = getFilenames(); - for (int i = 0; i < filenames.size(); i++) { - String hostsfile = (String)filenames.get(i); - Properties hosts = new Properties(); - try { - File f = new File(_context.getRouterDir(), hostsfile); - if ( (f.exists()) && (f.canRead()) ) { - DataHelper.loadProps(hosts, f, true); - Set keyset = hosts.keySet(); - Iterator iter = keyset.iterator(); - while (iter.hasNext()) { - String host = (String)iter.next(); - String key = hosts.getProperty(host); - if (destkey.equals(key)) - return host; - } - } - } catch (Exception ioe) { - _log.error("Error loading hosts file " + hostsfile, ioe); - } - } - return null; + public boolean put(String hostname, Destination d, Properties options) { + return super.put(hostname.toLowerCase(), d, options); } - /** @deprecated unused */ @Override - public String reverseLookup(Hash h) { - List filenames = getFilenames(); - for (int i = 0; i < filenames.size(); i++) { - String hostsfile = (String)filenames.get(i); - Properties hosts = new Properties(); - try { - File f = new File(_context.getRouterDir(), hostsfile); - if ( (f.exists()) && (f.canRead()) ) { - DataHelper.loadProps(hosts, f, true); - Set keyset = hosts.keySet(); - Iterator iter = keyset.iterator(); - while (iter.hasNext()) { - String host = (String)iter.next(); - String key = hosts.getProperty(host); - try { - Destination destkey = new Destination(); - destkey.fromBase64(key); - if (h.equals(destkey.calculateHash())) - return host; - } catch (DataFormatException dfe) {} - } - } - } catch (Exception ioe) { - _log.error("Error loading hosts file " + hostsfile, ioe); - } - } - return null; + public boolean putIfAbsent(String hostname, Destination d, Properties options) { + return super.putIfAbsent(hostname.toLowerCase(), d, options); } - /** - * Better than DataHelper.loadProps(), doesn't load the whole file into memory, - * and stops when it finds a match. - * - * @param host lower case - * @since 0.7.13 - */ - private static String getKey(File file, String host) throws IOException { - BufferedReader in = null; - try { - in = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF-8"), 16*1024); - String line = null; - while ( (line = in.readLine()) != null) { - if (!line.toLowerCase().startsWith(host + '=')) - continue; - if (line.indexOf('#') > 0) // trim off any end of line comment - line = line.substring(0, line.indexOf('#')).trim(); - int split = line.indexOf('='); - return line.substring(split+1); //.trim() ?????????????? - } - } finally { - if (in != null) try { in.close(); } catch (IOException ioe) {} - } - return null; + @Override + public boolean remove(String hostname, Properties options) { + return super.remove(hostname.toLowerCase(), options); } } diff --git a/core/java/src/net/i2p/client/naming/MetaNamingService.java b/core/java/src/net/i2p/client/naming/MetaNamingService.java index 8f98532c6..9fef0f735 100644 --- a/core/java/src/net/i2p/client/naming/MetaNamingService.java +++ b/core/java/src/net/i2p/client/naming/MetaNamingService.java @@ -2,61 +2,154 @@ package net.i2p.client.naming; import java.lang.reflect.Constructor; import java.util.ArrayList; +import java.util.Collections; import java.util.Iterator; import java.util.List; +import java.util.Properties; import java.util.StringTokenizer; +import java.util.concurrent.CopyOnWriteArrayList; import net.i2p.I2PAppContext; import net.i2p.data.Destination; -public class MetaNamingService extends NamingService { +/** + * A naming service of multiple naming services. + * Supports .b32.i2p and {b64} lookups. + * Supports caching. + */ +public class MetaNamingService extends DummyNamingService { private final static String PROP_NAME_SERVICES = "i2p.nameservicelist"; private final static String DEFAULT_NAME_SERVICES = - "net.i2p.client.naming.PetNameNamingService,net.i2p.client.naming.HostsTxtNamingService"; - private List _services; + "net.i2p.client.naming.HostsTxtNamingService"; + + protected final List _services; public MetaNamingService(I2PAppContext context) { super(context); - String list = _context.getProperty(PROP_NAME_SERVICES, DEFAULT_NAME_SERVICES); StringTokenizer tok = new StringTokenizer(list, ","); - _services = new ArrayList(tok.countTokens()); + _services = new CopyOnWriteArrayList(); while (tok.hasMoreTokens()) { try { Class cls = Class.forName(tok.nextToken()); Constructor con = cls.getConstructor(new Class[] { I2PAppContext.class }); - _services.add(con.newInstance(new Object[] { context })); + addNamingService((NamingService)con.newInstance(new Object[] { context }), false); } catch (Exception ex) { - _services.add(new DummyNamingService(context)); // fallback + } + } + } + + /** + * @param if non-null, services to be added. If null, this will only handle b32 and b64. + * @since 0.8.5 + */ + public MetaNamingService(I2PAppContext context, List services) { + super(context); + _services = new CopyOnWriteArrayList(); + if (services != null) { + for (NamingService ns : services) { + addNamingService(ns, false); } } } @Override - public Destination lookup(String hostname) { - Iterator iter = _services.iterator(); - while (iter.hasNext()) { - NamingService ns = (NamingService)iter.next(); - Destination dest = ns.lookup(hostname); - if (dest != null) { - return dest; + public boolean addNamingService(NamingService ns, boolean head) { + if (head) + _services.add(0, ns); + else + _services.add(ns); + return true; + } + + @Override + public List getNamingServices() { + return new Collections.unmodifiableList(_services); + } + + @Override + public boolean removeNamingService(NamingService ns) { + return _services.remove(ns); + } + + @Override + public void registerListener(NamingServiceListener nsl) { + for (NamingService ns : _services) { + ns.registerListener(nsl); + } + } + + @Override + public void unregisterListener(NamingServiceListener nsl) { + for (NamingService ns : _services) { + ns.unregisterListener(nsl); + } + } + + @Override + public Destination lookup(String hostname, Properties lookupOptions, Properties storedOptions) { + Destination d = super.lookup(hostname, null, null); + if (d != null) + return d; + + for (NamingService ns : _services) { + d = ns.lookup(hostname, lookupOptions, storedOptions); + if (d != null) { + putCache(hostname, d); + return d; } } - return lookupBase64(hostname); + return null; } @Override - public String reverseLookup(Destination dest) { - Iterator iter = _services.iterator(); - while (iter.hasNext()) { - NamingService ns = (NamingService)iter.next(); - String hostname = ns.reverseLookup(dest); - if (hostname != null) { - return hostname; + public String reverseLookup(Destination dest, Properties options) { + for (NamingService ns : _services) { + String host = ns.reverseLookup(dest, options); + if (host != null) { + return host; } } return null; } + /** + * Stores in the last service + */ + @Override + public boolean put(String hostname, Destination d, Properties options) { + if (_services.isEmpty()) + return false; + boolean rv = _services.get(_services.size() - 1).put(hostname, d, options); + // overwrite any previous entry in case it changed + if (rv) + putCache(hostname, d); + return rv; + } + + /** + * Stores in the last service + */ + @Override + public boolean putIfAbsent(String hostname, Destination d, Properties options) { + if (_services.isEmpty()) + return false; + return _services.get(_services.size() - 1).putIfAbsent(hostname, d, options); + } + + /** + * Removes from all services + */ + @Override + public boolean remove(String hostname, Properties options) { + boolean rv = false; + for (NamingService ns : _services) { + if (ns.remove(hostname, options)) + rv = true; + } + if (rv) + removeCache(hostname); + return rv; + } } diff --git a/core/java/src/net/i2p/client/naming/NamingService.java b/core/java/src/net/i2p/client/naming/NamingService.java index 66afd2c4a..ec82cd227 100644 --- a/core/java/src/net/i2p/client/naming/NamingService.java +++ b/core/java/src/net/i2p/client/naming/NamingService.java @@ -9,9 +9,13 @@ package net.i2p.client.naming; import java.lang.reflect.Constructor; import java.util.ArrayList; -import java.util.HashMap; +import java.util.Collections; import java.util.Iterator; +import java.util.List; import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; import net.i2p.I2PAppContext; import net.i2p.data.DataFormatException; @@ -25,15 +29,12 @@ import net.i2p.util.Log; public abstract class NamingService { private final static Log _log = new Log(NamingService.class); - protected I2PAppContext _context; - private /* FIXME final FIXME */ HashMap _cache; + protected final I2PAppContext _context; + protected final Set _listeners; /** what classname should be used as the naming service impl? */ public static final String PROP_IMPL = "i2p.naming.impl"; private static final String DEFAULT_IMPL = "net.i2p.client.naming.HostsTxtNamingService"; - - protected static final int CACHE_MAX_SIZE = 16; - /** * The naming service should only be constructed and accessed through the @@ -43,9 +44,7 @@ public abstract class NamingService { */ protected NamingService(I2PAppContext context) { _context = context; - _cache = new HashMap(CACHE_MAX_SIZE); - } - private NamingService() { // nop + _listeners = new CopyOnWriteArraySet(); } /** @@ -53,7 +52,9 @@ public abstract class NamingService { * @return the Destination for this host name, or * null if name is unknown. */ - public abstract Destination lookup(String hostname); + public Destination lookup(String hostname) { + return lookup(hostname, null, null); + } /** * Reverse look up a destination @@ -61,10 +62,12 @@ public abstract class NamingService { * if none is known. It is safe for subclasses to always return * null if no reverse lookup is possible. */ - public String reverseLookup(Destination dest) { return null; }; + public String reverseLookup(Destination dest) { + return reverseLookup(dest, null); + } /** @deprecated unused */ - public String reverseLookup(Hash h) { return null; }; + public String reverseLookup(Hash h) { return null; } /** * Check if host name is valid Base64 encoded dest and return this @@ -82,6 +85,313 @@ public abstract class NamingService { } } + ///// New API Starts Here + + /** + * @return Class simple name by default + * @since 0.8.5 + */ + public String getName() { + return getClass().getSimpleName(); + } + + /** + * @return NamingService-specific options or null + * @since 0.8.5 + */ + public Properties getConfiguration() { + return null; + } + + /** + * @return success + * @since 0.8.5 + */ + public boolean setConfiguration(Properties p) { + return true; + } + + // These are for daisy chaining (MetaNamingService) + + /** + * @return chained naming services or null + * @since 0.8.5 + */ + public List getNamingServices() { + return null; + } + + /** + * @return parent naming service or null if this is the root + * @since 0.8.5 + */ + public NamingService getParent() { + return null; + } + + /** + * Only for chaining-capable NamingServices. Add to end of the list. + * @return success + */ + public boolean addNamingService(NamingService ns) { + return addNamingService(ns, false); + } + + + /** + * Only for chaining-capable NamingServices + * @param head or tail + * @return success + */ + public boolean addNamingService(NamingService ns, boolean head) { + return false; + } + + /** + * Only for chaining-capable NamingServices + * @return success + * @since 0.8.5 + */ + public boolean removeNamingService(NamingService ns) { + return false; + } + + // options would be used to specify public / private / master ... + // or should we just daisy chain 3 HostsTxtNamingServices ? + // that might be better... then addressbook only talks to the 'router' HostsTxtNamingService + + /** + * @return number of entries or -1 if unknown + * @since 0.8.5 + */ + public int size() { + return size(null); + } + + /** + * @param options NamingService-specific, can be null + * @return number of entries (matching the options if non-null) or -1 if unknown + * @since 0.8.5 + */ + public int size(Properties options) { + return -1; + } + + /** + * @return all mappings + * or empty Map if none; + * Returned Map is not necessarily sorted, implementation dependent + * @since 0.8.5 + */ + public Map getEntries() { + return getEntries(null); + } + + /** + * @param options NamingService-specific, can be null + * @return all mappings (matching the options if non-null) + * or empty Map if none; + * Returned Map is not necessarily sorted, implementation dependent + * @since 0.8.5 + */ + public Map getEntries(Properties options) { + return Collections.EMPTY_MAP; + } + + /** + * This may be more or less efficient than getEntries() + * @param options NamingService-specific, can be null + * @return all mappings (matching the options if non-null) + * or empty Map if none; + * Returned Map is not necessarily sorted, implementation dependent + * @since 0.8.5 + */ + public Map getBase64Entries(Properties options) { + return Collections.EMPTY_MAP; + } + + /** + * @return all known host names + * or empty Set if none; + * Returned Set is not necessarily sorted, implementation dependent + * @since 0.8.5 + */ + public Set getNames() { + return getNames(null); + } + + /** + * @param options NamingService-specific, can be null + * @return all known host names (matching the options if non-null) + * or empty Set if none; + * Returned Set is not necessarily sorted, implementation dependent + * @since 0.8.5 + */ + public Set getNames(Properties options) { + return Collections.EMPTY_SET; + } + + /** + * @return success + * @since 0.8.5 + */ + public boolean put(String hostname, Destination d) { + return put(hostname, d, null); + } + + /** + * @param options NamingService-specific, can be null + * @return success + * @since 0.8.5 + */ + public boolean put(String hostname, Destination d, Properties options) { + return false; + } + + /** + * Fails if entry previously exists + * @return success + * @since 0.8.5 + */ + public boolean putIfAbsent(String hostname, Destination d) { + return putIfAbsent(hostname, d, null); + } + + /** + * Fails if entry previously exists + * @param options NamingService-specific, can be null + * @return success + * @since 0.8.5 + */ + public boolean putIfAbsent(String hostname, Destination d, Properties options) { + return false; + } + + /** + * @param options NamingService-specific, can be null + * @return success + * @since 0.8.5 + */ + public boolean putAll(Map entries, Properties options) { + boolean rv = true; + for (Map.Entry entry : entries.entrySet()) { + if (!put(entry.getKey(), entry.getValue(), options)) + rv = false; + } + return rv; + } + + /** + * Fails if entry did not previously exist + * @param d may be null if only options are changing + * @param options NamingService-specific, can be null + * @return success + * @since 0.8.5 + */ + public boolean update(String hostname, Destination d, Properties options) { + return false; + } + + /** + * @return success + * @since 0.8.5 + */ + public boolean remove(String hostname) { + return remove(hostname, null); + } + + /** + * @param options NamingService-specific, can be null + * @return success + * @since 0.8.5 + */ + public boolean remove(String hostname, Properties options) { + return false; + } + + /** + * Ask the NamingService to update its database + * Should this be a separate interface? This is what addressbook needs + * @param options NamingService-specific, can be null + * @since 0.8.5 + */ + public void requestUpdate(Properties options) {} + + /** + * @since 0.8.5 + */ + public void registerListener(NamingServiceListener nsl) { + _listeners.add(nsl); + } + + /** + * @since 0.8.5 + */ + public void unregisterListener(NamingServiceListener nsl) { + _listeners.remove(nsl); + } + + /** + * Same as lookup(hostname) but with in and out options + * Note that whether this (and lookup(hostname)) resolve B32 addresses is + * NamingService-specific. + * @param lookupOptions input parameter, NamingService-specific, can be null + * @param storedOptions output parameter, NamingService-specific, any stored properties will be added if non-null + * @return dest or null + * @since 0.8.5 + */ + public abstract Destination lookup(String hostname, Properties lookupOptions, Properties storedOptions); + + /** + * Same as reverseLookup(dest) but with options + * @param options NamingService-specific, can be null + * @return host name or null + * @since 0.8.5 + */ + public String reverseLookup(Destination d, Properties options) { + return null; + } + + /** + * Lookup a Base 32 address. This may require the router to fetch the LeaseSet, + * which may take quite a while. + * @param hostname must be {52 chars}.b32.i2p + * @param timeout in seconds; <= 0 means use router default + * @return dest or null + * @since 0.8.5 + */ + public Destination lookupBase32(String hostname, int timeout) { + return null; + } + + /** + * Same as lookupB32 but with the SHA256 Hash precalculated + * @param timeout in seconds; <= 0 means use router default + * @return dest or null + * @since 0.8.5 + */ + public Destination lookup(Hash hash, int timeout) { + return null; + } + + /** + * Parent will call when added. + * If this is the root naming service, the core will start it. + * Should not be called by others. + * @since 0.8.5 + */ + public void start() {} + + /** + * Parent will call when removed. + * If this is the root naming service, the core will stop it. + * Should not be called by others. + * @since 0.8.5 + */ + public void stop() {} + + //// End New API + /** * Get a naming service instance. This method ensures that there * will be only one naming service instance (singleton) as well as @@ -102,83 +412,4 @@ public abstract class NamingService { return instance; } - /** - * Provide basic caching for the service - * The service may override the age and/or size limit - */ - /** Don't know why a dest would ever change but keep this short anyway */ - protected static final long CACHE_MAX_AGE = 7*60*1000; - - private class CacheEntry { - public Destination dest; - public long exp; - public CacheEntry(Destination d) { - dest = d; - exp = _context.clock().now() + CACHE_MAX_AGE; - } - public boolean isExpired() { - return exp < _context.clock().now(); - } - } - - /** - * Clean up when full. - * Don't bother removing old entries unless full. - * Caller must synchronize on _cache. - */ - private void cacheClean() { - if (_cache.size() < CACHE_MAX_SIZE) - return; - boolean full = true; - Object oldestKey = null; - long oldestExp = Long.MAX_VALUE; - ArrayList deleteList = new ArrayList(CACHE_MAX_SIZE); - for (Iterator iter = _cache.entrySet().iterator(); iter.hasNext(); ) { - Map.Entry entry = (Map.Entry) iter.next(); - CacheEntry ce = (CacheEntry) entry.getValue(); - if (ce.isExpired()) { - deleteList.add(entry.getKey()); - full = false; - continue; - } - if (oldestKey == null || ce.exp < oldestExp) { - oldestKey = entry.getKey(); - oldestExp = ce.exp; - } - } - if (full && oldestKey != null) - deleteList.add(oldestKey); - for (Iterator iter = deleteList.iterator(); iter.hasNext(); ) { - _cache.remove(iter.next()); - } - } - - protected void putCache(String s, Destination d) { - if (d == null) - return; - synchronized (_cache) { - _cache.put(s, new CacheEntry(d)); - cacheClean(); - } - } - - protected Destination getCache(String s) { - synchronized (_cache) { - CacheEntry ce = (CacheEntry) _cache.get(s); - if (ce == null) - return null; - if (ce.isExpired()) { - _cache.remove(s); - return null; - } - return ce.dest; - } - } - - /** @since 0.8.1 */ - public void clearCache() { - synchronized (_cache) { - _cache.clear(); - } - } }