/* * 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 org.apache.lucene.search.Query; import org.apache.solr.common.params.SolrParams; import org.apache.solr.common.util.NamedList; import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.handler.component.SearchHandler; // jdoc import org.apache.commons.lang.StringUtils; /** * <p>A QParserPlugin that acts like a "switch/case" statement.</p> * * <p> * QParser's produced by this plugin will take their primary input string, * trimmed and prefixed with "<code>case.</code>", to use as a key to lookup a * "switch case" in the parser's local params. If a matching local param is * found the resulting param value will then be parsed as a subquery, and * returned as the parse result. * </p> * <p> * The "<code>case</code>" local param can be optionally be specified as a * switch case to match missing (or blank) input strings. * The "<code>default</code>" local param can optionally be specified * as a default case to use if the input string does not match any other * switch case local params. If <code>default</code> is not specified, * then any input which does not match a switch case local param will result * in a syntax error. * </p> * * <p> * In the examples below, the result of each query would be <code>XXX</code>.... * </p> * <pre> * q={!switch case.foo=XXX case.bar=zzz case.yak=qqq}foo * q={!switch case.foo=qqq case.bar=XXX case.yak=zzz} bar // extra whitespace * q={!switch case.foo=qqq case.bar=zzz default=XXX}asdf // fallback on default * q={!switch case=XXX case.bar=zzz case.yak=qqq} // blank input * </pre> * * <p> * A practical usage of this QParsePlugin, is in specifying "appends" * <code>fq</code> params in the configuration of a {@link SearchHandler}, to * provide a fixed set of filter options for clients using custom parameter * names. * Using the example configuration below, clients can optionally specify the * custom parameters <code>in_stock</code> and <code>shipping</code> to * override the default filtering behavior, but are limited to the specific * set of legal values (<code>shipping=any|free</code>, * <code>in_stock=yes|no|all</code>). * </p> * * <pre class="prettyprint"> * <requestHandler name="/select" class="solr.SearchHandler"> * <lst name="defaults"> * <str name="in_stock">yes</str> * <str name="shipping">any</str> * </lst> * <lst name="appends"> * <str name="fq">{!switch case.all='*:*' * case.yes='inStock:true' * case.no='inStock:false' * v=$in_stock}</str> * <str name="fq">{!switch case.any='*:*' * case.free='shipping_cost:0.0' * v=$shipping}</str> * </lst> * </requestHandler></pre> * * <p> * A slightly more interesting variant of the <code>shipping</code> example above, would be * to combine the switch parser with the frange parser, to allow the client to specify an * arbitrary "max shipping" amount that will be used to build a filter if and only if a * value is specified. Example: * </p> * <pre class="prettyprint"> * <requestHandler name="/select" class="solr.SearchHandler"> * <lst name="invariants"> * <str name="shipping_fq">{!frange u=$shipping}shipping_cost</str> * </lst> * <lst name="defaults"> * <str name="shipping">any</str> * </lst> * <lst name="appends"> * <str name="fq">{!switch case='*:*' * case.any='*:*' * default=$shipping_fq * v=$shipping}</str> * </lst> * </requestHandler></pre> * * <p> * With the above configuration a client that specifies <code>shipping=any</code>, or * does not specify a <code>shipping</code> param at all, will not have the results * filtered. But if a client specifies a numeric value (ie: <code>shipping=10</code>, * <code>shipping=5</code>, etc..) then the results will be limited to documents whose * <code>shipping_cost</code> field has a value less then that number. * </p> * * <p> * A similar use case would be to combine the switch parser with the bbox parser to * support an optional geographic filter that is applied if and only if the client * specifies a <code>location</code> param containing a lat,lon pair to be used as * the center of the bounding box: * </p> * <pre class="prettyprint"> * <requestHandler name="/select" class="solr.SearchHandler"> * <lst name="invariants"> * <str name="bbox_fq">{!bbox pt=$location sfield=geo d=$dist}</str> * </lst> * <lst name="defaults"> * <str name="dist">100</str> * </lst> * <lst name="appends"> * <str name="fq">{!switch case='*:*' * default=$bbox_fq * v=$location}</str> * </lst> * </requestHandler></pre> */ public class SwitchQParserPlugin extends QParserPlugin { public static final String NAME = "switch"; /** * Used as both a local params key to find the "default" if no * blank input is provided to the parser, as well as a prefix (followed by * '.' for looking up the switch input. */ public static String SWITCH_CASE = "case"; /** * A local param whose value, if specified, is used if no switch case * matches the parser input. If this param is not specified, and no * switch case matches the parser input, an error is returned. */ public static String SWITCH_DEFAULT = "default"; @Override public QParser createParser(String qstr, SolrParams localParams, SolrParams params, SolrQueryRequest req) { return new QParser(qstr, localParams, params, req) { QParser subParser; @Override public Query parse() throws SyntaxError { String val = localParams.get(QueryParsing.V); // we don't want to wrapDefaults arround params, because then // clients could add their own switch options String subQ = localParams.get(SWITCH_DEFAULT); subQ = StringUtils.isBlank(val) ? localParams.get(SWITCH_CASE, subQ) : localParams.get(SWITCH_CASE + "." + val.trim(), subQ); if (null == subQ) { throw new SyntaxError("No "+SWITCH_DEFAULT+", and no switch case matching specified query string: \"" + val + "\""); } subParser = subQuery(subQ, null); return subParser.getQuery(); } @Override public String[] getDefaultHighlightFields() { return subParser.getDefaultHighlightFields(); } @Override public Query getHighlightQuery() throws SyntaxError { return subParser.getHighlightQuery(); } @Override public void addDebugInfo(NamedList<Object> debugInfo) { subParser.addDebugInfo(debugInfo); } }; } }