/**
*
*/
package querqy.solr;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TopDocs;
import org.apache.solr.core.AbstractSolrEventListener;
import org.apache.solr.core.SolrCore;
import org.apache.solr.search.SolrCache;
import org.apache.solr.search.SolrIndexSearcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import querqy.lucene.rewrite.*;
import querqy.lucene.rewrite.cache.CacheKey;
import querqy.lucene.rewrite.cache.TermQueryCache;
import querqy.lucene.rewrite.cache.TermQueryCacheValue;
import querqy.lucene.rewrite.prms.PRMSQuery;
import querqy.model.Term;
import querqy.rewrite.RewriteChain;
import querqy.rewrite.RewriterFactory;
/**
* @author rene
*
*/
public class TermQueryCachePreloader extends AbstractSolrEventListener {
static final Logger LOG = LoggerFactory.getLogger(TermQueryCachePreloader.class);
public static final String CONF_Q_PARSER_PLUGIN = "qParserPlugin";
public static final String CONF_PRELOAD_FIELDS = "fields";
public static final String CONF_CACHE_NAME = "cacheName";
public static final String CONF_TEST_FOR_HITS = "testForHits";
public TermQueryCachePreloader(SolrCore core) {
super(core);
}
protected Map<String, Float> getPreloadFields() {
// REVISIT: we don't need the boost factors as they could be overridden per request
String fieldConf = (String) getArgs().get(CONF_PRELOAD_FIELDS);
return QuerqyDismaxQParser.parseFieldBoosts(fieldConf, 1f);
}
protected AbstractQuerqyDismaxQParserPlugin getQParserPlugin() {
String parserName = (String) getArgs().get(CONF_Q_PARSER_PLUGIN);
if (parserName == null) {
throw new RuntimeException("Missing configuration property: " + CONF_Q_PARSER_PLUGIN);
}
AbstractQuerqyDismaxQParserPlugin qParserPlugin = (AbstractQuerqyDismaxQParserPlugin) getCore().getQueryPlugin(parserName);
if (qParserPlugin == null) {
throw new RuntimeException("No query parser plugin for name '" + parserName + "'");
}
return qParserPlugin;
}
protected TermQueryCache getCache(SolrIndexSearcher searcher) {
String cacheName = (String) getArgs().get(CONF_CACHE_NAME);
if (cacheName == null) {
throw new RuntimeException("Missing configuration property: " + CONF_CACHE_NAME);
}
@SuppressWarnings("unchecked")
SolrCache<CacheKey, TermQueryCacheValue> solrCache = searcher.getCache(cacheName);
if (solrCache == null) {
throw new RuntimeException("No TermQueryCache for name '" + cacheName + "'");
}
return new SolrTermQueryCacheAdapter(false, solrCache);
}
protected boolean isTestForHits() {
Boolean doTest = getArgs().getBooleanArg(CONF_TEST_FOR_HITS);
return doTest != null && doTest;
}
@Override
public void newSearcher(SolrIndexSearcher newSearcher, SolrIndexSearcher currentSearcher) {
TermQueryCache cache = getCache(newSearcher);
Map<String, Float> preloadFields = getPreloadFields();
boolean testForHits = isTestForHits();
LOG.info("Starting preload for Querqy TermQueryCache. Testing for hits: {}", testForHits);
long t1 = System.currentTimeMillis();
AbstractQuerqyDismaxQParserPlugin queryPluginPlugin = getQParserPlugin();
RewriteChain rewriteChain = queryPluginPlugin.getRewriteChain();
if (rewriteChain != null && !preloadFields.isEmpty()) {
List<RewriterFactory> factories = rewriteChain.getRewriterFactories();
if (!factories.isEmpty()) {
TermSubQueryBuilder termSubQueryBuilder = new TermSubQueryBuilder(newSearcher.getSchema().getQueryAnalyzer(), cache);
for (RewriterFactory factory : factories) {
for (Term term: factory.getGenerableTerms()) {
String field = term.getField();
if (field != null) {
if (preloadFields.containsKey(field)) {
preloadTerm(newSearcher, termSubQueryBuilder, field, term, testForHits, cache);
}
} else {
for (String fieldname : preloadFields.keySet()) {
preloadTerm(newSearcher, termSubQueryBuilder, fieldname, term, testForHits, cache);
}
}
}
}
}
}
if (LOG.isInfoEnabled()) {
long t2 = System.currentTimeMillis();
LOG.info("Finished preload for Querqy TermQueryCache after {}ms", (t2 - t1));
}
}
protected void preloadTerm(IndexSearcher searcher, TermSubQueryBuilder termSubQueryBuilder, String field, Term term, boolean testForHits, TermQueryCache cache) {
try {
// luceneQueryBuilder.termToFactory creates the query and caches it (without the boost)
TermSubQueryFactory termSubQueryFactory = termSubQueryBuilder.termToFactory(field, term, ConstantFieldBoost.NORM_BOOST);
// test the query for hits and override the cache value with a factory that creates a query that never matches
// --> this query will never be executed against the index again
// no need to re-test for hits if we've seen this term before
if (testForHits && (termSubQueryFactory != null) && (!termSubQueryFactory.isNeverMatchQuery())) {
Query query = termSubQueryFactory.createQuery(ConstantFieldBoost.NORM_BOOST, 0.01f, null);
TopDocs topDocs = searcher.search(query, 1);
if (topDocs.totalHits < 1) {
cache.put(new CacheKey(field, term), new TermQueryCacheValue(NeverMatchQueryFactory.FACTORY, PRMSQuery.NEVER_MATCH_PRMS_QUERY));
}
}
} catch (IOException e) {
LOG.error("Error preloading term " + term.toString(), e);
}
}
}