Skip to content

Commit 66a930b

Browse files
authored
Adding ActionRequestLazyBuilder implementation of RequestBuilder (#104927)
This introduces a second implementation of RequestBuilder (#104778). As opposed to ActionRequestBuilder, ActionRequestLazyBuilder does not create its request until the request() method is called, and does not hold onto that request (so each call to request() gets a new request instance). This PR also updates BulkRequestBuilder to inherit from ActionRequestLazyBuilder as an example of its use.
1 parent a4ddd32 commit 66a930b

File tree

4 files changed

+234
-18
lines changed

4 files changed

+234
-18
lines changed

docs/changelog/104927.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 104927
2+
summary: Adding `ActionRequestLazyBuilder` implementation of `RequestBuilder`
3+
area: Ingest Node
4+
type: enhancement
5+
issues: []
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0 and the Server Side Public License, v 1; you may not use this file except
5+
* in compliance with, at your election, the Elastic License 2.0 or the Server
6+
* Side Public License, v 1.
7+
*/
8+
9+
package org.elasticsearch.action;
10+
11+
import org.elasticsearch.client.internal.ElasticsearchClient;
12+
import org.elasticsearch.core.TimeValue;
13+
14+
import java.util.Objects;
15+
16+
/**
17+
* This class is similar to ActionRequestBuilder, except that it does not build the request until the request() method is called.
18+
* @param <Request>
19+
* @param <Response>
20+
*/
21+
public abstract class ActionRequestLazyBuilder<Request extends ActionRequest, Response extends ActionResponse>
22+
implements
23+
RequestBuilder<Request, Response> {
24+
25+
protected final ActionType<Response> action;
26+
protected final ElasticsearchClient client;
27+
28+
protected ActionRequestLazyBuilder(ElasticsearchClient client, ActionType<Response> action) {
29+
Objects.requireNonNull(action, "action must not be null");
30+
this.action = action;
31+
this.client = client;
32+
}
33+
34+
/**
35+
* This method creates the request. The caller of this method is responsible for calling Request#decRef.
36+
* @return A newly-built Request, fully initialized by this builder.
37+
*/
38+
public abstract Request request();
39+
40+
public ActionFuture<Response> execute() {
41+
return client.execute(action, request());
42+
}
43+
44+
/**
45+
* Short version of execute().actionGet().
46+
*/
47+
public Response get() {
48+
return execute().actionGet();
49+
}
50+
51+
/**
52+
* Short version of execute().actionGet().
53+
*/
54+
public Response get(TimeValue timeout) {
55+
return execute().actionGet(timeout);
56+
}
57+
58+
public void execute(ActionListener<Response> listener) {
59+
client.execute(action, request(), listener);
60+
}
61+
}

server/src/main/java/org/elasticsearch/action/bulk/BulkRequestBuilder.java

Lines changed: 131 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,17 @@
88

99
package org.elasticsearch.action.bulk;
1010

11-
import org.elasticsearch.action.ActionRequestBuilder;
11+
import org.elasticsearch.action.ActionRequest;
12+
import org.elasticsearch.action.ActionRequestLazyBuilder;
13+
import org.elasticsearch.action.ActionResponse;
14+
import org.elasticsearch.action.DocWriteRequest;
15+
import org.elasticsearch.action.RequestBuilder;
1216
import org.elasticsearch.action.delete.DeleteRequest;
1317
import org.elasticsearch.action.delete.DeleteRequestBuilder;
1418
import org.elasticsearch.action.index.IndexRequest;
1519
import org.elasticsearch.action.index.IndexRequestBuilder;
1620
import org.elasticsearch.action.support.ActiveShardCount;
21+
import org.elasticsearch.action.support.WriteRequest;
1722
import org.elasticsearch.action.support.WriteRequestBuilder;
1823
import org.elasticsearch.action.support.replication.ReplicationRequest;
1924
import org.elasticsearch.action.update.UpdateRequest;
@@ -23,26 +28,50 @@
2328
import org.elasticsearch.core.TimeValue;
2429
import org.elasticsearch.xcontent.XContentType;
2530

31+
import java.io.IOException;
32+
import java.util.ArrayList;
33+
import java.util.List;
34+
2635
/**
2736
* A bulk request holds an ordered {@link IndexRequest}s and {@link DeleteRequest}s and allows to executes
2837
* it in a single batch.
2938
*/
30-
public class BulkRequestBuilder extends ActionRequestBuilder<BulkRequest, BulkResponse> implements WriteRequestBuilder<BulkRequestBuilder> {
39+
public class BulkRequestBuilder extends ActionRequestLazyBuilder<BulkRequest, BulkResponse>
40+
implements
41+
WriteRequestBuilder<BulkRequestBuilder> {
42+
private final String globalIndex;
43+
/*
44+
* The following 3 variables hold the list of requests that make up this bulk. Only one can be non-empty. That is, users can't add
45+
* some IndexRequests and some IndexRequestBuilders. They need to pick one (preferably builders) and stick with it.
46+
*/
47+
private final List<DocWriteRequest<?>> requests = new ArrayList<>();
48+
private final List<FramedData> framedData = new ArrayList<>();
49+
private final List<RequestBuilder<? extends ActionRequest, ? extends ActionResponse>> requestBuilders = new ArrayList<>();
50+
private ActiveShardCount waitForActiveShards;
51+
private TimeValue timeout;
52+
private String timeoutString;
53+
private String globalPipeline;
54+
private String globalRouting;
55+
private WriteRequest.RefreshPolicy refreshPolicy;
56+
private String refreshPolicyString;
3157

3258
public BulkRequestBuilder(ElasticsearchClient client, @Nullable String globalIndex) {
33-
super(client, BulkAction.INSTANCE, new BulkRequest(globalIndex));
59+
super(client, BulkAction.INSTANCE);
60+
this.globalIndex = globalIndex;
3461
}
3562

3663
public BulkRequestBuilder(ElasticsearchClient client) {
37-
super(client, BulkAction.INSTANCE, new BulkRequest());
64+
this(client, null);
3865
}
3966

4067
/**
4168
* Adds an {@link IndexRequest} to the list of actions to execute. Follows the same behavior of {@link IndexRequest}
4269
* (for example, if no id is provided, one will be generated, or usage of the create flag).
70+
* @deprecated use {@link #add(IndexRequestBuilder)} instead
4371
*/
72+
@Deprecated
4473
public BulkRequestBuilder add(IndexRequest request) {
45-
super.request.add(request);
74+
requests.add(request);
4675
return this;
4776
}
4877

@@ -51,47 +80,51 @@ public BulkRequestBuilder add(IndexRequest request) {
5180
* (for example, if no id is provided, one will be generated, or usage of the create flag).
5281
*/
5382
public BulkRequestBuilder add(IndexRequestBuilder request) {
54-
super.request.add(request.request());
83+
requestBuilders.add(request);
5584
return this;
5685
}
5786

5887
/**
5988
* Adds an {@link DeleteRequest} to the list of actions to execute.
89+
* @deprecated use {@link #add(DeleteRequestBuilder)} instead
6090
*/
91+
@Deprecated
6192
public BulkRequestBuilder add(DeleteRequest request) {
62-
super.request.add(request);
93+
requests.add(request);
6394
return this;
6495
}
6596

6697
/**
6798
* Adds an {@link DeleteRequest} to the list of actions to execute.
6899
*/
69100
public BulkRequestBuilder add(DeleteRequestBuilder request) {
70-
super.request.add(request.request());
101+
requestBuilders.add(request);
71102
return this;
72103
}
73104

74105
/**
75106
* Adds an {@link UpdateRequest} to the list of actions to execute.
107+
* @deprecated use {@link #add(UpdateRequestBuilder)} instead
76108
*/
109+
@Deprecated
77110
public BulkRequestBuilder add(UpdateRequest request) {
78-
super.request.add(request);
111+
requests.add(request);
79112
return this;
80113
}
81114

82115
/**
83116
* Adds an {@link UpdateRequest} to the list of actions to execute.
84117
*/
85118
public BulkRequestBuilder add(UpdateRequestBuilder request) {
86-
super.request.add(request.request());
119+
requestBuilders.add(request);
87120
return this;
88121
}
89122

90123
/**
91124
* Adds a framed data in binary format
92125
*/
93126
public BulkRequestBuilder add(byte[] data, int from, int length, XContentType xContentType) throws Exception {
94-
request.add(data, from, length, null, xContentType);
127+
framedData.add(new FramedData(data, from, length, null, xContentType));
95128
return this;
96129
}
97130

@@ -100,7 +133,7 @@ public BulkRequestBuilder add(byte[] data, int from, int length, XContentType xC
100133
*/
101134
public BulkRequestBuilder add(byte[] data, int from, int length, @Nullable String defaultIndex, XContentType xContentType)
102135
throws Exception {
103-
request.add(data, from, length, defaultIndex, xContentType);
136+
framedData.add(new FramedData(data, from, length, defaultIndex, xContentType));
104137
return this;
105138
}
106139

@@ -109,7 +142,7 @@ public BulkRequestBuilder add(byte[] data, int from, int length, @Nullable Strin
109142
* See {@link ReplicationRequest#waitForActiveShards(ActiveShardCount)} for details.
110143
*/
111144
public BulkRequestBuilder setWaitForActiveShards(ActiveShardCount waitForActiveShards) {
112-
request.waitForActiveShards(waitForActiveShards);
145+
this.waitForActiveShards = waitForActiveShards;
113146
return this;
114147
}
115148

@@ -126,32 +159,112 @@ public BulkRequestBuilder setWaitForActiveShards(final int waitForActiveShards)
126159
* A timeout to wait if the index operation can't be performed immediately. Defaults to {@code 1m}.
127160
*/
128161
public final BulkRequestBuilder setTimeout(TimeValue timeout) {
129-
request.timeout(timeout);
162+
this.timeout = timeout;
130163
return this;
131164
}
132165

133166
/**
134167
* A timeout to wait if the index operation can't be performed immediately. Defaults to {@code 1m}.
135168
*/
136169
public final BulkRequestBuilder setTimeout(String timeout) {
137-
request.timeout(timeout);
170+
this.timeoutString = timeout;
138171
return this;
139172
}
140173

141174
/**
142175
* The number of actions currently in the bulk.
143176
*/
144177
public int numberOfActions() {
145-
return request.numberOfActions();
178+
return requests.size() + requestBuilders.size() + framedData.size();
146179
}
147180

148181
public BulkRequestBuilder pipeline(String globalPipeline) {
149-
request.pipeline(globalPipeline);
182+
this.globalPipeline = globalPipeline;
150183
return this;
151184
}
152185

153186
public BulkRequestBuilder routing(String globalRouting) {
154-
request.routing(globalRouting);
187+
this.globalRouting = globalRouting;
188+
return this;
189+
}
190+
191+
@Override
192+
public BulkRequestBuilder setRefreshPolicy(WriteRequest.RefreshPolicy refreshPolicy) {
193+
this.refreshPolicy = refreshPolicy;
194+
return this;
195+
}
196+
197+
@Override
198+
public BulkRequestBuilder setRefreshPolicy(String refreshPolicy) {
199+
this.refreshPolicyString = refreshPolicy;
155200
return this;
156201
}
202+
203+
@Override
204+
public BulkRequest request() {
205+
validate();
206+
BulkRequest request = new BulkRequest(globalIndex);
207+
for (RequestBuilder<? extends ActionRequest, ? extends ActionResponse> requestBuilder : requestBuilders) {
208+
ActionRequest childRequest = requestBuilder.request();
209+
request.add((DocWriteRequest<?>) childRequest);
210+
}
211+
for (DocWriteRequest<?> childRequest : requests) {
212+
request.add(childRequest);
213+
}
214+
for (FramedData framedData : framedData) {
215+
try {
216+
request.add(framedData.data, framedData.from, framedData.length, framedData.defaultIndex, framedData.xContentType);
217+
} catch (IOException e) {
218+
throw new RuntimeException(e);
219+
}
220+
}
221+
if (waitForActiveShards != null) {
222+
request.waitForActiveShards(waitForActiveShards);
223+
}
224+
if (timeout != null) {
225+
request.timeout(timeout);
226+
}
227+
if (timeoutString != null) {
228+
request.timeout(timeoutString);
229+
}
230+
if (globalPipeline != null) {
231+
request.pipeline(globalPipeline);
232+
}
233+
if (globalRouting != null) {
234+
request.routing(globalRouting);
235+
}
236+
if (refreshPolicy != null) {
237+
request.setRefreshPolicy(refreshPolicy);
238+
}
239+
if (refreshPolicyString != null) {
240+
request.setRefreshPolicy(refreshPolicyString);
241+
}
242+
return request;
243+
}
244+
245+
private void validate() {
246+
if (countNonEmptyLists(requestBuilders, requests, framedData) > 1) {
247+
throw new IllegalStateException(
248+
"Must use only request builders, requests, or byte arrays within a single bulk request. Cannot mix and match"
249+
);
250+
}
251+
if (timeout != null && timeoutString != null) {
252+
throw new IllegalStateException("Must use only one setTimeout method");
253+
}
254+
if (refreshPolicy != null && refreshPolicyString != null) {
255+
throw new IllegalStateException("Must use only one setRefreshPolicy method");
256+
}
257+
}
258+
259+
private int countNonEmptyLists(List<?>... lists) {
260+
int sum = 0;
261+
for (List<?> list : lists) {
262+
if (list.isEmpty() == false) {
263+
sum++;
264+
}
265+
}
266+
return sum;
267+
}
268+
269+
private record FramedData(byte[] data, int from, int length, @Nullable String defaultIndex, XContentType xContentType) {}
157270
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0 and the Server Side Public License, v 1; you may not use this file except
5+
* in compliance with, at your election, the Elastic License 2.0 or the Server
6+
* Side Public License, v 1.
7+
*/
8+
9+
package org.elasticsearch.action.bulk;
10+
11+
import org.elasticsearch.action.index.IndexRequest;
12+
import org.elasticsearch.action.index.IndexRequestBuilder;
13+
import org.elasticsearch.action.support.WriteRequest;
14+
import org.elasticsearch.core.TimeValue;
15+
import org.elasticsearch.test.ESTestCase;
16+
17+
public class BulkRequestBuilderTests extends ESTestCase {
18+
19+
public void testValidation() {
20+
BulkRequestBuilder bulkRequestBuilder = new BulkRequestBuilder(null, null);
21+
bulkRequestBuilder.add(new IndexRequestBuilder(null, randomAlphaOfLength(10)));
22+
bulkRequestBuilder.add(new IndexRequest());
23+
expectThrows(IllegalStateException.class, bulkRequestBuilder::request);
24+
25+
bulkRequestBuilder = new BulkRequestBuilder(null, null);
26+
bulkRequestBuilder.add(new IndexRequestBuilder(null, randomAlphaOfLength(10)));
27+
bulkRequestBuilder.setTimeout(randomTimeValue());
28+
bulkRequestBuilder.setTimeout(TimeValue.timeValueSeconds(randomIntBetween(1, 30)));
29+
expectThrows(IllegalStateException.class, bulkRequestBuilder::request);
30+
31+
bulkRequestBuilder = new BulkRequestBuilder(null, null);
32+
bulkRequestBuilder.add(new IndexRequestBuilder(null, randomAlphaOfLength(10)));
33+
bulkRequestBuilder.setRefreshPolicy(randomFrom(WriteRequest.RefreshPolicy.values()).getValue());
34+
bulkRequestBuilder.setRefreshPolicy(randomFrom(WriteRequest.RefreshPolicy.values()));
35+
expectThrows(IllegalStateException.class, bulkRequestBuilder::request);
36+
}
37+
}

0 commit comments

Comments
 (0)