package net.i2p.client.naming; import java.io.IOException; import java.io.Writer; import java.lang.reflect.Constructor; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.StringTokenizer; import java.util.concurrent.CopyOnWriteArrayList; import net.i2p.I2PAppContext; import net.i2p.data.Destination; /** * 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.HostsTxtNamingService"; protected final List<NamingService> _services; /** * Adds the services from the i2p.nameservicelist property, in order, as chained services. */ public MetaNamingService(I2PAppContext context) { super(context); String list = _context.getProperty(PROP_NAME_SERVICES, DEFAULT_NAME_SERVICES); StringTokenizer tok = new StringTokenizer(list, ","); _services = new CopyOnWriteArrayList<NamingService>(); while (tok.hasMoreTokens()) { try { Class<?> cls = Class.forName(tok.nextToken()); Constructor<?> con = cls.getConstructor(I2PAppContext.class); addNamingService((NamingService)con.newInstance(), false); } catch (Exception ex) { } } } /** * @param services if non-null, services to be added. If null, this will only handle b32 and b64, * until addNamingService() is called later. * @since 0.8.7 */ public MetaNamingService(I2PAppContext context, List<NamingService> services) { super(context); _services = new CopyOnWriteArrayList<NamingService>(); if (services != null) { for (NamingService ns : services) { addNamingService(ns, false); } } } @Override public boolean addNamingService(NamingService ns, boolean head) { if (head) _services.add(0, ns); else _services.add(ns); return true; } @Override public List<NamingService> getNamingServices() { return 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) { // cache check is in super() Destination d = super.lookup(hostname, null, null); if (d != null) return d; // Base32 failed? if (hostname.length() == BASE32_HASH_LENGTH + 8 && hostname.toLowerCase(Locale.US).endsWith(".b32.i2p")) return null; for (NamingService ns : _services) { d = ns.lookup(hostname, lookupOptions, storedOptions); if (d != null) { putCache(hostname, d); return d; } } return null; } @Override 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; boolean rv = _services.get(_services.size() - 1).putIfAbsent(hostname, d, options); if (rv) putCache(hostname, d); return rv; } /** * 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; } /** * All services aggregated */ @Override public Map<String, Destination> getEntries(Properties options) { Map<String, Destination> rv = new HashMap<String, Destination>(); for (NamingService ns : _services) { rv.putAll(ns.getEntries(options)); } return rv; } /** * All services aggregated * @since 0.9.20 */ @Override public Map<String, String> getBase64Entries(Properties options) { Map<String, String> rv = new HashMap<String, String>(); for (NamingService ns : _services) { rv.putAll(ns.getBase64Entries(options)); } return rv; } /** * All services aggregated */ @Override public Set<String> getNames(Properties options) { Set<String> rv = new HashSet<String>(); for (NamingService ns : _services) { rv.addAll(ns.getNames(options)); } return rv; } /** * All services aggregated. * Duplicates not removed (for efficiency) * @since 0.9.20 */ public void export(Writer out, Properties options) throws IOException { for (NamingService ns : _services) { export(out, options); } } /** * All services aggregated */ @Override public int size(Properties options) { int rv = 0; for (NamingService ns : _services) { int s = ns.size(options); if (s > 0) rv += s; } return rv; } public void shutdown() { for (NamingService ns : _services) { ns.shutdown(); } } }