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, Map> EMPTY_MAP = x -> Collections.emptyMap();
private static final Function, Object> 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