From e42154ef0882c6dd6946841f9f965635e4d7e59b Mon Sep 17 00:00:00 2001 From: Andriy Redko Date: Tue, 26 Mar 2024 16:58:22 -0400 Subject: [PATCH] [FEATURE] Enable Generic HTTP Actions in Java Client Signed-off-by: Andriy Redko Signed-off-by: Andriy Redko Signed-off-by: Andriy Redko --- CHANGELOG.md | 1 + .../client/opensearch/OpenSearchClient.java | 4 + .../opensearch/generic/GenericBodies.java | 45 ++++++ .../opensearch/generic/GenericBody.java | 64 ++++++++ .../generic/GenericByteArrayBody.java | 42 +++++ .../opensearch/generic/GenericEndpoint.java | 54 +++++++ .../generic/GenericInputStreamBody.java | 41 +++++ .../opensearch/generic/GenericRequest.java | 150 ++++++++++++++++++ .../opensearch/generic/GenericResponse.java | 82 ++++++++++ .../generic/OpenSearchGenericClient.java | 55 +++++++ .../client/transport/RawEndpoint.java | 54 +++++++ .../ApacheHttpClient5Transport.java | 29 ++++ .../rest_client/RestClientTransport.java | 31 ++++ .../integTest/AbstractPingAndInfoIT.java | 46 ++++-- 14 files changed, 682 insertions(+), 16 deletions(-) create mode 100644 java-client/src/main/java/org/opensearch/client/opensearch/generic/GenericBodies.java create mode 100644 java-client/src/main/java/org/opensearch/client/opensearch/generic/GenericBody.java create mode 100644 java-client/src/main/java/org/opensearch/client/opensearch/generic/GenericByteArrayBody.java create mode 100644 java-client/src/main/java/org/opensearch/client/opensearch/generic/GenericEndpoint.java create mode 100644 java-client/src/main/java/org/opensearch/client/opensearch/generic/GenericInputStreamBody.java create mode 100644 java-client/src/main/java/org/opensearch/client/opensearch/generic/GenericRequest.java create mode 100644 java-client/src/main/java/org/opensearch/client/opensearch/generic/GenericResponse.java create mode 100644 java-client/src/main/java/org/opensearch/client/opensearch/generic/OpenSearchGenericClient.java create mode 100644 java-client/src/main/java/org/opensearch/client/transport/RawEndpoint.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ee8107917..e6f0ac107e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ This section is for maintaining a changelog for all breaking changes for the cli ## [Unreleased 2.x] ### Added +- [FEATURE] Enable Generic HTTP Actions in Java Client ([#910](https://github.com/opensearch-project/opensearch-java/pull/910)) ### Dependencies - Bumps `io.github.classgraph:classgraph` from 4.8.161 to 4.8.165 diff --git a/java-client/src/main/java/org/opensearch/client/opensearch/OpenSearchClient.java b/java-client/src/main/java/org/opensearch/client/opensearch/OpenSearchClient.java index 3b2d8d76ff..ee83bcc20d 100644 --- a/java-client/src/main/java/org/opensearch/client/opensearch/OpenSearchClient.java +++ b/java-client/src/main/java/org/opensearch/client/opensearch/OpenSearchClient.java @@ -123,6 +123,7 @@ import org.opensearch.client.opensearch.core.pit.ListAllPitResponse; import org.opensearch.client.opensearch.dangling_indices.OpenSearchDanglingIndicesClient; import org.opensearch.client.opensearch.features.OpenSearchFeaturesClient; +import org.opensearch.client.opensearch.generic.OpenSearchGenericClient; import org.opensearch.client.opensearch.indices.OpenSearchIndicesClient; import org.opensearch.client.opensearch.ingest.OpenSearchIngestClient; import org.opensearch.client.opensearch.nodes.OpenSearchNodesClient; @@ -155,6 +156,9 @@ public OpenSearchClient withTransportOptions(@Nullable TransportOptions transpor } // ----- Child clients + public OpenSearchGenericClient generic() { + return new OpenSearchGenericClient(this.transport, this.transportOptions); + } public OpenSearchCatClient cat() { return new OpenSearchCatClient(this.transport, this.transportOptions); diff --git a/java-client/src/main/java/org/opensearch/client/opensearch/generic/GenericBodies.java b/java-client/src/main/java/org/opensearch/client/opensearch/generic/GenericBodies.java new file mode 100644 index 0000000000..adf55f8b69 --- /dev/null +++ b/java-client/src/main/java/org/opensearch/client/opensearch/generic/GenericBodies.java @@ -0,0 +1,45 @@ +/* + * 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. + */ + +package org.opensearch.client.opensearch.generic; + +import jakarta.json.stream.JsonGenerator; +import jakarta.json.stream.JsonParser; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import org.opensearch.client.ApiClient; +import org.opensearch.client.json.JsonpMapper; + +public final class GenericBodies { + private static final String APPLICATION_JSON = "application/json; charset=UTF-8"; + + private GenericBodies() {} + + public static C json(GenericBody body, Class clazz, ApiClient client) { + return json(body, clazz, client._transport().jsonpMapper()); + } + + public static GenericBody json(C value, ApiClient client) throws IOException { + return json(value, client._transport().jsonpMapper()); + } + + public static C json(GenericBody body, Class clazz, JsonpMapper jsonpMapper) { + try (JsonParser parser = jsonpMapper.jsonProvider().createParser(body.body())) { + return jsonpMapper.deserialize(parser, clazz); + } + } + + public static GenericBody json(C value, JsonpMapper jsonpMapper) throws IOException { + try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { + try (JsonGenerator generator = jsonpMapper.jsonProvider().createGenerator(baos)) { + jsonpMapper.serialize(value, generator); + return GenericBody.from(baos.toByteArray(), APPLICATION_JSON); + } + } + } +} diff --git a/java-client/src/main/java/org/opensearch/client/opensearch/generic/GenericBody.java b/java-client/src/main/java/org/opensearch/client/opensearch/generic/GenericBody.java new file mode 100644 index 0000000000..f26f011ea7 --- /dev/null +++ b/java-client/src/main/java/org/opensearch/client/opensearch/generic/GenericBody.java @@ -0,0 +1,64 @@ +/* + * 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. + */ + +package org.opensearch.client.opensearch.generic; + +import java.io.IOException; +import java.io.InputStream; +import javax.annotation.Nullable; + +/** + * Generic HTTP request / response body. It is responsibility of the caller to close the body instance + * explicitly (or through {@link GenericResponse} instance) to release all associated streams. + */ +public interface GenericBody extends AutoCloseable { + /** + * Constructs the generic response body out of {@link InputStream} with assumed content type + * @param body response body stream + * @param contentType content type + * @return generic response body instance + */ + static @Nullable GenericBody from(@Nullable final InputStream body, @Nullable final String contentType) { + if (body == null) { + return null; + } else { + return new GenericInputStreamBody(body, contentType); + } + } + + /** + * Constructs the generic response body out of {@link InputStream} with assumed content type + * @param body response body stream + * @param contentType content type + * @return generic response body instance + */ + static @Nullable GenericBody from(@Nullable final byte[] body, @Nullable final String contentType) { + if (body == null) { + return null; + } else { + return new GenericByteArrayBody(body, contentType); + } + } + + /** + * Content type of this body + * @return content type + */ + String contentType(); + + /** + * Gets the body as {@link InputStream} + * @return + */ + InputStream body(); + + /** + * Releases all resources associated with this body stream. + */ + void close() throws IOException; +} diff --git a/java-client/src/main/java/org/opensearch/client/opensearch/generic/GenericByteArrayBody.java b/java-client/src/main/java/org/opensearch/client/opensearch/generic/GenericByteArrayBody.java new file mode 100644 index 0000000000..94aaf21187 --- /dev/null +++ b/java-client/src/main/java/org/opensearch/client/opensearch/generic/GenericByteArrayBody.java @@ -0,0 +1,42 @@ +/* + * 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. + */ + +package org.opensearch.client.opensearch.generic; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import javax.annotation.Nullable; + +/** + * The HTTP request / response body that uses {@link byte[]} + */ +class GenericByteArrayBody implements GenericBody { + private final InputStream in; + private final String contentType; + + GenericByteArrayBody(final byte[] bytes, @Nullable final String contentType) { + this.in = new ByteArrayInputStream(bytes); + this.contentType = contentType; + } + + @Override + public String contentType() { + return contentType; + } + + @Override + public InputStream body() { + return in; + } + + @Override + public void close() throws IOException { + in.close(); + } +} diff --git a/java-client/src/main/java/org/opensearch/client/opensearch/generic/GenericEndpoint.java b/java-client/src/main/java/org/opensearch/client/opensearch/generic/GenericEndpoint.java new file mode 100644 index 0000000000..ea6bbae1c0 --- /dev/null +++ b/java-client/src/main/java/org/opensearch/client/opensearch/generic/GenericEndpoint.java @@ -0,0 +1,54 @@ +/* + * 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. + */ + +package org.opensearch.client.opensearch.generic; + +import java.io.InputStream; +import java.util.List; +import java.util.Map.Entry; +import org.opensearch.client.transport.RawEndpoint; + +/** + * Generic endpoint instance + */ +class GenericEndpoint implements RawEndpoint { + private final GenericRequest request; + + public GenericEndpoint(GenericRequest request) { + this.request = request; + } + + @Override + public String method(GenericRequest request) { + return request.getMethod(); + } + + @Override + public String requestUrl(GenericRequest request) { + return request.getEndpoint(); + } + + @Override + public boolean hasRequestBody() { + return request.getBody().isPresent(); + } + + @Override + public GenericResponse responseDeserializer( + String uri, + String method, + String protocol, + int status, + String reason, + List> headers, + String contentType, + InputStream body + ) { + return new GenericResponse(uri, protocol, method, status, reason, headers, GenericBody.from(body, contentType)); + } +} diff --git a/java-client/src/main/java/org/opensearch/client/opensearch/generic/GenericInputStreamBody.java b/java-client/src/main/java/org/opensearch/client/opensearch/generic/GenericInputStreamBody.java new file mode 100644 index 0000000000..697126470b --- /dev/null +++ b/java-client/src/main/java/org/opensearch/client/opensearch/generic/GenericInputStreamBody.java @@ -0,0 +1,41 @@ +/* + * 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. + */ + +package org.opensearch.client.opensearch.generic; + +import java.io.IOException; +import java.io.InputStream; +import javax.annotation.Nullable; + +/** + * The HTTP request / response body that uses {@link InputStream} + */ +class GenericInputStreamBody implements GenericBody { + private final InputStream in; + private final String contentType; + + GenericInputStreamBody(final InputStream in, @Nullable final String contentType) { + this.in = in; + this.contentType = contentType; + } + + @Override + public String contentType() { + return contentType; + } + + @Override + public InputStream body() { + return in; + } + + @Override + public void close() throws IOException { + in.close(); + } +} diff --git a/java-client/src/main/java/org/opensearch/client/opensearch/generic/GenericRequest.java b/java-client/src/main/java/org/opensearch/client/opensearch/generic/GenericRequest.java new file mode 100644 index 0000000000..aed9503cd6 --- /dev/null +++ b/java-client/src/main/java/org/opensearch/client/opensearch/generic/GenericRequest.java @@ -0,0 +1,150 @@ +/* + * 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. + */ + +package org.opensearch.client.opensearch.generic; + +import static java.util.Collections.unmodifiableMap; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +/** + * Generic HTTP request to OpenSearch + */ +public final class GenericRequest { + private final String method; + private final String endpoint; + private final Map parameters = new HashMap<>(); + private GenericBody body; + + /** + * Create the {@linkplain GenericRequest}. + * @param method the HTTP method + * @param endpoint the path of the request (without scheme, host, port, or prefix) + */ + public GenericRequest(String method, String endpoint) { + this.method = Objects.requireNonNull(method, "method cannot be null"); + this.endpoint = Objects.requireNonNull(endpoint, "endpoint cannot be null"); + } + + /** + * The HTTP method. + */ + public String getMethod() { + return method; + } + + /** + * The path of the request (without scheme, host, port, or prefix). + */ + public String getEndpoint() { + return endpoint; + } + + /** + * Add a query string parameter. + * @param name the name of the url parameter. Must not be null. + * @param value the value of the url url parameter. If {@code null} then + * the parameter is sent as {@code name} rather than {@code name=value} + * @throws IllegalArgumentException if a parameter with that name has + * already been set + */ + public void addParameter(String name, String value) { + Objects.requireNonNull(name, "url parameter name cannot be null"); + if (parameters.containsKey(name)) { + throw new IllegalArgumentException("url parameter [" + name + "] has already been set to [" + parameters.get(name) + "]"); + } else { + parameters.put(name, value); + } + } + + /** + * Add query parameters using the provided map of key value pairs. + * + * @param paramSource a map of key value pairs where the key is the url parameter. + * @throws IllegalArgumentException if a parameter with that name has already been set. + */ + public void addParameters(Map paramSource) { + paramSource.forEach(this::addParameter); + } + + /** + * Query string parameters. The returned map is an unmodifiable view of the + * map in the request so calls to {@link #addParameter(String, String)} + * will change it. + */ + public Map getParameters() { + return unmodifiableMap(parameters); + } + + /** + * Set the body of the request. If not set or set to {@code null} then no + * body is sent with the request. + * + * @param body the {@link GenericBody} to be set as the body of the request. + */ + public void setBody(GenericBody body) { + this.body = body; + } + + /** + * The body of the request. If {@code null} then no body + * is sent with the request. + */ + public Optional getBody() { + return Optional.ofNullable(body); + } + + /** + * Convert request to string representation + */ + @Override + public String toString() { + StringBuilder b = new StringBuilder(); + b.append("Request{"); + b.append("method='").append(method).append('\''); + b.append(", endpoint='").append(endpoint).append('\''); + if (false == parameters.isEmpty()) { + b.append(", params=").append(parameters); + } + if (body != null) { + b.append(", body=").append(body); + } + return b.append('}').toString(); + } + + /** + * Compare two requests for equality + * @param obj request instance to compare with + */ + @Override + public boolean equals(Object obj) { + if (obj == null || (obj.getClass() != getClass())) { + return false; + } + if (obj == this) { + return true; + } + + GenericRequest other = (GenericRequest) obj; + return method.equals(other.method) + && endpoint.equals(other.endpoint) + && parameters.equals(other.parameters) + && Objects.equals(body, other.body); + } + + /** + * Calculate the hash code of the request + */ + @Override + public int hashCode() { + return Objects.hash(method, endpoint, parameters, body); + } +} diff --git a/java-client/src/main/java/org/opensearch/client/opensearch/generic/GenericResponse.java b/java-client/src/main/java/org/opensearch/client/opensearch/generic/GenericResponse.java new file mode 100644 index 0000000000..1f67920d3f --- /dev/null +++ b/java-client/src/main/java/org/opensearch/client/opensearch/generic/GenericResponse.java @@ -0,0 +1,82 @@ +/* + * 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. + */ + +package org.opensearch.client.opensearch.generic; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +/** + * Generic HTTP response from OpenSearch + */ +public class GenericResponse implements AutoCloseable { + private final String protocol; + private final String method; + private final String uri; + private final int status; + private final String reason; + private final List> headers; + private final GenericBody body; + + GenericResponse( + String uri, + String protocol, + String method, + int status, + String reason, + List> headers, + GenericBody body + ) { + this.uri = Objects.requireNonNull(uri, "uri cannot be null"); + this.protocol = Objects.requireNonNull(protocol, "protocol cannot be null"); + this.method = Objects.requireNonNull(method, "method cannot be null"); + this.status = status; + this.reason = reason; + this.headers = Objects.requireNonNull(headers, "headers cannot be null"); + this.body = body; + } + + public Optional getBody() { + return Optional.ofNullable(body); + } + + public String getProtocol() { + return protocol; + } + + public String getMethod() { + return method; + } + + public String getReason() { + return reason; + } + + public int getStatus() { + return status; + } + + public String getUri() { + return uri; + } + + public List> getHeaders() { + return Collections.unmodifiableList(headers); + } + + @Override + public void close() throws IOException { + if (body != null) { + body.close(); + } + } +} diff --git a/java-client/src/main/java/org/opensearch/client/opensearch/generic/OpenSearchGenericClient.java b/java-client/src/main/java/org/opensearch/client/opensearch/generic/OpenSearchGenericClient.java new file mode 100644 index 0000000000..7ace3060ce --- /dev/null +++ b/java-client/src/main/java/org/opensearch/client/opensearch/generic/OpenSearchGenericClient.java @@ -0,0 +1,55 @@ +/* + * 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. + */ + +package org.opensearch.client.opensearch.generic; + +import java.io.IOException; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nullable; +import org.opensearch.client.ApiClient; +import org.opensearch.client.transport.OpenSearchTransport; +import org.opensearch.client.transport.TransportOptions; + +/** + * Client for the generic HTTP requests. + */ +public class OpenSearchGenericClient extends ApiClient { + public OpenSearchGenericClient(OpenSearchTransport transport) { + super(transport, null); + } + + public OpenSearchGenericClient(OpenSearchTransport transport, @Nullable TransportOptions transportOptions) { + super(transport, transportOptions); + } + + @Override + public OpenSearchGenericClient withTransportOptions(@Nullable TransportOptions transportOptions) { + return new OpenSearchGenericClient(this.transport, transportOptions); + } + + /** + * Executes generic HTTP request and returns generic HTTP response. + * @param request generic HTTP request + * @param options transport options + * @return generic HTTP response + * @throws IOException I/O exception + */ + public GenericResponse execute(GenericRequest request, @Nullable TransportOptions options) throws IOException { + return transport.performRequest(request, new GenericEndpoint(request), options); + } + + /** + * Asynchronously executes generic HTTP request and returns generic HTTP response. + * @param request generic HTTP request + * @param options transport options + * @return generic HTTP response future + */ + public CompletableFuture executeAsync(GenericRequest request, @Nullable TransportOptions options) { + return transport.performRequestAsync(request, new GenericEndpoint(request), options); + } +} diff --git a/java-client/src/main/java/org/opensearch/client/transport/RawEndpoint.java b/java-client/src/main/java/org/opensearch/client/transport/RawEndpoint.java new file mode 100644 index 0000000000..695cabc540 --- /dev/null +++ b/java-client/src/main/java/org/opensearch/client/transport/RawEndpoint.java @@ -0,0 +1,54 @@ +/* + * 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. + */ + +package org.opensearch.client.transport; + +import java.io.InputStream; +import java.util.List; +import java.util.Map; +import javax.annotation.Nullable; +import org.opensearch.client.json.JsonpDeserializer; + +/** + * An endpoint with a raw, unparsed response body. The endpoind does not distinguish between errornous and successful response + * and always return the raw response body. + */ +public interface RawEndpoint extends Endpoint { + default public boolean isError(int statusCode) { + return false; /* never return an error since errorDeserializer is JSON specific */ + } + + /** + * The error is never deserialized explicitly, represented as the instance of {@link ResponseT} instead. + */ + default public JsonpDeserializer errorDeserializer(int statusCode) { + return null; + } + + /** + * Constructs the {@link ResponseT} instance + * @param uri request URI + * @param method HTTP method + * @param protocol HTTP protocol version + * @param status status code + * @param reason reason phrase + * @param headers response headers + * @param body optional body + * @return the {@link ResponseT} instance + */ + ResponseT responseDeserializer( + final String uri, + final String method, + final String protocol, + int status, + final String reason, + final List> headers, + @Nullable final String contentType, + @Nullable final InputStream body + ); +} diff --git a/java-client/src/main/java/org/opensearch/client/transport/httpclient5/ApacheHttpClient5Transport.java b/java-client/src/main/java/org/opensearch/client/transport/httpclient5/ApacheHttpClient5Transport.java index 28be09ae05..4005535805 100644 --- a/java-client/src/main/java/org/opensearch/client/transport/httpclient5/ApacheHttpClient5Transport.java +++ b/java-client/src/main/java/org/opensearch/client/transport/httpclient5/ApacheHttpClient5Transport.java @@ -17,6 +17,7 @@ import java.io.OutputStream; import java.net.URI; import java.net.URISyntaxException; +import java.util.AbstractMap; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -37,6 +38,7 @@ import java.util.concurrent.ConcurrentMap; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; import java.util.zip.GZIPOutputStream; import javax.annotation.Nullable; import org.apache.commons.logging.Log; @@ -66,6 +68,7 @@ import org.apache.hc.core5.http.io.entity.HttpEntityWrapper; import org.apache.hc.core5.http.message.BasicHeader; import org.apache.hc.core5.http.message.RequestLine; +import org.apache.hc.core5.http.message.StatusLine; import org.apache.hc.core5.http.nio.AsyncRequestProducer; import org.apache.hc.core5.http.nio.AsyncResponseConsumer; import org.apache.hc.core5.net.URIBuilder; @@ -78,6 +81,7 @@ import org.opensearch.client.transport.Endpoint; import org.opensearch.client.transport.JsonEndpoint; import org.opensearch.client.transport.OpenSearchTransport; +import org.opensearch.client.transport.RawEndpoint; import org.opensearch.client.transport.TransportException; import org.opensearch.client.transport.TransportOptions; import org.opensearch.client.transport.endpoints.BooleanEndpoint; @@ -630,6 +634,31 @@ private ResponseT decodeResponse( ; } return response; + } else if (endpoint instanceof RawEndpoint) { + @SuppressWarnings("unchecked") + final RawEndpoint rawEndpoint = (RawEndpoint) endpoint; + + String contentType = null; + InputStream content = null; + if (entity != null) { + contentType = entity.getContentType(); + content = entity.getContent(); + } + + final RequestLine requestLine = clientResp.getRequestLine(); + final StatusLine statusLine = clientResp.getStatusLine(); + return rawEndpoint.responseDeserializer( + requestLine.getUri(), + requestLine.getMethod(), + requestLine.getProtocolVersion().format(), + statusLine.getStatusCode(), + statusLine.getReasonPhrase(), + Arrays.stream(clientResp.getHeaders()) + .map(h -> new AbstractMap.SimpleEntry(h.getName(), h.getValue())) + .collect(Collectors.toList()), + contentType, + content + ); } else { throw new TransportException("Unhandled endpoint type: '" + endpoint.getClass().getName() + "'"); } diff --git a/java-client/src/main/java/org/opensearch/client/transport/rest_client/RestClientTransport.java b/java-client/src/main/java/org/opensearch/client/transport/rest_client/RestClientTransport.java index e2a40a2fff..a0eb6785c1 100644 --- a/java-client/src/main/java/org/opensearch/client/transport/rest_client/RestClientTransport.java +++ b/java-client/src/main/java/org/opensearch/client/transport/rest_client/RestClientTransport.java @@ -37,9 +37,12 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.util.AbstractMap; +import java.util.Arrays; import java.util.Iterator; import java.util.Map; import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; import javax.annotation.Nullable; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.HttpEntity; @@ -47,6 +50,8 @@ import org.apache.hc.core5.http.io.entity.BufferedHttpEntity; import org.apache.hc.core5.http.io.entity.ByteArrayEntity; import org.apache.hc.core5.http.io.entity.EntityUtils; +import org.apache.hc.core5.http.message.RequestLine; +import org.apache.hc.core5.http.message.StatusLine; import org.opensearch.client.Cancellable; import org.opensearch.client.RequestOptions; import org.opensearch.client.Response; @@ -61,6 +66,7 @@ import org.opensearch.client.transport.Endpoint; import org.opensearch.client.transport.JsonEndpoint; import org.opensearch.client.transport.OpenSearchTransport; +import org.opensearch.client.transport.RawEndpoint; import org.opensearch.client.transport.TransportException; import org.opensearch.client.transport.TransportOptions; import org.opensearch.client.transport.endpoints.BooleanEndpoint; @@ -324,6 +330,31 @@ private ResponseT decodeResponse( ; } return response; + } else if (endpoint instanceof RawEndpoint) { + @SuppressWarnings("unchecked") + final RawEndpoint rawEndpoint = (RawEndpoint) endpoint; + + String contentType = null; + InputStream content = null; + if (entity != null) { + contentType = entity.getContentType(); + content = entity.getContent(); + } + + final RequestLine requestLine = clientResp.getRequestLine(); + final StatusLine statusLine = clientResp.getStatusLine(); + return rawEndpoint.responseDeserializer( + requestLine.getUri(), + requestLine.getMethod(), + requestLine.getProtocolVersion().format(), + statusLine.getStatusCode(), + statusLine.getReasonPhrase(), + Arrays.stream(clientResp.getHeaders()) + .map(h -> new AbstractMap.SimpleEntry(h.getName(), h.getValue())) + .collect(Collectors.toList()), + contentType, + content + ); } else { throw new TransportException("Unhandled endpoint type: '" + endpoint.getClass().getName() + "'"); } diff --git a/java-client/src/test/java11/org/opensearch/client/opensearch/integTest/AbstractPingAndInfoIT.java b/java-client/src/test/java11/org/opensearch/client/opensearch/integTest/AbstractPingAndInfoIT.java index 9310764dba..ac5174c62e 100644 --- a/java-client/src/test/java11/org/opensearch/client/opensearch/integTest/AbstractPingAndInfoIT.java +++ b/java-client/src/test/java11/org/opensearch/client/opensearch/integTest/AbstractPingAndInfoIT.java @@ -8,12 +8,17 @@ package org.opensearch.client.opensearch.integTest; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; + import java.io.IOException; +import java.util.Collections; import java.util.Map; -import org.apache.hc.client5.http.classic.methods.HttpGet; -import org.opensearch.client.Request; import org.opensearch.client.opensearch.OpenSearchClient; import org.opensearch.client.opensearch.core.InfoResponse; +import org.opensearch.client.opensearch.generic.GenericBodies; +import org.opensearch.client.opensearch.generic.GenericRequest; +import org.opensearch.client.opensearch.generic.GenericResponse; import org.opensearch.client.transport.endpoints.BooleanResponse; public abstract class AbstractPingAndInfoIT extends OpenSearchJavaClientTestCase { @@ -27,19 +32,28 @@ public void testInfo() throws IOException { InfoResponse info = openSearchClient.info(); // compare with what the low level client outputs - Map infoAsMap = entityAsMap(adminClient().performRequest(new Request(HttpGet.METHOD_NAME, "/"))); - assertEquals(infoAsMap.get("cluster_name"), info.clusterName()); - assertEquals(infoAsMap.get("cluster_uuid"), info.clusterUuid()); - - @SuppressWarnings("unchecked") - Map versionMap = (Map) infoAsMap.get("version"); - assertEquals(versionMap.get("build_date"), info.version().buildDate()); - assertEquals(versionMap.get("build_flavor"), info.version().buildFlavor()); - assertEquals(versionMap.get("build_hash"), info.version().buildHash()); - assertEquals(versionMap.get("build_snapshot"), info.version().buildSnapshot()); - assertEquals(versionMap.get("build_type"), info.version().buildType()); - assertEquals(versionMap.get("distribution"), info.version().distribution()); - assertEquals(versionMap.get("lucene_version"), info.version().luceneVersion()); - assertTrue(versionMap.get("number").toString().startsWith(info.version().number())); + try (GenericResponse response = javaClient().generic().execute(new GenericRequest("GET", "/"), null)) { + assertThat(response.getStatus(), equalTo(200)); + assertThat(response.getProtocol(), equalTo("HTTP/1.1")); + assertThat(response.getBody().isEmpty(), is(false)); + + Map infoAsMap = response.getBody() + .map(b -> GenericBodies.json(b, Map.class, javaClient())) + .orElseGet(Collections::emptyMap); + + assertEquals(infoAsMap.get("cluster_name"), info.clusterName()); + assertEquals(infoAsMap.get("cluster_uuid"), info.clusterUuid()); + + @SuppressWarnings("unchecked") + Map versionMap = (Map) infoAsMap.get("version"); + assertEquals(versionMap.get("build_date"), info.version().buildDate()); + assertEquals(versionMap.get("build_flavor"), info.version().buildFlavor()); + assertEquals(versionMap.get("build_hash"), info.version().buildHash()); + assertEquals(versionMap.get("build_snapshot"), info.version().buildSnapshot()); + assertEquals(versionMap.get("build_type"), info.version().buildType()); + assertEquals(versionMap.get("distribution"), info.version().distribution()); + assertEquals(versionMap.get("lucene_version"), info.version().luceneVersion()); + assertTrue(versionMap.get("number").toString().startsWith(info.version().number())); + } } }