Skip to content

Commit 64e5c25

Browse files
authored
HLRC support for getTask (#35166)
Given a GetTaskRequest the API returns an Optional which is empty in the case of 404s or returns a TaskInfo object if found. Added Helper methods in RestHighLevelClient for returning empty Optionals when hitting 404s
1 parent 2591f66 commit 64e5c25

File tree

9 files changed

+478
-5
lines changed

9 files changed

+478
-5
lines changed

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

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1417,6 +1417,38 @@ private <Req, Resp> Resp internalPerformRequest(Req request,
14171417
throw new IOException("Unable to parse response body for " + response, e);
14181418
}
14191419
}
1420+
1421+
/**
1422+
* Defines a helper method for requests that can 404 and in which case will return an empty Optional
1423+
* otherwise tries to parse the response body
1424+
*/
1425+
protected final <Req extends Validatable, Resp> Optional<Resp> performRequestAndParseOptionalEntity(Req request,
1426+
CheckedFunction<Req, Request, IOException> requestConverter,
1427+
RequestOptions options,
1428+
CheckedFunction<XContentParser, Resp, IOException> entityParser
1429+
) throws IOException {
1430+
Optional<ValidationException> validationException = request.validate();
1431+
if (validationException != null && validationException.isPresent()) {
1432+
throw validationException.get();
1433+
}
1434+
Request req = requestConverter.apply(request);
1435+
req.setOptions(options);
1436+
Response response;
1437+
try {
1438+
response = client.performRequest(req);
1439+
} catch (ResponseException e) {
1440+
if (RestStatus.NOT_FOUND.getStatus() == e.getResponse().getStatusLine().getStatusCode()) {
1441+
return Optional.empty();
1442+
}
1443+
throw parseResponseException(e);
1444+
}
1445+
1446+
try {
1447+
return Optional.of(parseEntity(response.getEntity(), entityParser));
1448+
} catch (Exception e) {
1449+
throw new IOException("Unable to parse response body for " + response, e);
1450+
}
1451+
}
14201452

14211453
/**
14221454
* @deprecated If creating a new HLRC ReST API call, consider creating new actions instead of reusing server actions. The Validation
@@ -1538,6 +1570,62 @@ public void onFailure(Exception exception) {
15381570
}
15391571
};
15401572
}
1573+
1574+
/**
1575+
* Async request which returns empty Optionals in the case of 404s or parses entity into an Optional
1576+
*/
1577+
protected final <Req extends Validatable, Resp> void performRequestAsyncAndParseOptionalEntity(Req request,
1578+
CheckedFunction<Req, Request, IOException> requestConverter,
1579+
RequestOptions options,
1580+
CheckedFunction<XContentParser, Resp, IOException> entityParser,
1581+
ActionListener<Optional<Resp>> listener) {
1582+
Optional<ValidationException> validationException = request.validate();
1583+
if (validationException != null && validationException.isPresent()) {
1584+
listener.onFailure(validationException.get());
1585+
return;
1586+
}
1587+
Request req;
1588+
try {
1589+
req = requestConverter.apply(request);
1590+
} catch (Exception e) {
1591+
listener.onFailure(e);
1592+
return;
1593+
}
1594+
req.setOptions(options);
1595+
ResponseListener responseListener = wrapResponseListener404sOptional(response -> parseEntity(response.getEntity(),
1596+
entityParser), listener);
1597+
client.performRequestAsync(req, responseListener);
1598+
}
1599+
1600+
final <Resp> ResponseListener wrapResponseListener404sOptional(CheckedFunction<Response, Resp, IOException> responseConverter,
1601+
ActionListener<Optional<Resp>> actionListener) {
1602+
return new ResponseListener() {
1603+
@Override
1604+
public void onSuccess(Response response) {
1605+
try {
1606+
actionListener.onResponse(Optional.of(responseConverter.apply(response)));
1607+
} catch (Exception e) {
1608+
IOException ioe = new IOException("Unable to parse response body for " + response, e);
1609+
onFailure(ioe);
1610+
}
1611+
}
1612+
1613+
@Override
1614+
public void onFailure(Exception exception) {
1615+
if (exception instanceof ResponseException) {
1616+
ResponseException responseException = (ResponseException) exception;
1617+
Response response = responseException.getResponse();
1618+
if (RestStatus.NOT_FOUND.getStatus() == response.getStatusLine().getStatusCode()) {
1619+
actionListener.onResponse(Optional.empty());
1620+
} else {
1621+
actionListener.onFailure(parseResponseException(responseException));
1622+
}
1623+
} else {
1624+
actionListener.onFailure(exception);
1625+
}
1626+
}
1627+
};
1628+
}
15411629

15421630
/**
15431631
* Converts a {@link ResponseException} obtained from the low level REST client into an {@link ElasticsearchException}.

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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,11 @@
2424
import org.elasticsearch.action.admin.cluster.node.tasks.cancel.CancelTasksResponse;
2525
import org.elasticsearch.action.admin.cluster.node.tasks.list.ListTasksRequest;
2626
import org.elasticsearch.action.admin.cluster.node.tasks.list.ListTasksResponse;
27+
import org.elasticsearch.client.tasks.GetTaskRequest;
28+
import org.elasticsearch.client.tasks.GetTaskResponse;
2729

2830
import java.io.IOException;
31+
import java.util.Optional;
2932

3033
import static java.util.Collections.emptySet;
3134

@@ -67,6 +70,34 @@ public void listAsync(ListTasksRequest request, RequestOptions options, ActionLi
6770
restHighLevelClient.performRequestAsyncAndParseEntity(request, TasksRequestConverters::listTasks, options,
6871
ListTasksResponse::fromXContent, listener, emptySet());
6972
}
73+
74+
/**
75+
* Get a task using the Task Management API.
76+
* See
77+
* <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/tasks.html"> Task Management API on elastic.co</a>
78+
* @param request the request
79+
* @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
80+
* @return the response
81+
* @throws IOException in case there is a problem sending the request or parsing back the response
82+
*/
83+
public Optional<GetTaskResponse> get(GetTaskRequest request, RequestOptions options) throws IOException {
84+
return restHighLevelClient.performRequestAndParseOptionalEntity(request, TasksRequestConverters::getTask, options,
85+
GetTaskResponse::fromXContent);
86+
}
87+
88+
/**
89+
* Get a task using the Task Management API.
90+
* See
91+
* <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/tasks.html"> Task Management API on elastic.co</a>
92+
* @param request the request
93+
* @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
94+
* @param listener an actionlistener that takes an optional response (404s are returned as an empty Optional)
95+
*/
96+
public void getAsync(GetTaskRequest request, RequestOptions options, ActionListener<Optional<GetTaskResponse>> listener) {
97+
98+
restHighLevelClient.performRequestAsyncAndParseOptionalEntity(request, TasksRequestConverters::getTask, options,
99+
GetTaskResponse::fromXContent, listener);
100+
}
70101

71102
/**
72103
* Cancel one or more cluster tasks using the Task Management API.

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
import org.apache.http.client.methods.HttpPost;
2424
import org.elasticsearch.action.admin.cluster.node.tasks.cancel.CancelTasksRequest;
2525
import org.elasticsearch.action.admin.cluster.node.tasks.list.ListTasksRequest;
26+
import org.elasticsearch.client.RequestConverters.EndpointBuilder;
27+
import org.elasticsearch.client.tasks.GetTaskRequest;
2628

2729
final class TasksRequestConverters {
2830

@@ -54,4 +56,16 @@ static Request listTasks(ListTasksRequest listTaskRequest) {
5456
.putParam("group_by", "none");
5557
return request;
5658
}
59+
60+
static Request getTask(GetTaskRequest getTaskRequest) {
61+
String endpoint = new EndpointBuilder().addPathPartAsIs("_tasks")
62+
.addPathPartAsIs(getTaskRequest.getNodeId() + ":" + Long.toString(getTaskRequest.getTaskId()))
63+
.build();
64+
Request request = new Request(HttpGet.METHOD_NAME, endpoint);
65+
RequestConverters.Params params = new RequestConverters.Params(request);
66+
params.withTimeout(getTaskRequest.getTimeout())
67+
.withWaitForCompletion(getTaskRequest.getWaitForCompletion());
68+
return request;
69+
}
70+
5771
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.elasticsearch.client.tasks;
20+
21+
import org.elasticsearch.client.Validatable;
22+
import org.elasticsearch.client.ValidationException;
23+
import org.elasticsearch.common.unit.TimeValue;
24+
25+
import java.util.Objects;
26+
import java.util.Optional;
27+
28+
public class GetTaskRequest implements Validatable {
29+
private final String nodeId;
30+
private final long taskId;
31+
private boolean waitForCompletion = false;
32+
private TimeValue timeout = null;
33+
34+
public GetTaskRequest(String nodeId, long taskId) {
35+
this.nodeId = nodeId;
36+
this.taskId = taskId;
37+
}
38+
39+
public String getNodeId() {
40+
return nodeId;
41+
}
42+
43+
public long getTaskId() {
44+
return taskId;
45+
}
46+
47+
/**
48+
* Should this request wait for all found tasks to complete?
49+
*/
50+
public boolean getWaitForCompletion() {
51+
return waitForCompletion;
52+
}
53+
54+
/**
55+
* Should this request wait for all found tasks to complete?
56+
*/
57+
public GetTaskRequest setWaitForCompletion(boolean waitForCompletion) {
58+
this.waitForCompletion = waitForCompletion;
59+
return this;
60+
}
61+
62+
/**
63+
* Timeout to wait for any async actions this request must take. It must take anywhere from 0 to 2.
64+
*/
65+
public TimeValue getTimeout() {
66+
return timeout;
67+
}
68+
69+
/**
70+
* Timeout to wait for any async actions this request must take.
71+
*/
72+
public GetTaskRequest setTimeout(TimeValue timeout) {
73+
this.timeout = timeout;
74+
return this;
75+
}
76+
77+
@Override
78+
public Optional<ValidationException> validate() {
79+
final ValidationException validationException = new ValidationException();
80+
if (timeout != null && !waitForCompletion) {
81+
validationException.addValidationError("Timeout settings are only accepted if waitForCompletion is also set");
82+
}
83+
if (validationException.validationErrors().isEmpty()) {
84+
return Optional.empty();
85+
}
86+
return Optional.of(validationException);
87+
}
88+
89+
@Override
90+
public int hashCode() {
91+
return Objects.hash(nodeId, taskId, waitForCompletion, timeout);
92+
}
93+
94+
@Override
95+
public boolean equals(Object obj) {
96+
if (obj == null) {
97+
return false;
98+
}
99+
if (getClass() != obj.getClass()) {
100+
return false;
101+
}
102+
GetTaskRequest other = (GetTaskRequest) obj;
103+
return Objects.equals(nodeId, other.nodeId) &&
104+
taskId == other.taskId &&
105+
waitForCompletion == other.waitForCompletion &&
106+
Objects.equals(timeout, other.timeout);
107+
}
108+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.client.tasks;
21+
22+
import org.elasticsearch.common.ParseField;
23+
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
24+
import org.elasticsearch.common.xcontent.XContentParser;
25+
import org.elasticsearch.tasks.TaskInfo;
26+
27+
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg;
28+
29+
public class GetTaskResponse {
30+
private final boolean completed;
31+
private final TaskInfo taskInfo;
32+
public static final ParseField COMPLETED = new ParseField("completed");
33+
public static final ParseField TASK = new ParseField("task");
34+
35+
public GetTaskResponse(boolean completed, TaskInfo taskInfo) {
36+
this.completed = completed;
37+
this.taskInfo = taskInfo;
38+
}
39+
40+
public boolean isCompleted() {
41+
return completed;
42+
}
43+
44+
public TaskInfo getTaskInfo() {
45+
return taskInfo;
46+
}
47+
48+
private static final ConstructingObjectParser<GetTaskResponse, Void> PARSER = new ConstructingObjectParser<>("get_task",
49+
true, a -> new GetTaskResponse((boolean) a[0], (TaskInfo) a[1]));
50+
static {
51+
PARSER.declareBoolean(constructorArg(), COMPLETED);
52+
PARSER.declareObject(constructorArg(), (p, c) -> TaskInfo.fromXContent(p), TASK);
53+
}
54+
55+
public static GetTaskResponse fromXContent(XContentParser parser) {
56+
return PARSER.apply(parser, null);
57+
}
58+
}

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,11 @@ public void testMethodsVisibility() {
126126
"parseResponseException",
127127
"performRequest",
128128
"performRequestAndParseEntity",
129+
"performRequestAndParseOptionalEntity",
129130
"performRequestAsync",
130-
"performRequestAsyncAndParseEntity"};
131+
"performRequestAsyncAndParseEntity",
132+
"performRequestAsyncAndParseOptionalEntity"
133+
};
131134

132135
final Set<String> protectedMethods = Arrays.stream(RestHighLevelClient.class.getDeclaredMethods())
133136
.filter(method -> Modifier.isProtected(method.getModifiers()))

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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@
9797
import java.util.HashSet;
9898
import java.util.List;
9999
import java.util.Map;
100+
import java.util.Optional;
100101
import java.util.Set;
101102
import java.util.concurrent.atomic.AtomicInteger;
102103
import java.util.concurrent.atomic.AtomicReference;
@@ -675,8 +676,7 @@ public void testApiNamingConventions() throws Exception {
675676
"indices.put_alias",
676677
"mtermvectors",
677678
"render_search_template",
678-
"scripts_painless_execute",
679-
"tasks.get"
679+
"scripts_painless_execute"
680680
};
681681
//These API are not required for high-level client feature completeness
682682
String[] notRequiredApi = new String[] {
@@ -777,8 +777,11 @@ private void assertSyncMethod(Method method, String apiName) {
777777
assertThat("the return type for method [" + method + "] is incorrect",
778778
method.getReturnType().getSimpleName(), equalTo("boolean"));
779779
} else {
780-
assertThat("the return type for method [" + method + "] is incorrect",
781-
method.getReturnType().getSimpleName(), endsWith("Response"));
780+
// It's acceptable for 404s to be represented as empty Optionals
781+
if (!method.getReturnType().isAssignableFrom(Optional.class)) {
782+
assertThat("the return type for method [" + method + "] is incorrect",
783+
method.getReturnType().getSimpleName(), endsWith("Response"));
784+
}
782785
}
783786

784787
assertEquals("incorrect number of exceptions for method [" + method + "]", 1, method.getExceptionTypes().length);

0 commit comments

Comments
 (0)