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/GenericBody.java b/java-client/src/main/java/org/opensearch/client/opensearch/generic/GenericBody.java new file mode 100644 index 0000000000..3185474bd9 --- /dev/null +++ b/java-client/src/main/java/org/opensearch/client/opensearch/generic/GenericBody.java @@ -0,0 +1,50 @@ +/* + * 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(InputStream body, String contentType) { + if (body == null) { + return null; + } else { + return new GenericInputStreamBody(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/GenericEndpoint.java b/java-client/src/main/java/org/opensearch/client/opensearch/generic/GenericEndpoint.java new file mode 100644 index 0000000000..2f1e0df2a9 --- /dev/null +++ b/java-client/src/main/java/org/opensearch/client/opensearch/generic/GenericEndpoint.java @@ -0,0 +1,57 @@ +/* + * 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.json.JsonpMapper; +import org.opensearch.client.transport.RawEndpoint; + +/** + * Generic endpoint instance + */ +class GenericEndpoint implements RawEndpoint { + private final GenericRequest request; + private final JsonpMapper jsonpMapper; + + public GenericEndpoint(GenericRequest request, JsonpMapper jsonpMapper) { + this.request = request; + this.jsonpMapper = jsonpMapper; + } + + @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), jsonpMapper); + } +} 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..89d76407b6 --- /dev/null +++ b/java-client/src/main/java/org/opensearch/client/opensearch/generic/GenericInputStreamBody.java @@ -0,0 +1,32 @@ +package org.opensearch.client.opensearch.generic; + +import java.io.IOException; +import java.io.InputStream; + +/** + * The HTTP request / response body that uses {@link InputStream} + */ +class GenericInputStreamBody implements GenericBody { + private final InputStream in; + private final String contentType; + + GenericInputStreamBody(InputStream in, 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..7452483f58 --- /dev/null +++ b/java-client/src/main/java/org/opensearch/client/opensearch/generic/GenericRequest.java @@ -0,0 +1,174 @@ +/* + * 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.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..7d4565245c --- /dev/null +++ b/java-client/src/main/java/org/opensearch/client/opensearch/generic/GenericResponse.java @@ -0,0 +1,119 @@ +/* + * 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.client.opensearch.generic; + +import jakarta.json.stream.JsonParser; +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import org.opensearch.client.json.JsonpMapper; + +/** + * 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; + private final JsonpMapper jsonpMapper; + + GenericResponse( + String uri, + String protocol, + String method, + int status, + String reason, + List> headers, + GenericBody body, + JsonpMapper jsonpMapper + ) { + 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; + this.jsonpMapper = jsonpMapper; + } + + public Optional getBody() { + return Optional.ofNullable(body); + } + + public Optional getBodyAs(Class clazz) { + return getBody().map(body -> { + try (JsonParser parser = jsonpMapper.jsonProvider().createParser(body.body())) { + return jsonpMapper.deserialize(parser, clazz); + } + }); + } + + 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..9c202c81bb --- /dev/null +++ b/java-client/src/main/java/org/opensearch/client/opensearch/generic/OpenSearchGenericClient.java @@ -0,0 +1,56 @@ +/* + * 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, transport.jsonpMapper()), 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 + * @throws IOException I/O exception + */ + public CompletableFuture executeAsync(GenericRequest request, @Nullable TransportOptions options) { + return transport.performRequestAsync(request, new GenericEndpoint(request, transport.jsonpMapper()), 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..683d3c0e1b --- /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 + */ + ResponseT responseDeserializer( + final String uri, + final String method, + final String protocol, + int status, + final String reasonPhrase, + 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..393e806f62 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,16 @@ 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.GenericRequest; +import org.opensearch.client.opensearch.generic.GenericResponse; import org.opensearch.client.transport.endpoints.BooleanResponse; public abstract class AbstractPingAndInfoIT extends OpenSearchJavaClientTestCase { @@ -27,19 +31,25 @@ 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.getBodyAs(Map.class).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())); + } } }