package lux.functions; import java.util.ArrayList; import lux.Evaluator; import lux.QueryContext; import lux.TransformErrorListener; import lux.solr.SolrQueryContext; import net.sf.saxon.expr.XPathContext; import net.sf.saxon.lib.ExtensionFunctionCall; import net.sf.saxon.lib.ExtensionFunctionDefinition; import net.sf.saxon.om.Item; import net.sf.saxon.om.LazySequence; import net.sf.saxon.om.NodeInfo; import net.sf.saxon.om.Sequence; import net.sf.saxon.om.SequenceIterator; import net.sf.saxon.s9api.XdmNode; import net.sf.saxon.trans.XPathException; import net.sf.saxon.value.IntegerValue; import net.sf.saxon.value.SequenceType; import org.apache.lucene.search.Query; import org.apache.solr.handler.component.ResponseBuilder; import org.slf4j.LoggerFactory; /** * A base class for functions that execute search queries. */ public abstract class SearchBase extends ExtensionFunctionDefinition { public enum QueryParser { CLASSIC, XML } public SearchBase() { super(); } protected abstract SequenceIterator<? extends Item> iterate(final Query query, final Evaluator eval, final String[] sortCriteria, final int start) throws XPathException; protected abstract SequenceIterator<? extends Item> iterateDistributed(final String query, final QueryParser queryParser, final Evaluator eval, final String[] sortCriteria, final int start) throws XPathException; @Override public int getMinimumNumberOfArguments() { return 1; } @Override public int getMaximumNumberOfArguments() { return 1; } @Override public SequenceType[] getArgumentTypes() { return new SequenceType[] { SequenceType.SINGLE_ITEM }; } @Override public ExtensionFunctionCall makeCallExpression() { return new SearchCall (); } public static Evaluator getEvaluator (XPathContext context) { // TODO: check thread safety of controller's error listener TransformErrorListener listener = (TransformErrorListener) context.getController().getErrorListener(); return (Evaluator) listener.getUserData(); } public class SearchCall extends NamespaceAwareFunctionCall { @Override public Sequence call(XPathContext context, Sequence[] arguments) throws XPathException { if (arguments.length == 0 || arguments.length > 3) { throw new XPathException ("wrong number of arguments for " + getFunctionQName()); } Item queryArg = arguments[0].head(); String [] sortCriteria = null; if (arguments.length >= 2) { ArrayList<String> sortCriteriaColl = new ArrayList<String>(); Sequence sortArg = arguments[1]; if (sortArg != null) { SequenceIterator<? extends Item> sortArgs = sortArg.iterate(); while (sortArgs.next() != null) { sortCriteriaColl.add (sortArgs.current().getStringValue()); } } sortCriteria = sortCriteriaColl.toArray(new String[sortCriteriaColl.size()]); // FIXME: use lux_score as default sort criteria, and generate calls in optimizer // using document order explicitly } int start = 1; if (arguments.length >= 3) { Item startArg = arguments[2].head(); if (startArg != null) { IntegerValue integerValue = (IntegerValue)startArg; if (integerValue.longValue() > Integer.MAX_VALUE) { throw new XPathException ("integer overflow in search $start parameter"); } start = (int) integerValue.longValue(); } } Evaluator eval = getEvaluator(context); QueryContext queryContext = eval.getQueryContext(); if (queryContext instanceof SolrQueryContext) { ResponseBuilder rb = ((SolrQueryContext) queryContext).getResponseBuilder() ; if (rb != null && rb.shards != null) { // For cloud queries, we don't parse; just serialize the query and let the shard parse it QueryParser qp; String qstr; if (queryArg instanceof NodeInfo) { qp = QueryParser.XML; // cheap-ass serialization qstr = new XdmNode((NodeInfo)queryArg).toString(); } else { qp = QueryParser.CLASSIC; qstr = queryArg.getStringValue(); } return new LazySequence(iterateDistributed (qstr, qp, eval, sortCriteria, start)); } } Query query = parseQuery(queryArg, eval); LoggerFactory.getLogger(SearchBase.class).debug("executing query: {}", query); return new LazySequence(iterate (query, eval, sortCriteria, start)); } } }