Skip to content

Commit b225f5e

Browse files
authored
HLRest: Allow caller to set per request options (#30490)
This modifies the high level rest client to allow calling code to customize per request options for the bulk API. You do the actual customization by passing a `RequestOptions` object to the API call which is set on the `Request` that is generated by the high level client. It also makes the `RequestOptions` a thing in the low level rest client. For now that just means you use it to customize the headers and the `httpAsyncResponseConsumerFactory` and we'll add node selectors and per request timeouts in a follow up. I only implemented this on the bulk API because it is the first one in the list alphabetically and I wanted to keep the change small enough to review. I'll convert the remaining APIs in a followup.
1 parent d826cb3 commit b225f5e

File tree

19 files changed

+578
-195
lines changed

19 files changed

+578
-195
lines changed

client/rest-high-level/src/main/java/org/elasticsearch/client/RestHighLevelClient.java

+71-9
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,17 @@ public final TasksClient tasks() {
279279
*
280280
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html">Bulk API on elastic.co</a>
281281
*/
282+
public final BulkResponse bulk(BulkRequest bulkRequest, RequestOptions options) throws IOException {
283+
return performRequestAndParseEntity(bulkRequest, RequestConverters::bulk, options, BulkResponse::fromXContent, emptySet());
284+
}
285+
286+
/**
287+
* Executes a bulk request using the Bulk API
288+
*
289+
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html">Bulk API on elastic.co</a>
290+
* @deprecated Prefer {@link #bulk(BulkRequest, RequestOptions)}
291+
*/
292+
@Deprecated
282293
public final BulkResponse bulk(BulkRequest bulkRequest, Header... headers) throws IOException {
283294
return performRequestAndParseEntity(bulkRequest, RequestConverters::bulk, BulkResponse::fromXContent, emptySet(), headers);
284295
}
@@ -288,6 +299,17 @@ public final BulkResponse bulk(BulkRequest bulkRequest, Header... headers) throw
288299
*
289300
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html">Bulk API on elastic.co</a>
290301
*/
302+
public final void bulkAsync(BulkRequest bulkRequest, RequestOptions options, ActionListener<BulkResponse> listener) {
303+
performRequestAsyncAndParseEntity(bulkRequest, RequestConverters::bulk, options, BulkResponse::fromXContent, listener, emptySet());
304+
}
305+
306+
/**
307+
* Asynchronously executes a bulk request using the Bulk API
308+
*
309+
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html">Bulk API on elastic.co</a>
310+
* @deprecated Prefer {@link #bulkAsync(BulkRequest, RequestOptions, ActionListener)}
311+
*/
312+
@Deprecated
291313
public final void bulkAsync(BulkRequest bulkRequest, ActionListener<BulkResponse> listener, Header... headers) {
292314
performRequestAsyncAndParseEntity(bulkRequest, RequestConverters::bulk, BulkResponse::fromXContent, listener, emptySet(), headers);
293315
}
@@ -584,23 +606,42 @@ public final void fieldCapsAsync(FieldCapabilitiesRequest fieldCapabilitiesReque
584606
FieldCapabilitiesResponse::fromXContent, listener, emptySet(), headers);
585607
}
586608

609+
@Deprecated
587610
protected final <Req extends ActionRequest, Resp> Resp performRequestAndParseEntity(Req request,
588611
CheckedFunction<Req, Request, IOException> requestConverter,
589612
CheckedFunction<XContentParser, Resp, IOException> entityParser,
590613
Set<Integer> ignores, Header... headers) throws IOException {
591614
return performRequest(request, requestConverter, (response) -> parseEntity(response.getEntity(), entityParser), ignores, headers);
592615
}
593616

617+
protected final <Req extends ActionRequest, Resp> Resp performRequestAndParseEntity(Req request,
618+
CheckedFunction<Req, Request, IOException> requestConverter,
619+
RequestOptions options,
620+
CheckedFunction<XContentParser, Resp, IOException> entityParser,
621+
Set<Integer> ignores) throws IOException {
622+
return performRequest(request, requestConverter, options,
623+
response -> parseEntity(response.getEntity(), entityParser), ignores);
624+
}
625+
626+
@Deprecated
594627
protected final <Req extends ActionRequest, Resp> Resp performRequest(Req request,
595628
CheckedFunction<Req, Request, IOException> requestConverter,
596629
CheckedFunction<Response, Resp, IOException> responseConverter,
597630
Set<Integer> ignores, Header... headers) throws IOException {
631+
return performRequest(request, requestConverter, optionsForHeaders(headers), responseConverter, ignores);
632+
}
633+
634+
protected final <Req extends ActionRequest, Resp> Resp performRequest(Req request,
635+
CheckedFunction<Req, Request, IOException> requestConverter,
636+
RequestOptions options,
637+
CheckedFunction<Response, Resp, IOException> responseConverter,
638+
Set<Integer> ignores) throws IOException {
598639
ActionRequestValidationException validationException = request.validate();
599640
if (validationException != null) {
600641
throw validationException;
601642
}
602643
Request req = requestConverter.apply(request);
603-
addHeaders(req, headers);
644+
req.setOptions(options);
604645
Response response;
605646
try {
606647
response = client.performRequest(req);
@@ -626,6 +667,7 @@ protected final <Req extends ActionRequest, Resp> Resp performRequest(Req reques
626667
}
627668
}
628669

670+
@Deprecated
629671
protected final <Req extends ActionRequest, Resp> void performRequestAsyncAndParseEntity(Req request,
630672
CheckedFunction<Req, Request, IOException> requestConverter,
631673
CheckedFunction<XContentParser, Resp, IOException> entityParser,
@@ -634,10 +676,28 @@ protected final <Req extends ActionRequest, Resp> void performRequestAsyncAndPar
634676
listener, ignores, headers);
635677
}
636678

679+
protected final <Req extends ActionRequest, Resp> void performRequestAsyncAndParseEntity(Req request,
680+
CheckedFunction<Req, Request, IOException> requestConverter,
681+
RequestOptions options,
682+
CheckedFunction<XContentParser, Resp, IOException> entityParser,
683+
ActionListener<Resp> listener, Set<Integer> ignores) {
684+
performRequestAsync(request, requestConverter, options,
685+
response -> parseEntity(response.getEntity(), entityParser), listener, ignores);
686+
}
687+
688+
@Deprecated
637689
protected final <Req extends ActionRequest, Resp> void performRequestAsync(Req request,
638690
CheckedFunction<Req, Request, IOException> requestConverter,
639691
CheckedFunction<Response, Resp, IOException> responseConverter,
640692
ActionListener<Resp> listener, Set<Integer> ignores, Header... headers) {
693+
performRequestAsync(request, requestConverter, optionsForHeaders(headers), responseConverter, listener, ignores);
694+
}
695+
696+
protected final <Req extends ActionRequest, Resp> void performRequestAsync(Req request,
697+
CheckedFunction<Req, Request, IOException> requestConverter,
698+
RequestOptions options,
699+
CheckedFunction<Response, Resp, IOException> responseConverter,
700+
ActionListener<Resp> listener, Set<Integer> ignores) {
641701
ActionRequestValidationException validationException = request.validate();
642702
if (validationException != null) {
643703
listener.onFailure(validationException);
@@ -650,19 +710,12 @@ protected final <Req extends ActionRequest, Resp> void performRequestAsync(Req r
650710
listener.onFailure(e);
651711
return;
652712
}
653-
addHeaders(req, headers);
713+
req.setOptions(options);
654714

655715
ResponseListener responseListener = wrapResponseListener(responseConverter, listener, ignores);
656716
client.performRequestAsync(req, responseListener);
657717
}
658718

659-
private static void addHeaders(Request request, Header... headers) {
660-
Objects.requireNonNull(headers, "headers cannot be null");
661-
for (Header header : headers) {
662-
request.addHeader(header.getName(), header.getValue());
663-
}
664-
}
665-
666719
final <Resp> ResponseListener wrapResponseListener(CheckedFunction<Response, Resp, IOException> responseConverter,
667720
ActionListener<Resp> actionListener, Set<Integer> ignores) {
668721
return new ResponseListener() {
@@ -746,6 +799,15 @@ protected final <Resp> Resp parseEntity(final HttpEntity entity,
746799
}
747800
}
748801

802+
private static RequestOptions optionsForHeaders(Header[] headers) {
803+
RequestOptions.Builder options = RequestOptions.DEFAULT.toBuilder();
804+
for (Header header : headers) {
805+
Objects.requireNonNull(header, "header cannot be null");
806+
options.addHeader(header.getName(), header.getValue());
807+
}
808+
return options.build();
809+
}
810+
749811
static boolean convertExistsResponse(Response response) {
750812
return response.getStatusLine().getStatusCode() == 200;
751813
}

client/rest-high-level/src/test/java/org/elasticsearch/client/CustomRestHighLevelClientTests.java

+44-35
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
import org.apache.http.client.methods.HttpGet;
2727
import org.apache.http.entity.ByteArrayEntity;
2828
import org.apache.http.entity.ContentType;
29-
import org.apache.http.message.BasicHeader;
3029
import org.apache.http.message.BasicRequestLine;
3130
import org.apache.http.message.BasicStatusLine;
3231
import org.apache.lucene.util.BytesRef;
@@ -48,11 +47,13 @@
4847
import java.lang.reflect.Modifier;
4948
import java.util.Arrays;
5049
import java.util.Collections;
51-
import java.util.List;
50+
import java.util.Set;
51+
import java.util.TreeSet;
5252
import java.util.stream.Collectors;
5353

5454
import static java.util.Collections.emptySet;
55-
import static org.hamcrest.Matchers.containsInAnyOrder;
55+
import static org.hamcrest.Matchers.contains;
56+
import static org.hamcrest.Matchers.hasSize;
5657
import static org.mockito.Matchers.any;
5758
import static org.mockito.Mockito.doAnswer;
5859
import static org.mockito.Mockito.mock;
@@ -73,12 +74,12 @@ public void initClients() throws IOException {
7374
final RestClient restClient = mock(RestClient.class);
7475
restHighLevelClient = new CustomRestClient(restClient);
7576

76-
doAnswer(inv -> mockPerformRequest(((Request) inv.getArguments()[0]).getHeaders().iterator().next()))
77+
doAnswer(inv -> mockPerformRequest((Request) inv.getArguments()[0]))
7778
.when(restClient)
7879
.performRequest(any(Request.class));
7980

8081
doAnswer(inv -> mockPerformRequestAsync(
81-
((Request) inv.getArguments()[0]).getHeaders().iterator().next(),
82+
((Request) inv.getArguments()[0]),
8283
(ResponseListener) inv.getArguments()[1]))
8384
.when(restClient)
8485
.performRequestAsync(any(Request.class), any(ResponseListener.class));
@@ -87,26 +88,32 @@ public void initClients() throws IOException {
8788

8889
public void testCustomEndpoint() throws IOException {
8990
final MainRequest request = new MainRequest();
90-
final Header header = new BasicHeader("node_name", randomAlphaOfLengthBetween(1, 10));
91+
String nodeName = randomAlphaOfLengthBetween(1, 10);
9192

92-
MainResponse response = restHighLevelClient.custom(request, header);
93-
assertEquals(header.getValue(), response.getNodeName());
93+
MainResponse response = restHighLevelClient.custom(request, optionsForNodeName(nodeName));
94+
assertEquals(nodeName, response.getNodeName());
9495

95-
response = restHighLevelClient.customAndParse(request, header);
96-
assertEquals(header.getValue(), response.getNodeName());
96+
response = restHighLevelClient.customAndParse(request, optionsForNodeName(nodeName));
97+
assertEquals(nodeName, response.getNodeName());
9798
}
9899

99100
public void testCustomEndpointAsync() throws Exception {
100101
final MainRequest request = new MainRequest();
101-
final Header header = new BasicHeader("node_name", randomAlphaOfLengthBetween(1, 10));
102+
String nodeName = randomAlphaOfLengthBetween(1, 10);
102103

103104
PlainActionFuture<MainResponse> future = PlainActionFuture.newFuture();
104-
restHighLevelClient.customAsync(request, future, header);
105-
assertEquals(header.getValue(), future.get().getNodeName());
105+
restHighLevelClient.customAsync(request, optionsForNodeName(nodeName), future);
106+
assertEquals(nodeName, future.get().getNodeName());
106107

107108
future = PlainActionFuture.newFuture();
108-
restHighLevelClient.customAndParseAsync(request, future, header);
109-
assertEquals(header.getValue(), future.get().getNodeName());
109+
restHighLevelClient.customAndParseAsync(request, optionsForNodeName(nodeName), future);
110+
assertEquals(nodeName, future.get().getNodeName());
111+
}
112+
113+
private static RequestOptions optionsForNodeName(String nodeName) {
114+
RequestOptions.Builder options = RequestOptions.DEFAULT.toBuilder();
115+
options.addHeader("node_name", nodeName);
116+
return options.build();
110117
}
111118

112119
/**
@@ -115,27 +122,27 @@ public void testCustomEndpointAsync() throws Exception {
115122
*/
116123
@SuppressForbidden(reason = "We're forced to uses Class#getDeclaredMethods() here because this test checks protected methods")
117124
public void testMethodsVisibility() throws ClassNotFoundException {
118-
final String[] methodNames = new String[]{"performRequest",
119-
"performRequestAsync",
125+
final String[] methodNames = new String[]{"parseEntity",
126+
"parseResponseException",
127+
"performRequest",
120128
"performRequestAndParseEntity",
121-
"performRequestAsyncAndParseEntity",
122-
"parseEntity",
123-
"parseResponseException"};
129+
"performRequestAsync",
130+
"performRequestAsyncAndParseEntity"};
124131

125-
final List<String> protectedMethods = Arrays.stream(RestHighLevelClient.class.getDeclaredMethods())
132+
final Set<String> protectedMethods = Arrays.stream(RestHighLevelClient.class.getDeclaredMethods())
126133
.filter(method -> Modifier.isProtected(method.getModifiers()))
127134
.map(Method::getName)
128-
.collect(Collectors.toList());
135+
.collect(Collectors.toCollection(TreeSet::new));
129136

130-
assertThat(protectedMethods, containsInAnyOrder(methodNames));
137+
assertThat(protectedMethods, contains(methodNames));
131138
}
132139

133140
/**
134-
* Mocks the asynchronous request execution by calling the {@link #mockPerformRequest(Header)} method.
141+
* Mocks the asynchronous request execution by calling the {@link #mockPerformRequest(Request)} method.
135142
*/
136-
private Void mockPerformRequestAsync(Header httpHeader, ResponseListener responseListener) {
143+
private Void mockPerformRequestAsync(Request request, ResponseListener responseListener) {
137144
try {
138-
responseListener.onSuccess(mockPerformRequest(httpHeader));
145+
responseListener.onSuccess(mockPerformRequest(request));
139146
} catch (IOException e) {
140147
responseListener.onFailure(e);
141148
}
@@ -145,7 +152,9 @@ private Void mockPerformRequestAsync(Header httpHeader, ResponseListener respons
145152
/**
146153
* Mocks the synchronous request execution like if it was executed by Elasticsearch.
147154
*/
148-
private Response mockPerformRequest(Header httpHeader) throws IOException {
155+
private Response mockPerformRequest(Request request) throws IOException {
156+
assertThat(request.getOptions().getHeaders(), hasSize(1));
157+
Header httpHeader = request.getOptions().getHeaders().get(0);
149158
final Response mockResponse = mock(Response.class);
150159
when(mockResponse.getHost()).thenReturn(new HttpHost("localhost", 9200));
151160

@@ -171,20 +180,20 @@ private CustomRestClient(RestClient restClient) {
171180
super(restClient, RestClient::close, Collections.emptyList());
172181
}
173182

174-
MainResponse custom(MainRequest mainRequest, Header... headers) throws IOException {
175-
return performRequest(mainRequest, this::toRequest, this::toResponse, emptySet(), headers);
183+
MainResponse custom(MainRequest mainRequest, RequestOptions options) throws IOException {
184+
return performRequest(mainRequest, this::toRequest, options, this::toResponse, emptySet());
176185
}
177186

178-
MainResponse customAndParse(MainRequest mainRequest, Header... headers) throws IOException {
179-
return performRequestAndParseEntity(mainRequest, this::toRequest, MainResponse::fromXContent, emptySet(), headers);
187+
MainResponse customAndParse(MainRequest mainRequest, RequestOptions options) throws IOException {
188+
return performRequestAndParseEntity(mainRequest, this::toRequest, options, MainResponse::fromXContent, emptySet());
180189
}
181190

182-
void customAsync(MainRequest mainRequest, ActionListener<MainResponse> listener, Header... headers) {
183-
performRequestAsync(mainRequest, this::toRequest, this::toResponse, listener, emptySet(), headers);
191+
void customAsync(MainRequest mainRequest, RequestOptions options, ActionListener<MainResponse> listener) {
192+
performRequestAsync(mainRequest, this::toRequest, options, this::toResponse, listener, emptySet());
184193
}
185194

186-
void customAndParseAsync(MainRequest mainRequest, ActionListener<MainResponse> listener, Header... headers) {
187-
performRequestAsyncAndParseEntity(mainRequest, this::toRequest, MainResponse::fromXContent, listener, emptySet(), headers);
195+
void customAndParseAsync(MainRequest mainRequest, RequestOptions options, ActionListener<MainResponse> listener) {
196+
performRequestAsyncAndParseEntity(mainRequest, this::toRequest, options, MainResponse::fromXContent, listener, emptySet());
188197
}
189198

190199
Request toRequest(MainRequest mainRequest) throws IOException {

0 commit comments

Comments
 (0)