org.apache.solr.search.QParser.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.solr.search.QParser.java

Source

/*
 * 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.
 */
package org.apache.solr.search;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.lucene.search.Query;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.StrUtils;
import org.apache.solr.request.SolrQueryRequest;

/**
 * <b>Note: This API is experimental and may change in non backward-compatible ways in the future</b>
 * 
 *
 */
public abstract class QParser {
    /** @lucene.experimental  */
    public static final int FLAG_FILTER = 0x01;

    protected String qstr;
    protected SolrParams params;
    protected SolrParams localParams;
    protected SolrQueryRequest req;
    protected int recurseCount;

    /** @lucene.experimental  */
    protected int flags;

    protected Query query;

    protected String stringIncludingLocalParams; // the original query string including any local params
    protected boolean valFollowedParams; // true if the value "qstr" followed the localParams
    protected int localParamsEnd; // the position one past where the localParams ended 

    /**
     * Constructor for the QParser
     * @param qstr The part of the query string specific to this parser
     * @param localParams The set of parameters that are specific to this QParser.  See http://wiki.apache.org/solr/LocalParams
     * @param params The rest of the {@link org.apache.solr.common.params.SolrParams}
     * @param req The original {@link org.apache.solr.request.SolrQueryRequest}.
     */
    public QParser(String qstr, SolrParams localParams, SolrParams params, SolrQueryRequest req) {
        this.qstr = qstr;
        this.localParams = localParams;

        // insert tags into tagmap.
        // WARNING: the internal representation of tagged objects in the request context is
        // experimental and subject to change!
        if (localParams != null) {
            String tagStr = localParams.get(CommonParams.TAG);
            if (tagStr != null) {
                Map<Object, Object> context = req.getContext();
                @SuppressWarnings("unchecked")
                Map<Object, Collection<Object>> tagMap = (Map<Object, Collection<Object>>) req.getContext()
                        .get("tags");
                if (tagMap == null) {
                    tagMap = new HashMap<>();
                    context.put("tags", tagMap);
                }
                if (tagStr.indexOf(',') >= 0) {
                    List<String> tags = StrUtils.splitSmart(tagStr, ',');
                    for (String tag : tags) {
                        addTag(tagMap, tag, this);
                    }
                } else {
                    addTag(tagMap, tagStr, this);
                }
            }
        }

        this.params = params;
        this.req = req;
    }

    /** @lucene.experimental  */
    public void setFlags(int flags) {
        this.flags = flags;
    }

    /** @lucene.experimental  */
    public int getFlags() {
        return flags;
    }

    /** @lucene.experimental Query is in the context of a filter, where scores don't matter */
    public boolean isFilter() {
        return (flags & FLAG_FILTER) != 0;
    }

    /** @lucene.experimental  */
    public void setIsFilter(boolean isFilter) {
        if (isFilter)
            flags |= FLAG_FILTER;
        else
            flags &= ~FLAG_FILTER;
    }

    private static void addTag(Map<Object, Collection<Object>> tagMap, Object key, Object val) {
        Collection<Object> lst = tagMap.get(key);
        if (lst == null) {
            lst = new ArrayList<>(2);
            tagMap.put(key, lst);
        }
        lst.add(val);
    }

    /** Create and return the <code>Query</code> object represented by <code>qstr</code>.  Null MAY be returned to signify
     * there was no input (e.g. no query string) to parse.
     * @see #getQuery()
     **/
    public abstract Query parse() throws SyntaxError;

    public SolrParams getLocalParams() {
        return localParams;
    }

    public void setLocalParams(SolrParams localParams) {
        this.localParams = localParams;
    }

    public SolrParams getParams() {
        return params;
    }

    public void setParams(SolrParams params) {
        this.params = params;
    }

    public SolrQueryRequest getReq() {
        return req;
    }

    public void setReq(SolrQueryRequest req) {
        this.req = req;
    }

    public String getString() {
        return qstr;
    }

    public void setString(String s) {
        this.qstr = s;
    }

    /**
     * Returns the resulting query from this QParser, calling parse() only the
     * first time and caching the Query result. <em>A null return is possible!</em>
     */
    //TODO never return null; standardize the semantics
    public Query getQuery() throws SyntaxError {
        if (query == null) {
            query = parse();

            if (localParams != null) {
                String cacheStr = localParams.get(CommonParams.CACHE);
                if (cacheStr != null) {
                    if (CommonParams.FALSE.equals(cacheStr)) {
                        extendedQuery().setCache(false);
                    } else if (CommonParams.TRUE.equals(cacheStr)) {
                        extendedQuery().setCache(true);
                    } else if ("sep".equals(cacheStr)) {
                        extendedQuery().setCacheSep(true);
                    }
                }

                int cost = localParams.getInt(CommonParams.COST, Integer.MIN_VALUE);
                if (cost != Integer.MIN_VALUE) {
                    extendedQuery().setCost(cost);
                }
            }
        }
        return query;
    }

    // returns an extended query (and sets "query" to a new wrapped query if necessary)
    private ExtendedQuery extendedQuery() {
        if (query instanceof ExtendedQuery) {
            return (ExtendedQuery) query;
        } else {
            WrappedQuery wq = new WrappedQuery(query);
            query = wq;
            return wq;
        }
    }

    private void checkRecurse() throws SyntaxError {
        if (recurseCount++ >= 100) {
            throw new SyntaxError("Infinite Recursion detected parsing query '" + qstr + "'");
        }
    }

    // TODO: replace with a SolrParams that defaults to checking localParams first?
    // ideas..
    //   create params that satisfy field-specific overrides
    //   overrideable syntax $x=foo  (set global for limited scope) (invariants & security?)
    //                       $x+=foo (append to global for limited scope)

    /** check both local and global params */
    public String getParam(String name) {
        String val;
        if (localParams != null) {
            val = localParams.get(name);
            if (val != null)
                return val;
        }
        return params.get(name);
    }

    /** Create a new QParser for parsing an embedded sub-query */
    public QParser subQuery(String q, String defaultType) throws SyntaxError {
        checkRecurse();
        if (defaultType == null && localParams != null) {
            // if not passed, try and get the defaultType from local params
            defaultType = localParams.get(QueryParsing.DEFTYPE);
        }
        QParser nestedParser = getParser(q, defaultType, getReq());
        nestedParser.flags = this.flags; // TODO: this would be better passed in to the constructor... change to a ParserContext object?
        nestedParser.recurseCount = recurseCount;
        recurseCount--;
        return nestedParser;
    }

    /**
     * @param useGlobalParams look up sort, start, rows in global params if not in local params
     * @return the sort specification
     */
    public SortSpec getSortSpec(boolean useGlobalParams) throws SyntaxError {
        getQuery(); // ensure query is parsed first

        String sortStr = null;
        Integer start = null;
        Integer rows = null;

        if (localParams != null) {
            sortStr = localParams.get(CommonParams.SORT);
            start = localParams.getInt(CommonParams.START);
            rows = localParams.getInt(CommonParams.ROWS);

            // if any of these parameters are present, don't go back to the global params
            if (sortStr != null || start != null || rows != null) {
                useGlobalParams = false;
            }
        }

        if (useGlobalParams) {
            if (sortStr == null) {
                sortStr = params.get(CommonParams.SORT);
            }
            if (start == null) {
                start = params.getInt(CommonParams.START);
            }
            if (rows == null) {
                rows = params.getInt(CommonParams.ROWS);
            }
        }

        start = start != null ? start : CommonParams.START_DEFAULT;
        rows = rows != null ? rows : CommonParams.ROWS_DEFAULT;

        SortSpec sort = SortSpecParsing.parseSortSpec(sortStr, req);

        sort.setOffset(start);
        sort.setCount(rows);
        return sort;
    }

    public String[] getDefaultHighlightFields() {
        return new String[] {};
    }

    public Query getHighlightQuery() throws SyntaxError {
        Query query = getQuery();
        return query instanceof WrappedQuery ? ((WrappedQuery) query).getWrappedQuery() : query;
    }

    public void addDebugInfo(NamedList<Object> debugInfo) {
        debugInfo.add("QParser", this.getClass().getSimpleName());
    }

    /**
     * Create a {@link QParser} to parse <code>qstr</code>,
     * using the "lucene" (QParserPlugin.DEFAULT_QTYPE) query parser.
     * The query parser may be overridden by local-params in the query
     * string itself.  For example if
     * qstr=<code>{!prefix f=myfield}foo</code>
     * then the prefix query parser will be used.
     */
    public static QParser getParser(String qstr, SolrQueryRequest req) throws SyntaxError {
        return getParser(qstr, QParserPlugin.DEFAULT_QTYPE, req);
    }

    /**
     * Create a {@link QParser} to parse <code>qstr</code> using the <code>defaultParser</code>.
     * Note that local-params is only parsed when the defaultParser is "lucene" or "func".
     */
    public static QParser getParser(String qstr, String defaultParser, SolrQueryRequest req) throws SyntaxError {
        boolean allowLocalParams = defaultParser == null || defaultParser.equals(QParserPlugin.DEFAULT_QTYPE)
                || defaultParser.equals(FunctionQParserPlugin.NAME);
        return getParser(qstr, defaultParser, allowLocalParams, req);
    }

    /**
     * Expert: Create a {@link QParser} to parse {@code qstr} using the {@code parserName} parser, while allowing a
     * toggle for whether local-params may be parsed.
     * The query parser may be overridden by local parameters in the query string itself
     * (assuming {@code allowLocalParams}.
     * For example if parserName=<code>dismax</code> and qstr=<code>foo</code>,
     * then the dismax query parser will be used to parse and construct the query object.
     * However if qstr=<code>{!prefix f=myfield}foo</code> then the prefix query parser will be used.
     *
     * @param allowLocalParams Whether this query parser should parse local-params syntax.
     *                         Note that the "lucene" query parser natively parses local-params regardless.
     * @lucene.internal
     */
    public static QParser getParser(String qstr, String parserName, boolean allowLocalParams, SolrQueryRequest req)
            throws SyntaxError {
        // SolrParams localParams = QueryParsing.getLocalParams(qstr, req.getParams());
        if (parserName == null) {
            parserName = QParserPlugin.DEFAULT_QTYPE;//"lucene"
        }
        String stringIncludingLocalParams = qstr;
        ModifiableSolrParams localParams = null;
        SolrParams globalParams = req.getParams();
        boolean valFollowedParams = true;
        int localParamsEnd = -1;

        if (allowLocalParams && qstr != null && qstr.startsWith(QueryParsing.LOCALPARAM_START)) {
            localParams = new ModifiableSolrParams();
            localParamsEnd = QueryParsing.parseLocalParams(qstr, 0, localParams, globalParams);

            String val = localParams.get(QueryParsing.V);
            if (val != null) {
                // val was directly specified in localParams via v=<something> or v=$arg
                valFollowedParams = false;
                //TODO if remainder of query string after '}' is non-empty, then what? Throw error? Fall back to lucene QParser?
            } else {
                // use the remainder of the string as the value
                valFollowedParams = true;
                val = qstr.substring(localParamsEnd);
                localParams.set(QueryParsing.V, val);
            }

            parserName = localParams.get(QueryParsing.TYPE, parserName);
            qstr = localParams.get(QueryParsing.V);
        }

        QParserPlugin qplug = req.getCore().getQueryPlugin(parserName);
        if (qplug == null) {
            // there should a way to include parameter for which parsing failed
            throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
                    "invalid query parser '" + parserName + (stringIncludingLocalParams == null ? "'"
                            : "' for query '" + stringIncludingLocalParams + "'"));
        }
        QParser parser = qplug.createParser(qstr, localParams, req.getParams(), req);

        parser.stringIncludingLocalParams = stringIncludingLocalParams;
        parser.valFollowedParams = valFollowedParams;
        parser.localParamsEnd = localParamsEnd;
        return parser;
    }

}