Package org.apache.xindice.core.query

Source Code of org.apache.xindice.core.query.XPathQueryResolver$XPathQuery

/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements.  See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*
* $Id: XPathQueryResolver.java 594389 2007-11-13 01:29:40Z natalia $
*/

package org.apache.xindice.core.query;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.xindice.core.Collection;
import org.apache.xindice.core.DBException;
import org.apache.xindice.core.query.ftsearch.SpecialQueryParser;
import org.apache.xindice.core.data.Entry;
import org.apache.xindice.core.data.Key;
import org.apache.xindice.core.data.NodeSet;
import org.apache.xindice.core.data.RecordSet;
import org.apache.xindice.core.data.Value;
import org.apache.xindice.core.indexer.IndexManager;
import org.apache.xindice.core.indexer.IndexMatch;
import org.apache.xindice.core.indexer.IndexPattern;
import org.apache.xindice.core.indexer.IndexQuery;
import org.apache.xindice.core.indexer.Indexer;
import org.apache.xindice.core.indexer.LuceneIndexer;
import org.apache.xindice.core.indexer.helpers.IndexQueryANY;
import org.apache.xindice.core.indexer.helpers.IndexQueryEQ;
import org.apache.xindice.core.indexer.helpers.IndexQueryGEQ;
import org.apache.xindice.core.indexer.helpers.IndexQueryGT;
import org.apache.xindice.core.indexer.helpers.IndexQueryLEQ;
import org.apache.xindice.core.indexer.helpers.IndexQueryLT;
import org.apache.xindice.core.indexer.helpers.IndexQueryNEQ;
import org.apache.xindice.core.indexer.helpers.IndexQuerySW;
import org.apache.xindice.util.Configuration;
import org.apache.xindice.util.SimpleConfigurable;
import org.apache.xindice.util.XindiceException;
import org.apache.xindice.util.XindiceRuntimeException;
import org.apache.xindice.xml.NamespaceMap;
import org.apache.xindice.xml.SymbolTable;
import org.apache.xindice.xml.dom.DBDocument;
import org.apache.xindice.xml.dom.DBNode;
import org.apache.xindice.xml.dom.DocumentImpl;
import org.apache.xindice.xml.dom.TextImpl;
import org.apache.xml.utils.DefaultErrorHandler;
import org.apache.xml.utils.PrefixResolver;
import org.apache.xml.utils.PrefixResolverDefault;
import org.apache.xpath.Expression;
import org.apache.xpath.XPath;
import org.apache.xpath.XPathContext;
import org.apache.xpath.compiler.Compiler;
import org.apache.xpath.compiler.FunctionTable;
import org.apache.xpath.compiler.OpCodes;
import org.apache.xpath.compiler.XPathParser;
import org.apache.xpath.objects.XBoolean;
import org.apache.xpath.objects.XNumber;
import org.apache.xpath.objects.XObject;
import org.apache.xpath.objects.XString;
import org.apache.lucene.analysis.Analyzer;

import org.w3c.dom.DOMException;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.traversal.NodeFilter;
import org.w3c.dom.traversal.NodeIterator;
import org.xmldb.api.base.ErrorCodes;
import org.xmldb.api.base.XMLDBException;

import javax.xml.transform.ErrorListener;
import javax.xml.transform.SourceLocator;
import javax.xml.transform.TransformerException;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.HashMap;
import java.util.Map;

/**
* XPathQueryResolver
*
* @version $Revision: 594389 $, $Date: 2007-11-12 20:29:40 -0500 (Mon, 12 Nov 2007) $
*/
public final class XPathQueryResolver extends SimpleConfigurable
                                      implements QueryResolver {

    private static final Log log = LogFactory.getLog(XPathQueryResolver.class);

    private static final Key[] EMPTY_KEYS = new Key[0];
    private static final Key[][] EMPTY_KEYSET = new Key[0][0];
    private static final String WILDCARD = "*";

    public static final String STYLE_XPATH = "XPath";

    static final String PARAM_COLLECTION = "collection";
    static final String PARAM_ANALYZER = "analyzer";

    // Maps Xalan Comparisons To IndexQuery
    private static final int[] OPMAP = {
        IndexQuery.NEQ, IndexQuery.EQ, IndexQuery.LEQ, IndexQuery.LT, IndexQuery.GEQ, IndexQuery.GT
    };


    // Flag set if Xalan Compiler constructor has 3 arguments
    private static final boolean XCOMPILER3;
    // Xalan Compiler constructor
    private static final Constructor XCOMPILER;
    // XPath constructor
    private static final Constructor XPATH;

    static {
        boolean c3;
        Constructor c;
        Constructor x;
        try {
            c = Compiler.class.getConstructor(
                    new Class[] { ErrorListener.class, SourceLocator.class, FunctionTable.class });

            x = XPath.class.getConstructor(
                    new Class[] { String.class, SourceLocator.class, PrefixResolver.class,
                                  int.class, ErrorListener.class, FunctionTable.class });

            c3 = true;
        } catch (NoSuchMethodException nsme) {
            try {
                c = Compiler.class.getConstructor(
                        new Class[] { ErrorListener.class, SourceLocator.class });

                x = XPath.class.getConstructor(
                        new Class[] { String.class, SourceLocator.class, PrefixResolver.class,
                                      int.class, ErrorListener.class });

                c3 = false;
            } catch (NoSuchMethodException e) {
                // Should not happen
                throw new RuntimeException("Could not obtain org.apache.xpath.compiler.Compiler constructor. " +
                                           "Incompatible Xalan version?");
            }
        }

        XCOMPILER3 = c3;
        XCOMPILER = c;
        XPATH = x;
    }


    private DefaultErrorHandler errorListener;
    private FunctionTable functionTable;
    private boolean autoIndex;
    private int funcFTContainsId;


    public XPathQueryResolver() {
        super();

        errorListener = new DefaultErrorHandler();

        Object num = null;
        try {
            Method install;
            Object function;

            if (XCOMPILER3) {
                functionTable = new FunctionTable();
                function = FuncFTContains.class;
                install = functionTable.getClass().getMethod("installFunction",
                                                             new Class[] {String.class, Class.class});
                num = install.invoke(functionTable, new Object[] {"ftcontains", function});
            } else {
                function = new FuncFTContains();
                install = FunctionTable.class.getMethod("installFunction",
                                                        new Class[] {String.class, Expression.class});
                num = install.invoke(FunctionTable.class, new Object[] {"ftcontains", function});
            }
        } catch (Exception e) {
            // no extentions will be available
            log.error("Could not invoke installFunction method. Incompatible Xalan version?");
        }

        if (num != null) {
            funcFTContainsId = ((Integer) num).intValue();
        } else {
            log.error("Could not install ftcontains function.");
        }
    }

    public void setConfig(Configuration config) throws XindiceException {
        super.setConfig(config);
        this.autoIndex = config.getBooleanAttribute("autoindex", false);
    }

    public String getQueryStyle() {
        return STYLE_XPATH;
    }

    public void setQueryEngine(QueryEngine engine) {
        // FIXME Not used: this.engine = engine;
    }

    public Query compileQuery(Collection context, String query, NamespaceMap nsMap, Key[] keys)
            throws QueryException {
        return new XPathQuery(context, query, nsMap, keys);
    }

    public NodeSet query(Collection context, String query, NamespaceMap nsMap, Key[] keys)
            throws QueryException {
        XPathQuery xq = new XPathQuery(context, query, nsMap, keys);
        return xq.execute();
    }

    private Compiler createCompiler() {
        try {
            if (XCOMPILER3) {
                return (Compiler) XCOMPILER.newInstance(
                        new Object[] {errorListener, null, functionTable});
            } else {
                return (Compiler) XCOMPILER.newInstance(
                        new Object[] {errorListener, null});
            }
        } catch (Exception e) {
            throw new RuntimeException("Could not instantiate Compiler: " + e);
        }
    }

  /**
     * XPathQuery
     */
    private class XPathQuery implements Query {
        public Collection context;
        public IndexManager idxMgr;
        public NamespaceMap nsMap;
        public PrefixResolver pr;
        public SymbolTable symbols;
        public String query;
        public Compiler cmp;
        public XPath xp;
        public Key[] keys;
        public Analyzer analyzer;
        private HashMap parameters;

        public XPathQuery(Collection context, String query, NamespaceMap nsMap, Key[] keys)
                throws QueryException {
            this.context = context;
            this.query = query;
            this.nsMap = nsMap;
            this.keys = keys;

            parameters = new HashMap();
            parameters.put(PARAM_COLLECTION, context);

            Expression ex;
            try {
                if (nsMap != null) {
                    Node n = nsMap.getContextNode();
                    pr = new PrefixResolverDefault(n);
                }

                cmp = createCompiler();
                XPathParser parser = new XPathParser(errorListener, null);
                parser.initXPath(cmp, query, pr);
                ex = cmp.compile(0);

                symbols = context.getSymbols();
                idxMgr = context.getIndexManager();
            } catch (Exception e) {
                throw new CompilationException("Error Compiling XPath Expression: " + e.getMessage(), e);
            }

            if (ex == null) {
                throw new CompilationException("Error Compiling XPath Expression: XPath Compiler.compile returned null");
            }
        }

        public String getQueryStyle() {
            return STYLE_XPATH;
        }

        public String getQueryString() {
            return query;
        }

        public Collection getQueryContext() {
            return context;
        }

        public NamespaceMap getNamespaceMap() {
            return nsMap;
        }

        public Key[] getKeySet() {
            return keys;
        }

    public NodeSet execute() throws QueryException {
      try {
                Key[] keySet = keys;

                // TODO: Add logic to do an indexed check on provided
                //       keySets that are larger than a certain minimum

                if (keys == null && idxMgr != null) {
                    // Issue the query using Indexes
                    try {
                        Object obj = evaluate(null, 0);
                        if (obj instanceof NamedKeys) {
                            keySet = ((NamedKeys) obj).keys;
                        }
                    } catch (Exception e) {
                        if (log.isWarnEnabled()) {
                            log.warn("ignored exception", e);
                        }
                    }
                }

                if (keySet == null) {
                    // Fall back to a Collection scan
                    SortedSet set = new TreeSet();
                    RecordSet rs = context.getFiler().getRecordSet();
                    while (rs.hasMoreRecords()) {
                        set.add(rs.getNextKey());
                    }
                    keySet = (Key[]) set.toArray(EMPTY_KEYS);
                }

                return new ResultSet(context, pr, keySet, query, parameters);
            } catch (Exception e) {
                if (e instanceof QueryException) {
                    throw (QueryException) e.fillInStackTrace();
                }
                throw new ProcessingException("Error executing XPath query: " + e.getMessage(), e);
            }
        }

        private Key[] andKeys(List list) {
            if (!list.isEmpty()) {
                if (list.size() > 1) {
                    Key[][] keys = (Key[][]) list.toArray(EMPTY_KEYSET);
                    return QueryEngine.andKeySets(keys);
                } else
                    return (Key[]) list.get(0);
            } else {
                return null;
            }
        }

        // Evaluation Methods

    /**
         * evaluate does a partial evaluation of the XPath in order to
         * determine the optimal indexes to prepare for the query and retrieve
         * the Document subset that will be used for the actual XPath query.
         *
         * <p>This will return an instance of one of the following classes:
         * <pre>
         *    String    If the sub-expression resolves to a Node Name
         *    XNumber   If the sub-expression resolves to a Number
         *    XString   If the sub-expression resolves to a String
         *    XBoolean  If the sub-expression resolves to a Boolean
         *    NamedKeys If the sub-expression resolves to a Key set
         *    null      If the sub-expression resolves to anything else
         * </pre>
         *
         * @param owner The parent node name for this context
         * @param pos   The position to start at (recursively called)
         * @return Some Object result
         */
        private Object evaluate(String owner, int pos) throws Exception {
            int op = cmp.getOp(pos);
            if (op == -1) {
                return null;
            }

      switch (op) {
                case OpCodes.OP_LOCATIONPATH:
                    return evalLocationPath(owner, pos);

                case OpCodes.OP_ARGUMENT:
                case OpCodes.OP_XPATH:
                case OpCodes.OP_PREDICATE:
                    return evaluate(owner, Compiler.getFirstChildPos(pos));

                case OpCodes.OP_OR:
                case OpCodes.OP_AND:
                    return evalSetComparison(op, owner, pos);

                case OpCodes.OP_NOTEQUALS:
                case OpCodes.OP_EQUALS:
                case OpCodes.OP_LTE:
                case OpCodes.OP_LT:
                case OpCodes.OP_GTE:
                case OpCodes.OP_GT:
                    return evalValComparison(op, owner, pos);

                case OpCodes.OP_PLUS:
                case OpCodes.OP_MINUS:
                case OpCodes.OP_MULT:
                case OpCodes.OP_DIV:
                case OpCodes.OP_MOD:
                case OpCodes.OP_QUO:
                    return evalMathOperation(op, owner, pos);

                case OpCodes.OP_NEG:
                case OpCodes.OP_STRING:
                case OpCodes.OP_BOOL:
                case OpCodes.OP_NUMBER:
                    return evalUnaryOperation(op, owner, pos);

                case OpCodes.OP_UNION:
                    return evalUnion(owner, pos);

                case OpCodes.OP_VARIABLE:
                    break;

                case OpCodes.OP_GROUP:
                    return evaluate(owner, Compiler.getFirstChildPos(pos));

                case OpCodes.OP_EXTFUNCTION:
                    break;

                case OpCodes.OP_FUNCTION:
                    return evalFunction(owner, pos);

                case OpCodes.FROM_ANCESTORS:
                case OpCodes.FROM_ANCESTORS_OR_SELF:
                case OpCodes.FROM_ATTRIBUTES:
                case OpCodes.FROM_CHILDREN:
                case OpCodes.FROM_DESCENDANTS:
                case OpCodes.FROM_DESCENDANTS_OR_SELF:
                case OpCodes.FROM_FOLLOWING:
                case OpCodes.FROM_FOLLOWING_SIBLINGS:
                case OpCodes.FROM_PARENT:
                case OpCodes.FROM_PRECEDING:
                case OpCodes.FROM_PRECEDING_SIBLINGS:
                case OpCodes.FROM_NAMESPACE:
                case OpCodes.FROM_SELF:
                case OpCodes.FROM_ROOT:
                    return evalAxis(op, owner, pos);

                case OpCodes.NODENAME:
                case OpCodes.OP_LITERAL:
                case OpCodes.OP_NUMBERLIT:
                    return evalLiteral(owner, pos);

                case OpCodes.NODETYPE_TEXT:
                case OpCodes.NODETYPE_NODE:
                    return WILDCARD;

                case OpCodes.NODETYPE_ANYELEMENT:
                case OpCodes.ELEMWILDCARD:
                    return WILDCARD;

                case OpCodes.NODETYPE_ROOT:
                case OpCodes.NODETYPE_COMMENT:
                case OpCodes.NODETYPE_PI:
                case OpCodes.NODETYPE_FUNCTEST:
                    break;

                default :
                    if (log.isWarnEnabled()) {
                        log.warn("Unknown: " + op);
                    }
            }
            return null;
        }

    private Object evalLocationPath(String owner, int pos) throws Exception {
            int lp = Compiler.getFirstChildPos(pos);
            List ks = new ArrayList();

            String name = owner;
            boolean attr = false;
      while (cmp.getOp(lp) != -1) {
                Object obj = evaluate(name, lp);
        if (obj instanceof NamedKeys) {
                    NamedKeys nk = (NamedKeys) obj;
                    if (nk.name != null) {
                        attr = nk.attribute;
                        if (attr) {
                            if (name == null) {
                                name = owner;
                            }
                            if (name != null) {
                                StringBuffer sb = new StringBuffer(32);
                                sb.append(name);
                                sb.append('@');
                                sb.append(nk.name);
                                name = sb.toString();
                            }
                        } else {
                            name = nk.name;
                        }
                    }
          if (nk.keys != null) {
                        ks.add(nk.keys);
                    }
                    else if (name != null) {
                        // Try to use a NameIndex to resolve the path component
                        // can match a wildcard node name here if pattern is "*" then every document matches
                        if (!attr && "*".equals(name)) {
                            SortedSet set = new TreeSet();
                            RecordSet rs = context.getFiler().getRecordSet();
                            while (rs.hasMoreRecords()) {
                                set.add(rs.getNextKey());
                            }
                            ks.add(set.toArray(EMPTY_KEYS));
                        } else {
                            // Try to use a NameIndex to resolve the path component
                            IndexPattern pattern = new IndexPattern(symbols, name, nsMap);
                            Indexer idx = context.getIndexManager().getBestIndexer(Indexer.STYLE_NODENAME, pattern);
                            if (idx != null) {
                                IndexMatch[] matches = idx.queryMatches(new IndexQueryANY(pattern));
                                Key[] keys = QueryEngine.getUniqueKeys(matches);
                                ks.add(keys);
                            }
                        }
                    }
                }
                lp = cmp.getNextOpPos(lp);
            }
            return new NamedKeys(name, attr, andKeys(ks));
        }

        private Object evalUnion(String owner, int pos) throws Exception {
            int l = Compiler.getFirstChildPos(pos);
            int r = cmp.getNextOpPos(l);
            Object left = evaluate(owner, l);

            if (left instanceof NamedKeys && ((NamedKeys) left).keys != null) {
                Object right = evaluate(owner, r);

                if (right instanceof NamedKeys && ((NamedKeys) right).keys != null) {
                    Key[][] keys = new Key[][]{((NamedKeys) left).keys, ((NamedKeys) right).keys};
                    return new NamedKeys(null, false, QueryEngine.orKeySets(keys));
                }
            }

            // no index query of left part of union
            // or no index query of right part of union => must do
            /// collection scan
            return null;
        }

    private Object evalSetComparison(int op, String owner, int pos) throws Exception {
            int l = Compiler.getFirstChildPos(pos);
            int r = cmp.getNextOpPos(l);
            Object left = evaluate(owner, l);

      if (left instanceof NamedKeys && ((NamedKeys) left).keys != null) {
                // have left keys
                if (((NamedKeys) left).keys.length == 0 && op == OpCodes.OP_AND) {
                    // left keyset empty implies result of AND would be empty
                    return new NamedKeys(null, false, ((NamedKeys) left).keys);
                }

                Object right = evaluate(owner, r);
        if (right instanceof NamedKeys && ((NamedKeys) right).keys != null) {
                    // have keys for both left and right
          if (op == OpCodes.OP_AND) {
                        if (((NamedKeys) right).keys.length == 0) {
                            // right keyset empty implies result of AND would be empty
                            return new NamedKeys(null, false, ((NamedKeys) right).keys);
                        }

                        Key[][] keys = new Key[][]{((NamedKeys) left).keys, ((NamedKeys) right).keys};
                        return new NamedKeys(null, false, QueryEngine.andKeySets(keys));
                    } else {
                        // OR operation
                        if (((NamedKeys) left).keys.length == 0) {
                            // OR operation and left empty implies result is right set
                            return new NamedKeys(null, false, ((NamedKeys) right).keys);
                        }

                        if (((NamedKeys) right).keys.length == 0) {
                            // OR operation and right empty implies result is left set
                            return new NamedKeys(null, false, ((NamedKeys) left).keys);
                        }

                        Key[][] keys = new Key[][]{((NamedKeys) left).keys, ((NamedKeys) right).keys};
                        return new NamedKeys(null, false, QueryEngine.orKeySets(keys));
                    }
                } else {
                    // have left keys but not right can infer that AND operation
                    // result cannot contain more than left set so return that
                    if (op == OpCodes.OP_AND) {
                        return new NamedKeys(null, false, ((NamedKeys) left).keys);
                    }
                }
            } else {
                // do not have left keys
                Object right = evaluate(owner, r);
                if (right instanceof NamedKeys && ((NamedKeys) right).keys != null) {
                    // have right keys but not left can infer that AND operation
                    // result cannot contain more than right set so return that
                    if (op == OpCodes.OP_AND) {
                        return new NamedKeys(null, false, ((NamedKeys) right).keys);
                    }
                }
            }

            // punt
            return null;
        }

        private Object evalValComparison(int op, String owner, int pos) throws Exception {
            int l = Compiler.getFirstChildPos(pos);
            int r = cmp.getNextOpPos(l);

            Object left = evaluate(owner, l);
            if (!(left instanceof XObject || left instanceof NamedKeys)) {
                // can't evaluate
                return null;
            }

            Object right = evaluate(owner, r);
            if ((left instanceof NamedKeys && right instanceof XObject)
                    || (left instanceof XObject && right instanceof NamedKeys)) {
                // try to evaluate through indexed search
                return queryComparison(op, owner, left, right);
            }

            // could handle comparison of nodeset to boolean here
            // boolean converts to 1.0 (true) or 0.0 (false)
            // nodeset converts to 1.0 (non-empty) or 0.0 (empty)
            // but since this is a rare and odd comparison we don't bother now...
            if (left instanceof XObject && right instanceof XObject) {
                switch (op) {
                    case OpCodes.OP_NOTEQUALS:
                        return new XBoolean(((XObject) left).notEquals((XObject) right));
                    case OpCodes.OP_EQUALS:
                        return new XBoolean(((XObject) left).equals((XObject) right));
                    case OpCodes.OP_LTE:
                        return new XBoolean(((XObject) left).lessThanOrEqual((XObject) right));
                    case OpCodes.OP_LT:
                        return new XBoolean(((XObject) left).lessThan((XObject) right));
                    case OpCodes.OP_GTE:
                        return new XBoolean(((XObject) left).greaterThanOrEqual((XObject) right));
                    case OpCodes.OP_GT:
                        return new XBoolean(((XObject) left).greaterThan((XObject) right));
                    default :
                        return null; // Won't happen
                }
            }

            // can't evaluate here...
            return null;
        }

        private strictfp Object evalMathOperation(int op, String owner, int pos) throws Exception {
            int lc = Compiler.getFirstChildPos(pos);
            int rc = cmp.getNextOpPos(lc);
            Object left = evaluate(owner, lc);

            if (left instanceof XObject) {
                Object right = evaluate(owner, rc);
                if (right instanceof XObject) {
                    switch (op) {
                        case OpCodes.OP_PLUS:
                            return new XNumber(((XObject) left).num() + ((XObject) right).num());
                        case OpCodes.OP_MINUS:
                            return new XNumber(((XObject) left).num() - ((XObject) right).num());
                        case OpCodes.OP_MULT:
                            return new XNumber(((XObject) left).num() * ((XObject) right).num());
                        case OpCodes.OP_DIV:
                            return new XNumber(((XObject) left).num() / ((XObject) right).num());
                        case OpCodes.OP_MOD:
                            return new XNumber(((XObject) left).num() % ((XObject) right).num());
                        case OpCodes.OP_QUO:
                            return new XNumber(((XObject) left).num() / ((XObject) right).num());
                        default :
                            return null; // Won't happen
                    }
                }
            }

            // can't evaluate
            return null;
        }

        private Object evalUnaryOperation(int op, String owner, int pos) throws Exception {
            Object val = evaluate(owner, Compiler.getFirstChildPos(pos));
            if (val instanceof XObject) {
                switch (op) {
                    case OpCodes.OP_NEG:
                        return new XNumber(-((XObject) val).num());
                    case OpCodes.OP_STRING:
                        return new XString(((XObject) val).str());
                    case OpCodes.OP_BOOL:
                        return new XBoolean(((XObject) val).bool());
                    case OpCodes.OP_NUMBER:
                        return new XNumber(((XObject) val).num());
                    default:
                        return null; // Won't happen
                }
            }
            if (val instanceof NamedKeys) {
                NamedKeys nk = (NamedKeys) val;
                // we may be able to convert the nodeset to the proper type
                // and return an answer numeric operations imply conversion to
                // string and then to number
                // we can't convert to string
                // we can handle conversion to boolean (empty or not empty)
                if (nk.keys != null && op == OpCodes.OP_BOOL) {
                    return nk.keys.length == 0 ? XBoolean.S_FALSE : XBoolean.S_TRUE;
                }
            }
            return null;
        }

        private Object evalFunction(String owner, int pos) throws Exception {
            int idx = Compiler.getFirstChildPos(pos);
            int id = cmp.getOp(idx);

            // NOTE: In the XPath op table, the
            // op code is stored at the current position index passed to us
            // the size of the current op is stored in the next location
            // thus, the index of the location just beyond the function
            // arguments for the current op (which is also the index of the next op)
            // is equal to the current postion + the size of the current operation - 1
            // (the getOp(index) method merely returns the int value stored in the op table
            // at the specified index. That value is an op code, a size, or the index
            // of an token or ... (see org.apache.xpath.compiler.OpCodes for the
            // various items that can be part of an operation)
            int endFunc = pos + cmp.getOp(pos + 1) - 1;

            List args = new ArrayList();
            int lp = idx + 1;
            while (lp < endFunc) {
                args.add(evaluate(owner, lp));
                lp = cmp.getNextOpPos(lp);
            }

            switch (id) {
                case FunctionTable.FUNC_BOOLEAN:
                    return funcBoolean(args);
                case FunctionTable.FUNC_CEILING:
                    return funcCeiling(args);
                case FunctionTable.FUNC_CONCAT:
                    return funcConcat(args);
                case FunctionTable.FUNC_CONTAINS:
                    return funcContains(args);
                case FunctionTable.FUNC_FALSE:
                    return XBoolean.S_FALSE;
                case FunctionTable.FUNC_FLOOR:
                    return funcFloor(args);
                case FunctionTable.FUNC_NORMALIZE_SPACE:
                    return funcNormalizeSpace(args);
                case FunctionTable.FUNC_NOT:
                    return funcNot(args);
                case FunctionTable.FUNC_NUMBER:
                    return funcNumber(args);
                case FunctionTable.FUNC_ROUND:
                    return funcRound(args);
                case FunctionTable.FUNC_STARTS_WITH:
                    return funcStartsWith(owner, args);
                case FunctionTable.FUNC_STRING:
                    return funcString(args);
                case FunctionTable.FUNC_STRING_LENGTH:
                    return funcStringLength(args);
                case FunctionTable.FUNC_SUBSTRING:
                    return funcSubstring(args);
                case FunctionTable.FUNC_SUBSTRING_AFTER:
                    return funcSubstringAfter(args);
                case FunctionTable.FUNC_SUBSTRING_BEFORE:
                    return funcSubstringBefore(args);
                case FunctionTable.FUNC_TRANSLATE:
                    return funcTranslate(args);
                case FunctionTable.FUNC_TRUE:
                    return XBoolean.S_TRUE;
                default:
                    // custom extention
                    if (id == funcFTContainsId) {
                        return funcFTContains(owner, args);
                    }
                    return null;
            }
        }

        private Object evalAxis(int op, String owner, int pos) throws Exception {
            String nsURI = cmp.getStepNS(pos);
            String name = (String) evaluate(owner, Compiler.getFirstChildPosOfStep(pos));
            // owner = cmp.getStepLocalName(pos);

            if (nsURI != null && nsMap != null) {
                // We have to determine the prefix that was used
                // There has to be an easier way to do this with Xalan
                String pfx = null;
                Iterator i = nsMap.keySet().iterator();
                while (i.hasNext()) {
                    String p = (String) i.next();
                    if (nsMap.getNamespaceURI(p).equals(nsURI)) {
                        pfx = p;
                        break;
                    }
                }
                if (pfx != null) {
                    StringBuffer sb = new StringBuffer(32);
                    sb.append(pfx);
                    sb.append(':');
                    sb.append(owner);
                    name = sb.toString();
                }
            }

            if (op == OpCodes.FROM_ATTRIBUTES) {
                owner = owner + '@' + name;
            } else if (op == OpCodes.FROM_SELF && WILDCARD.equals(name)) {
                name = owner;
            } else {
                owner = name;
            }

            int rp = cmp.getFirstPredicateOpPos(pos);

            List ks = new ArrayList();
            while (rp < pos + cmp.getOp(pos + 1)) {
                Object obj = evaluate(owner, rp);
                if (obj instanceof NamedKeys) {
                    NamedKeys nk = (NamedKeys) obj;
                    if (nk.keys != null) {
                        ks.add(nk.keys);
                    }
                }
                rp = cmp.getNextOpPos(rp);
            }
            return new NamedKeys(name, (op == OpCodes.FROM_ATTRIBUTES), andKeys(ks));
        }

        private Object evalLiteral(String owner, int pos) {
            int idx = cmp.getOp(Compiler.getFirstChildPos(pos));
            switch (idx) {
                case OpCodes.EMPTY:
                    return owner;
                case OpCodes.ELEMWILDCARD:
                    return WILDCARD;
                default:
                    return cmp.getToken(idx);
            }
        }

        // XPath Functions

        private Object funcBoolean(List args) throws Exception {
            if (args.size() == 1) {
                Object o = args.get(0);
                if (o instanceof XObject) {
                    if (((XObject) o).bool()) {
                        return XBoolean.S_TRUE;
                    } else {
                        return XBoolean.S_FALSE;
                    }
                } else {
                    if (o instanceof NamedKeys) {
                        NamedKeys nk = (NamedKeys) o;
                        if (nk.keys == null) {
                            return null;
                        }
                        if (nk.keys.length == 0) {
                            // nodeset empty converts to boolean false
                            return XBoolean.S_FALSE;
                        }
                        return XBoolean.S_TRUE;
                    }
                }
            }
            return null;
        }

        private Object funcCeiling(List args) throws Exception {
            if (args.size() == 1) {
                Object o = args.get(0);
                if (o instanceof XObject) {
                    return new XNumber(Math.ceil(((XObject) o).num()));
                }
            }
            return null;
        }

        private Object funcConcat(List args) {
            StringBuffer sb = new StringBuffer();
            for (int i = 0; i < args.size(); i++) {
                Object o = args.get(i);
                if (o instanceof XObject) {
                    sb.append(((XObject) o).str());
                } else {
                    return null;
                }
            }
            return new XString(sb.toString());
        }

        private Object funcContains(List args) {
            if (args.size() == 2) {
                Object o = args.get(0);
                Object s = args.get(1);
                if (o instanceof XObject && s instanceof XObject) {
                    if (((XObject) o).str().indexOf(((XObject) s).str()) != -1) {
                        return XBoolean.S_TRUE;
                    } else {
                        return XBoolean.S_FALSE;
                    }
                }
            }
            return null;
        }

        private Object funcFTContains(String owner, List args) throws Exception {
            if (args.size() != 1) {
                return null;
            }

            if (parameters == null) {
                parameters = new HashMap();
            }

            Object o = args.get(0);

            if (o instanceof XString) {
                // extract text query
                String query = ((XString) o).str();
                return queryTextIndex(owner, query);
            }
           
            return null;
        }

        private Object funcFloor(List args) throws Exception {
            if (args.size() == 1) {
                Object o = args.get(0);
                if (o instanceof XObject) {
                    return new XNumber(Math.floor(((XObject) o).num()));
                }
            }
            return null;
        }

        private Object funcNormalizeSpace(List args) {
            if (args.size() == 1) {
                Object o = args.get(0);
                if (o instanceof XObject) {
                    return new XString(QueryEngine.normalizeString(((XObject) o).str()));
                }
            }
            return null;
        }

        private Object funcNot(List args) throws Exception {
            if (args.size() == 1) {
                Object o = args.get(0);
                if (o instanceof XObject) {
                    if (((XObject) o).bool()) {
                        return XBoolean.S_FALSE;
                    } else {
                        return XBoolean.S_TRUE;
                    }
                } else {
                    if (o instanceof NamedKeys) {
                        NamedKeys nk = (NamedKeys) o;
                        if (nk.keys == null) {
                            return null;
                        }
                        if (nk.keys.length == 0) {
                            // nodeset empty converts to boolean false => not false => true
                            return XBoolean.S_TRUE;
                        }
                        return XBoolean.S_FALSE;
                    }
                    return null;
                }

            }
            return null;
        }

        private Object funcNumber(List args) throws Exception {
            if (args.size() == 1) {
                Object o = args.get(0);
                if (o instanceof XObject) {
                    return new XNumber(((XObject) o).num());
                }
            }
            return null;
        }

        private Object funcRound(List args) throws Exception {
            if (args.size() == 1) {
                Object o = args.get(0);
                if (o instanceof XObject) {
                    return new XNumber(Math.round(((XObject) o).num()));
                }
            }
            return null;
        }

        private Object funcStartsWith(String owner, List args) throws Exception {
            if (args.size() == 2) {
                Object o = args.get(0);
                Object s = args.get(1);

                if (o instanceof XObject && s instanceof XObject) {
                    if (((XObject) o).str().startsWith(((XObject) s).str())) {
                        return XBoolean.S_TRUE;
                    } else {
                        return XBoolean.S_FALSE;
                    }
                } else if (o instanceof NamedKeys && s instanceof XObject) {
                    NamedKeys nk = (NamedKeys) o;
                    if (nk.name != null) {
                        String ps;
                        if (nk.attribute && nk.name.indexOf('@') == -1) {
                            ps = owner + "@" + nk.name;
                        } else {
                            ps = nk.name;
                        }

                        IndexPattern pattern = new IndexPattern(symbols, ps, nsMap);

                        XObject obj = (XObject) s;
                        Value val1 = new Value(obj.str());

                        IndexQuery iq = new IndexQuerySW(pattern, val1);
                        return queryIndexes(nk, iq, ps, obj.getType());
                    }
                }
            }
            return null;
        }

        private Object funcString(List args) {
            if (args.size() == 1) {
                Object o = args.get(0);
                if (o instanceof XObject) {
                    return new XString(((XObject) o).str());
                }
            }
            return null;
        }

        private Object funcStringLength(List args) {
            if (args.size() == 1) {
                Object o = args.get(0);
                if (o instanceof XObject) {
                    return new XNumber(((XObject) o).str().length());
                }
            }
            return null;
        }

        private Object funcSubstring(List args) throws Exception {
            if (args.size() == 2 || args.size() == 3) {
                Object o = args.get(0);
                Object pos = args.get(1);
                Object len = args.size() == 3 ? args.get(2) : null;
                if (o instanceof XObject && pos instanceof XObject && (len == null || len instanceof XObject)) {
                    int ipos = (int) ((XObject) pos).num() - 1;
                    if (len != null) {
                        int ilen = (int) ((XObject) len).num();
                        return new XString(((XObject) o).str().substring(ipos, ipos + ilen));
                    } else
                        return new XString(((XObject) o).str().substring(ipos));
                }
            }
            return null;
        }

        private Object funcSubstringAfter(List args) {
            if (args.size() == 2) {
                Object o = args.get(0);
                Object s = args.get(1);
                if (o instanceof XObject && s instanceof XObject) {
                    String val = ((XObject) o).str();
                    String sub = ((XObject) s).str();
                    int i = val.indexOf(sub);
                    if (i == -1) {
                        return new XString("");
                    } else {
                        return new XString(val.substring(i + sub.length()));
                    }
                }
            }
            return null;
        }

        private Object funcSubstringBefore(List args) {
            if (args.size() == 2) {
                Object o = args.get(0);
                Object s = args.get(1);
                if (o instanceof XObject && s instanceof XObject) {
                    String val = ((XObject) o).str();
                    String sub = ((XObject) s).str();
                    int i = val.indexOf(sub);
                    if (i == -1) {
                        return new XString("");
                    } else {
                        return new XString(val.substring(0, i));
                    }
                }
            }
            return null;
        }

        private Object funcTranslate(List args) {
            if (args.size() == 3) {
                Object o = args.get(0);
                Object c1 = args.get(1);
                Object c2 = args.get(2);
                if (o instanceof XObject && c1 instanceof XObject && c2 instanceof XObject) {
                    char ch1 = ((XObject) c1).str().charAt(0);
                    char ch2 = ((XObject) c2).str().charAt(0);
                    return new XString(((XObject) o).str().replace(ch1, ch2));
                }
            }
            return null;
        }

        // The Actual Querying Methods

    /**
         * queryIndexes actually performs index-based querying on behalf of the evaluation methods.
         *
         * @param nk      The NamedKeys instance to use for matches
         * @param iq      The actual IndexQuery to use for resolution
         * @param ps      The pattern String to possibly use for index gen
         * @param objType The object type to possibly use for index gen
         * @return The resulting Keys (if any)
         */
        private Object queryIndexes(NamedKeys nk, IndexQuery iq, String ps, int objType) {
      try {
                // TODO: Add logic to use an EmptyKeySet if a name doesn't already
                //       exist in the SymbolTable.  This will eliminate the need
                //       to do a collection scan in those cases where somebody
                //       typed an element or attribute name incorrectly.

                IndexPattern pattern = iq.getPattern();

                Indexer idx = context.getIndexManager().getBestIndexer(Indexer.STYLE_NODEVALUE, pattern);
        if (idx != null) {
                    return new NamedKeys(nk.name, nk.attribute, QueryEngine.getUniqueKeys(idx.queryMatches(iq)));
                } else if (autoIndex) {
                    // TODO: This has to *not* be hardcoded
                    Element e = new DocumentImpl().createElement("index");
                    e.setAttribute("class", "org.apache.xindice.core.indexer.ValueIndexer");
                    e.setAttribute("name", "xp_" + ps);
                    e.setAttribute("pattern", ps);

                    // Set the type for the index
                    String type = null;
                    switch (objType) {
                        case XObject.CLASS_BOOLEAN:
                            type = "boolean";
                            break;
                        case XObject.CLASS_NUMBER:
                            type = "double";
                            break;
                        case XObject.CLASS_STRING:
                            if (ps.indexOf('@') != -1) {
                                type = "string";
                            } else {
                                type = "trimmed";
                            }
                            break;
                        default :
                            if (log.isWarnEnabled()) {
                                log.warn("invalid object type : " + objType);
                            }
                    }

                    if (type != null) {
                        e.setAttribute("type", type);
                    }

                    idxMgr.create(new Configuration(e));
                } else {
                    log.debug("Query performance may be improved by using ValueIndex with pattern '" + ps + "'");
                }
            } catch (Exception e) {
                if (log.isWarnEnabled()) {
                    log.warn("ignored exception", e);
                }
            }
            return null;
        }

        private Object queryTextIndex(String ps, String query) throws Exception {
            IndexPattern pattern = new IndexPattern(symbols, ps, nsMap);

            // check if there is full text indexer for this collection
            Indexer idx = context.getIndexManager().getBestIndexer(Indexer.STYLE_FULLTEXT, pattern);
            if (idx instanceof LuceneIndexer) {
                LuceneIndexer textInd = ((LuceneIndexer) idx);
                analyzer = textInd.getAnalyzer();
                parameters.put(PARAM_ANALYZER, analyzer);

                // see if index has matching pattern
                String alias = textInd.getPatternAlias(pattern);

                if (alias != null) {
                    // Queries that contain 'NOT', '!', '-' operators cannot be used here
                    // because LuceneIndexer searches for documents, in that context
                    // "NOT term" query means to find documents where 'term' does not
                    // appear in certain field at all. For XPath, however, it means that
                    // 'term' must not appear in text of the element that currently under
                    // evaluation, but may appear in the other elements that match the
                    // same IndexPattern.
                    //
                    // To make sure that all potentially matching documents are returned
                    // by the search, all subqueries with these operators are ignored
                    // on this step.
                    org.apache.lucene.search.Query parsedQuery = new SpecialQueryParser(alias, analyzer).parse(query);
                    IndexMatch[] matches = textInd.queryMatches(parsedQuery);
                    Key[] keys = QueryEngine.getUniqueKeys(matches);

                    return new NamedKeys(ps, ps.indexOf('@') != -1, keys);
                }
            } else {
                // there is no Lucene indexer, fall back to default analyzer
                log.debug("Query performance may be improved by using LuceneIndex with pattern '" + ps + "'");
                analyzer = (Analyzer) Class.forName(LuceneIndexer.DEFANALYZER).newInstance();
                parameters.put(PARAM_ANALYZER, analyzer);
            }

            return null;
        }

        /**
         * queryComparison performs a comparison query use the operands that are passed to it, and returns the resulting
         * Keys.
         *
         * @param op    The Operator
         * @param owner The Owner Node
         * @param left  The left Operand
         * @param right The right Operand
         * @return The resulting Keys (if any)
         */
        private Object queryComparison(int op, String owner, Object left, Object right) throws Exception {
            op = OPMAP[op - OpCodes.OP_NOTEQUALS];

            if (left instanceof XObject) {
                // Check if we have to switch the operation
                if (op == IndexQuery.GT || op == IndexQuery.LT || op == IndexQuery.GEQ || op == IndexQuery.LEQ) {
                    op = -op;
                }
                // Swap the operands
                Object tmp = left;
                left = right;
                right = tmp;
            }

            if (!(left instanceof NamedKeys && right instanceof XObject)) {
                // can't handle it
                return null;
            }

            NamedKeys nk = (NamedKeys) left;
            XObject obj = (XObject) right;

            if (nk.name != null) {
                String ps;
                if (nk.attribute && nk.name.indexOf('@') == -1) {
                    ps = owner + "@" + nk.name;
                } else {
                    ps = nk.name;
                }

                IndexQuery iq;
                IndexPattern pattern = new IndexPattern(symbols, ps, nsMap);
                String value = obj.str();

                switch (op) {
                    case IndexQuery.NEQ:
                        iq = new IndexQueryNEQ(pattern, value);
                        break;
                    case IndexQuery.EQ:
                        iq = new IndexQueryEQ(pattern, value);
                        break;
                    case IndexQuery.LEQ:
                        iq = new IndexQueryLEQ(pattern, value);
                        break;
                    case IndexQuery.LT:
                        iq = new IndexQueryLT(pattern, value);
                        break;
                    case IndexQuery.GEQ:
                        iq = new IndexQueryGEQ(pattern, value);
                        break;
                    case IndexQuery.GT:
                        iq = new IndexQueryGT(pattern, value);
                        break;
                    default :
                        iq = null; // Won't happen
                }

                return queryIndexes(nk, iq, ps, obj.getType());
            } else {
                return null;
            }
        }
    }

    /**
     * NamedKeys
     */

    private class NamedKeys {
        public boolean attribute = false;
        public String name;
        public Key[] keys;

        public NamedKeys(String name, boolean attribute, Key[] keys) {
            this.name = name;
            this.attribute = attribute;
            this.keys = keys;
        }
    }

  /**
     * ResultSet
     */

    private class ResultSet implements NodeSet {
        public Collection context;
        public String query;
        public ErrorListener errors;
        public PrefixResolver pr;
        public XPath xp;

        public Key[] keySet;
        public int keyPos = 0;
        public NodeIterator ni;
        public Object node;
        private Map parameters;

        public ResultSet(Collection context, PrefixResolver pr, Key[] keySet, String query, Map parameters) {
            this.context = context;
            this.pr = pr;
            this.keySet = keySet;
            this.query = query;
            this.parameters = parameters;

            errors = new ErrorListener() {
                public void fatalError(TransformerException te) {
                    if (log.isFatalEnabled()) {
                        log.fatal("No message", te);
                    }
                }

                public void error(TransformerException te) {
                    if (log.isErrorEnabled()) {
                        log.error("No message", te);
                    }
                }

                public void warning(TransformerException te) {
                    if (log.isWarnEnabled()) {
                        log.warn("No message", te);
                    }
                }
            };

            try {
                prepareNextNode();
            } catch (Exception e) {
                throw new XindiceRuntimeException(e);
            }
        }

        private XPath createXPath(PrefixResolver pfx) {
            try {
                if (XCOMPILER3) {
                    return (XPath) XPATH.newInstance(
                            new Object[] {query, null, pfx, new Integer(XPath.SELECT), errors, functionTable});
                } else {
                    return (XPath) XPATH.newInstance(
                            new Object[] {query, null, pfx, new Integer(XPath.SELECT), errors});
                }
            } catch (Exception e) {
                throw new RuntimeException("Could not instantiate Compiler: " + e);
            }
        }

        private void prepareNextNode() throws XMLDBException, TransformerException, DBException {
            node = null;

      while (keyPos < keySet.length) {
                final Key key = keySet[keyPos++];

                Entry entry = context.getEntry(key);
                if (entry.getEntryType() != Entry.DOCUMENT) {
                    continue;
                }

                DBDocument d = (DBDocument) entry.getValue();

                final Node n = d.getDocumentElement();
                if (n == null) {
                    if (log.isInfoEnabled()) {
                        log.info("Document " + context.getCanonicalDocumentName(key+ " is empty, skipping.");
                    }
                    continue;
                }

                XPathResolverContext xpc = new XPathResolverContext(parameters);
                PrefixResolver pfx;
                if (pr == null) {
                    pfx = new PrefixResolverDefault(d.getDocumentElement());
                    xp = createXPath(pfx);
                } else {
                    pfx = pr;
                    if (xp == null) {
                        xp = createXPath(pfx);
                    }
                }

                final XObject xobject = xp.execute(xpc, n, pfx);
        switch (xobject.getType()) {
                    // case XObject.CLASS_RTREEEFRAG :
                    default :
                        throw new XMLDBException(ErrorCodes.NOT_IMPLEMENTED,
                                                 "Unsupported result type: " + xobject.getTypeString());

                    case XObject.CLASS_NODESET:
                        ni = xobject.nodeset();
                        node = ni.nextNode();
                        break;

                    case XObject.CLASS_BOOLEAN:
                        ni = EMPTY_NODE_ITERATOR;

                        node = new DocumentImpl().createTextNode(String.valueOf(xobject.bool()));
                        if (n instanceof DBNode) {
                            ((TextImpl) node).setSource(((DBNode) n).getSource());
                        }
                        break;

                    case XObject.CLASS_STRING:
                        ni = EMPTY_NODE_ITERATOR;

                        node = new DocumentImpl().createTextNode(xobject.str());
                        if (n instanceof DBNode) {
                            ((TextImpl) node).setSource(((DBNode) n).getSource());
                        }
                        break;

                    case XObject.CLASS_NUMBER:
                        ni = EMPTY_NODE_ITERATOR;

                        node = new DocumentImpl().createTextNode(Double.toString(xobject.num()));
                        if (n instanceof DBNode) {
                            ((TextImpl) node).setSource(((DBNode) n).getSource());
                        }
                        break;
                }

                if (node != null) {
                    break;
                }
            }
        }

        public boolean hasMoreNodes() {
            return node != null;
        }

        public Object getNextNode() {
            Object n = node;

            node = ni.nextNode();
            if (node == null) {
                try {
                    prepareNextNode();
                } catch (Exception e) {
                    throw new XindiceRuntimeException(e);
                }
            }

            return n;
        }
    }

  /* This only implements what we need internally */
    private static class EmptyNodeIterator implements NodeIterator {

        /* (non-Javadoc)
         * @see org.w3c.dom.traversal.NodeIterator#getWhatToShow()
         */
        public int getWhatToShow() {
            throw new UnsupportedOperationException();
        }

        /* (non-Javadoc)
         * @see org.w3c.dom.traversal.NodeIterator#detach()
         */
        public void detach() {
            throw new UnsupportedOperationException();
        }

        /* (non-Javadoc)
         * @see org.w3c.dom.traversal.NodeIterator#getExpandEntityReferences()
         */
        public boolean getExpandEntityReferences() {
            throw new UnsupportedOperationException();
        }

        /* (non-Javadoc)
         * @see org.w3c.dom.traversal.NodeIterator#getRoot()
         */
        public Node getRoot() {
            throw new UnsupportedOperationException();
        }

        /* (non-Javadoc)
         * @see org.w3c.dom.traversal.NodeIterator#nextNode()
         */
        public Node nextNode() throws DOMException {
            return null;
        }

        /* (non-Javadoc)
         * @see org.w3c.dom.traversal.NodeIterator#previousNode()
         */
        public Node previousNode() throws DOMException {
            throw new UnsupportedOperationException();
        }

        /* (non-Javadoc)
         * @see org.w3c.dom.traversal.NodeIterator#getFilter()
         */
        public NodeFilter getFilter() {
            throw new UnsupportedOperationException();
        }
    }

    private final static NodeIterator EMPTY_NODE_ITERATOR = new EmptyNodeIterator();

    /**
     * XPathContext with optional parameters
     */
    static class XPathResolverContext extends XPathContext {
        private Map parameters;

        public XPathResolverContext(Map parameters) {
            this.parameters = parameters;
        }

        public Object getParameter(String name) {
            return parameters.get(name);
        }

        public void setParameter(String name, Object object) {
            parameters.put(name, object);
        }
    }
}
TOP

Related Classes of org.apache.xindice.core.query.XPathQueryResolver$XPathQuery

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.