| /* |
| * 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.lang.invoke.MethodHandles; |
| import java.util.Collection; |
| import java.util.Map; |
| import java.util.concurrent.ConcurrentHashMap; |
| |
| 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.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.metrics.MetricsMap; |
| 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 { |
| |
| @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> |
| * <lst name="defaults"> |
| * <str name="echoParams">explicit</str> |
| * <str name="qf">text^0.5 features^1.0 name^1.2 sku^1.5 id^10.0</str> |
| * <str name="mm">2<-1 5<-2 6<90%</str> |
| * <str name="bq">incubationdate_dt:[* TO NOW/DAY-1MONTH]^2.2</str> |
| * </lst> |
| * <lst name="appends"> |
| * <str name="fq">inStock:true</str> |
| * </lst> |
| * |
| * <lst name="invariants"> |
| * <str name="facet.field">cat</str> |
| * <str name="facet.field">manu_exact</str> |
| * <str name="facet.query">price:[* TO 500]</str> |
| * <str name="facet.query">price:[500 TO *]</str> |
| * </lst> |
| * </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); |
| 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) { |
| 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 (Exception 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); |
| } |
| } |
| |
| 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); |
| } |
| } |
| } |
| |
| //////////////////////// 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 (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))); |
| } |
| } |
| |
| |