blob: b103f0976f3e3347b394a2419e390a3ac546c17a [file] [log] [blame]
/*
* 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.handler;
import java.io.Closeable;
import java.lang.invoke.MethodHandles;
import java.util.Collection;
import com.codahale.metrics.Counter;
import com.codahale.metrics.Meter;
import com.codahale.metrics.Timer;
import com.google.common.collect.ImmutableList;
import org.apache.solr.api.Api;
import org.apache.solr.api.ApiBag;
import org.apache.solr.api.ApiSupport;
import org.apache.solr.common.AlreadyClosedException;
import org.apache.solr.common.ParWork;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.params.ShardParams;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.SuppressForbidden;
import org.apache.solr.core.PluginBag;
import org.apache.solr.core.PluginInfo;
import org.apache.solr.core.SolrInfoBean;
import org.apache.solr.handler.admin.PrepRecoveryOp;
import org.apache.solr.logging.MDCLoggingContext;
import org.apache.solr.metrics.SolrMetricsContext;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.request.SolrRequestHandler;
import org.apache.solr.response.SolrQueryResponse;
import org.apache.solr.search.SyntaxError;
import org.apache.solr.util.SolrPluginUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.apache.solr.core.RequestParams.USEPARAM;
/**
*
*/
public abstract class RequestHandlerBase implements SolrRequestHandler, SolrInfoBean, NestedRequestHandler, ApiSupport, Closeable {
@SuppressWarnings({"rawtypes"})
protected NamedList initArgs = null;
protected SolrParams defaults;
protected SolrParams appends;
protected SolrParams invariants;
protected boolean httpCaching = true;
// Statistics
private Meter numErrors = new Meter();
private Meter numServerErrors = new Meter();
private Meter numClientErrors = new Meter();
private Meter numTimeouts = new Meter();
private Counter requests = new Counter();
// private final Map<String, Counter> shardPurposes = new ConcurrentHashMap<>();
private Timer requestTimes = new Timer();
// private Timer distribRequestTimes = new Timer();
// private Timer localRequestTimes = new Timer();
private Counter totalTime = new Counter();
private Counter distribTotalTime = new Counter();
private Counter localTotalTime = new Counter();
private final long handlerStart;
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private PluginInfo pluginInfo;
protected SolrMetricsContext solrMetricsContext;
@SuppressForbidden(reason = "Need currentTimeMillis, used only for stats output")
public RequestHandlerBase() {
handlerStart = System.currentTimeMillis();
}
/**
* Initializes the {@link org.apache.solr.request.SolrRequestHandler} by creating three {@link org.apache.solr.common.params.SolrParams} named.
* <table style="border: 1px solid">
* <caption>table of parameters</caption>
* <tr><th>Name</th><th>Description</th></tr>
* <tr><td>defaults</td><td>Contains all of the named arguments contained within the list element named "defaults".</td></tr>
* <tr><td>appends</td><td>Contains all of the named arguments contained within the list element named "appends".</td></tr>
* <tr><td>invariants</td><td>Contains all of the named arguments contained within the list element named "invariants".</td></tr>
* </table>
* <p>
* Example:
* <pre>
* &lt;lst name="defaults"&gt;
* &lt;str name="echoParams"&gt;explicit&lt;/str&gt;
* &lt;str name="qf"&gt;text^0.5 features^1.0 name^1.2 sku^1.5 id^10.0&lt;/str&gt;
* &lt;str name="mm"&gt;2&lt;-1 5&lt;-2 6&lt;90%&lt;/str&gt;
* &lt;str name="bq"&gt;incubationdate_dt:[* TO NOW/DAY-1MONTH]^2.2&lt;/str&gt;
* &lt;/lst&gt;
* &lt;lst name="appends"&gt;
* &lt;str name="fq"&gt;inStock:true&lt;/str&gt;
* &lt;/lst&gt;
*
* &lt;lst name="invariants"&gt;
* &lt;str name="facet.field"&gt;cat&lt;/str&gt;
* &lt;str name="facet.field"&gt;manu_exact&lt;/str&gt;
* &lt;str name="facet.query"&gt;price:[* TO 500]&lt;/str&gt;
* &lt;str name="facet.query"&gt;price:[500 TO *]&lt;/str&gt;
* &lt;/lst&gt;
* </pre>
*
* @param args The {@link org.apache.solr.common.util.NamedList} to initialize from
* @see #handleRequest(org.apache.solr.request.SolrQueryRequest, org.apache.solr.response.SolrQueryResponse)
* @see #handleRequestBody(org.apache.solr.request.SolrQueryRequest, org.apache.solr.response.SolrQueryResponse)
* @see org.apache.solr.util.SolrPluginUtils#setDefaults(org.apache.solr.request.SolrQueryRequest, org.apache.solr.common.params.SolrParams, org.apache.solr.common.params.SolrParams, org.apache.solr.common.params.SolrParams)
* @see NamedList#toSolrParams()
* <p>
* See also the example solrconfig.xml located in the Solr codebase (example/solr/conf).
*/
@Override
public void init(@SuppressWarnings({"rawtypes"})NamedList args) {
initArgs = args;
if (args != null) {
defaults = getSolrParamsFromNamedList(args, "defaults");
appends = getSolrParamsFromNamedList(args, "appends");
invariants = getSolrParamsFromNamedList(args, "invariants");
}
if (initArgs != null) {
Object caching = initArgs.get("httpCaching");
httpCaching = caching != null ? Boolean.parseBoolean(caching.toString()) : true;
}
}
@Override
public SolrMetricsContext getSolrMetricsContext() {
return solrMetricsContext;
}
@Override
public void initializeMetrics(SolrMetricsContext parentContext, String scope) {
this.solrMetricsContext = parentContext.getChildContext(this);
numErrors = solrMetricsContext.meter("errors", getCategory().toString(), scope);
numServerErrors = solrMetricsContext.meter("serverErrors", getCategory().toString(), scope);
numClientErrors = solrMetricsContext.meter("clientErrors", getCategory().toString(), scope);
numTimeouts = solrMetricsContext.meter("timeouts", getCategory().toString(), scope);
requests = solrMetricsContext.counter("requests", getCategory().toString(), scope);
// MRM TODO:
// MetricsMap metricsMap = new MetricsMap((detail, map) ->
// shardPurposes.forEach((k, v) -> map.put(k, v.getCount())));
//solrMetricsContext.gauge(metricsMap, true, "shardRequests", getCategory().toString(), scope);
requestTimes = solrMetricsContext.timer("requestTimes", getCategory().toString(), scope);
// distribRequestTimes = solrMetricsContext.timer("requestTimes", getCategory().toString(), scope, "distrib");
// localRequestTimes = solrMetricsContext.timer("requestTimes", getCategory().toString(), scope, "local");
totalTime = solrMetricsContext.counter("totalTime", getCategory().toString(), scope);
distribTotalTime = solrMetricsContext.counter("totalTime", getCategory().toString(), scope, "distrib");
localTotalTime = solrMetricsContext.counter("totalTime", getCategory().toString(), scope, "local");
solrMetricsContext.gauge(() -> handlerStart, true, "handlerStart", getCategory().toString(), scope);
}
public static SolrParams getSolrParamsFromNamedList(@SuppressWarnings({"rawtypes"})NamedList args, String key) {
Object o = args.get(key);
if (o != null && o instanceof NamedList) {
return ((NamedList) o).toSolrParams();
}
return null;
}
@SuppressWarnings({"rawtypes"})
public NamedList getInitArgs() {
return initArgs;
}
public abstract void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception;
@Override
public void handleRequest(SolrQueryRequest req, SolrQueryResponse rsp) {
if (req.getCore() != null) {
MDCLoggingContext.setCoreName(req.getCore().getName());
}
try {
requests.inc();
// requests are distributed by default when ZK is in use, unless indicated otherwise
boolean distrib = req.getParams().getBool(CommonParams.DISTRIB, req.getCore() != null ? req.getCore().getCoreContainer().isZooKeeperAware() : false);
if (req.getParams().getBool(ShardParams.IS_SHARD, false)) {
// shardPurposes.computeIfAbsent("total", name -> new Counter()).inc();
int purpose = req.getParams().getInt(ShardParams.SHARDS_PURPOSE, 0);
if (purpose != 0) {
String[] names = SolrPluginUtils.getRequestPurposeNames(purpose);
// for (String n : names) {
// shardPurposes.computeIfAbsent(n, name -> new Counter()).inc();
// }
}
}
Timer.Context timer = requestTimes.time();
// @SuppressWarnings("resource")
// Timer.Context dTimer = distrib ? distribRequestTimes.time() : localRequestTimes.time();
try {
if (pluginInfo != null && pluginInfo.attributes.containsKey(USEPARAM)) req.getContext().put(USEPARAM, pluginInfo.attributes.get(USEPARAM));
SolrPluginUtils.setDefaults(this, req, defaults, appends, invariants);
req.getContext().remove(USEPARAM);
rsp.setHttpCaching(httpCaching);
handleRequestBody(req, rsp);
// count timeouts
@SuppressWarnings({"rawtypes"}) NamedList header = rsp.getResponseHeader();
if (header != null) {
if (Boolean.TRUE.equals(header.getBooleanArg(SolrQueryResponse.RESPONSE_HEADER_PARTIAL_RESULTS_KEY))) {
numTimeouts.mark();
rsp.setHttpCaching(false);
}
}
} catch (InterruptedException e) {
ParWork.propagateInterrupt(e);
throw new AlreadyClosedException(e);
} catch (Exception e) {
if (log.isDebugEnabled() && !(e instanceof PrepRecoveryOp.NotValidLeader)) {
log.error("Exception handling request", e);
}
if (req.getCore() != null) {
boolean isTragic = req.getCore().getCoreContainer().checkTragicException(req.getCore());
if (isTragic) {
if (e instanceof SolrException) {
// Tragic exceptions should always throw a server error
//assert ((SolrException) e).code() == 500;
} else {
// wrap it in a solr exception
e = new SolrException(SolrException.ErrorCode.SERVER_ERROR, e.getMessage(), e);
}
}
}
boolean incrementErrors = true;
boolean isServerError = true;
if (e instanceof SolrException) {
SolrException se = (SolrException) e;
if (se.code() == SolrException.ErrorCode.CONFLICT.code) {
incrementErrors = false;
} else if (se.code() >= 400 && se.code() < 500) {
isServerError = false;
}
} else {
if (e instanceof SyntaxError) {
isServerError = false;
e = new SolrException(SolrException.ErrorCode.BAD_REQUEST, e);
}
}
if (e instanceof PrepRecoveryOp.NotValidLeader) {
isServerError = false;
incrementErrors = false;
}
rsp.setException(e);
if (incrementErrors) {
SolrException.log(log, e);
numErrors.mark();
if (isServerError) {
numServerErrors.mark();
} else {
numClientErrors.mark();
}
}
} finally {
//dTimer.stop();
long elapsed = timer.stop();
totalTime.inc(elapsed);
if (distrib) {
//distribTotalTime.inc(elapsed);
} else {
//localTotalTime.inc(elapsed);
}
}
} finally {
if (req.getCore() != null) {
MDCLoggingContext.clear();
}
}
}
//////////////////////// SolrInfoMBeans methods //////////////////////
@Override
public String getName() {
return this.getClass().getName();
}
@Override
public abstract String getDescription();
@Override
public Category getCategory() {
return Category.QUERY;
}
@Override
public SolrRequestHandler getSubHandler(String subPath) {
return null;
}
/**
* Get the request handler registered to a given name.
* <p>
* This function is thread safe.
*/
public static SolrRequestHandler getRequestHandler(String handlerName, PluginBag<SolrRequestHandler> reqHandlers) {
if (log.isDebugEnabled()) {
log.debug("get request handler {} from {}", handlerName, reqHandlers);
}
if (handlerName == null) return null;
SolrRequestHandler handler = reqHandlers.get(handlerName);
int idx = 0;
if (handler == null) {
for (; ; ) {
idx = handlerName.indexOf('/', idx + 1);
if (idx > 0) {
String firstPart = handlerName.substring(0, idx);
handler = reqHandlers.get(firstPart);
if (handler == null) continue;
if (handler instanceof NestedRequestHandler) {
return ((NestedRequestHandler) handler).getSubHandler(handlerName.substring(idx));
}
} else {
break;
}
}
}
return handler;
}
public void setPluginInfo(PluginInfo pluginInfo) {
if (this.pluginInfo == null) this.pluginInfo = pluginInfo;
}
public PluginInfo getPluginInfo() {
return pluginInfo;
}
@Override
public Collection<Api> getApis() {
return ImmutableList.of(new ApiBag.ReqHandlerToApi(this, ApiBag.constructSpec(pluginInfo)));
}
@Override
public void close() {
}
}