package com.bagri.xquery.saxon; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.bagri.core.model.Document; import com.bagri.core.query.AxisType; import com.bagri.core.query.Comparison; import com.bagri.core.query.ExpressionBuilder; import com.bagri.core.query.ExpressionContainer; import com.bagri.core.query.PathBuilder; import com.bagri.core.query.PathSegment; import com.bagri.core.query.QueryBuilder; import com.bagri.core.server.api.SchemaRepository; import com.bagri.core.server.api.impl.ModelManagementBase; import com.bagri.core.system.Collection; import com.bagri.core.system.Schema; import net.sf.saxon.expr.Atomizer; import net.sf.saxon.expr.AxisExpression; import net.sf.saxon.expr.BinaryExpression; import net.sf.saxon.expr.Binding; import net.sf.saxon.expr.BindingReference; import net.sf.saxon.expr.BooleanExpression; import net.sf.saxon.expr.ComparisonExpression; import net.sf.saxon.expr.Expression; import net.sf.saxon.expr.GeneralComparison10; import net.sf.saxon.expr.GeneralComparison20; import net.sf.saxon.expr.LetExpression; import net.sf.saxon.expr.Literal; import net.sf.saxon.expr.Operand; import net.sf.saxon.expr.StringLiteral; import net.sf.saxon.expr.SystemFunctionCall; import net.sf.saxon.expr.UserFunctionCall; import net.sf.saxon.expr.ValueComparison; import net.sf.saxon.expr.VariableReference; import net.sf.saxon.expr.XPathContext; import net.sf.saxon.expr.instruct.Block; import net.sf.saxon.expr.parser.Token; import net.sf.saxon.functions.IntegratedFunctionCall; import net.sf.saxon.lib.CollectionFinder; import net.sf.saxon.lib.ResourceCollection; import net.sf.saxon.om.AxisInfo; import net.sf.saxon.om.Item; import net.sf.saxon.om.Sequence; import net.sf.saxon.om.SequenceIterator; import net.sf.saxon.om.StructuredQName; import net.sf.saxon.pattern.NodeTest; import net.sf.saxon.query.XQueryExpression; import net.sf.saxon.trans.XPathException; public class CollectionFinderImpl implements CollectionFinder { private static final Logger logger = LoggerFactory.getLogger(CollectionFinderImpl.class); private SchemaRepository repo; private QueryBuilder query; private XQueryExpression exp; private PathBuilder currentPath; // private int collectType; private int currentType; public CollectionFinderImpl(SchemaRepository repo) { this.repo = repo; } QueryBuilder getQuery() { // should return Container's copy!? return query; } void setQuery(QueryBuilder query) { // copy it! already copied, actually logger.trace("setQuery. got: {}; this: {}", query, this); this.query = query; } void setExpression(XQueryExpression exp) { this.exp = exp; } @Override public ResourceCollection findCollection(XPathContext context, String collectionURI) throws XPathException { logger.trace("findCollection. context: {}; uri: {}", context, collectionURI); long stamp = System.currentTimeMillis(); int collectId; if (collectionURI == null || collectionURI.isEmpty()) { // means default collection: all schema documents collectId = Document.clnDefault; currentType = Document.clnDefault; } else { collectId = getCollectionId(collectionURI, exp.getExpression().getStaticBaseURIString()); currentType = collectId; // 0; logger.trace("findCollection. got collection type: {} for uri: {}", collectId, collectionURI); } if (query == null) { query = new QueryBuilder(); currentPath = new PathBuilder(); // query.addContainer(currentType, new ExpressionContainer()); iterate(exp.getExpression(), context); } else if (query.hasEmptyParams()) { iterateParams(exp.getExpression(), context); } stamp = System.currentTimeMillis() - stamp; logger.debug("findCollection; time taken: {}; query: {}; this: {}", stamp, query, this); ExpressionContainer exCont = getCurrentContainer(); if (exCont.getBuilder().getRoot() == null) { exCont.addExpression(currentType); logger.trace("findCollection; added always expression for type: {}", currentType); } // provide builder's copy here. exCont = query.getContainer(collectId); ResourceCollection result = new ResourceCollectionImpl(collectionURI, repo, exCont); logger.trace("findCollection. returning result: {} for collection ID: {}", result, collectId); return result; } private int getCollectionId(String uri, String baseUri) { Schema schema = repo.getSchema(); if (baseUri != null && !baseUri.isEmpty() && uri.startsWith(baseUri)) { uri = uri.substring(baseUri.length()); } Collection cln = schema.getCollection(uri); if (cln != null) { return cln.getId(); } logger.info("getCollectionId; no collection found for uri: {}; baseUri: {}, collections: {}", uri, baseUri, schema.getCollections()); return ModelManagementBase.WRONG_PATH; } private Object getValues(Sequence sq) throws XPathException { if (sq != null) { List<Object> result = new ArrayList<>(); SequenceIterator itr = sq.iterate(); while (true) { Item item = itr.next(); if (item == null) { break; } Object o = SaxonUtils.itemToObject(item); // logger.trace("getVariable; got item: {}", o); result.add(o); } return result; } return null; } private AxisType getAxisType(byte axis) { switch (axis) { case AxisInfo.ANCESTOR: return AxisType.ANCESTOR; case AxisInfo.ANCESTOR_OR_SELF: return AxisType.ANCESTOR_OR_SELF; case AxisInfo.ATTRIBUTE: return AxisType.ATTRIBUTE; case AxisInfo.CHILD: return AxisType.CHILD; case AxisInfo.DESCENDANT: return AxisType.DESCENDANT; case AxisInfo.DESCENDANT_OR_SELF: return AxisType.DESCENDANT_OR_SELF; case AxisInfo.FOLLOWING: return AxisType.FOLLOWING; case AxisInfo.FOLLOWING_SIBLING: return AxisType.FOLLOWING_SIBLING; case AxisInfo.NAMESPACE: return AxisType.NAMESPACE; case AxisInfo.PARENT: return AxisType.PARENT; case AxisInfo.PRECEDING: return AxisType.PRECEDING; case AxisInfo.PRECEDING_OR_ANCESTOR: return null; // ?? case AxisInfo.PRECEDING_SIBLING: return AxisType.PRECEDING_SIBLING; case AxisInfo.SELF: return AxisType.SELF; } return null; } private Comparison getComparison(int operator) { switch (operator) { case Token.AND: return Comparison.AND; case Token.OR: return Comparison.OR; case Token.FEQ: case Token.EQUALS: return Comparison.EQ; case Token.FLE: case Token.LE: return Comparison.LE; case Token.FLT: case Token.LT: return Comparison.LT; case Token.FGE: case Token.GE: return Comparison.GE; case Token.FGT: case Token.GT: return Comparison.GT; default: return null; } } private void setParentPath(ExpressionBuilder eb, int exIndex, PathBuilder path) { com.bagri.core.query.Expression ex = eb.getExpression(exIndex); if (ex != null) { path.setPath(ex.getPath()); logger.trace("iterate; path switched to: {}; from index: {}", path, exIndex); } } private void iterateParams(Expression ex, XPathContext ctx) throws XPathException { // if (ex instanceof Block) { // return; // } Iterator<Operand> itr = ex.operands().iterator(); while (itr.hasNext()) { Expression e = itr.next().getChildExpression(); iterateParams(e, ctx); } if (ex instanceof GeneralComparison10 || ex instanceof GeneralComparison20 || ex instanceof ValueComparison) { BinaryExpression be = (BinaryExpression) ex; Object value = null; String pName = null; Expression le = be.getLhsExpression(); Comparison compType = getComparison(be.getOperator()); if (compType == null) { logger.debug("iterate; can't get comparison from {}", be); throw new XPathException("Unknown comparison type for expression: " + be); } Expression var; if (le instanceof VariableReference || le instanceof Literal) { compType = Comparison.negate(compType); var = le; } else { var = be.getRhsExpression(); } if (var instanceof VariableReference) { Binding bind = ((VariableReference) var).getBinding(); if (bind instanceof LetExpression) { Expression e2 = ((LetExpression) bind).getSequence(); if (e2 instanceof Atomizer) { e2 = ((Atomizer) e2).getBaseExpression(); if (e2 instanceof VariableReference) { // paired ref to the e pName = ((VariableReference) e2).getBinding().getVariableQName().getLocalPart(); } } } if (pName == null) { pName = bind.getVariableQName().getClarkName(); } // value = // getValues(ctx.evaluateLocalVariable(bind.getLocalSlotNumber())); try { value = getValues(bind.evaluateVariable(ctx)); } catch (NullPointerException ee) { value = null; } // } else if (var instanceof StringLiteral) { // value = ((StringLiteral) var).getStringValue(); // } else if (var instanceof Literal) { // value = getValues(((Literal) var).getValue()); } logger.trace("iterateParams; got reference: {}, value: {}", pName, value); if (pName != null && value != null) { query.setEmptyParam(pName, value); } } } private void iterate(Expression ex, XPathContext ctx) throws XPathException { logger.trace("start: {}; expression: {}", ex.getClass().getName(), ex); PathBuilder path = currentPath; // if (ex instanceof Block) { // logger.trace("end: {}; path: {}", ex.getClass().getName(), // path.getFullPath()); // return; // } if (ex instanceof SystemFunctionCall) { SystemFunctionCall clx = (SystemFunctionCall) ex; // if ("collection".equals(clx.getDisplayName()) || // "uri-collection".equals(clx.getDisplayName())) { if (clx.isCallOnSystemFunction("collection") || clx.isCallOnSystemFunction("uri-collection")) { String collectUri = ""; if (clx.getArity() > 0) { Expression arg = clx.getArg(0); if (arg instanceof StringLiteral) { collectUri = ((StringLiteral) arg).getStringValue(); } else { // evaluate ? collectUri = arg.evaluateAsString(ctx).toString(); } } int clnId = getCollectionId(collectUri, ex.getStaticBaseURIString()); if (clnId < 0) { clnId = -1 * (query.getContainers().size() + 1); } currentType = clnId; logger.trace("iterate; set collectionId: {} for uri: {}", currentType, collectUri); currentPath = new PathBuilder(); path = currentPath; ExpressionContainer exCont = new ExpressionContainer(); query.addContainer(currentType, exCont); } } if (ex instanceof AxisExpression) { AxisExpression ae = (AxisExpression) ex; logger.trace("iterate: axis: {}", AxisInfo.axisName[ae.getAxis()]); AxisType axis = getAxisType(ae.getAxis()); String namespace = null; String segment = null; NodeTest test = ae.getNodeTest(); if (test != null) { int code = test.getFingerprint(); if (code >= 0) { StructuredQName name = ctx.getNamePool().getStructuredQName(code); //namespace = repo.getModelManagement().getNamespacePrefix(name.getURI()); namespace = name.getURI(); segment = name.getLocalPart(); } else { // case with regex.. logger.trace("iterate: empty code; test: {}", test); // depends on axis... segment = "*"; } } path.addPathSegment(axis, namespace, segment); } int exIndex = -1; if (ex instanceof BooleanExpression) { Comparison compType = getComparison(((BooleanExpression) ex).getOperator()); if (compType != null) { // if (currentType == collectType) { ExpressionContainer exCont = getCurrentContainer(); exIndex = exCont.addExpression(currentType, compType, path); logger.trace("iterate; added expression at index: {}", exIndex); // } } else { throw new XPathException("Unknown comparison type for expression: " + ex); } } if (ex instanceof UserFunctionCall) { UserFunctionCall ufc = (UserFunctionCall) ex; logger.trace("iterate; switching to UDF: {}", ufc.getFunctionName()); // ex = ufc.getTargetFunction(ctx).getBody(); ex = ufc.getFunction().getBody(); } Iterator<Operand> itr = ex.operands().iterator(); while (itr.hasNext()) { Expression e = itr.next().getChildExpression(); iterate(e, ctx); } if (ex instanceof GeneralComparison10 || ex instanceof ComparisonExpression) { // GeneralComparison20 // || // ex // instanceof // ValueComparison) // { BinaryExpression be = (BinaryExpression) ex; Object value = null; String pName = null; Expression le = be.getLhsExpression(); Comparison compType = getComparison(be.getOperator()); if (compType == null) { logger.debug("iterate; can't get comparison from {}", be); throw new XPathException("Unknown comparison type for expression: " + be); } Expression var; if (le instanceof VariableReference || le instanceof Literal) { compType = Comparison.revert(compType); var = le; } else { var = be.getRhsExpression(); } if (var instanceof VariableReference) { Binding bind = ((VariableReference) var).getBinding(); if (bind instanceof LetExpression) { Expression e2 = ((LetExpression) bind).getSequence(); if (e2 instanceof Atomizer) { e2 = ((Atomizer) e2).getBaseExpression(); if (e2 instanceof VariableReference) { // paired ref to the e pName = ((VariableReference) e2).getBinding().getVariableQName().getLocalPart(); } } } if (pName == null) { pName = bind.getVariableQName().getClarkName(); } // value = // getValues(ctx.evaluateLocalVariable(bind.getLocalSlotNumber())); try { value = getValues(bind.evaluateVariable(ctx)); } catch (NullPointerException ee) { value = null; } logger.trace("iterate; got reference: {}, value: {}", pName, value); } else if (var instanceof StringLiteral) { value = ((StringLiteral) var).getStringValue(); } else if (var instanceof Literal) { value = getValues(((Literal) var).getValue()); } if (path.getSegments().size() > 0) { ExpressionContainer exCont = getCurrentContainer(); exIndex = exCont.addExpression(currentType, compType, path, pName, value); logger.trace("iterate; added path expression at index: {}", exIndex); setParentPath(exCont.getBuilder(), exIndex, path); logger.trace("iterate; parent path {} set at index: {}", path, exIndex); } } if (ex instanceof BooleanExpression) { ExpressionContainer exCont = getCurrentContainer(); setParentPath(exCont.getBuilder(), exIndex, path); logger.trace("iterate; parent path {} set at index: {}", path, exIndex); } if (ex instanceof IntegratedFunctionCall) { IntegratedFunctionCall ifc = (IntegratedFunctionCall) ex; if ("map:get".equals(ifc.getDisplayName())) { Expression arg = ifc.getArg(1); if (arg instanceof StringLiteral) { String namespace = null; String segment = ((StringLiteral) arg).getStringValue(); AxisType axis = AxisType.CHILD; if (segment.startsWith("@")) { axis = AxisType.ATTRIBUTE; segment = segment.substring(1); } else if (segment.startsWith("#")) { axis = AxisType.NAMESPACE; segment = segment.substring(1); } path.addPathSegment(axis, namespace, segment); } else { // the arg can be instanceof VariableReference pointing to // array of // literals. Can it be used in comparison condition..? } } } // if (ex instanceof SystemFunctionCall) { // add FunctionExpression..? // } if (ex instanceof Atomizer) { Atomizer at = (Atomizer) ex; logger.trace("iterate; atomizing: {}", at.getBaseExpression()); if ((at.getBaseExpression() instanceof BindingReference) || (at.getBaseExpression() instanceof IntegratedFunctionCall && "map:get".equals(((IntegratedFunctionCall) at.getBaseExpression()).getDisplayName()))) { // logger.trace("iterate; got base ref: {}", // at.getBaseExpression()); } else { PathSegment ps = path.getLastSegment(); if (ps != null && ps.getAxis() == AxisType.CHILD) { path.addPathSegment(AxisType.CHILD, null, "text()"); } } } logger.trace("end: {}; path: {}", ex.getClass().getName(), path.getFullPath()); } private ExpressionContainer getCurrentContainer() { ExpressionContainer exCont = query.getContainer(currentType); if (exCont == null) { exCont = new ExpressionContainer(); // not sure is it ok for currentType = -1! query.addContainer(currentType, exCont); logger.trace("getCurrentContainer; added new container {} for cType: {}", exCont, currentType); } return exCont; } }