001: /**
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */package org.apache.solr.util;
017:
018: import org.apache.lucene.analysis.Analyzer;
019: import org.apache.lucene.document.Document;
020: import org.apache.lucene.queryParser.ParseException;
021: import org.apache.lucene.queryParser.QueryParser;
022: import org.apache.lucene.search.*;
023: import org.apache.lucene.search.BooleanClause.Occur;
024: import org.apache.solr.core.SolrCore;
025: import org.apache.solr.core.SolrException;
026: import org.apache.solr.request.SolrParams;
027: import org.apache.solr.request.SolrQueryRequest;
028: import org.apache.solr.request.SolrQueryResponse;
029: import org.apache.solr.request.DefaultSolrParams;
030: import org.apache.solr.request.AppendedSolrParams;
031: import org.apache.solr.schema.IndexSchema;
032: import org.apache.solr.schema.SchemaField;
033: import org.apache.solr.search.*;
034:
035: import java.io.IOException;
036: import java.io.StringReader;
037: import java.util.*;
038: import java.util.logging.Level;
039: import java.util.regex.Pattern;
040:
041: /**
042: * <p>Utilities that may be of use to RequestHandlers.</p>
043: *
044: * <p>
045: * Many of these functions have code that was stolen/mutated from
046: * StandardRequestHandler.
047: * </p>
048: *
049: * <p>:TODO: refactor StandardRequestHandler to use these utilities</p>
050: *
051: * <p>:TODO: Many "standard" functionality methods are not cognisant of
052: * default parameter settings.
053: */
054: public class SolrPluginUtils {
055:
056: /**
057: * Set defaults on a SolrQueryRequest.
058: *
059: * RequestHandlers can use this method to ensure their defaults are
060: * visible to other components such as the response writer
061: */
062: public static void setDefaults(SolrQueryRequest req,
063: SolrParams defaults) {
064: setDefaults(req, defaults, null, null);
065: }
066:
067: /**
068: * Set default-ish params on a SolrQueryRequest.
069: *
070: * RequestHandlers can use this method to ensure their defaults and
071: * overrides are visible to other components such as the response writer
072: *
073: * @param req The request whose params we are interested i
074: * @param defaults values to be used if no values are specified in the request params
075: * @param appends values to be appended to those from the request (or defaults) when dealing with multi-val params, or treated as another layer of defaults for singl-val params.
076: * @param invariants values which will be used instead of any request, or default values, regardless of context.
077: */
078: public static void setDefaults(SolrQueryRequest req,
079: SolrParams defaults, SolrParams appends,
080: SolrParams invariants) {
081:
082: SolrParams p = req.getParams();
083: if (defaults != null) {
084: p = new DefaultSolrParams(p, defaults);
085: }
086: if (appends != null) {
087: p = new AppendedSolrParams(p, appends);
088: }
089: if (invariants != null) {
090: p = new DefaultSolrParams(invariants, p);
091: }
092: req.setParams(p);
093: }
094:
095: /** standard param for field list */
096: @Deprecated
097: public static String FL = SolrParams.FL;
098:
099: /**
100: * SolrIndexSearch.numDocs(Query,Query) freaks out if the filtering
101: * query is null, so we use this workarround.
102: */
103: public static int numDocs(SolrIndexSearcher s, Query q, Query f)
104: throws IOException {
105:
106: return (null == f) ? s.getDocSet(q).size() : s.numDocs(q, f);
107:
108: }
109:
110: /**
111: * Returns the param, or the default if it's empty or not specified.
112: * @deprecated use SolrParam.get(String,String)
113: */
114: public static String getParam(SolrQueryRequest req, String param,
115: String def) {
116:
117: String v = req.getParam(param);
118: // Note: parameters passed but given only white-space value are
119: // considered equvalent to passing nothing for that parameter.
120: if (null == v || "".equals(v.trim())) {
121: return def;
122: }
123: return v;
124: }
125:
126: /**
127: * Treats the param value as a Number, returns the default if nothing is
128: * there or if it's not a number.
129: * @deprecated use SolrParam.getFloat(String,float)
130: */
131: public static Number getNumberParam(SolrQueryRequest req,
132: String param, Number def) {
133:
134: Number r = def;
135: String v = req.getParam(param);
136: if (null == v || "".equals(v.trim())) {
137: return r;
138: }
139: try {
140: r = new Float(v);
141: } catch (NumberFormatException e) {
142: /* :NOOP" */
143: }
144: return r;
145: }
146:
147: /**
148: * Treats parameter value as a boolean. The string 'false' is false;
149: * any other non-empty string is true.
150: * @deprecated use SolrParam.getBool(String,boolean)
151: */
152: public static boolean getBooleanParam(SolrQueryRequest req,
153: String param, boolean def) {
154: String v = req.getParam(param);
155: if (null == v || "".equals(v.trim())) {
156: return def;
157: }
158: return !"false".equals(v.trim());
159: }
160:
161: private final static Pattern splitList = Pattern.compile(",| ");
162:
163: /** Split a value that may contain a comma, space of bar separated list. */
164: public static String[] split(String value) {
165: return splitList.split(value.trim(), 0);
166: }
167:
168: /**
169: * Assumes the standard query param of "fl" to specify the return fields
170: * @see #setReturnFields(String,SolrQueryResponse)
171: */
172: public static int setReturnFields(SolrQueryRequest req,
173: SolrQueryResponse res) {
174:
175: return setReturnFields(req.getParam(FL), res);
176: }
177:
178: /**
179: * Given a space seperated list of field names, sets the field list on the
180: * SolrQueryResponse.
181: *
182: * @return bitfield of SolrIndexSearcher flags that need to be set
183: */
184: public static int setReturnFields(String fl, SolrQueryResponse res) {
185: int flags = 0;
186: if (fl != null) {
187: // TODO - this could become more efficient if widely used.
188: // TODO - should field order be maintained?
189: String[] flst = split(fl);
190: if (flst.length > 0
191: && !(flst.length == 1 && flst[0].length() == 0)) {
192: Set<String> set = new HashSet<String>();
193: for (String fname : flst) {
194: if ("score".equalsIgnoreCase(fname))
195: flags |= SolrIndexSearcher.GET_SCORES;
196: set.add(fname);
197: }
198: res.setReturnFields(set);
199: }
200: }
201: return flags;
202: }
203:
204: /**
205: * Pre-fetch documents into the index searcher's document cache.
206: *
207: * This is an entirely optional step which you might want to perform for
208: * the following reasons:
209: *
210: * <ul>
211: * <li>Locates the document-retrieval costs in one spot, which helps
212: * detailed performance measurement</li>
213: *
214: * <li>Determines a priori what fields will be needed to be fetched by
215: * various subtasks, like response writing and highlighting. This
216: * minimizes the chance that many needed fields will be loaded lazily.
217: * (it is more efficient to load all the field we require normally).</li>
218: * </ul>
219: *
220: * If lazy field loading is disabled, this method does nothing.
221: */
222: public static void optimizePreFetchDocs(DocList docs, Query query,
223: SolrQueryRequest req, SolrQueryResponse res)
224: throws IOException {
225: SolrIndexSearcher searcher = req.getSearcher();
226: if (!searcher.enableLazyFieldLoading) {
227: // nothing to do
228: return;
229: }
230:
231: Set<String> fieldFilter = null;
232: Set<String> returnFields = res.getReturnFields();
233: if (returnFields != null) {
234: // copy return fields list
235: fieldFilter = new HashSet<String>(returnFields);
236: // add highlight fields
237: if (HighlightingUtils.isHighlightingEnabled(req)) {
238: for (String field : HighlightingUtils
239: .getHighlightFields(query, req, null))
240: fieldFilter.add(field);
241: }
242: // fetch unique key if one exists.
243: SchemaField keyField = req.getSearcher().getSchema()
244: .getUniqueKeyField();
245: if (null != keyField)
246: fieldFilter.add(keyField.getName());
247: }
248:
249: // get documents
250: DocIterator iter = docs.iterator();
251: for (int i = 0; i < docs.size(); i++) {
252: searcher.doc(iter.nextDoc(), fieldFilter);
253: }
254: }
255:
256: /**
257: * <p>
258: * Returns a NamedList containing many "standard" pieces of debugging
259: * information.
260: * </p>
261: *
262: * <ul>
263: * <li>rawquerystring - the 'q' param exactly as specified by the client
264: * </li>
265: * <li>querystring - the 'q' param after any preprocessing done by the plugin
266: * </li>
267: * <li>parsedquery - the main query executed formated by the Solr
268: * QueryParsing utils class (which knows about field types)
269: * </li>
270: * <li>parsedquery_toString - the main query executed formated by it's
271: * own toString method (in case it has internal state Solr
272: * doesn't know about)
273: * </li>
274: * <li>expain - the list of score explanations for each document in
275: * results against query.
276: * </li>
277: * <li>otherQuery - the query string specified in 'explainOther' query param.
278: * </li>
279: * <li>explainOther - the list of score explanations for each document in
280: * results against 'otherQuery'
281: * </li>
282: * </ul>
283: *
284: * @param req the request we are dealing with
285: * @param userQuery the users query as a string, after any basic
286: * preprocessing has been done
287: * @param query the query built from the userQuery
288: * (and perhaps other clauses) that identifies the main
289: * result set of the response.
290: * @param results the main result set of the response
291: * @deprecated Use doStandardDebug(SolrQueryRequest,String,Query,DocList) with setDefaults
292: */
293: public static NamedList doStandardDebug(SolrQueryRequest req,
294: String userQuery, Query query, DocList results,
295: CommonParams params) throws IOException {
296:
297: String debug = getParam(req, SolrParams.DEBUG_QUERY,
298: params.debugQuery);
299:
300: NamedList dbg = null;
301: if (debug != null) {
302: dbg = new SimpleOrderedMap();
303:
304: /* userQuery may have been pre-processes .. expose that */
305: dbg.add("rawquerystring", req.getQueryString());
306: dbg.add("querystring", userQuery);
307:
308: /* QueryParsing.toString isn't perfect, use it to see converted
309: * values, use regular toString to see any attributes of the
310: * underlying Query it may have missed.
311: */
312: dbg.add("parsedquery", QueryParsing.toString(query, req
313: .getSchema()));
314: dbg.add("parsedquery_toString", query.toString());
315:
316: dbg.add("explain", getExplainList(query, results, req
317: .getSearcher(), req.getSchema()));
318: String otherQueryS = req.getParam("explainOther");
319: if (otherQueryS != null && otherQueryS.length() > 0) {
320: DocList otherResults = doSimpleQuery(otherQueryS, req
321: .getSearcher(), req.getSchema(), 0, 10);
322: dbg.add("otherQuery", otherQueryS);
323: dbg.add("explainOther", getExplainList(query,
324: otherResults, req.getSearcher(), req
325: .getSchema()));
326: }
327: }
328:
329: return dbg;
330: }
331:
332: /**
333: * <p>
334: * Returns a NamedList containing many "standard" pieces of debugging
335: * information.
336: * </p>
337: *
338: * <ul>
339: * <li>rawquerystring - the 'q' param exactly as specified by the client
340: * </li>
341: * <li>querystring - the 'q' param after any preprocessing done by the plugin
342: * </li>
343: * <li>parsedquery - the main query executed formated by the Solr
344: * QueryParsing utils class (which knows about field types)
345: * </li>
346: * <li>parsedquery_toString - the main query executed formated by it's
347: * own toString method (in case it has internal state Solr
348: * doesn't know about)
349: * </li>
350: * <li>expain - the list of score explanations for each document in
351: * results against query.
352: * </li>
353: * <li>otherQuery - the query string specified in 'explainOther' query param.
354: * </li>
355: * <li>explainOther - the list of score explanations for each document in
356: * results against 'otherQuery'
357: * </li>
358: * </ul>
359: *
360: * @param req the request we are dealing with
361: * @param userQuery the users query as a string, after any basic
362: * preprocessing has been done
363: * @param query the query built from the userQuery
364: * (and perhaps other clauses) that identifies the main
365: * result set of the response.
366: * @param results the main result set of the response
367: */
368: public static NamedList doStandardDebug(SolrQueryRequest req,
369: String userQuery, Query query, DocList results)
370: throws IOException {
371:
372: String debug = req.getParam(SolrParams.DEBUG_QUERY);
373:
374: NamedList dbg = null;
375: if (debug != null) {
376: dbg = new SimpleOrderedMap();
377:
378: /* userQuery may have been pre-processes .. expose that */
379: dbg.add("rawquerystring", req.getQueryString());
380: dbg.add("querystring", userQuery);
381:
382: /* QueryParsing.toString isn't perfect, use it to see converted
383: * values, use regular toString to see any attributes of the
384: * underlying Query it may have missed.
385: */
386: dbg.add("parsedquery", QueryParsing.toString(query, req
387: .getSchema()));
388: dbg.add("parsedquery_toString", query.toString());
389:
390: dbg.add("explain", getExplainList(query, results, req
391: .getSearcher(), req.getSchema()));
392: String otherQueryS = req.getParam("explainOther");
393: if (otherQueryS != null && otherQueryS.length() > 0) {
394: DocList otherResults = doSimpleQuery(otherQueryS, req
395: .getSearcher(), req.getSchema(), 0, 10);
396: dbg.add("otherQuery", otherQueryS);
397: dbg.add("explainOther", getExplainList(query,
398: otherResults, req.getSearcher(), req
399: .getSchema()));
400: }
401: }
402:
403: return dbg;
404: }
405:
406: /**
407: * Generates an list of Explanations for each item in a list of docs.
408: *
409: * @param query The Query you want explanations in the context of
410: * @param docs The Documents you want explained relative that query
411: */
412: public static NamedList getExplainList(Query query, DocList docs,
413: SolrIndexSearcher searcher, IndexSchema schema)
414: throws IOException {
415:
416: NamedList explainList = new SimpleOrderedMap();
417: DocIterator iterator = docs.iterator();
418: for (int i = 0; i < docs.size(); i++) {
419: int id = iterator.nextDoc();
420:
421: Explanation explain = searcher.explain(query, id);
422:
423: Document doc = searcher.doc(id);
424: String strid = schema.printableUniqueKey(doc);
425: String docname = "";
426: if (strid != null)
427: docname = "id=" + strid + ",";
428: docname = docname + "internal_docid=" + id;
429:
430: explainList.add(docname, "\n" + explain.toString());
431: }
432: return explainList;
433: }
434:
435: /**
436: * Executes a basic query in lucene syntax
437: */
438: public static DocList doSimpleQuery(String sreq,
439: SolrIndexSearcher searcher, IndexSchema schema, int start,
440: int limit) throws IOException {
441: List<String> commands = StrUtils.splitSmart(sreq, ';');
442:
443: String qs = commands.size() >= 1 ? commands.get(0) : "";
444: Query query = QueryParsing.parseQuery(qs, schema);
445:
446: // If the first non-query, non-filter command is a simple sort on an indexed field, then
447: // we can use the Lucene sort ability.
448: Sort sort = null;
449: if (commands.size() >= 2) {
450: QueryParsing.SortSpec sortSpec = QueryParsing.parseSort(
451: commands.get(1), schema);
452: if (sortSpec != null) {
453: sort = sortSpec.getSort();
454: if (sortSpec.getCount() >= 0) {
455: limit = sortSpec.getCount();
456: }
457: }
458: }
459:
460: DocList results = searcher.getDocList(query, (DocSet) null,
461: sort, start, limit);
462: return results;
463: }
464:
465: /**
466: * Given a string containing fieldNames and boost info,
467: * converts it to a Map from field name to boost info.
468: *
469: * <p>
470: * Doesn't care if boost info is negative, you're on your own.
471: * </p>
472: * <p>
473: * Doesn't care if boost info is missing, again: you're on your own.
474: * </p>
475: *
476: * @param in a String like "fieldOne^2.3 fieldTwo fieldThree^-0.4"
477: * @return Map of fieldOne => 2.3, fieldTwo => null, fieldThree => -0.4
478: */
479: public static Map<String, Float> parseFieldBoosts(String in) {
480: return parseFieldBoosts(new String[] { in });
481: }
482:
483: /**
484: * Like <code>parseFieldBoosts(String)</code>, but parses all the strings
485: * in the provided array (which may be null).
486: *
487: * @param fieldLists an array of Strings eg. <code>{"fieldOne^2.3", "fieldTwo"}</code>
488: * @return Map of fieldOne => 2.3, fieldThree => -0.4
489: */
490: public static Map<String, Float> parseFieldBoosts(
491: String[] fieldLists) {
492: if (null == fieldLists || 0 == fieldLists.length) {
493: return new HashMap<String, Float>();
494: }
495: Map<String, Float> out = new HashMap<String, Float>(7);
496: for (String in : fieldLists) {
497: if (null == in || "".equals(in.trim()))
498: continue;
499: String[] bb = in.trim().split("\\s+");
500: for (String s : bb) {
501: String[] bbb = s.split("\\^");
502: out.put(bbb[0], 1 == bbb.length ? null : Float
503: .valueOf(bbb[1]));
504: }
505: }
506: return out;
507: }
508:
509: /**
510: * Given a string containing functions with optional boosts, returns
511: * an array of Queries representing those functions with the specified
512: * boosts.
513: * <p>
514: * NOTE: intra-function whitespace is not allowed.
515: * </p>
516: * @see #parseFieldBoosts
517: */
518: public static List<Query> parseFuncs(IndexSchema s, String in)
519: throws ParseException {
520:
521: Map<String, Float> ff = parseFieldBoosts(in);
522: List<Query> funcs = new ArrayList<Query>(ff.keySet().size());
523: for (String f : ff.keySet()) {
524: Query fq = QueryParsing.parseFunction(f, s);
525: Float b = ff.get(f);
526: if (null != b) {
527: fq.setBoost(b);
528: }
529: funcs.add(fq);
530: }
531: return funcs;
532: }
533:
534: /**
535: * Checks the number of optional clauses in the query, and compares it
536: * with the specification string to determine the proper value to use.
537: *
538: * <p>
539: * Details about the specification format can be found
540: * <a href="doc-files/min-should-match.html">here</a>
541: * </p>
542: *
543: * <p>A few important notes...</p>
544: * <ul>
545: * <li>
546: * If the calculations based on the specification determine that no
547: * optional clauses are needed, BooleanQuerysetMinMumberShouldMatch
548: * will never be called, but the usual rules about BooleanQueries
549: * still apply at search time (a BooleanQuery containing no required
550: * clauses must still match at least one optional clause)
551: * <li>
552: * <li>
553: * No matter what number the calculation arrives at,
554: * BooleanQuery.setMinShouldMatch() will never be called with a
555: * value greater then the number of optional clauses (or less then 1)
556: * </li>
557: * </ul>
558: *
559: * <p>:TODO: should optimize the case where number is same
560: * as clauses to just make them all "required"
561: * </p>
562: */
563: public static void setMinShouldMatch(BooleanQuery q, String spec) {
564:
565: int optionalClauses = 0;
566: for (BooleanClause c : (List<BooleanClause>) q.clauses()) {
567: if (c.getOccur() == Occur.SHOULD) {
568: optionalClauses++;
569: }
570: }
571:
572: int msm = calculateMinShouldMatch(optionalClauses, spec);
573: if (0 < msm) {
574: q.setMinimumNumberShouldMatch(msm);
575: }
576: }
577:
578: /**
579: * helper exposed for UnitTests
580: * @see #setMinShouldMatch
581: */
582: static int calculateMinShouldMatch(int optionalClauseCount,
583: String spec) {
584:
585: int result = optionalClauseCount;
586:
587: if (-1 < spec.indexOf("<")) {
588: /* we have conditional spec(s) */
589:
590: for (String s : spec.trim().split(" ")) {
591: String[] parts = s.split("<");
592: int upperBound = (new Integer(parts[0])).intValue();
593: if (optionalClauseCount <= upperBound) {
594: return result;
595: } else {
596: result = calculateMinShouldMatch(
597: optionalClauseCount, parts[1]);
598: }
599: }
600: return result;
601: }
602:
603: /* otherwise, simple expresion */
604:
605: if (-1 < spec.indexOf("%")) {
606: /* percentage */
607: int percent = new Integer(spec.replace("%", "")).intValue();
608: float calc = (result * percent) / 100f;
609: result = calc < 0 ? result + (int) calc : (int) calc;
610: } else {
611: int calc = (new Integer(spec)).intValue();
612: result = calc < 0 ? result + calc : calc;
613: }
614:
615: return (optionalClauseCount < result ? optionalClauseCount
616: : (result < 0 ? 0 : result));
617:
618: }
619:
620: /**
621: * Recursively walks the "from" query pulling out sub-queries and
622: * adding them to the "to" query.
623: *
624: * <p>
625: * Boosts are multiplied as needed. Sub-BooleanQueryies which are not
626: * optional will not be flattened. From will be mangled durring the walk,
627: * so do not attempt to reuse it.
628: * </p>
629: */
630: public static void flattenBooleanQuery(BooleanQuery to,
631: BooleanQuery from) {
632:
633: for (BooleanClause clause : (List<BooleanClause>) from
634: .clauses()) {
635:
636: Query cq = clause.getQuery();
637: cq.setBoost(cq.getBoost() * from.getBoost());
638:
639: if (cq instanceof BooleanQuery && !clause.isRequired()
640: && !clause.isProhibited()) {
641:
642: /* we can recurse */
643: flattenBooleanQuery(to, (BooleanQuery) cq);
644:
645: } else {
646: to.add(clause);
647: }
648: }
649: }
650:
651: /**
652: * Escapes all special characters except '"', '-', and '+'
653: *
654: * @see QueryParser#escape
655: */
656: public static CharSequence partialEscape(CharSequence s) {
657: StringBuffer sb = new StringBuffer();
658: for (int i = 0; i < s.length(); i++) {
659: char c = s.charAt(i);
660: if (c == '\\' || c == '!' || c == '(' || c == ')'
661: || c == ':' || c == '^' || c == '[' || c == ']'
662: || c == '{' || c == '}' || c == '~' || c == '*'
663: || c == '?') {
664: sb.append('\\');
665: }
666: sb.append(c);
667: }
668: return sb;
669: }
670:
671: /**
672: * Returns it's input if there is an even (ie: balanced) number of
673: * '"' characters -- otherwise returns a String in which all '"'
674: * characters are striped out.
675: */
676: public static CharSequence stripUnbalancedQuotes(CharSequence s) {
677: int count = 0;
678: for (int i = 0; i < s.length(); i++) {
679: if (s.charAt(i) == '\"') {
680: count++;
681: }
682: }
683: if (0 == (count & 1)) {
684: return s;
685: }
686: return s.toString().replace("\"", "");
687: }
688:
689: /**
690: * A subclass of SolrQueryParser that supports aliasing fields for
691: * constructing DisjunctionMaxQueries.
692: */
693: public static class DisjunctionMaxQueryParser extends
694: SolrQueryParser {
695:
696: /** A simple container for storing alias info
697: * @see #aliases
698: */
699: protected static class Alias {
700: public float tie;
701: public Map<String, Float> fields;
702: }
703:
704: /**
705: * Where we store a map from field name we expect to see in our query
706: * string, to Alias object containing the fields to use in our
707: * DisjunctionMaxQuery and the tiebreaker to use.
708: */
709: protected Map<String, Alias> aliases = new HashMap<String, Alias>(
710: 3);
711:
712: public DisjunctionMaxQueryParser(IndexSchema s,
713: String defaultField) {
714: super (s, defaultField);
715: // don't trust that our parent class won't ever change it's default
716: setDefaultOperator(QueryParser.Operator.OR);
717: }
718:
719: public DisjunctionMaxQueryParser(IndexSchema s) {
720: this (s, null);
721: }
722:
723: /**
724: * Add an alias to this query parser.
725: *
726: * @param field the field name that should trigger alias mapping
727: * @param fieldBoosts the mapping from fieldname to boost value that
728: * should be used to build up the clauses of the
729: * DisjunctionMaxQuery.
730: * @param tiebreaker to the tiebreaker to be used in the
731: * DisjunctionMaxQuery
732: * @see SolrPluginUtils#parseFieldBoosts
733: */
734: public void addAlias(String field, float tiebreaker,
735: Map<String, Float> fieldBoosts) {
736:
737: Alias a = new Alias();
738: a.tie = tiebreaker;
739: a.fields = fieldBoosts;
740: aliases.put(field, a);
741: }
742:
743: /**
744: * Delegates to the super class unless the field has been specified
745: * as an alias -- in which case we recurse on each of
746: * the aliased fields, and the results are composed into a
747: * DisjunctionMaxQuery. (so yes: aliases which point at other
748: * aliases should work)
749: */
750: protected Query getFieldQuery(String field, String queryText)
751: throws ParseException {
752:
753: if (aliases.containsKey(field)) {
754:
755: Alias a = aliases.get(field);
756: DisjunctionMaxQuery q = new DisjunctionMaxQuery(a.tie);
757:
758: /* we might not get any valid queries from delegation,
759: * in which we should return null
760: */
761: boolean ok = false;
762:
763: for (String f : a.fields.keySet()) {
764:
765: Query sub = getFieldQuery(f, queryText);
766: if (null != sub) {
767: if (null != a.fields.get(f)) {
768: sub.setBoost(a.fields.get(f));
769: }
770: q.add(sub);
771: ok = true;
772: }
773: }
774: return ok ? q : null;
775:
776: } else {
777: return super .getFieldQuery(field, queryText);
778: }
779: }
780:
781: }
782:
783: /**
784: * Determines the correct Sort based on the request parameter "sort"
785: *
786: * @return null if no sort is specified.
787: */
788: public static Sort getSort(SolrQueryRequest req) {
789:
790: String sort = req.getParam(SolrParams.SORT);
791: if (null == sort || sort.equals("")) {
792: return null;
793: }
794:
795: SolrException sortE = null;
796: QueryParsing.SortSpec ss = null;
797: try {
798: ss = QueryParsing.parseSort(sort, req.getSchema());
799: } catch (SolrException e) {
800: sortE = e;
801: }
802:
803: if ((null == ss) || (null != sortE)) {
804: /* we definitely had some sort of sort string from the user,
805: * but no SortSpec came out of it
806: */
807: SolrCore.log.log(Level.WARNING, "Invalid sort \"" + sort
808: + "\" was specified, ignoring", sortE);
809: return null;
810: }
811:
812: return ss.getSort();
813: }
814:
815: /**
816: * Builds a list of Query objects that should be used to filter results
817: * @see SolrParams#FQ
818: * @return null if no filter queries
819: */
820: public static List<Query> parseFilterQueries(SolrQueryRequest req)
821: throws ParseException {
822: return parseQueryStrings(req, req.getParams().getParams(
823: SolrParams.FQ));
824: }
825:
826: /** Turns an array of query strings into a List of Query objects.
827: *
828: * @return null if no queries are generated
829: */
830: public static List<Query> parseQueryStrings(SolrQueryRequest req,
831: String[] queries) throws ParseException {
832: if (null == queries || 0 == queries.length)
833: return null;
834: List<Query> out = new LinkedList<Query>();
835: SolrIndexSearcher s = req.getSearcher();
836: /* Ignore SolrParams.DF - could have init param FQs assuming the
837: * schema default with query param DF intented to only affect Q.
838: * If user doesn't want schema default, they should be explicit in the FQ.
839: */
840: SolrQueryParser qp = new SolrQueryParser(s.getSchema(), null);
841: for (String q : queries) {
842: if (null != q && 0 != q.trim().length()) {
843: out.add(qp.parse(q));
844: }
845: }
846: return out;
847: }
848:
849: /**
850: * A CacheRegenerator that can be used whenever the items in the cache
851: * are not dependant on the current searcher.
852: *
853: * <p>
854: * Flat out copies the oldKey=>oldVal pair into the newCache
855: * </p>
856: */
857: public static class IdentityRegenerator implements CacheRegenerator {
858: public boolean regenerateItem(SolrIndexSearcher newSearcher,
859: SolrCache newCache, SolrCache oldCache, Object oldKey,
860: Object oldVal) throws IOException {
861:
862: newCache.put(oldKey, oldVal);
863: return true;
864: }
865:
866: }
867:
868: }
|