org.apache.solr.request.json.RequestUtil Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of solr-core Show documentation
Show all versions of solr-core Show documentation
Apache Solr (module: core)
/*
* 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.request.json;
import static org.apache.solr.common.params.CommonParams.JSON;
import static org.apache.solr.common.params.CommonParams.SORT;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.params.MultiMapSolrParams;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.util.ContentStream;
import org.apache.solr.common.util.StrUtils;
import org.apache.solr.handler.component.SearchHandler;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.request.SolrRequestHandler;
import org.apache.solr.request.macro.MacroExpander;
import org.apache.solr.search.QueryParsing;
import org.noggit.JSONParser;
import org.noggit.ObjectBuilder;
public class RequestUtil {
/**
* Set default-ish params on a SolrQueryRequest as well as do standard macro processing and JSON
* request parsing.
*
* @param handler The search handler this is for (may be null if you don't want this method
* touching the content streams)
* @param req The request whose params we are interested in
* @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 processParams(
SolrRequestHandler handler,
SolrQueryRequest req,
SolrParams defaults,
SolrParams appends,
SolrParams invariants) {
boolean searchHandler = handler instanceof SearchHandler;
SolrParams params = req.getParams();
// Handle JSON stream for search requests
if (searchHandler && req.getContentStreams() != null) {
Map map = MultiMapSolrParams.asMultiMap(params, false);
if (!(params instanceof MultiMapSolrParams || params instanceof ModifiableSolrParams)) {
// need to set params on request since we weren't able to access the original map
params = new MultiMapSolrParams(map);
req.setParams(params);
}
// params from the query string should come after (and hence override) JSON content streams
String[] jsonFromParams = map.remove(JSON);
for (ContentStream cs : req.getContentStreams()) {
// if BinaryResponseParser.BINARY_CONTENT_TYPE, let the following fail below - we may have
// adjusted the content without updating the content type
// problem in this case happens in a few tests, one seems to happen with kerberos and remote
// node query (HttpSolrCall's request proxy)
String contentType = cs.getContentType();
if (contentType == null || !contentType.contains("/json")) {
throw new SolrException(
SolrException.ErrorCode.BAD_REQUEST,
"Bad contentType for search handler :" + contentType + " request=" + req);
}
try {
String jsonString = StrUtils.stringFromReader(cs.getReader());
MultiMapSolrParams.addParam(JSON, jsonString, map);
} catch (IOException e) {
throw new SolrException(
SolrException.ErrorCode.BAD_REQUEST,
"Exception reading content stream for request:" + req,
e);
}
}
// append existing "json" params
if (jsonFromParams != null) {
for (String json : jsonFromParams) {
MultiMapSolrParams.addParam(JSON, json, map);
}
}
}
String[] jsonS = params.getParams(JSON);
boolean hasAdditions =
defaults != null || invariants != null || appends != null || jsonS != null;
// short circuit processing
if (!hasAdditions && !params.getBool("expandMacros", true)) {
return; // nothing to do...
}
boolean isShard = params.getBool("isShard", false);
Map newMap = MultiMapSolrParams.asMultiMap(params, hasAdditions);
// see if the json has a "params" section
// TODO: we should currently *not* do this if this is a leaf of a distributed search since it
// could overwrite parameters set by the top-level
// The parameters we extract will be propagated anyway.
if (jsonS != null && !isShard) {
for (String json : jsonS) {
getParamsFromJSON(newMap, json);
}
}
// first populate defaults, etc..
if (defaults != null) {
Map defaultsMap = MultiMapSolrParams.asMultiMap(defaults);
for (Map.Entry entry : defaultsMap.entrySet()) {
String key = entry.getKey();
if (!newMap.containsKey(key)) {
newMap.put(key, entry.getValue());
}
}
}
if (appends != null) {
Map appendsMap = MultiMapSolrParams.asMultiMap(appends);
for (Map.Entry entry : appendsMap.entrySet()) {
String key = entry.getKey();
String[] arr = newMap.get(key);
if (arr == null) {
newMap.put(key, entry.getValue());
} else {
String[] appendArr = entry.getValue();
String[] newArr = new String[arr.length + appendArr.length];
System.arraycopy(arr, 0, newArr, 0, arr.length);
System.arraycopy(appendArr, 0, newArr, arr.length, appendArr.length);
newMap.put(key, newArr);
}
}
}
if (invariants != null) {
newMap.putAll(MultiMapSolrParams.asMultiMap(invariants));
}
if (!isShard) { // Don't expand macros in shard requests
String[] doMacrosStr = newMap.get("expandMacros");
boolean doMacros = true;
if (doMacrosStr != null) {
doMacros = "true".equals(doMacrosStr[0]);
}
if (doMacros) {
newMap = MacroExpander.expand(newMap);
}
}
// Set these params as soon as possible so if there is an error processing later, things like
// "wt=json" will take effect from the defaults.
SolrParams newParams =
new MultiMapSolrParams(newMap); // newMap may still change below, but that should be OK
req.setParams(newParams);
// Skip the rest of the processing (including json processing for now) if this isn't a search
// handler. For example json.command started to be used in SOLR-6294, and that caused errors
// here.
if (!searchHandler) return;
Map json = null;
// Handle JSON body first, so query params will always overlay on that
jsonS = newMap.get(JSON);
if (jsonS != null) {
if (json == null) {
json = new LinkedHashMap<>();
}
mergeJSON(json, JSON, jsonS, new ObjectUtil.ConflictHandler());
}
for (Map.Entry entry : newMap.entrySet()) {
String key = entry.getKey();
// json.nl, json.wrf are existing query parameters
if (key.startsWith("json.") && !("json.nl".equals(key) || "json.wrf".equals(key))) {
if (json == null) {
json = new LinkedHashMap<>();
}
mergeJSON(json, key, entry.getValue(), new ObjectUtil.ConflictHandler());
}
}
// implement compat for existing components...
JsonQueryConverter jsonQueryConverter = new JsonQueryConverter();
if (json != null && !isShard) {
for (Map.Entry entry : json.entrySet()) {
String key = entry.getKey();
String out = null;
boolean isQuery = false;
boolean arr = false;
if ("query".equals(key)) {
out = "q";
isQuery = true;
// if the value is not a String, then it'll get converted to a localParams query String.
// Only the "lucene" query parser can parse it. Ignore anything else that may exist.
if (!(entry.getValue() instanceof String)) {
newMap.put(QueryParsing.DEFTYPE, new String[] {"lucene"});
}
} else if ("filter".equals(key)) {
out = "fq";
arr = true;
isQuery = true;
} else if ("fields".equals(key)) {
out = "fl";
arr = true;
} else if ("offset".equals(key)) {
out = "start";
} else if ("limit".equals(key)) {
out = "rows";
} else if (SORT.equals(key)) {
out = SORT;
} else if ("queries".equals(key)) {
Object queriesJsonObj = entry.getValue();
if (queriesJsonObj instanceof Map && queriesJsonObj != null) {
@SuppressWarnings("unchecked")
final Map queriesAsMap = (Map) queriesJsonObj;
for (Map.Entry queryJsonProperty : queriesAsMap.entrySet()) {
out = queryJsonProperty.getKey();
arr = true;
isQuery = true;
convertJsonPropertyToLocalParams(
newMap, jsonQueryConverter, queryJsonProperty, out, isQuery, arr);
}
continue;
} else {
throw new SolrException(
SolrException.ErrorCode.BAD_REQUEST,
"Expected Map for 'queries', received " + queriesJsonObj);
}
} else if ("params".equals(key) || "facet".equals(key)) {
// handled elsewhere
continue;
} else {
throw new SolrException(
SolrException.ErrorCode.BAD_REQUEST,
"Unknown top-level key in JSON request : " + key);
}
convertJsonPropertyToLocalParams(newMap, jsonQueryConverter, entry, out, isQuery, arr);
}
}
if (json != null) {
req.setJSON(json);
}
}
private static void convertJsonPropertyToLocalParams(
Map outMap,
JsonQueryConverter jsonQueryConverter,
Map.Entry jsonProperty,
String outKey,
boolean isQuery,
boolean arr) {
Object val = jsonProperty.getValue();
if (arr) {
String[] existing = outMap.get(outKey);
List lst = val instanceof List ? (List) val : null;
int existingSize = existing == null ? 0 : existing.length;
int jsonSize = lst == null ? 1 : lst.size();
String[] newval = new String[existingSize + jsonSize];
for (int i = 0; i < existingSize; i++) {
newval[i] = existing[i];
}
if (lst != null) {
for (int i = 0; i < jsonSize; i++) {
Object v = lst.get(i);
newval[existingSize + i] =
isQuery ? jsonQueryConverter.toLocalParams(v, outMap) : v.toString();
}
} else {
newval[newval.length - 1] =
isQuery ? jsonQueryConverter.toLocalParams(val, outMap) : val.toString();
}
outMap.put(outKey, newval);
} else {
outMap.put(
outKey,
new String[] {isQuery ? jsonQueryConverter.toLocalParams(val, outMap) : val.toString()});
}
}
// queryParamName is something like json.facet or json.query, or just json...
private static void mergeJSON(
Map json,
String queryParamName,
String[] vals,
ObjectUtil.ConflictHandler handler) {
try {
List path = StrUtils.splitSmart(queryParamName, ".", true);
path = path.subList(1, path.size());
for (String jsonStr : vals) {
Object o = ObjectBuilder.fromJSONStrict(jsonStr);
// zero-length strings or comments can cause this to be null (and a zero-length string can
// result from a json content-type w/o a body)
if (o != null) {
ObjectUtil.mergeObjects(json, path, o, handler);
}
}
} catch (JSONParser.ParseException e) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, e);
} catch (IOException e) {
// impossible
}
}
private static void getParamsFromJSON(Map params, String json) {
if (!json.contains("params")) {
return;
}
JSONParser parser = new JSONParser(json);
try {
JSONUtil.expect(parser, JSONParser.OBJECT_START);
boolean found = JSONUtil.advanceToMapKey(parser, "params", false);
if (!found) {
return;
}
parser.nextEvent(); // advance to the value
Object o = ObjectBuilder.getVal(parser);
if (!(o instanceof Map)) return;
@SuppressWarnings("unchecked")
Map map = (Map) o;
// To make consistent with json.param handling, we should make query params come after json
// params (i.e. query params should appear to overwrite json params.
// Solr params are based on String though, so we need to convert
for (Map.Entry entry : map.entrySet()) {
String key = entry.getKey();
Object val = entry.getValue();
if (params.get(key) != null) {
continue;
}
if (val == null) {
params.remove(key);
} else if (val instanceof List) {
List lst = (List) val;
String[] vals = new String[lst.size()];
for (int i = 0; i < vals.length; i++) {
vals[i] = lst.get(i).toString();
}
params.put(key, vals);
} else {
params.put(key, new String[] {val.toString()});
}
}
} catch (Exception e) {
// ignore parse exceptions at this stage, they may be caused by incomplete macro expansions
return;
}
}
}