/**
* 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.util;
import org.apache.lucene.document.Document;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.*;
import org.apache.lucene.search.BooleanClause.Occur;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrDocumentList;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.params.AppendedSolrParams;
import org.apache.solr.common.params.DefaultSolrParams;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.SimpleOrderedMap;
import org.apache.solr.common.util.StrUtils;
import org.apache.solr.core.SolrCore;
import org.apache.solr.handler.component.ResponseBuilder;
import org.apache.solr.highlight.SolrHighlighter;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.response.SolrQueryResponse;
import org.apache.solr.schema.IndexSchema;
import org.apache.solr.schema.SchemaField;
import org.apache.solr.search.*;
import org.apache.solr.update.DocumentBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.*;
import java.util.regex.Pattern;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;
/**
* <p>Utilities that may be of use to RequestHandlers.</p>
*
* <p>
* Many of these functions have code that was stolen/mutated from
* StandardRequestHandler.
* </p>
*
* <p>:TODO: refactor StandardRequestHandler to use these utilities</p>
*
* <p>:TODO: Many "standard" functionality methods are not cognisant of
* default parameter settings.
*/
public class SolrPluginUtils {
final static Logger log = LoggerFactory.getLogger( SolrPluginUtils.class );
/**
* Set defaults on a SolrQueryRequest.
*
* RequestHandlers can use this method to ensure their defaults are
* visible to other components such as the response writer
*/
public static void setDefaults(SolrQueryRequest req, SolrParams defaults) {
setDefaults(req, defaults, null, null);
}
/**
* Set default-ish params on a SolrQueryRequest.
*
* RequestHandlers can use this method to ensure their defaults and
* overrides are visible to other components such as the response writer
*
* @param req The request whose params we are interested i
* @param defaults values to be used if no values are specified in the request params
* @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.
* @param invariants values which will be used instead of any request, or default values, regardless of context.
*/
public static void setDefaults(SolrQueryRequest req, SolrParams defaults,
SolrParams appends, SolrParams invariants) {
SolrParams p = req.getParams();
if (defaults != null) {
p = new DefaultSolrParams(p,defaults);
}
if (appends != null) {
p = new AppendedSolrParams(p,appends);
}
if (invariants != null) {
p = new DefaultSolrParams(invariants,p);
}
req.setParams(p);
}
/**
* standard param for field list
*
* @deprecated Use org.apache.solr.common.params.CommonParams.FL.
*/
@Deprecated
public static String FL = CommonParams.FL;
/**
* SolrIndexSearch.numDocs(Query,Query) freaks out if the filtering
* query is null, so we use this workarround.
*/
public static int numDocs(SolrIndexSearcher s, Query q, Query f)
throws IOException {
return (null == f) ? s.getDocSet(q).size() : s.numDocs(q,f);
}
/**
* Returns the param, or the default if it's empty or not specified.
* @deprecated use SolrParam.get(String,String)
*/
public static String getParam(SolrQueryRequest req,
String param, String def) {
String v = req.getParam(param);
// Note: parameters passed but given only white-space value are
// considered equivalent to passing nothing for that parameter.
if (null == v || "".equals(v.trim())) {
return def;
}
return v;
}
/**
* Treats the param value as a Number, returns the default if nothing is
* there or if it's not a number.
* @deprecated use SolrParam.getFloat(String,float)
*/
public static Number getNumberParam(SolrQueryRequest req,
String param, Number def) {
Number r = def;
String v = req.getParam(param);
if (null == v || "".equals(v.trim())) {
return r;
}
try {
r = new Float(v);
} catch (NumberFormatException e) {
/* :NOOP" */
}
return r;
}
/**
* Treats parameter value as a boolean. The string 'false' is false;
* any other non-empty string is true.
* @deprecated use SolrParam.getBool(String,boolean)
*/
public static boolean getBooleanParam(SolrQueryRequest req,
String param, boolean def) {
String v = req.getParam(param);
if (null == v || "".equals(v.trim())) {
return def;
}
return !"false".equals(v.trim());
}
private final static Pattern splitList=Pattern.compile(",| ");
/** Split a value that may contain a comma, space of bar separated list. */
public static String[] split(String value){
return splitList.split(value.trim(), 0);
}
/**
* Assumes the standard query param of "fl" to specify the return fields
* @see #setReturnFields(String,SolrQueryResponse)
*/
public static int setReturnFields(SolrQueryRequest req,
SolrQueryResponse res) {
return setReturnFields(req.getParams().get(CommonParams.FL), res);
}
/**
* Given a space seperated list of field names, sets the field list on the
* SolrQueryResponse.
*
* @return bitfield of SolrIndexSearcher flags that need to be set
*/
public static int setReturnFields(String fl,
SolrQueryResponse res) {
int flags = 0;
if (fl != null) {
// TODO - this could become more efficient if widely used.
// TODO - should field order be maintained?
String[] flst = split(fl);
if (flst.length > 0 && !(flst.length==1 && flst[0].length()==0)) {
Set<String> set = new LinkedHashSet<String>();
for (String fname : flst) {
if("score".equalsIgnoreCase(fname))
flags |= SolrIndexSearcher.GET_SCORES;
set.add(fname);
}
res.setReturnFields(set);
}
}
return flags;
}
/**
* Pre-fetch documents into the index searcher's document cache.
*
* This is an entirely optional step which you might want to perform for
* the following reasons:
*
* <ul>
* <li>Locates the document-retrieval costs in one spot, which helps
* detailed performance measurement</li>
*
* <li>Determines a priori what fields will be needed to be fetched by
* various subtasks, like response writing and highlighting. This
* minimizes the chance that many needed fields will be loaded lazily.
* (it is more efficient to load all the field we require normally).</li>
* </ul>
*
* If lazy field loading is disabled, this method does nothing.
*/
public static void optimizePreFetchDocs(DocList docs,
Query query,
SolrQueryRequest req,
SolrQueryResponse res) throws IOException {
SolrIndexSearcher searcher = req.getSearcher();
if(!searcher.enableLazyFieldLoading) {
// nothing to do
return;
}
Set<String> fieldFilter = null;
Set<String> returnFields = res.getReturnFields();
if(returnFields != null) {
// copy return fields list
fieldFilter = new HashSet<String>(returnFields);
// add highlight fields
SolrHighlighter highligher = req.getCore().getHighlighter();
if(highligher.isHighlightingEnabled(req.getParams())) {
for(String field: highligher.getHighlightFields(query, req, null))
fieldFilter.add(field);
}
// fetch unique key if one exists.
SchemaField keyField = req.getSearcher().getSchema().getUniqueKeyField();
if(null != keyField)
fieldFilter.add(keyField.getName());
}
// get documents
DocIterator iter = docs.iterator();
for (int i=0; i<docs.size(); i++) {
searcher.doc(iter.nextDoc(), fieldFilter);
}
}
public static Set<String> getDebugInterests(String[] params, ResponseBuilder rb){
Set<String> debugInterests = new HashSet<String>();
if (params != null) {
for (int i = 0; i < params.length; i++) {
if (params[i].equalsIgnoreCase("all") || params[i].equalsIgnoreCase("true")){
rb.setDebug(true);
break;
//still might add others
} else if (params[i].equals(CommonParams.TIMING)){
rb.setDebugTimings(true);
} else if (params[i].equals(CommonParams.QUERY)){
rb.setDebugQuery(true);
} else if (params[i].equals(CommonParams.RESULTS)){
rb.setDebugResults(true);
}
}
}
return debugInterests;
}
/**
* <p>
* Returns a NamedList containing many "standard" pieces of debugging
* information.
* </p>
*
* <ul>
* <li>rawquerystring - the 'q' param exactly as specified by the client
* </li>
* <li>querystring - the 'q' param after any preprocessing done by the plugin
* </li>
* <li>parsedquery - the main query executed formated by the Solr
* QueryParsing utils class (which knows about field types)
* </li>
* <li>parsedquery_toString - the main query executed formated by it's
* own toString method (in case it has internal state Solr
* doesn't know about)
* </li>
* <li>explain - the list of score explanations for each document in
* results against query.
* </li>
* <li>otherQuery - the query string specified in 'explainOther' query param.
* </li>
* <li>explainOther - the list of score explanations for each document in
* results against 'otherQuery'
* </li>
* </ul>
*
* @param req the request we are dealing with
* @param userQuery the users query as a string, after any basic
* preprocessing has been done
* @param query the query built from the userQuery
* (and perhaps other clauses) that identifies the main
* result set of the response.
* @param results the main result set of the response
* @return The debug info
* @throws java.io.IOException if there was an IO error
*/
public static NamedList doStandardDebug(SolrQueryRequest req,
String userQuery,
Query query,
DocList results, boolean dbgQuery, boolean dbgResults)
throws IOException {
NamedList dbg = null;
dbg = new SimpleOrderedMap();
SolrIndexSearcher searcher = req.getSearcher();
IndexSchema schema = req.getSchema();
boolean explainStruct
= req.getParams().getBool(CommonParams.EXPLAIN_STRUCT, false);
/* userQuery may have been pre-processes .. expose that */
if (dbgQuery) {
dbg.add("rawquerystring", req.getParams().get(CommonParams.Q));
dbg.add("querystring", userQuery);
/* QueryParsing.toString isn't perfect, use it to see converted
* values, use regular toString to see any attributes of the
* underlying Query it may have missed.
*/
dbg.add("parsedquery", QueryParsing.toString(query, schema));
dbg.add("parsedquery_toString", query.toString());
}
if (dbgResults) {
NamedList<Explanation> explain
= getExplanations(query, results, searcher, schema);
dbg.add("explain", explainStruct ?
explanationsToNamedLists(explain) :
explanationsToStrings(explain));
String otherQueryS = req.getParams().get(CommonParams.EXPLAIN_OTHER);
if (otherQueryS != null && otherQueryS.length() > 0) {
DocList otherResults = doSimpleQuery
(otherQueryS, req.getSearcher(), req.getSchema(), 0, 10);
dbg.add("otherQuery", otherQueryS);
NamedList<Explanation> explainO
= getExplanations(query, otherResults, searcher, schema);
dbg.add("explainOther", explainStruct ?
explanationsToNamedLists(explainO) :
explanationsToStrings(explainO));
}
}
return dbg;
}
public static NamedList<Object> explanationToNamedList(Explanation e) {
NamedList<Object> out = new SimpleOrderedMap<Object>();
out.add("match", e.isMatch());
out.add("value", e.getValue());
out.add("description", e.getDescription());
Explanation[] details = e.getDetails();
// short circut out
if (null == details || 0 == details.length) return out;
List<NamedList<Object>> kids
= new ArrayList<NamedList<Object>>(details.length);
for (Explanation d : details) {
kids.add(explanationToNamedList(d));
}
out.add("details", kids);
return out;
}
public static NamedList<NamedList<Object>> explanationsToNamedLists
(NamedList<Explanation> explanations) {
NamedList<NamedList<Object>> out
= new SimpleOrderedMap<NamedList<Object>>();
for (Map.Entry<String,Explanation> entry : explanations) {
out.add(entry.getKey(), explanationToNamedList(entry.getValue()));
}
return out;
}
/**
* Generates an NamedList of Explanations for each item in a list of docs.
*
* @param query The Query you want explanations in the context of
* @param docs The Documents you want explained relative that query
*/
public static NamedList<Explanation> getExplanations
(Query query,
DocList docs,
SolrIndexSearcher searcher,
IndexSchema schema) throws IOException {
NamedList<Explanation> explainList = new SimpleOrderedMap<Explanation>();
DocIterator iterator = docs.iterator();
for (int i=0; i<docs.size(); i++) {
int id = iterator.nextDoc();
Document doc = searcher.doc(id);
String strid = schema.printableUniqueKey(doc);
explainList.add(strid, searcher.explain(query, id) );
}
return explainList;
}
private static NamedList<String> explanationsToStrings
(NamedList<Explanation> explanations) {
NamedList<String> out = new SimpleOrderedMap<String>();
for (Map.Entry<String,Explanation> entry : explanations) {
out.add(entry.getKey(), "\n"+entry.getValue().toString());
}
return out;
}
/**
* Generates an list of Explanations for each item in a list of docs.
*
* @param query The Query you want explanations in the context of
* @param docs The Documents you want explained relative that query
* @deprecated this returns the explanations as Strings, instead it
* is recommeded to use getExplanations and call toString()
* yourself, or use explanationsToNamedLists
*/
@Deprecated
public static NamedList getExplainList(Query query, DocList docs,
SolrIndexSearcher searcher,
IndexSchema schema)
throws IOException {
return explanationsToStrings(getExplanations(query,docs,searcher,schema));
}
/**
* Executes a basic query in lucene syntax
*/
public static DocList doSimpleQuery(String sreq,
SolrIndexSearcher searcher,
IndexSchema schema,
int start, int limit) throws IOException {
List<String> commands = StrUtils.splitSmart(sreq,';');
String qs = commands.size() >= 1 ? commands.get(0) : "";
Query query = QueryParsing.parseQuery(qs, schema);
// If the first non-query, non-filter command is a simple sort on an indexed field, then
// we can use the Lucene sort ability.
Sort sort = null;
if (commands.size() >= 2) {
sort = QueryParsing.parseSort(commands.get(1), schema);
}
DocList results = searcher.getDocList(query,(DocSet)null, sort, start, limit);
return results;
}
/**
* Given a string containing fieldNames and boost info,
* converts it to a Map from field name to boost info.
*
* <p>
* Doesn't care if boost info is negative, you're on your own.
* </p>
* <p>
* Doesn't care if boost info is missing, again: you're on your own.
* </p>
*
* @param in a String like "fieldOne^2.3 fieldTwo fieldThree^-0.4"
* @return Map of fieldOne => 2.3, fieldTwo => null, fieldThree => -0.4
*/
public static Map<String,Float> parseFieldBoosts(String in) {
return parseFieldBoosts(new String[]{in});
}
/**
* Like <code>parseFieldBoosts(String)</code>, but parses all the strings
* in the provided array (which may be null).
*
* @param fieldLists an array of Strings eg. <code>{"fieldOne^2.3", "fieldTwo", fieldThree^-0.4}</code>
* @return Map of fieldOne => 2.3, fieldTwo => null, fieldThree => -0.4
*/
public static Map<String,Float> parseFieldBoosts(String[] fieldLists) {
if (null == fieldLists || 0 == fieldLists.length) {
return new HashMap<String,Float>();
}
Map<String, Float> out = new HashMap<String,Float>(7);
for (String in : fieldLists) {
if (null == in || "".equals(in.trim()))
continue;
String[] bb = in.trim().split("\\s+");
for (String s : bb) {
String[] bbb = s.split("\\^");
out.put(bbb[0], 1 == bbb.length ? null : Float.valueOf(bbb[1]));
}
}
return out;
}
/**
* Given a string containing functions with optional boosts, returns
* an array of Queries representing those functions with the specified
* boosts.
* <p>
* NOTE: intra-function whitespace is not allowed.
* </p>
* @see #parseFieldBoosts
* @deprecated
*/
public static List<Query> parseFuncs(IndexSchema s, String in)
throws ParseException {
Map<String,Float> ff = parseFieldBoosts(in);
List<Query> funcs = new ArrayList<Query>(ff.keySet().size());
for (String f : ff.keySet()) {
Query fq = QueryParsing.parseFunction(f, s);
Float b = ff.get(f);
if (null != b) {
fq.setBoost(b);
}
funcs.add(fq);
}
return funcs;
}
/**
* Checks the number of optional clauses in the query, and compares it
* with the specification string to determine the proper value to use.
*
* <p>
* Details about the specification format can be found
* <a href="doc-files/min-should-match.html">here</a>
* </p>
*
* <p>A few important notes...</p>
* <ul>
* <li>
* If the calculations based on the specification determine that no
* optional clauses are needed, BooleanQuerysetMinMumberShouldMatch
* will never be called, but the usual rules about BooleanQueries
* still apply at search time (a BooleanQuery containing no required
* clauses must still match at least one optional clause)
* <li>
* <li>
* No matter what number the calculation arrives at,
* BooleanQuery.setMinShouldMatch() will never be called with a
* value greater then the number of optional clauses (or less then 1)
* </li>
* </ul>
*
* <p>:TODO: should optimize the case where number is same
* as clauses to just make them all "required"
* </p>
*/
public static void setMinShouldMatch(BooleanQuery q, String spec) {
int optionalClauses = 0;
for (BooleanClause c : (List<BooleanClause>)q.clauses()) {
if (c.getOccur() == Occur.SHOULD) {
optionalClauses++;
}
}
int msm = calculateMinShouldMatch(optionalClauses, spec);
if (0 < msm) {
q.setMinimumNumberShouldMatch(msm);
}
}
/**
* helper exposed for UnitTests
* @see #setMinShouldMatch
*/
static int calculateMinShouldMatch(int optionalClauseCount, String spec) {
int result = optionalClauseCount;
if (-1 < spec.indexOf("<")) {
/* we have conditional spec(s) */
for (String s : spec.trim().split(" ")) {
String[] parts = s.split("<");
int upperBound = (new Integer(parts[0])).intValue();
if (optionalClauseCount <= upperBound) {
return result;
} else {
result = calculateMinShouldMatch
(optionalClauseCount, parts[1]);
}
}
return result;
}
/* otherwise, simple expresion */
if (-1 < spec.indexOf("%")) {
/* percentage */
int percent = new Integer(spec.replace("%","")).intValue();
float calc = (result * percent) / 100f;
result = calc < 0 ? result + (int)calc : (int)calc;
} else {
int calc = (new Integer(spec)).intValue();
result = calc < 0 ? result + calc : calc;
}
return (optionalClauseCount < result ?
optionalClauseCount : (result < 0 ? 0 : result));
}
/**
* Recursively walks the "from" query pulling out sub-queries and
* adding them to the "to" query.
*
* <p>
* Boosts are multiplied as needed. Sub-BooleanQueryies which are not
* optional will not be flattened. From will be mangled durring the walk,
* so do not attempt to reuse it.
* </p>
*/
public static void flattenBooleanQuery(BooleanQuery to, BooleanQuery from) {
for (BooleanClause clause : (List<BooleanClause>)from.clauses()) {
Query cq = clause.getQuery();
cq.setBoost(cq.getBoost() * from.getBoost());
if (cq instanceof BooleanQuery
&& !clause.isRequired()
&& !clause.isProhibited()) {
/* we can recurse */
flattenBooleanQuery(to, (BooleanQuery)cq);
} else {
to.add(clause);
}
}
}
/**
* Escapes all special characters except '"', '-', and '+'
*
* @see QueryParser#escape
*/
public static CharSequence partialEscape(CharSequence s) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c == '\\' || c == '!' || c == '(' || c == ')' ||
c == ':' || c == '^' || c == '[' || c == ']' ||
c == '{' || c == '}' || c == '~' || c == '*' || c == '?'
) {
sb.append('\\');
}
sb.append(c);
}
return sb;
}
// Pattern to detect dangling operator(s) at end of query
// \s+[-+\s]+$
private final static Pattern DANGLING_OP_PATTERN = Pattern.compile( "\\s+[-+\\s]+$" );
// Pattern to detect consecutive + and/or - operators
// \s+[+-](?:\s*[+-]+)+
private final static Pattern CONSECUTIVE_OP_PATTERN = Pattern.compile( "\\s+[+-](?:\\s*[+-]+)+" );
/**
* Strips operators that are used illegally, otherwise reuturns it's
* input. Some examples of illegal user queries are: "chocolate +-
* chip", "chocolate - - chip", and "chocolate chip -".
*/
public static CharSequence stripIllegalOperators(CharSequence s) {
String temp = CONSECUTIVE_OP_PATTERN.matcher( s ).replaceAll( " " );
return DANGLING_OP_PATTERN.matcher( temp ).replaceAll( "" );
}
/**
* Returns it's input if there is an even (ie: balanced) number of
* '"' characters -- otherwise returns a String in which all '"'
* characters are striped out.
*/
public static CharSequence stripUnbalancedQuotes(CharSequence s) {
int count = 0;
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) == '\"') { count++; }
}
if (0 == (count & 1)) {
return s;
}
return s.toString().replace("\"","");
}
public static NamedList removeNulls(NamedList nl) {
for (int i=0; i<nl.size(); i++) {
if (nl.getName(i)==null) {
NamedList newList = nl instanceof SimpleOrderedMap ? new SimpleOrderedMap() : new NamedList();
for (int j=0; j<nl.size(); j++) {
String n = nl.getName(j);
if (n != null) {
newList.add(n, nl.getVal(j));
}
}
return newList;
}
}
return nl;
}
/**
* A subclass of SolrQueryParser that supports aliasing fields for
* constructing DisjunctionMaxQueries.
*/
public static class DisjunctionMaxQueryParser extends SolrQueryParser {
/** A simple container for storing alias info
* @see #aliases
*/
protected static class Alias {
public float tie;
public Map<String,Float> fields;
}
/**
* Where we store a map from field name we expect to see in our query
* string, to Alias object containing the fields to use in our
* DisjunctionMaxQuery and the tiebreaker to use.
*/
protected Map<String,Alias> aliases = new HashMap<String,Alias>(3);
public DisjunctionMaxQueryParser(QParser qp, String defaultField) {
super(qp,defaultField);
// don't trust that our parent class won't ever change it's default
setDefaultOperator(QueryParser.Operator.OR);
}
public DisjunctionMaxQueryParser(IndexSchema s, String defaultField) {
super(s,defaultField);
// don't trust that our parent class won't ever change it's default
setDefaultOperator(QueryParser.Operator.OR);
}
public DisjunctionMaxQueryParser(IndexSchema s) {
this(s,null);
}
/**
* Add an alias to this query parser.
*
* @param field the field name that should trigger alias mapping
* @param fieldBoosts the mapping from fieldname to boost value that
* should be used to build up the clauses of the
* DisjunctionMaxQuery.
* @param tiebreaker to the tiebreaker to be used in the
* DisjunctionMaxQuery
* @see SolrPluginUtils#parseFieldBoosts
*/
public void addAlias(String field, float tiebreaker,
Map<String,Float> fieldBoosts) {
Alias a = new Alias();
a.tie = tiebreaker;
a.fields = fieldBoosts;
aliases.put(field, a);
}
/**
* Delegates to the super class unless the field has been specified
* as an alias -- in which case we recurse on each of
* the aliased fields, and the results are composed into a
* DisjunctionMaxQuery. (so yes: aliases which point at other
* aliases should work)
*/
protected Query getFieldQuery(String field, String queryText, boolean quoted)
throws ParseException {
if (aliases.containsKey(field)) {
Alias a = aliases.get(field);
DisjunctionMaxQuery q = new DisjunctionMaxQuery(a.tie);
/* we might not get any valid queries from delegation,
* in which case we should return null
*/
boolean ok = false;
for (String f : a.fields.keySet()) {
Query sub = getFieldQuery(f,queryText,quoted);
if (null != sub) {
if (null != a.fields.get(f)) {
sub.setBoost(a.fields.get(f));
}
q.add(sub);
ok = true;
}
}
return ok ? q : null;
} else {
try {
return super.getFieldQuery(field, queryText, quoted);
} catch (Exception e) {
return null;
}
}
}
}
/**
* Determines the correct Sort based on the request parameter "sort"
*
* @return null if no sort is specified.
*/
public static Sort getSort(SolrQueryRequest req) {
String sort = req.getParams().get(CommonParams.SORT);
if (null == sort || sort.equals("")) {
return null;
}
SolrException sortE = null;
Sort ss = null;
try {
ss = QueryParsing.parseSort(sort, req.getSchema());
} catch (SolrException e) {
sortE = e;
}
if ((null == ss) || (null != sortE)) {
/* we definitely had some sort of sort string from the user,
* but no SortSpec came out of it
*/
SolrCore.log.warn("Invalid sort \""+sort+"\" was specified, ignoring", sortE);
return null;
}
return ss;
}
/**
* Builds a list of Query objects that should be used to filter results
* @see CommonParams#FQ
* @return null if no filter queries
*/
public static List<Query> parseFilterQueries(SolrQueryRequest req) throws ParseException {
return parseQueryStrings(req, req.getParams().getParams(CommonParams.FQ));
}
/** Turns an array of query strings into a List of Query objects.
*
* @return null if no queries are generated
*/
public static List<Query> parseQueryStrings(SolrQueryRequest req,
String[] queries) throws ParseException {
if (null == queries || 0 == queries.length) return null;
List<Query> out = new ArrayList<Query>(queries.length);
for (String q : queries) {
if (null != q && 0 != q.trim().length()) {
out.add(QParser.getParser(q, null, req).getQuery());
}
}
return out;
}
/**
* A CacheRegenerator that can be used whenever the items in the cache
* are not dependant on the current searcher.
*
* <p>
* Flat out copies the oldKey=>oldVal pair into the newCache
* </p>
*/
public static class IdentityRegenerator implements CacheRegenerator {
public boolean regenerateItem(SolrIndexSearcher newSearcher,
SolrCache newCache,
SolrCache oldCache,
Object oldKey,
Object oldVal)
throws IOException {
newCache.put(oldKey,oldVal);
return true;
}
}
/**
* Convert a DocList to a SolrDocumentList
*
* The optional param "ids" is populated with the lucene document id
* for each SolrDocument.
*
* @param docs The {@link org.apache.solr.search.DocList} to convert
* @param searcher The {@link org.apache.solr.search.SolrIndexSearcher} to use to load the docs from the Lucene index
* @param fields The names of the Fields to load
* @param ids A map to store the ids of the docs
* @return The new {@link org.apache.solr.common.SolrDocumentList} containing all the loaded docs
* @throws java.io.IOException if there was a problem loading the docs
* @since solr 1.4
*/
public static SolrDocumentList docListToSolrDocumentList(
DocList docs,
SolrIndexSearcher searcher,
Set<String> fields,
Map<SolrDocument, Integer> ids ) throws IOException
{
DocumentBuilder db = new DocumentBuilder(searcher.getSchema());
SolrDocumentList list = new SolrDocumentList();
list.setNumFound(docs.matches());
list.setMaxScore(docs.maxScore());
list.setStart(docs.offset());
DocIterator dit = docs.iterator();
while (dit.hasNext()) {
int docid = dit.nextDoc();
Document luceneDoc = searcher.doc(docid, fields);
SolrDocument doc = new SolrDocument();
db.loadStoredFields(doc, luceneDoc);
// this may be removed if XMLWriter gets patched to
// include score from doc iterator in solrdoclist
if (docs.hasScores()) {
doc.addField("score", dit.score());
} else {
doc.addField("score", 0.0f);
}
list.add( doc );
if( ids != null ) {
ids.put( doc, new Integer(docid) );
}
}
return list;
}
/**
* Given a SolrQueryResponse replace the DocList if it is in the result.
* Otherwise add it to the response
*
* @since solr 1.4
*/
public static void addOrReplaceResults(SolrQueryResponse rsp, SolrDocumentList docs)
{
NamedList vals = rsp.getValues();
int idx = vals.indexOf( "response", 0 );
if( idx >= 0 ) {
log.debug("Replacing DocList with SolrDocumentList " + docs.size());
vals.setVal( idx, docs );
}
else {
log.debug("Adding SolrDocumentList response" + docs.size());
vals.add( "response", docs );
}
}
public static void invokeSetters(Object bean, NamedList initArgs) {
if (initArgs == null) return;
Class clazz = bean.getClass();
Method[] methods = clazz.getMethods();
Iterator<Map.Entry<String, Object>> iterator = initArgs.iterator();
while (iterator.hasNext()) {
Map.Entry<String, Object> entry = iterator.next();
String key = entry.getKey();
String setterName = "set" + String.valueOf(Character.toUpperCase(key.charAt(0))) + key.substring(1);
Method method = null;
try {
for (Method m : methods) {
if (m.getName().equals(setterName) && m.getParameterTypes().length == 1) {
method = m;
break;
}
}
if (method == null) {
throw new RuntimeException("no setter corrresponding to '" + key + "' in " + clazz.getName());
}
Class pClazz = method.getParameterTypes()[0];
Object val = entry.getValue();
method.invoke(bean, val);
} catch (InvocationTargetException e1) {
throw new RuntimeException("Error invoking setter " + setterName + " on class : " + clazz.getName(), e1);
} catch (IllegalAccessException e1) {
throw new RuntimeException("Error invoking setter " + setterName + " on class : " + clazz.getName(), e1);
}
}
}
}