All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.elasticsearch.rest.BaseRestHandler Maven / Gradle / Ivy

There is a newer version: 8.16.0
Show newest version
/*
 * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
 * or more contributor license agreements. Licensed under the Elastic License
 * 2.0 and the Server Side Public License, v 1; you may not use this file except
 * in compliance with, at your election, the Elastic License 2.0 or the Server
 * Side Public License, v 1.
 */

package org.elasticsearch.rest;

import org.apache.lucene.search.spell.LevenshteinDistance;
import org.elasticsearch.client.internal.node.NodeClient;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Setting.Property;
import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.core.CheckedConsumer;
import org.elasticsearch.core.RefCounted;
import org.elasticsearch.core.Releasable;
import org.elasticsearch.core.RestApiVersion;
import org.elasticsearch.core.Tuple;
import org.elasticsearch.plugins.ActionPlugin;
import org.elasticsearch.rest.action.admin.cluster.RestNodesUsageAction;

import java.io.IOException;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.atomic.LongAdder;
import java.util.stream.Collectors;

/**
 * Base handler for REST requests.
 * 

* This handler makes sure that the headers & context of the handled {@link RestRequest requests} are copied over to * the transport requests executed by the associated client. While the context is fully copied over, not all the headers * are copied, but a selected few. It is possible to control what headers are copied over by returning them in * {@link ActionPlugin#getRestHeaders()}. */ public abstract class BaseRestHandler implements RestHandler { /** * Parameter that controls whether certain REST apis should include type names in their requests or responses. * Note: This parameter is only available through compatible rest api for {@link RestApiVersion#V_7}. */ public static final String INCLUDE_TYPE_NAME_PARAMETER = "include_type_name"; public static final boolean DEFAULT_INCLUDE_TYPE_NAME_POLICY = false; public static final Setting MULTI_ALLOW_EXPLICIT_INDEX = Setting.boolSetting( "rest.action.multi.allow_explicit_index", true, Property.NodeScope ); private final LongAdder usageCount = new LongAdder(); public final long getUsageCount() { return usageCount.sum(); } /** * @return the name of this handler. The name should be human readable and * should describe the action that will performed when this API is * called. This name is used in the response to the * {@link RestNodesUsageAction}. */ public abstract String getName(); /** * {@inheritDoc} */ @Override public abstract List routes(); private static final Set ALWAYS_SUPPORTED = Set.of("format", "filter_path", "pretty", "human"); @Override public final void handleRequest(RestRequest request, RestChannel channel, NodeClient client) throws Exception { // check if the query has any parameters that are not in the supported set (if declared) Set supported = allSupportedParameters(); if (supported != null) { var allSupported = Sets.union(RestResponse.RESPONSE_PARAMS, ALWAYS_SUPPORTED, supported); if (allSupported.containsAll(request.params().keySet()) == false) { Set unsupported = Sets.difference(request.params().keySet(), allSupported); throw new IllegalArgumentException(unrecognized(request, unsupported, allSupported, "parameter")); } } // prepare the request for execution; has the side effect of touching the request parameters try (var action = prepareRequest(request, client)) { // validate unconsumed params, but we must exclude params used to format the response // use a sorted set so the unconsumed parameters appear in a reliable sorted order final SortedSet unconsumedParams = request.unconsumedParams() .stream() .filter(p -> RestResponse.RESPONSE_PARAMS.contains(p) == false) .filter(p -> responseParams(request.getRestApiVersion()).contains(p) == false) .collect(Collectors.toCollection(TreeSet::new)); // validate the non-response params if (unconsumedParams.isEmpty() == false) { final Set candidateParams = new HashSet<>(); candidateParams.addAll(request.consumedParams()); candidateParams.addAll(responseParams(request.getRestApiVersion())); throw new IllegalArgumentException(unrecognized(request, unconsumedParams, candidateParams, "parameter")); } if (request.hasContent() && request.isContentConsumed() == false) { throw new IllegalArgumentException( "request [" + request.method() + " " + request.path() + "] does not support having a body" ); } usageCount.increment(); // execute the action action.accept(channel); } } protected static String unrecognized(RestRequest request, Set invalids, Set candidates, String detail) { StringBuilder message = new StringBuilder().append("request [") .append(request.path()) .append("] contains unrecognized ") .append(detail) .append(invalids.size() > 1 ? "s" : "") .append(": "); for (Iterator it = invalids.iterator(); it.hasNext();) { String invalid = it.next(); LevenshteinDistance ld = new LevenshteinDistance(); List candidateParams = candidates.stream() .map(c -> Tuple.tuple(ld.getDistance(invalid, c), c)) .filter(t -> t.v1() > 0.5f) .sorted(Comparator., Float>comparing(Tuple::v1).reversed().thenComparing(Tuple::v2)) .map(Tuple::v2) .toList(); message.append("[").append(invalid).append("]"); if (candidateParams.isEmpty() == false) { message.append(" -> did you mean "); if (candidateParams.size() > 1) { message.append("any of "); } message.append(candidateParams); message.append("?"); } if (it.hasNext()) { message.append(", "); } } return message.toString(); } /** * REST requests are handled by preparing a channel consumer that represents the execution of the request against a channel. */ @FunctionalInterface protected interface RestChannelConsumer extends CheckedConsumer, Releasable { /** * Called just after the execution has started (or failed, if the request was invalid), but typically well before the execution has * completed. This callback should be used to release (refs to) resources that were acquired when constructing this consumer, for * instance by calling {@link RefCounted#decRef()} on any newly-created transport requests with nontrivial lifecycles. */ @Override default void close() {} } /** * Prepare the request for execution. Implementations should consume all request params before * returning the runnable for actual execution. Unconsumed params will immediately terminate * execution of the request. However, some params are only used in processing the response; * implementations can override {@link BaseRestHandler#responseParams()} to indicate such * params. * * @param request the request to execute * @param client client for executing actions on the local node * @return the action to execute * @throws IOException if an I/O exception occurred parsing the request and preparing for * execution */ protected abstract RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException; /** * Parameters used for controlling the response and thus might not be consumed during * preparation of the request execution in * {@link BaseRestHandler#prepareRequest(RestRequest, NodeClient)}. * * @return a set of parameters used to control the response and thus should not trip strict * URL parameter checks. */ protected Set responseParams() { return Collections.emptySet(); } /** * Parameters used for controlling the response and thus might not be consumed during * preparation of the request execution. The value depends on the RestApiVersion provided * by a user on a request. * Used in RestHandlers with Compatible Rest Api * @param restApiVersion - a version provided by a user on a request * @return a set of parameters used to control the response, depending on a restApiVersion */ protected Set responseParams(RestApiVersion restApiVersion) { return responseParams(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy