From 1a8eb853bb1f23313a0030506cc417288c71315e Mon Sep 17 00:00:00 2001 From: Tanguy Leroux Date: Wed, 7 Jun 2017 16:34:34 +0200 Subject: [PATCH 1/5] [Test] Add test for a custom RestHighLevelClient This commit adds a test that tests and demonstrates how {@link RestHighLevelClient} can be extended to support custom requests and responses. --- .../CustomRestHighLevelClientTests.java | 236 ++++++++++++++++++ 1 file changed, 236 insertions(+) create mode 100644 client/rest-high-level/src/test/java/org/elasticsearch/client/CustomRestHighLevelClientTests.java diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/CustomRestHighLevelClientTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/CustomRestHighLevelClientTests.java new file mode 100644 index 0000000000000..060421b066141 --- /dev/null +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/CustomRestHighLevelClientTests.java @@ -0,0 +1,236 @@ +/* + * 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. + */ + +package org.elasticsearch.client; + +import org.apache.http.Header; +import org.apache.http.HttpEntity; +import org.apache.http.HttpHost; +import org.apache.http.HttpResponse; +import org.apache.http.ProtocolVersion; +import org.apache.http.RequestLine; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.entity.ByteArrayEntity; +import org.apache.http.entity.ContentType; +import org.apache.http.message.BasicHttpResponse; +import org.apache.http.message.BasicRequestLine; +import org.apache.http.message.BasicStatusLine; +import org.apache.lucene.util.BytesRef; +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.ActionRequest; +import org.elasticsearch.action.ActionRequestValidationException; +import org.elasticsearch.action.ActionResponse; +import org.elasticsearch.action.ValidateActions; +import org.elasticsearch.common.ParseField; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.xcontent.ConstructingObjectParser; +import org.elasticsearch.common.xcontent.ObjectParser; +import org.elasticsearch.common.xcontent.StatusToXContentObject; +import org.elasticsearch.common.xcontent.ToXContentObject; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentHelper; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.common.xcontent.XContentType; +import org.elasticsearch.rest.RestStatus; +import org.elasticsearch.test.ESTestCase; +import org.junit.Before; + +import java.io.IOException; + +import static java.util.Collections.emptyMap; +import static java.util.Collections.emptySet; +import static org.elasticsearch.client.ESRestHighLevelClientTestCase.execute; +import static org.elasticsearch.client.Request.REQUEST_BODY_CONTENT_TYPE; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyMapOf; +import static org.mockito.Matchers.anyObject; +import static org.mockito.Matchers.anyVararg; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; + +/** + * Test and demonstrates how {@link RestHighLevelClient} can be extended to support custom requests and responses. + */ +public class CustomRestHighLevelClientTests extends ESTestCase { + + private static final String ENDPOINT = "/_custom"; + + private CustomRestClient restHighLevelClient; + + @Before + public void iniClients() throws IOException { + if (restHighLevelClient == null) { + final RestClient restClient = mock(RestClient.class); + restHighLevelClient = new CustomRestClient(restClient); + + doAnswer(mock -> performRequest((HttpEntity) mock.getArguments()[3])) + .when(restClient) + .performRequest(eq(HttpGet.METHOD_NAME), eq(ENDPOINT), anyMapOf(String.class, String.class), anyObject(), anyVararg()); + + doAnswer(mock -> performRequestAsync((HttpEntity) mock.getArguments()[3], (ResponseListener) mock.getArguments()[4])) + .when(restClient) + .performRequestAsync(eq(HttpGet.METHOD_NAME), eq(ENDPOINT), anyMapOf(String.class, String.class), + any(HttpEntity.class), any(ResponseListener.class), anyVararg()); + } + } + + public void testCustomRequest() throws IOException { + final int length = randomIntBetween(1, 10); + + CustomRequest customRequest = new CustomRequest(); + customRequest.setValue(randomAlphaOfLength(length)); + + CustomResponse customResponse = execute(customRequest, restHighLevelClient::custom, restHighLevelClient::customAsync); + assertEquals(length, customResponse.getLength()); + assertEquals(expectedStatus(length), customResponse.status()); + } + + private Void performRequestAsync(HttpEntity httpEntity, ResponseListener responseListener) { + try { + responseListener.onSuccess(performRequest(httpEntity)); + } catch (IOException e) { + responseListener.onFailure(e); + } + return null; + } + + private Response performRequest(HttpEntity httpEntity) throws IOException { + try (XContentParser parser = createParser(REQUEST_BODY_CONTENT_TYPE.xContent(), httpEntity.getContent())) { + CustomRequest request = CustomRequest.fromXContent(parser); + + int length = request.getValue() != null ? request.getValue().length() : -1; + CustomResponse response = new CustomResponse(length); + + ProtocolVersion protocol = new ProtocolVersion("HTTP", 1, 1); + RestStatus status = response.status(); + HttpResponse httpResponse = new BasicHttpResponse(new BasicStatusLine(protocol, status.getStatus(), status.name())); + + BytesRef bytesRef = XContentHelper.toXContent(response, XContentType.JSON, false).toBytesRef(); + httpResponse.setEntity(new ByteArrayEntity(bytesRef.bytes, ContentType.APPLICATION_JSON)); + + RequestLine requestLine = new BasicRequestLine(HttpGet.METHOD_NAME, ENDPOINT, protocol); + return new Response(requestLine, new HttpHost("localhost", 9200), httpResponse); + } + } + + /** + * A custom high level client that provides methods to execute a custom request and get its associate response back. + */ + static class CustomRestClient extends RestHighLevelClient { + + private CustomRestClient(RestClient restClient) { + super(restClient); + } + + public CustomResponse custom(CustomRequest customRequest, Header... headers) throws IOException { + return performRequest(customRequest, this::toRequest, this::toResponse, emptySet(), headers); + } + + public void customAsync(CustomRequest customRequest, ActionListener listener, Header... headers) { + performRequestAsync(customRequest, this::toRequest, this::toResponse, listener, emptySet(), headers); + } + + Request toRequest(CustomRequest customRequest) throws IOException { + BytesRef source = XContentHelper.toXContent(customRequest, REQUEST_BODY_CONTENT_TYPE, false).toBytesRef(); + ContentType contentType = ContentType.create(REQUEST_BODY_CONTENT_TYPE.mediaType()); + HttpEntity entity = new ByteArrayEntity(source.bytes, source.offset, source.length, contentType); + return new Request(HttpGet.METHOD_NAME, ENDPOINT, emptyMap(), entity); + } + + CustomResponse toResponse(Response response) throws IOException { + return parseEntity(response.getEntity(), CustomResponse::fromXContent); + } + } + + static class CustomRequest extends ActionRequest implements ToXContentObject { + + private String value; + + public CustomRequest() { + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + @Override + public ActionRequestValidationException validate() { + if (Strings.hasLength(value) == false) { + return ValidateActions.addValidationError("value is missing", null); + } + return null; + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + return builder.startObject().field("value", value).endObject(); + } + + private static final ObjectParser PARSER = new ObjectParser<>("custom_request", CustomRequest::new); + static { + PARSER.declareString(CustomRequest::setValue, new ParseField("value")); + } + + static CustomRequest fromXContent(XContentParser parser) throws IOException { + return PARSER.parse(parser, null); + } + } + + static class CustomResponse extends ActionResponse implements StatusToXContentObject { + + private final int length; + + CustomResponse(int length) { + this.length = length; + } + + public int getLength() { + return length; + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + return builder.startObject().field("length", length).endObject(); + } + + @Override + public RestStatus status() { + return expectedStatus(getLength()); + } + + private static final ConstructingObjectParser PARSER = + new ConstructingObjectParser<>("custom_response", args -> new CustomResponse((int) args[0])); + static { + PARSER.declareInt(ConstructingObjectParser.constructorArg(), new ParseField("length")); + } + + static CustomResponse fromXContent(XContentParser parser) throws IOException { + return PARSER.parse(parser, null); + } + } + + private static RestStatus expectedStatus(int length) { + return length > 5 ? RestStatus.OK : RestStatus.BAD_REQUEST; + } +} \ No newline at end of file From cb8d8996335a08e23ad9ec38c898ce76a10bbd0a Mon Sep 17 00:00:00 2001 From: Tanguy Leroux Date: Wed, 7 Jun 2017 16:49:41 +0200 Subject: [PATCH 2/5] Fix checkstyle violation --- .../elasticsearch/client/CustomRestHighLevelClientTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/CustomRestHighLevelClientTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/CustomRestHighLevelClientTests.java index 060421b066141..f3e9e1e9bf340 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/CustomRestHighLevelClientTests.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/CustomRestHighLevelClientTests.java @@ -163,7 +163,7 @@ static class CustomRequest extends ActionRequest implements ToXContentObject { private String value; - public CustomRequest() { + CustomRequest() { } public String getValue() { From d77bfaa3f30c734c5af9cb68588c3dd35c351df2 Mon Sep 17 00:00:00 2001 From: Tanguy Leroux Date: Thu, 8 Jun 2017 16:44:47 +0200 Subject: [PATCH 3/5] Apply feedback --- .../CustomRestHighLevelClientTests.java | 148 ++++++------------ 1 file changed, 44 insertions(+), 104 deletions(-) diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/CustomRestHighLevelClientTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/CustomRestHighLevelClientTests.java index f3e9e1e9bf340..fb8c4940c3aee 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/CustomRestHighLevelClientTests.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/CustomRestHighLevelClientTests.java @@ -26,37 +26,22 @@ import org.apache.http.ProtocolVersion; import org.apache.http.RequestLine; import org.apache.http.client.methods.HttpGet; -import org.apache.http.entity.ByteArrayEntity; -import org.apache.http.entity.ContentType; import org.apache.http.message.BasicHttpResponse; import org.apache.http.message.BasicRequestLine; import org.apache.http.message.BasicStatusLine; -import org.apache.lucene.util.BytesRef; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.ActionRequest; import org.elasticsearch.action.ActionRequestValidationException; import org.elasticsearch.action.ActionResponse; -import org.elasticsearch.action.ValidateActions; -import org.elasticsearch.common.ParseField; -import org.elasticsearch.common.Strings; -import org.elasticsearch.common.xcontent.ConstructingObjectParser; -import org.elasticsearch.common.xcontent.ObjectParser; -import org.elasticsearch.common.xcontent.StatusToXContentObject; -import org.elasticsearch.common.xcontent.ToXContentObject; -import org.elasticsearch.common.xcontent.XContentBuilder; -import org.elasticsearch.common.xcontent.XContentHelper; -import org.elasticsearch.common.xcontent.XContentParser; -import org.elasticsearch.common.xcontent.XContentType; -import org.elasticsearch.rest.RestStatus; import org.elasticsearch.test.ESTestCase; import org.junit.Before; import java.io.IOException; +import java.util.Map; -import static java.util.Collections.emptyMap; import static java.util.Collections.emptySet; +import static java.util.Collections.singletonMap; import static org.elasticsearch.client.ESRestHighLevelClientTestCase.execute; -import static org.elasticsearch.client.Request.REQUEST_BODY_CONTENT_TYPE; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyMapOf; import static org.mockito.Matchers.anyObject; @@ -66,7 +51,8 @@ import static org.mockito.Mockito.mock; /** - * Test and demonstrates how {@link RestHighLevelClient} can be extended to support custom requests and responses. + * Test and demonstrates how {@link RestHighLevelClient} can be extended to support + * custom requests and responses against custom endpoints. */ public class CustomRestHighLevelClientTests extends ESTestCase { @@ -75,16 +61,17 @@ public class CustomRestHighLevelClientTests extends ESTestCase { private CustomRestClient restHighLevelClient; @Before - public void iniClients() throws IOException { + @SuppressWarnings("unchecked") + public void initClients() throws IOException { if (restHighLevelClient == null) { final RestClient restClient = mock(RestClient.class); restHighLevelClient = new CustomRestClient(restClient); - doAnswer(mock -> performRequest((HttpEntity) mock.getArguments()[3])) + doAnswer(mock -> mockPerformRequest((Map) mock.getArguments()[2])) .when(restClient) .performRequest(eq(HttpGet.METHOD_NAME), eq(ENDPOINT), anyMapOf(String.class, String.class), anyObject(), anyVararg()); - doAnswer(mock -> performRequestAsync((HttpEntity) mock.getArguments()[3], (ResponseListener) mock.getArguments()[4])) + doAnswer(mock -> mockPerformRequestAsync((Map) mock.getArguments()[2], (ResponseListener) mock.getArguments()[4])) .when(restClient) .performRequestAsync(eq(HttpGet.METHOD_NAME), eq(ENDPOINT), anyMapOf(String.class, String.class), any(HttpEntity.class), any(ResponseListener.class), anyVararg()); @@ -92,42 +79,36 @@ public void iniClients() throws IOException { } public void testCustomRequest() throws IOException { - final int length = randomIntBetween(1, 10); - - CustomRequest customRequest = new CustomRequest(); - customRequest.setValue(randomAlphaOfLength(length)); + final CustomRequest customRequest = new CustomRequest(randomAlphaOfLength(5)); CustomResponse customResponse = execute(customRequest, restHighLevelClient::custom, restHighLevelClient::customAsync); - assertEquals(length, customResponse.getLength()); - assertEquals(expectedStatus(length), customResponse.status()); + assertEquals(customRequest.getValue(), customResponse.getValue()); } - private Void performRequestAsync(HttpEntity httpEntity, ResponseListener responseListener) { + /** + * Mocks the asynchronous request execution by calling the {@link #mockPerformRequest(Map)} method. + */ + private Void mockPerformRequestAsync(Map httpHeaders, ResponseListener responseListener) { try { - responseListener.onSuccess(performRequest(httpEntity)); + responseListener.onSuccess(mockPerformRequest(httpHeaders)); } catch (IOException e) { responseListener.onFailure(e); } return null; } - private Response performRequest(HttpEntity httpEntity) throws IOException { - try (XContentParser parser = createParser(REQUEST_BODY_CONTENT_TYPE.xContent(), httpEntity.getContent())) { - CustomRequest request = CustomRequest.fromXContent(parser); - - int length = request.getValue() != null ? request.getValue().length() : -1; - CustomResponse response = new CustomResponse(length); - - ProtocolVersion protocol = new ProtocolVersion("HTTP", 1, 1); - RestStatus status = response.status(); - HttpResponse httpResponse = new BasicHttpResponse(new BasicStatusLine(protocol, status.getStatus(), status.name())); + /** + * Mocks the synchronous request execution like if it was executed by Elasticsearch. + */ + private Response mockPerformRequest(Map httpHeaders) throws IOException { + assertEquals(1, httpHeaders.size()); - BytesRef bytesRef = XContentHelper.toXContent(response, XContentType.JSON, false).toBytesRef(); - httpResponse.setEntity(new ByteArrayEntity(bytesRef.bytes, ContentType.APPLICATION_JSON)); + ProtocolVersion protocol = new ProtocolVersion("HTTP", 1, 1); + HttpResponse httpResponse = new BasicHttpResponse(new BasicStatusLine(protocol, 200, "OK")); + httpResponse.setHeader("custom", httpHeaders.get("custom")); - RequestLine requestLine = new BasicRequestLine(HttpGet.METHOD_NAME, ENDPOINT, protocol); - return new Response(requestLine, new HttpHost("localhost", 9200), httpResponse); - } + RequestLine requestLine = new BasicRequestLine(HttpGet.METHOD_NAME, ENDPOINT, protocol); + return new Response(requestLine, new HttpHost("localhost", 9200), httpResponse); } /** @@ -148,89 +129,48 @@ public void customAsync(CustomRequest customRequest, ActionListener PARSER = new ObjectParser<>("custom_request", CustomRequest::new); - static { - PARSER.declareString(CustomRequest::setValue, new ParseField("value")); - } - - static CustomRequest fromXContent(XContentParser parser) throws IOException { - return PARSER.parse(parser, null); - } } - static class CustomResponse extends ActionResponse implements StatusToXContentObject { - - private final int length; - - CustomResponse(int length) { - this.length = length; - } - - public int getLength() { - return length; - } - - @Override - public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - return builder.startObject().field("length", length).endObject(); - } + /** + * A custom response + */ + static class CustomResponse extends ActionResponse { - @Override - public RestStatus status() { - return expectedStatus(getLength()); - } + private final String value; - private static final ConstructingObjectParser PARSER = - new ConstructingObjectParser<>("custom_response", args -> new CustomResponse((int) args[0])); - static { - PARSER.declareInt(ConstructingObjectParser.constructorArg(), new ParseField("length")); + CustomResponse(String value) { + this.value = value; } - static CustomResponse fromXContent(XContentParser parser) throws IOException { - return PARSER.parse(parser, null); + String getValue() { + return value; } } - - private static RestStatus expectedStatus(int length) { - return length > 5 ? RestStatus.OK : RestStatus.BAD_REQUEST; - } } \ No newline at end of file From 0713822743dac7014cc6c7e89600615bfc0a899e Mon Sep 17 00:00:00 2001 From: Tanguy Leroux Date: Fri, 9 Jun 2017 14:21:23 +0200 Subject: [PATCH 4/5] Apply feedback --- .../client/RestHighLevelClient.java | 8 +- .../CustomRestHighLevelClientTests.java | 127 +++++++++--------- 2 files changed, 69 insertions(+), 66 deletions(-) diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/RestHighLevelClient.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/RestHighLevelClient.java index fa9980977f4f1..a354bdfb7ba5a 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/RestHighLevelClient.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/RestHighLevelClient.java @@ -371,14 +371,14 @@ public void clearScrollAsync(ClearScrollRequest clearScrollRequest, ActionListen listener, emptySet(), headers); } - private Resp performRequestAndParseEntity(Req request, + protected Resp performRequestAndParseEntity(Req request, CheckedFunction requestConverter, CheckedFunction entityParser, Set ignores, Header... headers) throws IOException { return performRequest(request, requestConverter, (response) -> parseEntity(response.getEntity(), entityParser), ignores, headers); } - Resp performRequest(Req request, + protected Resp performRequest(Req request, CheckedFunction requestConverter, CheckedFunction responseConverter, Set ignores, Header... headers) throws IOException { @@ -408,7 +408,7 @@ Resp performRequest(Req request, } } - private void performRequestAsyncAndParseEntity(Req request, + protected void performRequestAsyncAndParseEntity(Req request, CheckedFunction requestConverter, CheckedFunction entityParser, ActionListener listener, Set ignores, Header... headers) { @@ -416,7 +416,7 @@ private void performRequestAsyncAndParseEntity listener, ignores, headers); } - void performRequestAsync(Req request, + protected void performRequestAsync(Req request, CheckedFunction requestConverter, CheckedFunction responseConverter, ActionListener listener, Set ignores, Header... headers) { diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/CustomRestHighLevelClientTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/CustomRestHighLevelClientTests.java index fb8c4940c3aee..609b56f991fdc 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/CustomRestHighLevelClientTests.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/CustomRestHighLevelClientTests.java @@ -26,21 +26,30 @@ import org.apache.http.ProtocolVersion; import org.apache.http.RequestLine; import org.apache.http.client.methods.HttpGet; +import org.apache.http.entity.ByteArrayEntity; +import org.apache.http.entity.ContentType; +import org.apache.http.message.BasicHeader; import org.apache.http.message.BasicHttpResponse; import org.apache.http.message.BasicRequestLine; import org.apache.http.message.BasicStatusLine; +import org.apache.lucene.util.BytesRef; +import org.elasticsearch.Build; +import org.elasticsearch.Version; import org.elasticsearch.action.ActionListener; -import org.elasticsearch.action.ActionRequest; -import org.elasticsearch.action.ActionRequestValidationException; -import org.elasticsearch.action.ActionResponse; +import org.elasticsearch.action.main.MainRequest; +import org.elasticsearch.action.main.MainResponse; +import org.elasticsearch.cluster.ClusterName; +import org.elasticsearch.common.xcontent.XContentHelper; +import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.test.ESTestCase; import org.junit.Before; import java.io.IOException; -import java.util.Map; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import static java.util.Collections.emptyMap; import static java.util.Collections.emptySet; -import static java.util.Collections.singletonMap; import static org.elasticsearch.client.ESRestHighLevelClientTestCase.execute; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyMapOf; @@ -51,8 +60,7 @@ import static org.mockito.Mockito.mock; /** - * Test and demonstrates how {@link RestHighLevelClient} can be extended to support - * custom requests and responses against custom endpoints. + * Test and demonstrates how {@link RestHighLevelClient} can be extended to support custom endpoints. */ public class CustomRestHighLevelClientTests extends ESTestCase { @@ -67,30 +75,53 @@ public void initClients() throws IOException { final RestClient restClient = mock(RestClient.class); restHighLevelClient = new CustomRestClient(restClient); - doAnswer(mock -> mockPerformRequest((Map) mock.getArguments()[2])) + doAnswer(mock -> mockPerformRequest((Header) mock.getArguments()[4])) .when(restClient) .performRequest(eq(HttpGet.METHOD_NAME), eq(ENDPOINT), anyMapOf(String.class, String.class), anyObject(), anyVararg()); - doAnswer(mock -> mockPerformRequestAsync((Map) mock.getArguments()[2], (ResponseListener) mock.getArguments()[4])) + doAnswer(mock -> mockPerformRequestAsync((Header) mock.getArguments()[5], (ResponseListener) mock.getArguments()[4])) .when(restClient) .performRequestAsync(eq(HttpGet.METHOD_NAME), eq(ENDPOINT), anyMapOf(String.class, String.class), any(HttpEntity.class), any(ResponseListener.class), anyVararg()); } } - public void testCustomRequest() throws IOException { - final CustomRequest customRequest = new CustomRequest(randomAlphaOfLength(5)); + public void testCustomEndpoint() throws IOException { + final MainRequest request = new MainRequest(); + final Header header = new BasicHeader("node_name", randomAlphaOfLengthBetween(1, 10)); - CustomResponse customResponse = execute(customRequest, restHighLevelClient::custom, restHighLevelClient::customAsync); - assertEquals(customRequest.getValue(), customResponse.getValue()); + MainResponse response = execute(request, restHighLevelClient::custom, restHighLevelClient::customAsync, header); + assertEquals(header.getValue(), response.getNodeName()); + + response = execute(request, restHighLevelClient::customAndParse, restHighLevelClient::customAndParseAsync, header); + assertEquals(header.getValue(), response.getNodeName()); + } + + /** + * The {@link RestHighLevelClient} must declare the following execution methods using the protected modifier + * so that they can be used by subclasses to implement custom logic. + */ + public void testMethodsVisibility() throws ClassNotFoundException { + String[] methodNames = new String[]{"performRequest", "performRequestAndParseEntity", "performRequestAsync", + "performRequestAsyncAndParseEntity"}; + for (String methodName : methodNames) { + boolean found = false; + for (Method method : RestHighLevelClient.class.getDeclaredMethods()) { + if (method.getName().equals(methodName)) { + assertTrue("Method " + methodName + " must be protected", Modifier.isProtected(method.getModifiers())); + found = true; + } + } + assertTrue("Failed to find method " + methodName, found); + } } /** - * Mocks the asynchronous request execution by calling the {@link #mockPerformRequest(Map)} method. + * Mocks the asynchronous request execution by calling the {@link #mockPerformRequest(Header)} method. */ - private Void mockPerformRequestAsync(Map httpHeaders, ResponseListener responseListener) { + private Void mockPerformRequestAsync(Header httpHeader, ResponseListener responseListener) { try { - responseListener.onSuccess(mockPerformRequest(httpHeaders)); + responseListener.onSuccess(mockPerformRequest(httpHeader)); } catch (IOException e) { responseListener.onFailure(e); } @@ -100,19 +131,20 @@ private Void mockPerformRequestAsync(Map httpHeaders, ResponseLi /** * Mocks the synchronous request execution like if it was executed by Elasticsearch. */ - private Response mockPerformRequest(Map httpHeaders) throws IOException { - assertEquals(1, httpHeaders.size()); - + private Response mockPerformRequest(Header httpHeader) throws IOException { ProtocolVersion protocol = new ProtocolVersion("HTTP", 1, 1); HttpResponse httpResponse = new BasicHttpResponse(new BasicStatusLine(protocol, 200, "OK")); - httpResponse.setHeader("custom", httpHeaders.get("custom")); + + MainResponse response = new MainResponse(httpHeader.getValue(), Version.CURRENT, ClusterName.DEFAULT, "_na", Build.CURRENT, true); + BytesRef bytesRef = XContentHelper.toXContent(response, XContentType.JSON, false).toBytesRef(); + httpResponse.setEntity(new ByteArrayEntity(bytesRef.bytes, ContentType.APPLICATION_JSON)); RequestLine requestLine = new BasicRequestLine(HttpGet.METHOD_NAME, ENDPOINT, protocol); return new Response(requestLine, new HttpHost("localhost", 9200), httpResponse); } /** - * A custom high level client that provides methods to execute a custom request and get its associate response back. + * A custom high level client that provides custom methods to execute a request and get its associate response back. */ static class CustomRestClient extends RestHighLevelClient { @@ -120,57 +152,28 @@ private CustomRestClient(RestClient restClient) { super(restClient); } - public CustomResponse custom(CustomRequest customRequest, Header... headers) throws IOException { - return performRequest(customRequest, this::toRequest, this::toResponse, emptySet(), headers); - } - - public void customAsync(CustomRequest customRequest, ActionListener listener, Header... headers) { - performRequestAsync(customRequest, this::toRequest, this::toResponse, listener, emptySet(), headers); - } - - Request toRequest(CustomRequest customRequest) throws IOException { - return new Request(HttpGet.METHOD_NAME, ENDPOINT, singletonMap("custom", customRequest.getValue()), null); + MainResponse custom(MainRequest mainRequest, Header... headers) throws IOException { + return performRequest(mainRequest, this::toRequest, this::toResponse, emptySet(), headers); } - CustomResponse toResponse(Response response) throws IOException { - return new CustomResponse(response.getHeader("custom")); - } - } - - /** - * A custom request - */ - static class CustomRequest extends ActionRequest { - - private final String value; - - CustomRequest(String value) { - this.value = value; + MainResponse customAndParse(MainRequest mainRequest, Header... headers) throws IOException { + return performRequestAndParseEntity(mainRequest, this::toRequest, MainResponse::fromXContent, emptySet(), headers); } - String getValue() { - return value; + void customAsync(MainRequest mainRequest, ActionListener listener, Header... headers) { + performRequestAsync(mainRequest, this::toRequest, this::toResponse, listener, emptySet(), headers); } - @Override - public ActionRequestValidationException validate() { - return null; + void customAndParseAsync(MainRequest mainRequest, ActionListener listener, Header... headers) { + performRequestAsyncAndParseEntity(mainRequest, this::toRequest, MainResponse::fromXContent, listener, emptySet(), headers); } - } - - /** - * A custom response - */ - static class CustomResponse extends ActionResponse { - - private final String value; - CustomResponse(String value) { - this.value = value; + Request toRequest(MainRequest mainRequest) throws IOException { + return new Request(HttpGet.METHOD_NAME, ENDPOINT, emptyMap(), null); } - String getValue() { - return value; + MainResponse toResponse(Response response) throws IOException { + return parseEntity(response.getEntity(), MainResponse::fromXContent); } } } \ No newline at end of file From e2770b019d103f305b3d0db9d44b0ba083e0fc2c Mon Sep 17 00:00:00 2001 From: Tanguy Leroux Date: Fri, 9 Jun 2017 15:31:20 +0200 Subject: [PATCH 5/5] Add @SuppressForbidden --- .../elasticsearch/client/CustomRestHighLevelClientTests.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/CustomRestHighLevelClientTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/CustomRestHighLevelClientTests.java index 609b56f991fdc..8ad42c2232020 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/CustomRestHighLevelClientTests.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/CustomRestHighLevelClientTests.java @@ -39,6 +39,7 @@ import org.elasticsearch.action.main.MainRequest; import org.elasticsearch.action.main.MainResponse; import org.elasticsearch.cluster.ClusterName; +import org.elasticsearch.common.SuppressForbidden; import org.elasticsearch.common.xcontent.XContentHelper; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.test.ESTestCase; @@ -101,6 +102,7 @@ public void testCustomEndpoint() throws IOException { * The {@link RestHighLevelClient} must declare the following execution methods using the protected modifier * so that they can be used by subclasses to implement custom logic. */ + @SuppressForbidden(reason = "We're forced to uses Class#getDeclaredMethods() here because this test checks protected methods") public void testMethodsVisibility() throws ClassNotFoundException { String[] methodNames = new String[]{"performRequest", "performRequestAndParseEntity", "performRequestAsync", "performRequestAsyncAndParseEntity"};