package com.limegroup.gnutella.search; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutorService; import org.limewire.concurrent.ExecutorsHelper; import org.limewire.inspection.Inspectable; import org.limewire.inspection.InspectableForSize; import org.limewire.inspection.InspectablePrimitive; import org.limewire.inspection.InspectionPoint; import org.limewire.io.GUID; import org.limewire.service.ErrorService; import com.google.inject.Singleton; import com.limegroup.gnutella.ReplyHandler; /** * Manages dynamic querying for Ultrapeers. * <p> * This maintains the data for all active queries for this Ultrapeer and any * of its leaves, also providing an interface for removing active queries. * Queries may be removed, for example, when a leaf node with an active query * disconnects from the Ultrapeer. */ @Singleton public final class QueryDispatcherImpl implements QueryDispatcher { /** * <tt>Map</tt> of outstanding queries. */ @InspectableForSize("number of dispatched queries") private final Map<GUID, QueryHandler> QUERIES = new HashMap<GUID, QueryHandler>(); /** Details about the queries. */ @InspectionPoint("dispatched queries details") public final Inspectable queryDetail = new Inspectable() { @Override public Object inspect() { List<Object> l = new ArrayList<Object>(QUERIES.size()); for(QueryHandler qh : QUERIES.values()) l.add(((Inspectable)qh).inspect()); return l; } }; /** * <tt>List</tt> of new queries to add. * LOCKING: Thread-safe, although you must obtain a lock on NEW_QUERIES if * it's ever iterated over. */ @InspectableForSize("number of newly dispatched queries") private final List<QueryHandler> NEW_QUERIES = Collections.synchronizedList(new LinkedList<QueryHandler>()); /** * The ProcessingQueue that handles sending queries out. * * Items are added to this only if it's not already processing anything. */ private final ExecutorService PROCESSOR = ExecutorsHelper.newProcessingQueue("QueryDispatcher"); /** * Whether or not processing is already active. If it is, we don't start it up again * when adding new queries. */ @InspectablePrimitive("querydispatcher active") private boolean _active; /* (non-Javadoc) * @see com.limegroup.gnutella.search.QueryDispatcher#addQuery(com.limegroup.gnutella.search.QueryHandler) */ public void addQuery(QueryHandler handler) { handler.sendQuery(); // immediately send out one query. synchronized(NEW_QUERIES) { NEW_QUERIES.add(handler); if(NEW_QUERIES.size() == 1 && !_active) { _active = true; PROCESSOR.execute(this); } } } /* (non-Javadoc) * @see com.limegroup.gnutella.search.QueryDispatcher#removeReplyHandler(com.limegroup.gnutella.ReplyHandler) */ public void removeReplyHandler(ReplyHandler handler) { // if it's not a leaf connection, we don't care that it's closed if(!handler.isSupernodeClientConnection()) return; remove(handler); } /* (non-Javadoc) * @see com.limegroup.gnutella.search.QueryDispatcher#updateLeafResultsForQuery(com.limegroup.gnutella.GUID, int) */ public void updateLeafResultsForQuery(GUID queryGUID, int numResults) { synchronized (QUERIES) { QueryHandler qh = QUERIES.get(queryGUID); if (qh != null) qh.updateLeafResults(numResults); } } /* (non-Javadoc) * @see com.limegroup.gnutella.search.QueryDispatcher#getLeafResultsForQuery(com.limegroup.gnutella.GUID) */ public int getLeafResultsForQuery(GUID queryGUID) { synchronized (QUERIES) { QueryHandler qh = QUERIES.get(queryGUID); if (qh == null) return -1; else return qh.getNumResultsReportedByLeaf(); } } /** * Removes all queries using the specified <tt>ReplyHandler</tt> * from NEW_QUERIES & QUERIES. * * @param handler the <tt>ReplyHandler</tt> to remove */ private void remove(ReplyHandler handler) { synchronized(NEW_QUERIES) { Iterator<QueryHandler> iter = NEW_QUERIES.iterator(); while(iter.hasNext()) { QueryHandler qh = iter.next(); if(qh.getReplyHandler() == handler) iter.remove(); } } synchronized(QUERIES) { Iterator<QueryHandler> iter = QUERIES.values().iterator(); while(iter.hasNext()) { QueryHandler qh = iter.next(); if(qh.getReplyHandler() == handler) iter.remove(); } } } /** * Removes the specified <tt>ReplyHandler</tt> from NEW_QUERIES & QUERIES. */ private void remove(GUID guid) { synchronized(NEW_QUERIES) { Iterator<QueryHandler> iter = NEW_QUERIES.iterator(); while(iter.hasNext()) { QueryHandler qh = iter.next(); if(qh.getGUID().equals(guid)) iter.remove(); } } synchronized(QUERIES) { Iterator<QueryHandler> iter = QUERIES.values().iterator(); while(iter.hasNext()) { QueryHandler qh = iter.next(); if(qh.getGUID().equals(guid)) iter.remove(); } } } /* (non-Javadoc) * @see com.limegroup.gnutella.search.QueryDispatcher#run() */ public void run() { while(true) { try { Thread.sleep(400); } catch(InterruptedException ignored) {} try { // If there are no more queries to process... if(!processQueries()) { synchronized(NEW_QUERIES) { // If there are no new queries to add, // set active to false & leave. if(NEW_QUERIES.isEmpty()) { _active = false; return; } // else, loop. } } // else, loop. } catch(Throwable t) { ErrorService.error(t); } } } /** * Processes current queries. */ private boolean processQueries() { synchronized (NEW_QUERIES) { synchronized (QUERIES) { for (QueryHandler qh : NEW_QUERIES) QUERIES.put(qh.getGUID(), qh); } NEW_QUERIES.clear(); } synchronized(QUERIES) { Iterator<QueryHandler> iter = QUERIES.values().iterator(); while(iter.hasNext()) { QueryHandler handler = iter.next(); handler.sendQuery(); if(handler.hasEnoughResults()) iter.remove(); } return !QUERIES.isEmpty(); } } /* (non-Javadoc) * @see com.limegroup.gnutella.search.QueryDispatcher#addToRemove(com.limegroup.gnutella.GUID) */ public void addToRemove(GUID g) { remove(g); } }