package lux.functions;
import java.util.Collection;
import lux.Evaluator;
import lux.index.field.FieldDefinition;
import lux.index.field.XPathField;
import lux.xpath.FunCall;
import net.sf.saxon.expr.XPathContext;
import net.sf.saxon.lib.ExtensionFunctionCall;
import net.sf.saxon.lib.ExtensionFunctionDefinition;
import net.sf.saxon.om.AtomicArray;
import net.sf.saxon.om.Item;
import net.sf.saxon.om.NodeInfo;
import net.sf.saxon.om.Sequence;
import net.sf.saxon.om.StructuredQName;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.value.EmptySequence;
import net.sf.saxon.value.Int64Value;
import net.sf.saxon.value.SequenceType;
import net.sf.saxon.value.StringValue;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.IndexableField;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.schema.SchemaField;
import org.slf4j.LoggerFactory;
/**
* <code>function lux:key($field-name as xs:string, $node as node()) as xs:anyAtomicItem*</code>
* <code>function lux:key($field-name as xs:string) as xs:anyAtomicItem*</code>
*
* <p>Accepts the name of a lucene field and optionally, a node, and returns
* any stored value(s) of the field for the document containing
* the node, or the context item if no node is specified. Analogous to the XSLT key() function.
* </p>
*
* <p>
* If the node (or context item) is not a node drawn from the index, lux:key will return the
* empty sequence.
* </p>
*
* <p>
* Order by expressions containing lux:key calls are subject to special optimization and are often able to be
* implemented by index-optimized sorting in Lucene (for fields whose values are string-, integer-, or long-valued only).
* An error results if an attempt is made
* to sort by a field that has multiple values for any of the documents in the sequence.
* </p>
*/
public class Key extends ExtensionFunctionDefinition {
@Override
public StructuredQName getFunctionQName() {
return new StructuredQName ("lux", FunCall.LUX_NAMESPACE, "key");
}
@Override
public SequenceType[] getArgumentTypes() {
return new SequenceType[] {
SequenceType.SINGLE_STRING,
SequenceType.OPTIONAL_NODE
};
}
@Override
public int getMinimumNumberOfArguments() {
return 1;
}
@Override
public int getMaximumNumberOfArguments() {
return 2;
}
@Override
public boolean trustResultType() {
return true;
}
@Override
public boolean dependsOnFocus () {
return true;
}
@Override
public net.sf.saxon.value.SequenceType getResultType(net.sf.saxon.value.SequenceType[] suppliedArgumentTypes) {
return SequenceType.ATOMIC_SEQUENCE;
}
@Override
public ExtensionFunctionCall makeCallExpression() {
return new KeyCall();
}
class KeyCall extends ExtensionFunctionCall {
@Override
public Sequence call(XPathContext context, Sequence[] arguments) throws XPathException {
String fieldName = arguments[0].head().getStringValue();
NodeInfo node;
if (arguments.length == 1) {
Item contextItem = context.getContextItem();
if (! (contextItem instanceof NodeInfo)) {
throw new XPathException ("Call to lux:key($field-name) depends on context, but there is no context defined");
}
node = (NodeInfo) contextItem;
} else {
node = (NodeInfo) arguments[1].head();
}
if (node == null) {
return EmptySequence.getInstance();
}
Evaluator eval = SearchBase.getEvaluator(context);
Document doc = (Document) node.getDocumentRoot().getUserData(Document.class.getName());
FieldDefinition field = eval.getCompiler().getIndexConfiguration().getField(fieldName);
if (field == null) {
LoggerFactory.getLogger(Key.class).warn("Attempt to retrieve values of non-existent field: {}", fieldName);
}
else if (field.isStored() == Field.Store.NO) {
LoggerFactory.getLogger(Key.class).warn("Attempt to retrieve values of non-stored field: {}", fieldName);
}
if (doc != null) {
return getFieldValue (doc, eval, fieldName, field);
} else {
SolrDocument solrDoc = (SolrDocument) node.getDocumentRoot().getUserData(SolrDocument.class.getName());
if (solrDoc != null) {
return getFieldValue (solrDoc, eval, fieldName, field);
}
}
return EmptySequence.getInstance();
}
private Sequence getFieldValue (Document doc, Evaluator eval, String fieldName, FieldDefinition field) throws XPathException {
// TODO refactor the repeated code here
if (field == null || field.getType() == FieldDefinition.Type.STRING || field.getType() == FieldDefinition.Type.TEXT) {
Object[] values = doc.getValues(fieldName);
StringValue[] valueItems = new StringValue[values.length];
for (int i = 0; i < values.length; i++) {
valueItems[i] = new StringValue (values[i].toString());
}
return new AtomicArray(valueItems);
}
if (field.getType() == FieldDefinition.Type.INT || field.getType() == FieldDefinition.Type.LONG) {
IndexableField [] fieldValues = doc.getFields(fieldName);
Int64Value[] valueItems = new Int64Value[fieldValues.length];
for (int i = 0; i < fieldValues.length; i++) {
valueItems[i] = Int64Value.makeIntegerValue(fieldValues[i].numericValue().longValue());
}
return new AtomicArray(valueItems);
}
// TODO: convert Solr dates to xs:dateTime? but the user can manage that, perhaps, for now
if (field.getType() == FieldDefinition.Type.SOLR_FIELD) {
SchemaField schemaField = ((XPathField)field).getSchemaField();
IndexableField [] fieldValues = doc.getFields(fieldName);
StringValue[] valueItems = new StringValue[fieldValues.length];
for (int i = 0; i < fieldValues.length; i++) {
valueItems[i] = StringValue.makeStringValue(schemaField.getType().toExternal(fieldValues[i]));
}
return new AtomicArray(valueItems);
}
return EmptySequence.getInstance();
}
// Get field values from a SolrDocument; used for distributed queries. In this case the document
// will have resulted from a query to a remote Solr instance
private Sequence getFieldValue (SolrDocument doc, Evaluator eval, String fieldName, FieldDefinition field) throws XPathException {
Collection<?> valuesCollection = doc.getFieldValues(fieldName);
if (valuesCollection == null) {
return EmptySequence.getInstance();
}
Object[] values = valuesCollection.toArray();
if (field == null || field.getType() == FieldDefinition.Type.STRING || field.getType() == FieldDefinition.Type.TEXT) {
StringValue[] valueItems = new StringValue[values.length];
for (int i = 0; i < values.length; i++) {
valueItems[i] = new StringValue (values[i].toString());
}
return new AtomicArray(valueItems);
}
if (field.getType() == FieldDefinition.Type.INT || field.getType() == FieldDefinition.Type.LONG) {
Int64Value[] valueItems = new Int64Value[values.length];
for (int i = 0; i < values.length; i++) {
valueItems[i] = Int64Value.makeIntegerValue(((Number)values[i]).longValue());
}
return new AtomicArray(valueItems);
}
if (field.getType() == FieldDefinition.Type.SOLR_FIELD) {
StringValue[] valueItems = new StringValue[values.length];
for (int i = 0; i < values.length; i++) {
valueItems[i] = StringValue.makeStringValue(values[i].toString());
}
return new AtomicArray(valueItems);
}
return EmptySequence.getInstance();
}
}
}
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */