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

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

There is a newer version: 2.18.0
Show newest version
/*
 * SPDX-License-Identifier: Apache-2.0
 *
 * The OpenSearch Contributors require contributions made to
 * this file be licensed under the Apache-2.0 license or a
 * compatible open source license.
 */

/*
 * Licensed to Elasticsearch under one or more contributor
 * license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright
 * ownership. Elasticsearch 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.
 */

/*
 * Modifications Copyright OpenSearch Contributors. See
 * GitHub history for details.
 */

package org.opensearch.rest;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.lucene.search.spell.LevenshteinDistance;
import org.apache.lucene.util.CollectionUtil;
import org.opensearch.OpenSearchParseException;
import org.opensearch.action.support.clustermanager.ClusterManagerNodeRequest;
import org.opensearch.client.node.NodeClient;
import org.opensearch.common.CheckedConsumer;
import org.opensearch.common.annotation.ExperimentalApi;
import org.opensearch.common.annotation.PublicApi;
import org.opensearch.common.collect.Tuple;
import org.opensearch.common.settings.Setting;
import org.opensearch.common.settings.Setting.Property;
import org.opensearch.core.rest.RestStatus;
import org.opensearch.core.xcontent.XContentBuilder;
import org.opensearch.plugins.ActionPlugin;
import org.opensearch.rest.action.admin.cluster.RestNodesUsageAction;
import org.opensearch.tasks.Task;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
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()}. * * @opensearch.api */ @PublicApi(since = "1.0.0") public abstract class BaseRestHandler implements RestHandler { 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(); /** * @deprecated declare your own logger. */ @Deprecated protected Logger logger = LogManager.getLogger(getClass()); 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(); @Override public final void handleRequest(RestRequest request, RestChannel channel, NodeClient client) throws Exception { // prepare the request for execution; has the side effect of touching the request parameters final RestChannelConsumer 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 -> !responseParams().contains(p)) .collect(Collectors.toCollection(TreeSet::new)); // validate the non-response params if (!unconsumedParams.isEmpty()) { final Set candidateParams = new HashSet<>(); candidateParams.addAll(request.consumedParams()); candidateParams.addAll(responseParams()); throw new IllegalArgumentException(unrecognized(request, unconsumedParams, candidateParams, "parameter")); } usageCount.increment(); // execute the action action.accept(channel); } public static String unrecognizedStrings( final RestRequest request, final Set invalids, final Set candidates, final String detail ) { StringBuilder message = new StringBuilder( String.format(Locale.ROOT, "request [%s] contains unrecognized %s%s: ", request.path(), detail, invalids.size() > 1 ? "s" : "") ); boolean first = true; for (final String invalid : invalids) { final LevenshteinDistance ld = new LevenshteinDistance(); final List> scoredParams = new ArrayList<>(); for (final String candidate : candidates) { final float distance = ld.getDistance(invalid, candidate); if (distance > 0.5f) { scoredParams.add(new Tuple<>(distance, candidate)); } } CollectionUtil.timSort(scoredParams, (a, b) -> { // sort by distance in reverse order, then parameter name for equal distances int compare = a.v1().compareTo(b.v1()); if (compare != 0) return -compare; else return a.v2().compareTo(b.v2()); }); if (first == false) { message.append(", "); } message.append("[").append(invalid).append("]"); final List keys = scoredParams.stream().map(Tuple::v2).collect(Collectors.toList()); if (keys.isEmpty() == false) { message.append(" -> did you mean "); if (keys.size() == 1) { message.append("[").append(keys.get(0)).append("]"); } else { message.append("any of ").append(keys.toString()); } message.append("?"); } first = false; } return message.toString(); } /** * Returns a String message of the detail of any unrecognized error occurred. The string is intended for use in error messages to be returned to the user. * * @param request The request that caused the exception * @param invalids Strings from the request which were unable to be understood. * @param candidates A set of words that are most likely to be the valid strings determined invalid, to be suggested to the user. * @param detail The parameter contains the details of the exception. * @return a String that contains the message. */ protected final String unrecognized( final RestRequest request, final Set invalids, final Set candidates, final String detail ) { return unrecognizedStrings(request, invalids, candidates, detail); } /** * REST requests are handled by preparing a channel consumer that represents the execution of * the request against a channel. * * @opensearch.api */ @FunctionalInterface @PublicApi(since = "1.0.0") protected interface RestChannelConsumer extends CheckedConsumer {} /** * Streaming REST requests are handled by preparing a streaming channel consumer that represents the execution of * the request against a channel. * * @opensearch.experimental */ @FunctionalInterface @ExperimentalApi protected interface StreamingRestChannelConsumer extends CheckedConsumer {} /** * 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(); } protected static final String DUPLICATE_PARAMETER_ERROR_MESSAGE = "Please only use one of the request parameters [master_timeout, cluster_manager_timeout]."; /** * Parse the deprecated request parameter 'master_timeout', and add deprecated log if the parameter is used. * It also validates whether the two parameters 'master_timeout' and 'cluster_manager_timeout' are not assigned together. * Deprecation log is not emitted intentionally, because 'master_timeout' is remained in High-Level-REST-Client to keep compatibility with server 1.x. * The method is temporarily added in 2.0 during applying inclusive language. Remove the method along with the parameter 'master_timeout'. * @param mnr the action request * @param request the REST request to handle */ public static void parseDeprecatedMasterTimeoutParameter(ClusterManagerNodeRequest mnr, RestRequest request) { if (request.hasParam("master_timeout")) { if (request.hasParam("cluster_manager_timeout")) { throw new OpenSearchParseException(DUPLICATE_PARAMETER_ERROR_MESSAGE); } mnr.clusterManagerNodeTimeout(request.paramAsTime("master_timeout", mnr.clusterManagerNodeTimeout())); } } /** * A wrapper for the base handler. * * @opensearch.internal */ public static class Wrapper extends BaseRestHandler { protected final BaseRestHandler delegate; public Wrapper(BaseRestHandler delegate) { this.delegate = Objects.requireNonNull(delegate, "BaseRestHandler delegate can not be null"); } @Override public String getName() { return delegate.getName(); } @Override public List routes() { return delegate.routes(); } @Override public List deprecatedRoutes() { return delegate.deprecatedRoutes(); } @Override public List replacedRoutes() { return delegate.replacedRoutes(); } @Override protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { return delegate.prepareRequest(request, client); } @Override protected Set responseParams() { return delegate.responseParams(); } @Override public boolean canTripCircuitBreaker() { return delegate.canTripCircuitBreaker(); } @Override public boolean supportsContentStream() { return delegate.supportsContentStream(); } @Override public boolean allowsUnsafeBuffers() { return delegate.allowsUnsafeBuffers(); } @Override public boolean allowSystemIndexAccessByDefault() { return delegate.allowSystemIndexAccessByDefault(); } @Override public boolean supportsStreaming() { return delegate.supportsStreaming(); } } /** * Return a task immediately when executing some long-running operations asynchronously, like reindex, resize, open, force merge */ public RestChannelConsumer sendTask(String nodeId, Task task) { return channel -> { try (XContentBuilder builder = channel.newBuilder()) { builder.startObject(); builder.field("task", nodeId + ":" + task.getId()); builder.endObject(); channel.sendResponse(new BytesRestResponse(RestStatus.OK, builder)); } }; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy