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

co.elastic.clients.transport.endpoints.EndpointBase Maven / Gradle / Ivy

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

import co.elastic.clients.elasticsearch._types.ErrorCause;
import co.elastic.clients.elasticsearch._types.ErrorResponse;
import co.elastic.clients.json.JsonpDeserializer;
import co.elastic.clients.json.JsonpDeserializerBase;
import co.elastic.clients.json.JsonpMapper;
import co.elastic.clients.json.JsonpUtils;
import co.elastic.clients.transport.Endpoint;
import jakarta.json.stream.JsonParser;

import javax.annotation.Nullable;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.BitSet;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Map;
import java.util.function.Function;

public class EndpointBase implements Endpoint {

    private static final Function> EMPTY_MAP = x -> Collections.emptyMap();
    private static final Function RETURN_NULL = x -> null;
    private static final Function RETURN_SELF = x -> x;

    /**
     * Returns a function that always returns an empty String to String map. Useful to avoid creating lots of
     * duplicate lambdas in endpoints that don't have headers or parameters.
     */
    @SuppressWarnings("unchecked")
    public static  Function> emptyMap() {
        return (Function>) EMPTY_MAP;
    }

    /**
     * Returns a function that always returns {@code null}.
     */
    @SuppressWarnings("unchecked")
    static  Function returnNull() {
        return (Function) RETURN_NULL;
    }

    /**
     * Returns a function that always returns its parameter. It's similar to {@code Function.identity()} with the difference
     * that the input and output generic parameters are different, making it suitable for use in a wider range of use cases.
     */
    @SuppressWarnings("unchecked")
    static  Function returnSelf() {
        return (Function) RETURN_SELF;
    }

    protected final String id;
    protected final Function method;
    protected final Function requestUrl;
    protected final Function> pathParameters;
    protected final Function> queryParameters;
    protected final Function> headers;
    protected final Function body;

    public EndpointBase(
        String id,
        Function method,
        Function requestUrl,
        Function> pathParameters,
        Function> queryParameters,
        Function> headers,
        Function body
    ) {
        this.id = id;
        this.method = method;
        this.requestUrl = requestUrl;
        this.pathParameters = pathParameters;
        this.queryParameters = queryParameters;
        this.headers = headers;
        this.body = body;
    }

    @Override
    public String id() {
        return this.id;
    }

    @Override
    public String method(RequestT request) {
        return this.method.apply(request);
    }

    @Override
    public String requestUrl(RequestT request) {
        return this.requestUrl.apply(request);
    }

    @Override
    public Map pathParameters(RequestT request) {
        return this.pathParameters.apply(request);
    }

    @Override
    public Map queryParameters(RequestT request) {
        return this.queryParameters.apply(request);
    }

    @Override
    public Map headers(RequestT request) {
        return this.headers.apply(request);
    }

    @Nullable
    @Override
    public Object body(RequestT request) {
        return this.body.apply(request);
    }

    // ES-specific
    @Override
    public boolean isError(int statusCode) {
        return statusCode >= 400;
    }

    @Override
    public JsonpDeserializer errorDeserializer(int statusCode) {
        // Some errors (typically 404) only consist of a single "error" string (which is a shortcut for ErrorCause.reason)
        // and no "status". So we need a deserializer that will set the status value if it's not in the payload
        return new JsonpDeserializerBase(EnumSet.of(JsonParser.Event.START_OBJECT)) {
            @Override
            public ErrorResponse deserialize(JsonParser parser, JsonpMapper mapper, JsonParser.Event event) {
                ErrorResponse.Builder builder = new ErrorResponse.Builder();
                builder.status(statusCode);
                while ((event = parser.next()) != JsonParser.Event.END_OBJECT) {
                    JsonpUtils.expectEvent(parser, JsonParser.Event.KEY_NAME, event);
                    switch (parser.getString()) {
                        case "error":
                            switch (event = parser.next()) {
                                case VALUE_STRING:
                                    builder.error(e -> e.reason(parser.getString()).type("http_status_" + statusCode));
                                    break;
                                default:
                                    JsonpUtils.expectEvent(parser, JsonParser.Event.START_OBJECT, event);
                                    builder.error(ErrorCause._DESERIALIZER.deserialize(parser, mapper, event));
                                    break;
                            }
                            break;
                        case "status":
                            JsonpUtils.expectNextEvent(parser, JsonParser.Event.VALUE_NUMBER);
                            builder.status(parser.getInt());
                            break;
                    }
                }

                return builder.build();
            }
        };
    }

    public  SimpleEndpoint withResponseDeserializer(
        JsonpDeserializer newResponseParser
    ) {
        return new SimpleEndpoint<>(
            id,
            method,
            requestUrl,
            pathParameters,
            queryParameters,
            headers,
            body,
            newResponseParser
        );
    }

    public static RuntimeException noPathTemplateFound(String what) {
        return new RuntimeException("Could not find a request " + what + " with this set of properties. " +
            "Please check the API documentation, or raise an issue if this should be a valid request.");
    }

    private static final BitSet PATH_SAFE;
    private static final char[] HEX_CHARS;

    static {
        PATH_SAFE   = new BitSet(256);
        // From RFC 3986
        // unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
        PATH_SAFE.set('a', 'z'+1);
        PATH_SAFE.set('A', 'Z'+1);
        PATH_SAFE.set('0', '9'+1);
        PATH_SAFE.set('-');
        PATH_SAFE.set('.');
        PATH_SAFE.set('_');
        PATH_SAFE.set('~');

        // sub-delims  = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
        PATH_SAFE.set('!');
        PATH_SAFE.set('$');
        PATH_SAFE.set('&');
        PATH_SAFE.set('\'');
        PATH_SAFE.set('(');
        PATH_SAFE.set(')');
        PATH_SAFE.set('*');
        PATH_SAFE.set('+');
        PATH_SAFE.set(',');
        PATH_SAFE.set(';');
        PATH_SAFE.set('=');

        // pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
        PATH_SAFE.set(':');
        PATH_SAFE.set('@');

        HEX_CHARS = "0123456789ABCDEF".toCharArray();
    }

    public static void pathEncode(final String src, StringBuilder dest) {
        final ByteBuffer buf = StandardCharsets.UTF_8.encode(src);
        // In UTF-8 multibyte encoding, all bytes have the high bit set. This means we can iterate
        // on all bytes and percent-encode without having to care about code point context.
        while (buf.hasRemaining()) {
            int b = buf.get() & 0xff;
            if (PATH_SAFE.get(b)) {
                dest.append((char) b);
            } else {
                dest.append("%");
                dest.append(HEX_CHARS[b >> 4 & 0xF]);
                dest.append(HEX_CHARS[b & 0xF]);
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy