/*
* Ivory: A Hadoop toolkit for web-scale information retrieval
*
* Licensed under the Apache License, Version 2.0 (the "License"); you
* may not use this file except in compliance with the License. You may
* obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package ivory.smrf.retrieval;
import ivory.core.ConfigurationException;
import ivory.smrf.model.MarkovRandomField;
import ivory.smrf.model.builder.MRFBuilder;
import ivory.smrf.model.expander.MRFExpander;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import org.apache.log4j.Logger;
import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;
/**
* Multi-threaded implementation of class to run queries.
*
* @author Don Metzler
* @author Jimmy Lin
*/
public class ThreadedQueryRunner implements QueryRunner {
private static final Logger LOG = Logger.getLogger(ThreadedQueryRunner.class);
private final MRFBuilder builder;
private final MRFExpander expander;
private final ExecutorService threadPool;
private final Map<String, Future<Accumulator[]>> queryResults = Maps.newLinkedHashMap();
private final int numHits;
public ThreadedQueryRunner(MRFBuilder builder, MRFExpander expander, int numThreads, int numHits) {
Preconditions.checkArgument(numThreads > 0);
Preconditions.checkArgument(numHits > 0);
this.builder = Preconditions.checkNotNull(builder);
this.expander = expander; // Expander can be null.
this.threadPool = Executors.newFixedThreadPool(numThreads);
this.numHits = numHits;
}
/**
* Runs a query asynchronously. Results can be fetched using {@link #getResults}.
*/
public void runQuery(String qid, String[] query) {
Preconditions.checkNotNull(qid);
Preconditions.checkNotNull(query);
Future<Accumulator[]> future = threadPool.submit(new ThreadTask(query, builder,
expander, qid, numHits));
queryResults.put(qid, future);
}
/**
* Runs a query synchronously, waiting until completion.
*/
public Accumulator[] runQuery(String[] query) {
Preconditions.checkNotNull(query);
Future<Accumulator[]> future = threadPool.submit(new ThreadTask(query, builder,
expander, "query", numHits));
Accumulator[] results = null;
try {
results = future.get();
} catch (Exception e) {
e.printStackTrace();
}
return results;
}
/**
* Fetches the results of a query. If necessary, waits until completion of the query.
*
* @param qid query id
*/
public Accumulator[] getResults(String qid) {
try {
return queryResults.get(qid).get();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* Clears all stored results.
*/
public void clearResults() {
queryResults.clear();
}
/**
* Returns results of all queries executed.
*/
public Map<String, Accumulator[]> getResults() {
Map<String, Accumulator[]> results = new LinkedHashMap<String, Accumulator[]>();
for (Map.Entry<String, Future<Accumulator[]>> entry : queryResults.entrySet()) {
try {
Accumulator[] a = entry.getValue().get();
if (a != null) {
results.put(entry.getKey(), a);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
return results;
}
// Thread for running a query. No need to expose implementation.
private class ThreadTask implements Callable<Accumulator[]> {
private final String[] query;
private final MRFBuilder builder;
private final MRFExpander expander;
private final String qid;
private final int numHits;
public ThreadTask(String[] query, MRFBuilder builder, MRFExpander expander, String qid,
int numHits) {
this.query = query;
this.builder = builder;
this.expander = expander;
this.qid = qid;
this.numHits = numHits;
}
public Accumulator[] call() {
try {
long startTime;
long endTime;
startTime = System.currentTimeMillis();
// Build the MRF for this query.
MarkovRandomField mrf = builder.buildMRF(query);
// Retrieve documents using this MRF.
MRFDocumentRanker ranker = new MRFDocumentRanker(mrf, numHits);
// Run initial query, if necessary.
Accumulator[] results = null;
if (expander != null) {
results = ranker.rank();
}
// Perform pseudo-relevance feedback, if requested.
if (expander != null) {
// Get expanded MRF.
MarkovRandomField expandedMRF = expander.getExpandedMRF(mrf, results);
// Re-rank documents according to expanded MRF.
ranker = new MRFDocumentRanker(expandedMRF, numHits);
}
// Rank the documents.
results = ranker.rank();
endTime = System.currentTimeMillis();
LOG.info("Processed query " + qid + " in " + (endTime - startTime) + " ms.");
return results;
} catch (ConfigurationException e) {
e.printStackTrace();
LOG.error(e.getMessage());
return null;
}
}
}
}