package com.limegroup.gnutella.dht.db;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.limewire.core.settings.DHTSettings;
import org.limewire.io.GUID;
import org.limewire.io.IpPort;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.google.inject.name.Named;
import com.limegroup.gnutella.PushEndpoint;
import com.limegroup.gnutella.PushEndpointCache;
/**
* Transparently interfaces with {@link PushEndpointService}
* and {@link PushEndpointCache}.
* <p>
* It looks up {@link PushEndpoint push endpoints} in the cache and if it
* can't find a good instance there decides if it asks the finder to look up
* the push endpoint.
*/
@Singleton
class PushEndpointManagerImpl implements PushEndpointService {
private static final Log LOG = LogFactory.getLog(PushEndpointManagerImpl.class);
private final PushEndpointCache pushEndpointCache;
private final PushEndpointService pushEndpointFinder;
private final ConcurrentMap<GUID, AtomicLong> lastSearchTimeByGUID = new ConcurrentHashMap<GUID, AtomicLong>();
private volatile long timeBetweenSearches = DHTSettings.TIME_BETWEEN_PUSH_PROXY_QUERIES.getValue();
@Inject
public PushEndpointManagerImpl(PushEndpointCache pushEndpointCache,
@Named("dhtPushEndpointFinder") PushEndpointService pushEndpointFinder) {
this.pushEndpointCache = pushEndpointCache;
this.pushEndpointFinder = pushEndpointFinder;
}
/**
* Set time in milliseconds between searches.
*
* Mainly for testing but could be exposed if necessary.
*/
void setTimeBetweenSearches(long timeBetweenSearches) {
this.timeBetweenSearches = timeBetweenSearches;
}
/**
* Gets time between searches in milliseconds.
*/
long getTimeBetweenSearches() {
return timeBetweenSearches;
}
public void findPushEndpoint(GUID guid, SearchListener<PushEndpoint> listener) {
listener = SearchListenerAdapter.nonNullListener(listener);
PushEndpoint cachedPushEndpoint = pushEndpointCache.getPushEndpoint(guid);
if (cachedPushEndpoint != null && !cachedPushEndpoint.getProxies().isEmpty()) {
if (LOG.isDebugEnabled()) {
LOG.debug("Found cached endpoint: " + cachedPushEndpoint);
}
listener.handleResult(cachedPushEndpoint);
} else {
long currentTime = System.currentTimeMillis();
AtomicLong lastSearchTime = lastSearchTimeByGUID.putIfAbsent(guid, new AtomicLong(currentTime));
// no old value, just start a search, knowing we are set as last search time
if (lastSearchTime == null) {
startSearch(guid, listener);
} else {
long lastSearch = lastSearchTime.longValue();
// check if we can start a new search
if (currentTime - lastSearch > timeBetweenSearches) {
// only start a search if we succeed setting the current time and it was still the last time
if (lastSearchTime.compareAndSet(lastSearch, currentTime)) {
startSearch(guid, listener);
} else {
listener.searchFailed();
}
} else {
listener.searchFailed();
}
}
}
}
void startSearch(GUID guid, final SearchListener<PushEndpoint> listener) {
if (LOG.isDebugEnabled()) {
LOG.debug("Starting dht search for: " + guid);
}
if (!DHTSettings.ENABLE_PUSH_PROXY_QUERIES.getValue()) {
listener.searchFailed();
return;
}
pushEndpointFinder.findPushEndpoint(guid, new SearchListener<PushEndpoint>() {
public void handleResult(PushEndpoint result) {
// notify cache about it
result.updateProxies(true);
IpPort externalAddress = result.getValidExternalAddress();
if (externalAddress != null) {
pushEndpointCache.setAddr(result.getClientGUID(), externalAddress);
}
listener.handleResult(result);
}
public void searchFailed() {
LOG.debug("dht search failed");
listener.searchFailed();
}
});
purge();
}
public PushEndpoint getPushEndpoint(GUID guid) {
BlockingSearchListener<PushEndpoint> listener = new BlockingSearchListener<PushEndpoint>();
findPushEndpoint(guid, listener);
return listener.getResult();
}
/**
* Purges entries older than 2 * {@link #getTimeBetweenSearches()}.
*/
void purge() {
long currentTime = System.currentTimeMillis();
for (Entry<GUID, AtomicLong> entry : lastSearchTimeByGUID.entrySet()) {
assert entry != null : "entry is null: " + lastSearchTimeByGUID;
assert entry.getValue() != null : "value of entry is null: " + lastSearchTimeByGUID;
if (currentTime - entry.getValue().get() > 2 * timeBetweenSearches) {
lastSearchTimeByGUID.remove(entry.getKey(), entry.getValue());
}
}
}
/**
* Access for testing only
*/
ConcurrentMap<GUID, AtomicLong> getLastSearchTimeByGUID() {
return lastSearchTimeByGUID;
}
}