package com.limegroup.gnutella.dht;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.limewire.mojito.KUID;
import org.limewire.mojito.MojitoDHT;
import org.limewire.mojito.concurrent.DHTExecutorService;
import org.limewire.mojito.concurrent.DHTFuture;
import org.limewire.mojito.concurrent.DHTFutureAdapter;
import org.limewire.mojito.concurrent.DHTFutureListener;
import org.limewire.mojito.result.PingResult;
import org.limewire.mojito.routing.Bucket;
import org.limewire.mojito.routing.ClassfulNetworkCounter;
import org.limewire.mojito.routing.Contact;
import org.limewire.mojito.routing.RouteTable;
/**
* Passive Nodes (Ultrapeers) use this slightly extended version
* of the <code>RouteTable</code>. It maintains an internal mapping of DHT
* enabled leaves that are currently connected to the Ultrapeer (Gnutella
* connections).
*/
@SuppressWarnings("serial")
class PassiveDHTNodeRouteTable implements RouteTable {
private static final Log LOG = LogFactory.getLog(PassiveDHTNodeRouteTable.class);
/**
* MojitoDHT instance
*/
private final MojitoDHT dht;
/**
* The actual RouteTable
*/
private final RouteTable delegate;
/**
* The Map storing the leaf nodes connected to this ultrapeer. The mapping is
* used to go from Gnutella <tt>IpPort</tt> to DHT <tt>RemoteContact</tt>.
*/
private final Map<SocketAddress, KUID> leafDHTNodes = new HashMap<SocketAddress, KUID>();
public PassiveDHTNodeRouteTable(MojitoDHT dht) {
assert (dht.isFirewalled()); // Must be firewalled
this.dht = dht;
delegate = dht.getRouteTable();
}
/**
* Adds a DHT leaf node to the DHT routing table, setting a very high timestamp
* to make sure it always gets contacted first for the first hop of lookups, and
* make sure it always gets returned first when selecting the Most Recently Seen
* (MRS) nodes from the RT.
*
* @param host the IP address of the remote host as a string
* @param port the listening port for the remote host
*/
public void addLeafDHTNode(String host, int port) {
if(LOG.isDebugEnabled()) {
LOG.debug("Pinging leaf: " + host + ": " + port);
}
final InetSocketAddress addr = new InetSocketAddress(host, port);
DHTFuture<PingResult> future = dht.ping(addr);
DHTFutureListener<PingResult> listener = new DHTFutureAdapter<PingResult>() {
@Override
public void handleFutureSuccess(PingResult result) {
if(LOG.isDebugEnabled()) {
LOG.debug("Ping succeeded to: " + result);
}
Contact node = result.getContact();
synchronized (PassiveDHTNodeRouteTable.this) {
KUID previous = leafDHTNodes.put(addr, node.getNodeID());
if (previous == null || !previous.equals(node.getNodeID())) {
// Add it as a priority Node
node.setTimeStamp(Contact.PRIORITY_CONTACT);
add(node);
}
}
}
@Override
public void handleExecutionException(ExecutionException e) {
if(LOG.isDebugEnabled()) {
LOG.debug("Ping failed to: " + addr, e);
}
}
};
future.addDHTFutureListener(listener);
}
/**
* Removes this DHT leaf from our routing table and returns it.
*/
public synchronized SocketAddress removeLeafDHTNode(String host, int port) {
SocketAddress addr = new InetSocketAddress(host, port);
KUID nodeId = leafDHTNodes.remove(addr);
if(nodeId != null) {
if(LOG.isDebugEnabled()) {
LOG.debug("Removed leaf: " + host + ": " + port);
}
removeAndReplaceWithMRSCachedContact(nodeId);
return addr;
}
return null;
}
/**
* Removes the given node from the routing table and tries to replace
* it with the Most Recently Seen (MRS) cached contact.
*
* @param nodeId the KUID of the node to remove
*/
private synchronized void removeAndReplaceWithMRSCachedContact(KUID nodeId) {
Bucket bucket = getBucket(nodeId);
boolean removed = bucket.removeActiveContact(nodeId);
if (removed) {
if (bucket.getCacheSize() > 0) {
ClassfulNetworkCounter counter = bucket.getClassfulNetworkCounter();
Contact mrs = null;
while((mrs = bucket.getMostRecentlySeenCachedContact()) != null) {
removed = bucket.removeCachedContact(mrs.getNodeID());
assert (removed == true);
if (counter == null || counter.isOkayToAdd(mrs)) {
bucket.addActiveContact(mrs);
break;
}
}
}
} else {
bucket.removeCachedContact(nodeId);
}
}
/**
* Returns whether or not this Ultrapeer has any
* DHT enabled leaves
*/
public synchronized boolean hasDHTLeaves() {
return !leafDHTNodes.isEmpty();
}
/**
* Returns the IP:Ports of this Ultrapeer's DHT enabled leaves
*
* Hold a lock on 'this' when using the Iterator!
*/
public synchronized Set<SocketAddress> getDHTLeaves() {
return Collections.unmodifiableSet(leafDHTNodes.keySet());
}
// --- ROUTE TABLE ---
public synchronized void add(Contact node) {
delegate.add(node);
}
public synchronized void addRouteTableListener(RouteTableListener l) {
delegate.addRouteTableListener(l);
}
public synchronized void removeRouteTableListener(RouteTableListener l) {
delegate.removeRouteTableListener(l);
}
public synchronized Contact get(KUID nodeId) {
return delegate.get(nodeId);
}
public synchronized Collection<Contact> getActiveContacts() {
return delegate.getActiveContacts();
}
public synchronized Bucket getBucket(KUID nodeId) {
return delegate.getBucket(nodeId);
}
public synchronized Collection<Bucket> getBuckets() {
return delegate.getBuckets();
}
public synchronized Collection<Contact> getCachedContacts() {
return delegate.getCachedContacts();
}
public synchronized Collection<Contact> getContacts() {
return delegate.getContacts();
}
public synchronized Contact getLocalNode() {
return delegate.getLocalNode();
}
public synchronized Collection<KUID> getRefreshIDs(boolean bootstrapping) {
return delegate.getRefreshIDs(bootstrapping);
}
public synchronized void handleFailure(KUID nodeId, SocketAddress address) {
delegate.handleFailure(nodeId, address);
}
public synchronized boolean isLocalNode(Contact node) {
return delegate.isLocalNode(node);
}
public synchronized void purge(long elapsedTimeSinceLastContact) {
delegate.purge(elapsedTimeSinceLastContact);
}
public synchronized void purge(PurgeMode first, PurgeMode... rest) {
delegate.purge(first, rest);
}
public synchronized Contact select(KUID nodeId) {
return delegate.select(nodeId);
}
public synchronized Collection<Contact> select(KUID nodeId, int count, SelectMode mode) {
return delegate.select(nodeId, count, mode);
}
public synchronized void setContactPinger(ContactPinger pinger) {
delegate.setContactPinger(pinger);
}
public synchronized void setNotifier(DHTExecutorService e) {
delegate.setNotifier(e);
}
public synchronized int size() {
return delegate.size();
}
public synchronized void clear() {
delegate.clear();
}
@Override
public synchronized String toString() {
return "Passive RouteTable: " + delegate.toString();
}
}