package org.apache.lucene.queryparser.flexible.aqp;
/**
* 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.
*/
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.ListIterator;
import java.util.Locale;
import java.util.Map;
import java.util.TooManyListenersException;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.DateTools;
import org.apache.lucene.document.DateTools.Resolution;
import org.apache.lucene.queryparser.flexible.core.QueryNodeException;
import org.apache.lucene.queryparser.flexible.core.QueryParserHelper;
import org.apache.lucene.queryparser.flexible.core.builders.QueryBuilder;
import org.apache.lucene.queryparser.flexible.core.builders.QueryTreeBuilder;
import org.apache.lucene.queryparser.flexible.core.config.QueryConfigHandler;
import org.apache.lucene.queryparser.flexible.core.processors.QueryNodeProcessor;
import org.apache.lucene.queryparser.flexible.core.processors.QueryNodeProcessorPipeline;
import org.apache.lucene.queryparser.flexible.standard.StandardQueryParser;
import org.apache.lucene.queryparser.flexible.standard.builders.StandardQueryTreeBuilder;
import org.apache.lucene.queryparser.flexible.standard.config.FuzzyConfig;
import org.apache.lucene.queryparser.flexible.standard.config.LegacyNumericConfig;
import org.apache.lucene.queryparser.flexible.standard.config.StandardQueryConfigHandler;
import org.apache.lucene.queryparser.flexible.standard.config.StandardQueryConfigHandler.ConfigurationKeys;
import org.apache.lucene.queryparser.flexible.standard.config.StandardQueryConfigHandler.Operator;
import org.apache.lucene.queryparser.flexible.standard.parser.StandardSyntaxParser;
import org.apache.lucene.queryparser.flexible.standard.processors.StandardQueryNodeProcessorPipeline;
import org.apache.lucene.queryparser.flexible.aqp.AqpSyntaxParser;
import org.apache.lucene.queryparser.flexible.aqp.builders.AqpQueryTreeBuilder;
import org.apache.lucene.queryparser.flexible.aqp.config.AqpFeedback;
import org.apache.lucene.queryparser.flexible.aqp.parser.AqpStandardQueryConfigHandler;
import org.apache.lucene.queryparser.flexible.aqp.util.AqpDebuggingQueryNodeProcessorPipeline;
import org.apache.lucene.search.FuzzyQuery;
import org.apache.lucene.search.MultiTermQuery;
import org.apache.lucene.search.Query;
/**
* <p>
* This class is a helper that enables users to easily use the Lucene query
* parser written in EBNF grammar using ANTLR
* <p>
* To construct a Query object from a query string, use the
* {@link #parse(String, String)} method:
* <pre>
* ANTLRQueryParser queryParserHelper = new ANTLRQueryParser();
* Query query = queryParserHelper.parse("a AND b", "defaultField");
* </pre>
* <p>
* To change any configuration before parsing the query string do, for example:
* <code>
* // the query config handler returned by {@link StandardQueryParser} is a
* {@link StandardQueryConfigHandler}
* queryParserHelper.getQueryConfigHandler().setAnalyzer(new
* WhitespaceAnalyzer());
* </code>
* <p>
* The syntax for query strings is as follows (copied from the old QueryParser
* javadoc):
* <p>
* A Query is a series of clauses. A clause may be prefixed by:
* <p>
* a plus (<code>+</code>) or a minus (<code>-</code>) sign, indicating that
* the clause is required or prohibited respectively; or
* <p>
* a term followed by a colon, indicating the field to be searched. This
* enables one to construct queries which search multiple fields.
*
*
* A clause may be either:
* <ul>
* <li>a term, indicating all the documents that contain this term; or
* <li>a nested query, enclosed in parentheses. Note that this may be used with
* a <code>+</code>/<code>-</code> prefix to require any of a set of terms.
* </ul>
*
* Thus, in BNF, the query grammar is:
*
* <pre>
* Query ::= ( Clause )*
* Clause ::= ["+", "-"] [<TERM> ":"] ( <TERM> | "(" Query ")" )
* </pre>
*
* <p>
* Examples of appropriately formatted queries can be found in the <a
* href="../../../../../../queryparsersyntax.html">query syntax
* documentation</a>.
* <p>
* The text parser used by this helper is a {@link StandardSyntaxParser}.
* <p>
* The query node processor used by this helper is a
* {@link StandardQueryNodeProcessorPipeline}.
* <p>
* The builder used by this helper is a {@link StandardQueryTreeBuilder}.
* <p>
*
* @see StandardQueryParser
* @see StandardQueryConfigHandler
* @see StandardSyntaxParser
* @see StandardQueryNodeProcessorPipeline
* @see StandardQueryTreeBuilder
*
*
* TODO: add the constructor to the SQP and remove the duplicated code
*
* public StandardQueryParser(QueryConfigHandler config, SyntaxParser
* parser, QueryNodeProcessor processor, QueryBuilder builder) {
* super(config, parser,processor, builder); }
*/
public class AqpQueryParser extends QueryParserHelper {
private boolean debugMode = false;
private String syntaxName = null;
public AqpQueryParser(QueryConfigHandler config, AqpSyntaxParser parser,
QueryNodeProcessorPipeline processor, QueryTreeBuilder builder) {
super(config, parser, processor, builder);
syntaxName = parser.getClass().getName();
}
@Override
public String toString() {
return "<AqpQueryParser config=\"" + this.getQueryConfigHandler()
+ "\" grammar=\"" + syntaxName + "\"/>";
}
/**
* De/activates the debugging output of the query parser
*
* It works by wrapping the processor pipeline into a debugging
* class and by calling setDebug on the underlying builder.
*
* @see AqpDebuggingQueryNodeProcessorPipeline
* @see AqpQueryTreeBuilder
*/
@SuppressWarnings("unchecked")
public void setDebug(boolean debug) throws InstantiationException,
IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException {
if (debugMode != debug) {
QueryNodeProcessorPipeline processor = (QueryNodeProcessorPipeline) this.getQueryNodeProcessor();
QueryBuilder builder = this.getQueryBuilder();
QueryNodeProcessorPipeline newPipeline;
QueryConfigHandler configHandler = this.getQueryConfigHandler();
if (debug) {
newPipeline = new AqpDebuggingQueryNodeProcessorPipeline(
this.getQueryConfigHandler(), processor.getClass());
}
else {
// can't use the simple form because parser pipelines may be using config to adjust themselves
// newPipeline = clazz.newInstance();
Class<? extends QueryNodeProcessorPipeline> clazz = ((AqpDebuggingQueryNodeProcessorPipeline) processor)
.getOriginalProcessorClass();
Constructor<? extends QueryNodeProcessorPipeline> constructor = clazz.getConstructor(QueryConfigHandler.class);
newPipeline = constructor.newInstance(new Object[]{configHandler});
}
List<QueryNodeProcessor> listOfProcessors = (List<QueryNodeProcessor>) processor;
ListIterator<QueryNodeProcessor> it = listOfProcessors.listIterator();
while (it.hasNext()) {
newPipeline.add(it.next());
}
this.setQueryNodeProcessor(newPipeline);
QueryBuilder newBuilder = builder.getClass().newInstance();
if (newBuilder instanceof AqpQueryTreeBuilder) {
((AqpQueryTreeBuilder) newBuilder).setDebug(debug);
this.setQueryBuilder(newBuilder);
}
}
debugMode = debug;
}
public boolean getDebug() {
return debugMode;
}
/**
* Overrides {@link QueryParserHelper#parse(String, String)} so it casts the
* return object to {@link Query}. For more reference about this method, check
* {@link QueryParserHelper#parse(String, String)}.
*
* @param query
* the query string
* @param defaultField
* the default field used by the text parser
*
* @return the object built from the query
*
* @throws QueryNodeException
* if something wrong happens along the three phases
*/
@Override
public Query parse(String query, String defaultField)
throws QueryNodeException {
if (defaultField != null) {
setDefaultField(defaultField);
}
try {
return (Query) super.parse(query, defaultField);
} catch (NestedParseException e) {
throw new QueryNodeException(e);
}
}
public String getDefaultField() {
return getQueryConfigHandler().get(
AqpStandardQueryConfigHandler.ConfigurationKeys.DEFAULT_FIELD);
}
public void setDefaultField(String field) {
getQueryConfigHandler().set(
AqpStandardQueryConfigHandler.ConfigurationKeys.DEFAULT_FIELD, field);
}
public Integer getDefaultProximity() {
return getQueryConfigHandler().get(
AqpStandardQueryConfigHandler.ConfigurationKeys.DEFAULT_PROXIMITY);
}
public void setDefaultProximity(Integer value) {
getQueryConfigHandler().set(
AqpStandardQueryConfigHandler.ConfigurationKeys.DEFAULT_PROXIMITY,
value);
}
public Float getImplicitBoost() {
return getQueryConfigHandler().get(
AqpStandardQueryConfigHandler.ConfigurationKeys.IMPLICIT_BOOST);
}
public void setImplicitBoost(Float value) {
getQueryConfigHandler().set(
AqpStandardQueryConfigHandler.ConfigurationKeys.IMPLICIT_BOOST, value);
}
public AqpFeedback getFeedback() {
return getQueryConfigHandler().get(
AqpStandardQueryConfigHandler.ConfigurationKeys.FEEDBACK);
}
public void setFeedback(AqpFeedback feedbackInstance) {
getQueryConfigHandler().set(
AqpStandardQueryConfigHandler.ConfigurationKeys.FEEDBACK,
feedbackInstance);
}
public Float getImplicitFuzzy() {
return getQueryConfigHandler().get(
AqpStandardQueryConfigHandler.ConfigurationKeys.IMPLICIT_FUZZY);
}
public void setImplicitFuzzy(Float value) {
getQueryConfigHandler().set(
AqpStandardQueryConfigHandler.ConfigurationKeys.IMPLICIT_FUZZY, value);
}
public Boolean getAllowSlowFuzzy() {
return getQueryConfigHandler().get(
AqpStandardQueryConfigHandler.ConfigurationKeys.ALLOW_SLOW_FUZZY);
}
public void setAllowSlowFuzzy(Boolean value) {
getQueryConfigHandler()
.set(AqpStandardQueryConfigHandler.ConfigurationKeys.ALLOW_SLOW_FUZZY,
value);
}
/********************************************************************
* Everything below is simpy copy of the StandardQueryParser *
*******************************************************************/
/**
* Gets implicit operator setting, which will be either {@link Operator#AND}
* or {@link Operator#OR}.
*/
public StandardQueryConfigHandler.Operator getDefaultOperator() {
return getQueryConfigHandler().get(ConfigurationKeys.DEFAULT_OPERATOR);
}
/**
* Sets the boolean operator of the QueryParser. In default mode (
* {@link Operator#OR}) terms without any modifiers are considered optional:
* for example <code>capital of Hungary</code> is equal to
* <code>capital OR of OR Hungary</code>.
*
* <p>
*
* In {@link Operator#AND} mode terms are considered to be in conjunction: the
* above mentioned query is parsed as <code>capital AND of AND Hungary</code>
*/
public void setDefaultOperator(StandardQueryConfigHandler.Operator operator) {
getQueryConfigHandler().set(ConfigurationKeys.DEFAULT_OPERATOR, operator);
}
/**
* Set to <code>true</code> to allow leading wildcard characters.
* <p>
* When set, <code>*</code> or <code>?</code> are allowed as the first
* character of a PrefixQuery and WildcardQuery. Note that this can produce
* very slow queries on big indexes.
* <p>
* Default: false.
*/
public void setLowercaseExpandedTerms(boolean lowercaseExpandedTerms) {
getQueryConfigHandler().set(ConfigurationKeys.LOWERCASE_EXPANDED_TERMS,
lowercaseExpandedTerms);
}
/**
* @see #setLowercaseExpandedTerms(boolean)
*/
public boolean getLowercaseExpandedTerms() {
Boolean lowercaseExpandedTerms = getQueryConfigHandler().get(
ConfigurationKeys.LOWERCASE_EXPANDED_TERMS);
if (lowercaseExpandedTerms == null) {
return true;
} else {
return lowercaseExpandedTerms;
}
}
/**
* Set to <code>true</code> to allow leading wildcard characters.
* <p>
* When set, <code>*</code> or <code>?</code> are allowed as the first
* character of a PrefixQuery and WildcardQuery. Note that this can produce
* very slow queries on big indexes.
* <p>
* Default: false.
*/
public void setAllowLeadingWildcard(boolean allowLeadingWildcard) {
getQueryConfigHandler().set(ConfigurationKeys.ALLOW_LEADING_WILDCARD,
allowLeadingWildcard);
}
/**
* Set to <code>true</code> to enable position increments in result query.
* <p>
* When set, result phrase and multi-phrase queries will be aware of position
* increments. Useful when e.g. a StopFilter increases the position increment
* of the token that follows an omitted token.
* <p>
* Default: false.
*/
public void setEnablePositionIncrements(boolean enabled) {
getQueryConfigHandler().set(ConfigurationKeys.ENABLE_POSITION_INCREMENTS,
enabled);
}
/**
* @see #setEnablePositionIncrements(boolean)
*/
public boolean getEnablePositionIncrements() {
Boolean enablePositionsIncrements = getQueryConfigHandler().get(
ConfigurationKeys.ENABLE_POSITION_INCREMENTS);
if (enablePositionsIncrements == null) {
return false;
} else {
return enablePositionsIncrements;
}
}
/**
* By default, it uses
* {@link MultiTermQuery#CONSTANT_SCORE_BOOLEAN_REWRITE} when creating a
* prefix, wildcard and range queries. This implementation is generally
* preferable because it a) Runs faster b) Does not have the scarcity of terms
* unduly influence score c) avoids any {@link TooManyListenersException}
* exception. However, if your application really needs to use the
* old-fashioned boolean queries expansion rewriting and the above points are
* not relevant then use this change the rewrite method.
*/
public void setMultiTermRewriteMethod(MultiTermQuery.RewriteMethod method) {
getQueryConfigHandler().set(ConfigurationKeys.MULTI_TERM_REWRITE_METHOD,
method);
}
/**
* @see #setMultiTermRewriteMethod(org.apache.lucene.search.MultiTermQuery.RewriteMethod)
*/
public MultiTermQuery.RewriteMethod getMultiTermRewriteMethod() {
return getQueryConfigHandler().get(
ConfigurationKeys.MULTI_TERM_REWRITE_METHOD);
}
/**
* Set the fields a query should be expanded to when the field is
* <code>null</code>
*
* @param fields
* the fields used to expand the query
*/
public void setMultiFields(CharSequence[] fields) {
if (fields == null) {
fields = new CharSequence[0];
}
getQueryConfigHandler().set(ConfigurationKeys.MULTI_FIELDS, fields);
}
/**
* Returns the fields used to expand the query when the field for a certain
* query is <code>null</code>
*
* @param fields
* the fields used to expand the query
*/
public void getMultiFields(CharSequence[] fields) {
getQueryConfigHandler().get(ConfigurationKeys.MULTI_FIELDS);
}
/**
* Set the prefix length for fuzzy queries. Default is 0.
*
* @param fuzzyPrefixLength
* The fuzzyPrefixLength to set.
*/
public void setFuzzyPrefixLength(int fuzzyPrefixLength) {
QueryConfigHandler config = getQueryConfigHandler();
FuzzyConfig fuzzyConfig = config.get(ConfigurationKeys.FUZZY_CONFIG);
if (fuzzyConfig == null) {
fuzzyConfig = new FuzzyConfig();
config.set(ConfigurationKeys.FUZZY_CONFIG, fuzzyConfig);
}
fuzzyConfig.setPrefixLength(fuzzyPrefixLength);
}
public void setNumericConfigMap(Map<String, LegacyNumericConfig> numericConfigMap) {
getQueryConfigHandler().set(ConfigurationKeys.LEGACY_NUMERIC_CONFIG_MAP,
numericConfigMap);
}
public Map<String, LegacyNumericConfig> getNumericConfigMap() {
return getQueryConfigHandler().get(ConfigurationKeys.LEGACY_NUMERIC_CONFIG_MAP);
}
/**
* Set locale used by date range parsing.
*/
public void setLocale(Locale locale) {
getQueryConfigHandler().set(ConfigurationKeys.LOCALE, locale);
}
/**
* Returns current locale, allowing access by subclasses.
*/
public Locale getLocale() {
return getQueryConfigHandler().get(ConfigurationKeys.LOCALE);
}
/**
* Sets the default slop for phrases. If zero, then exact phrase matches are
* required. Default value is zero.
*
* @deprecated renamed to {@link #setPhraseSlop(int)}
*/
@Deprecated
public void setDefaultPhraseSlop(int defaultPhraseSlop) {
getQueryConfigHandler().set(ConfigurationKeys.PHRASE_SLOP,
defaultPhraseSlop);
}
/**
* Sets the default slop for phrases. If zero, then exact phrase matches are
* required. Default value is zero.
*/
public void setPhraseSlop(int defaultPhraseSlop) {
getQueryConfigHandler().set(ConfigurationKeys.PHRASE_SLOP,
defaultPhraseSlop);
}
public void setAnalyzer(Analyzer analyzer) {
getQueryConfigHandler().set(ConfigurationKeys.ANALYZER, analyzer);
}
public Analyzer getAnalyzer() {
return getQueryConfigHandler().get(ConfigurationKeys.ANALYZER);
}
/**
* @see #setAllowLeadingWildcard(boolean)
*/
public boolean getAllowLeadingWildcard() {
Boolean allowLeadingWildcard = getQueryConfigHandler().get(
ConfigurationKeys.ALLOW_LEADING_WILDCARD);
if (allowLeadingWildcard == null) {
return false;
} else {
return allowLeadingWildcard;
}
}
/**
* Get the minimal similarity for fuzzy queries.
*/
public float getFuzzyMinSim() {
FuzzyConfig fuzzyConfig = getQueryConfigHandler().get(
ConfigurationKeys.FUZZY_CONFIG);
if (fuzzyConfig == null) {
return FuzzyQuery.defaultMinSimilarity;
} else {
return fuzzyConfig.getMinSimilarity();
}
}
/**
* Get the prefix length for fuzzy queries.
*
* @return Returns the fuzzyPrefixLength.
*/
public int getFuzzyPrefixLength() {
FuzzyConfig fuzzyConfig = getQueryConfigHandler().get(
ConfigurationKeys.FUZZY_CONFIG);
if (fuzzyConfig == null) {
return FuzzyQuery.defaultPrefixLength;
} else {
return fuzzyConfig.getPrefixLength();
}
}
/**
* Gets the default slop for phrases.
*/
public int getPhraseSlop() {
Integer phraseSlop = getQueryConfigHandler().get(
ConfigurationKeys.PHRASE_SLOP);
if (phraseSlop == null) {
return 0;
} else {
return phraseSlop;
}
}
/**
* Set the minimum similarity for fuzzy queries. Default is defined on
* {@link FuzzyQuery#defaultMinSimilarity}.
*/
public void setFuzzyMinSim(float fuzzyMinSim) {
QueryConfigHandler config = getQueryConfigHandler();
FuzzyConfig fuzzyConfig = config.get(ConfigurationKeys.FUZZY_CONFIG);
if (fuzzyConfig == null) {
fuzzyConfig = new FuzzyConfig();
config.set(ConfigurationKeys.FUZZY_CONFIG, fuzzyConfig);
}
fuzzyConfig.setMinSimilarity(fuzzyMinSim);
}
/**
* Sets the boost used for each field.
*
* @param boosts
* a collection that maps a field to its boost
*/
public void setFieldsBoost(Map<String, Float> boosts) {
getQueryConfigHandler().set(ConfigurationKeys.FIELD_BOOST_MAP, boosts);
}
/**
* Returns the field to boost map used to set boost for each field.
*
* @return the field to boost map
*/
public Map<String, Float> getFieldsBoost() {
return getQueryConfigHandler().get(ConfigurationKeys.FIELD_BOOST_MAP);
}
/**
* Sets the default {@link Resolution} used for certain field when no
* {@link Resolution} is defined for this field.
*
* @param dateResolution
* the default {@link Resolution}
*/
public void setDateResolution(DateTools.Resolution dateResolution) {
getQueryConfigHandler().set(ConfigurationKeys.DATE_RESOLUTION,
dateResolution);
}
/**
* Returns the default {@link Resolution} used for certain field when no
* {@link Resolution} is defined for this field.
*
* @return the default {@link Resolution}
*/
public DateTools.Resolution getDateResolution() {
return getQueryConfigHandler().get(ConfigurationKeys.DATE_RESOLUTION);
}
/**
* Sets the {@link Resolution} used for each field
*
* @param dateRes
* a collection that maps a field to its {@link Resolution}
*
* @deprecated this method was renamed to {@link #setDateResolutionMap(Map)}
*/
@Deprecated
public void setDateResolution(Map<CharSequence, DateTools.Resolution> dateRes) {
setDateResolutionMap(dateRes);
}
/**
* Returns the field to {@link Resolution} map used to normalize each date
* field.
*
* @return the field to {@link Resolution} map
*/
public Map<CharSequence, DateTools.Resolution> getDateResolutionMap() {
return getQueryConfigHandler().get(
ConfigurationKeys.FIELD_DATE_RESOLUTION_MAP);
}
/**
* Sets the {@link Resolution} used for each field
*
* @param dateRes
* a collection that maps a field to its {@link Resolution}
*/
public void setDateResolutionMap(
Map<CharSequence, DateTools.Resolution> dateRes) {
getQueryConfigHandler().set(ConfigurationKeys.FIELD_DATE_RESOLUTION_MAP,
dateRes);
}
/**
* Returns the string value of the NAMED_PARAMETER if set or
* null if nothing is there
*
* @return the NAMED_PARAMETER value for the given key
*/
public String getNamedParameter(String key) {
Map<String, String> map = getQueryConfigHandler().get(
AqpStandardQueryConfigHandler.ConfigurationKeys.NAMED_PARAMETER);
if (map.containsKey(key)) {
return map.get(key);
}
return null;
}
/**
* Sets NAMED_PARAMETER
*
* @param key
* name of the parameter; the value will depend on what
* processors are using and what they accept
* @param value
* string value
*/
public void setNamedParameter(String key, String value) {
Map<String, String> map = getQueryConfigHandler().get(
AqpStandardQueryConfigHandler.ConfigurationKeys.NAMED_PARAMETER);
map.put(key, value);
}
}