diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/RequestConverters.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/RequestConverters.java index ab85af9f1fd7..9d4582494eb9 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/RequestConverters.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/RequestConverters.java @@ -37,6 +37,8 @@ import org.elasticsearch.action.admin.cluster.repositories.get.GetRepositoriesRe import org.elasticsearch.action.admin.cluster.repositories.put.PutRepositoryRequest; import org.elasticsearch.action.admin.cluster.repositories.verify.VerifyRepositoryRequest; import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsRequest; +import org.elasticsearch.action.admin.cluster.storedscripts.DeleteStoredScriptRequest; +import org.elasticsearch.action.admin.cluster.storedscripts.GetStoredScriptRequest; import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest; import org.elasticsearch.action.admin.indices.alias.get.GetAliasesRequest; import org.elasticsearch.action.admin.indices.cache.clear.ClearIndicesCacheRequest; @@ -892,6 +894,23 @@ final class RequestConverters { return request; } + static Request getScript(GetStoredScriptRequest getStoredScriptRequest) { + String endpoint = new EndpointBuilder().addPathPartAsIs("_scripts").addPathPart(getStoredScriptRequest.id()).build(); + Request request = new Request(HttpGet.METHOD_NAME, endpoint); + Params params = new Params(request); + params.withMasterTimeout(getStoredScriptRequest.masterNodeTimeout()); + return request; + } + + static Request deleteScript(DeleteStoredScriptRequest deleteStoredScriptRequest) { + String endpoint = new EndpointBuilder().addPathPartAsIs("_scripts").addPathPart(deleteStoredScriptRequest.id()).build(); + Request request = new Request(HttpDelete.METHOD_NAME, endpoint); + Params params = new Params(request); + params.withTimeout(deleteStoredScriptRequest.timeout()); + params.withMasterTimeout(deleteStoredScriptRequest.masterNodeTimeout()); + return request; + } + private static HttpEntity createEntity(ToXContent toXContent, XContentType xContentType) throws IOException { BytesRef source = XContentHelper.toXContent(toXContent, xContentType, false).toBytesRef(); return new ByteArrayEntity(source.bytes, source.offset, source.length, createContentType(xContentType)); 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 536b85925a4b..6905cfdb8f71 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 @@ -26,6 +26,10 @@ import org.elasticsearch.ElasticsearchStatusException; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.ActionRequest; import org.elasticsearch.action.ActionRequestValidationException; +import org.elasticsearch.action.admin.cluster.storedscripts.DeleteStoredScriptRequest; +import org.elasticsearch.action.admin.cluster.storedscripts.DeleteStoredScriptResponse; +import org.elasticsearch.action.admin.cluster.storedscripts.GetStoredScriptRequest; +import org.elasticsearch.action.admin.cluster.storedscripts.GetStoredScriptResponse; import org.elasticsearch.action.bulk.BulkRequest; import org.elasticsearch.action.bulk.BulkResponse; import org.elasticsearch.action.delete.DeleteRequest; @@ -652,6 +656,62 @@ public class RestHighLevelClient implements Closeable { FieldCapabilitiesResponse::fromXContent, emptySet()); } + /** + * Get stored script by id. + * See + * How to use scripts on elastic.co + * @param request the request + * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized + * @return the response + * @throws IOException in case there is a problem sending the request or parsing back the response + */ + public GetStoredScriptResponse getScript(GetStoredScriptRequest request, RequestOptions options) throws IOException { + return performRequestAndParseEntity(request, RequestConverters::getScript, options, + GetStoredScriptResponse::fromXContent, emptySet()); + } + + /** + * Asynchronously get stored script by id. + * See + * How to use scripts on elastic.co + * @param request the request + * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized + * @param listener the listener to be notified upon request completion + */ + public void getScriptAsync(GetStoredScriptRequest request, RequestOptions options, + ActionListener listener) { + performRequestAsyncAndParseEntity(request, RequestConverters::getScript, options, + GetStoredScriptResponse::fromXContent, listener, emptySet()); + } + + /** + * Delete stored script by id. + * See + * How to use scripts on elastic.co + * @param request the request + * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized + * @return the response + * @throws IOException in case there is a problem sending the request or parsing back the response + */ + public DeleteStoredScriptResponse deleteScript(DeleteStoredScriptRequest request, RequestOptions options) throws IOException { + return performRequestAndParseEntity(request, RequestConverters::deleteScript, options, + DeleteStoredScriptResponse::fromXContent, emptySet()); + } + + /** + * Asynchronously delete stored script by id. + * See + * How to use scripts on elastic.co + * @param request the request + * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized + * @param listener the listener to be notified upon request completion + */ + public void deleteScriptAsync(DeleteStoredScriptRequest request, RequestOptions options, + ActionListener listener) { + performRequestAsyncAndParseEntity(request, RequestConverters::deleteScript, options, + DeleteStoredScriptResponse::fromXContent, listener, emptySet()); + } + /** * Asynchronously executes a request using the Field Capabilities API. * See Field Capabilities API diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestConvertersTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestConvertersTests.java index 60f427b49046..e7d56a4332b8 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestConvertersTests.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/RequestConvertersTests.java @@ -37,6 +37,8 @@ import org.elasticsearch.action.admin.cluster.repositories.get.GetRepositoriesRe import org.elasticsearch.action.admin.cluster.repositories.put.PutRepositoryRequest; import org.elasticsearch.action.admin.cluster.repositories.verify.VerifyRepositoryRequest; import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsRequest; +import org.elasticsearch.action.admin.cluster.storedscripts.DeleteStoredScriptRequest; +import org.elasticsearch.action.admin.cluster.storedscripts.GetStoredScriptRequest; import org.elasticsearch.action.admin.indices.alias.Alias; import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest; import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest.AliasActions; @@ -1948,6 +1950,32 @@ public class RequestConvertersTests extends ESTestCase { assertThat(request.getEntity(), nullValue()); } + public void testGetScriptRequest() { + GetStoredScriptRequest getStoredScriptRequest = new GetStoredScriptRequest("x-script"); + Map expectedParams = new HashMap<>(); + setRandomMasterTimeout(getStoredScriptRequest, expectedParams); + + Request request = RequestConverters.getScript(getStoredScriptRequest); + assertThat(request.getEndpoint(), equalTo("/_scripts/" + getStoredScriptRequest.id())); + assertThat(request.getMethod(), equalTo(HttpGet.METHOD_NAME)); + assertThat(request.getParameters(), equalTo(expectedParams)); + assertThat(request.getEntity(), nullValue()); + } + + public void testDeleteScriptRequest() { + DeleteStoredScriptRequest deleteStoredScriptRequest = new DeleteStoredScriptRequest("x-script"); + + Map expectedParams = new HashMap<>(); + setRandomTimeout(deleteStoredScriptRequest::timeout, AcknowledgedRequest.DEFAULT_ACK_TIMEOUT, expectedParams); + setRandomMasterTimeout(deleteStoredScriptRequest, expectedParams); + + Request request = RequestConverters.deleteScript(deleteStoredScriptRequest); + assertThat(request.getEndpoint(), equalTo("/_scripts/" + deleteStoredScriptRequest.id())); + assertThat(request.getMethod(), equalTo(HttpDelete.METHOD_NAME)); + assertThat(request.getParameters(), equalTo(expectedParams)); + assertThat(request.getEntity(), nullValue()); + } + private static void assertToXContentBody(ToXContent expectedBody, HttpEntity actualEntity) throws IOException { BytesReference expectedBytes = XContentHelper.toXContent(expectedBody, REQUEST_BODY_CONTENT_TYPE, false); assertEquals(XContentType.JSON.mediaTypeWithoutParameters(), actualEntity.getContentType().getValue()); diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/StoredScriptsIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/StoredScriptsIT.java new file mode 100644 index 000000000000..e6d380a4cc0e --- /dev/null +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/StoredScriptsIT.java @@ -0,0 +1,105 @@ +package org.elasticsearch.client;/* + * 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. + */ + + +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; +import org.apache.http.util.EntityUtils; +import org.elasticsearch.ElasticsearchStatusException; +import org.elasticsearch.action.admin.cluster.storedscripts.DeleteStoredScriptRequest; +import org.elasticsearch.action.admin.cluster.storedscripts.DeleteStoredScriptResponse; +import org.elasticsearch.action.admin.cluster.storedscripts.GetStoredScriptRequest; +import org.elasticsearch.action.admin.cluster.storedscripts.GetStoredScriptResponse; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.xcontent.ToXContent; +import org.elasticsearch.common.xcontent.XContentType; +import org.elasticsearch.rest.RestStatus; +import org.elasticsearch.script.Script; +import org.elasticsearch.script.StoredScriptSource; + +import java.util.Collections; + +import static java.util.Collections.emptyMap; +import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; +import static org.hamcrest.Matchers.equalTo; + +public class StoredScriptsIT extends ESRestHighLevelClientTestCase { + + final String id = "calculate-score"; + + public void testGetStoredScript() throws Exception { + final StoredScriptSource scriptSource = + new StoredScriptSource("painless", + "Math.log(_score * 2) + params.my_modifier", + Collections.singletonMap(Script.CONTENT_TYPE_OPTION, XContentType.JSON.mediaType())); + + final String script = Strings.toString(scriptSource.toXContent(jsonBuilder(), ToXContent.EMPTY_PARAMS)); + // TODO: change to HighLevel PutStoredScriptRequest when it will be ready + // so far - using low-level REST API + Response putResponse = + adminClient() + .performRequest("PUT", "/_scripts/calculate-score", emptyMap(), + new StringEntity("{\"script\":" + script + "}", + ContentType.APPLICATION_JSON)); + assertEquals(putResponse.getStatusLine().getReasonPhrase(), 200, putResponse.getStatusLine().getStatusCode()); + assertEquals("{\"acknowledged\":true}", EntityUtils.toString(putResponse.getEntity())); + + GetStoredScriptRequest getRequest = new GetStoredScriptRequest("calculate-score"); + getRequest.masterNodeTimeout("50s"); + + GetStoredScriptResponse getResponse = execute(getRequest, highLevelClient()::getScript, + highLevelClient()::getScriptAsync); + + assertThat(getResponse.getSource(), equalTo(scriptSource)); + } + + public void testDeleteStoredScript() throws Exception { + final StoredScriptSource scriptSource = + new StoredScriptSource("painless", + "Math.log(_score * 2) + params.my_modifier", + Collections.singletonMap(Script.CONTENT_TYPE_OPTION, XContentType.JSON.mediaType())); + + final String script = Strings.toString(scriptSource.toXContent(jsonBuilder(), ToXContent.EMPTY_PARAMS)); + // TODO: change to HighLevel PutStoredScriptRequest when it will be ready + // so far - using low-level REST API + Response putResponse = + adminClient() + .performRequest("PUT", "/_scripts/" + id, emptyMap(), + new StringEntity("{\"script\":" + script + "}", + ContentType.APPLICATION_JSON)); + assertEquals(putResponse.getStatusLine().getReasonPhrase(), 200, putResponse.getStatusLine().getStatusCode()); + assertEquals("{\"acknowledged\":true}", EntityUtils.toString(putResponse.getEntity())); + + DeleteStoredScriptRequest deleteRequest = new DeleteStoredScriptRequest(id); + deleteRequest.masterNodeTimeout("50s"); + deleteRequest.timeout("50s"); + + DeleteStoredScriptResponse deleteResponse = execute(deleteRequest, highLevelClient()::deleteScript, + highLevelClient()::deleteScriptAsync); + + assertThat(deleteResponse.isAcknowledged(), equalTo(true)); + + GetStoredScriptRequest getRequest = new GetStoredScriptRequest(id); + + final ElasticsearchStatusException statusException = expectThrows(ElasticsearchStatusException.class, + () -> execute(getRequest, highLevelClient()::getScript, + highLevelClient()::getScriptAsync)); + assertThat(statusException.status(), equalTo(RestStatus.NOT_FOUND)); + } +} diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/StoredScriptsDocumentationIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/StoredScriptsDocumentationIT.java new file mode 100644 index 000000000000..0aadae73ce66 --- /dev/null +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/StoredScriptsDocumentationIT.java @@ -0,0 +1,204 @@ +package org.elasticsearch.client.documentation;/* + * 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. + */ + +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; +import org.apache.http.util.EntityUtils; +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.LatchedActionListener; +import org.elasticsearch.action.admin.cluster.storedscripts.DeleteStoredScriptRequest; +import org.elasticsearch.action.admin.cluster.storedscripts.DeleteStoredScriptResponse; +import org.elasticsearch.action.admin.cluster.storedscripts.GetStoredScriptRequest; +import org.elasticsearch.action.admin.cluster.storedscripts.GetStoredScriptResponse; +import org.elasticsearch.client.ESRestHighLevelClientTestCase; +import org.elasticsearch.client.RequestOptions; +import org.elasticsearch.client.Response; +import org.elasticsearch.client.RestHighLevelClient; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.unit.TimeValue; +import org.elasticsearch.common.xcontent.ToXContent; +import org.elasticsearch.common.xcontent.XContentType; +import org.elasticsearch.script.Script; +import org.elasticsearch.script.StoredScriptSource; + +import java.io.IOException; +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static java.util.Collections.emptyMap; +import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; +import static org.hamcrest.Matchers.equalTo; + +/** + * This class is used to generate the Java Stored Scripts API documentation. + * You need to wrap your code between two tags like: + * // tag::example + * // end::example + * + * Where example is your tag name. + * + * Then in the documentation, you can extract what is between tag and end tags with + * ["source","java",subs="attributes,callouts,macros"] + * -------------------------------------------------- + * include-tagged::{doc-tests}/StoredScriptsDocumentationIT.java[example] + * -------------------------------------------------- + * + * The column width of the code block is 84. If the code contains a line longer + * than 84, the line will be cut and a horizontal scroll bar will be displayed. + * (the code indentation of the tag is not included in the width) + */ +public class StoredScriptsDocumentationIT extends ESRestHighLevelClientTestCase { + + public void testGetStoredScript() throws Exception { + RestHighLevelClient client = highLevelClient(); + + final StoredScriptSource scriptSource = + new StoredScriptSource("painless", + "Math.log(_score * 2) + params.my_modifier", + Collections.singletonMap(Script.CONTENT_TYPE_OPTION, XContentType.JSON.mediaType())); + + putStoredScript("calculate-score", scriptSource); + + { + // tag::get-stored-script-request + GetStoredScriptRequest request = new GetStoredScriptRequest("calculate-score"); // <1> + // end::get-stored-script-request + + // tag::get-stored-script-request-masterTimeout + request.masterNodeTimeout(TimeValue.timeValueSeconds(50)); // <1> + request.masterNodeTimeout("50s"); // <2> + // end::get-stored-script-request-masterTimeout + + // tag::get-stored-script-execute + GetStoredScriptResponse getResponse = client.getScript(request, RequestOptions.DEFAULT); + // end::get-stored-script-execute + + // tag::get-stored-script-response + StoredScriptSource storedScriptSource = getResponse.getSource(); // <1> + + String lang = storedScriptSource.getLang(); // <2> + String source = storedScriptSource.getSource(); // <3> + Map options = storedScriptSource.getOptions(); // <4> + // end::get-stored-script-response + + assertThat(storedScriptSource, equalTo(scriptSource)); + + // tag::get-stored-script-execute-listener + ActionListener listener = + new ActionListener() { + @Override + public void onResponse(GetStoredScriptResponse response) { + // <1> + } + + @Override + public void onFailure(Exception e) { + // <2> + } + }; + // end::get-stored-script-execute-listener + + // Replace the empty listener by a blocking listener in test + final CountDownLatch latch = new CountDownLatch(1); + listener = new LatchedActionListener<>(listener, latch); + + // tag::get-stored-script-execute-async + client.getScriptAsync(request, RequestOptions.DEFAULT, listener); // <1> + // end::get-stored-script-execute-async + + assertTrue(latch.await(30L, TimeUnit.SECONDS)); + } + + } + + public void testDeleteStoredScript() throws Exception { + RestHighLevelClient client = highLevelClient(); + + final StoredScriptSource scriptSource = + new StoredScriptSource("painless", + "Math.log(_score * 2) + params.my_modifier", + Collections.singletonMap(Script.CONTENT_TYPE_OPTION, XContentType.JSON.mediaType())); + + putStoredScript("calculate-score", scriptSource); + + // tag::delete-stored-script-request + DeleteStoredScriptRequest deleteRequest = new DeleteStoredScriptRequest("calculate-score"); // <1> + // end::delete-stored-script-request + + // tag::delete-stored-script-request-masterTimeout + deleteRequest.masterNodeTimeout(TimeValue.timeValueSeconds(50)); // <1> + deleteRequest.masterNodeTimeout("50s"); // <2> + // end::delete-stored-script-request-masterTimeout + + // tag::delete-stored-script-request-timeout + deleteRequest.timeout(TimeValue.timeValueSeconds(60)); // <1> + deleteRequest.timeout("60s"); // <2> + // end::delete-stored-script-request-timeout + + // tag::delete-stored-script-execute + DeleteStoredScriptResponse deleteResponse = client.deleteScript(deleteRequest, RequestOptions.DEFAULT); + // end::delete-stored-script-execute + + // tag::delete-stored-script-response + boolean acknowledged = deleteResponse.isAcknowledged();// <1> + // end::delete-stored-script-response + + putStoredScript("calculate-score", scriptSource); + + // tag::delete-stored-script-execute-listener + ActionListener listener = + new ActionListener() { + @Override + public void onResponse(DeleteStoredScriptResponse response) { + // <1> + } + + @Override + public void onFailure(Exception e) { + // <2> + } + }; + // end::delete-stored-script-execute-listener + + // Replace the empty listener by a blocking listener in test + final CountDownLatch latch = new CountDownLatch(1); + listener = new LatchedActionListener<>(listener, latch); + + // tag::delete-stored-script-execute-async + client.deleteScriptAsync(deleteRequest, RequestOptions.DEFAULT, listener); // <1> + // end::delete-stored-script-execute-async + + assertTrue(latch.await(30L, TimeUnit.SECONDS)); + } + + private void putStoredScript(String id, StoredScriptSource scriptSource) throws IOException { + final String script = Strings.toString(scriptSource.toXContent(jsonBuilder(), ToXContent.EMPTY_PARAMS)); + // TODO: change to HighLevel PutStoredScriptRequest when it will be ready + // so far - using low-level REST API + Response putResponse = + adminClient() + .performRequest("PUT", "/_scripts/" + id, emptyMap(), + new StringEntity("{\"script\":" + script + "}", + ContentType.APPLICATION_JSON)); + assertEquals(putResponse.getStatusLine().getReasonPhrase(), 200, putResponse.getStatusLine().getStatusCode()); + assertEquals("{\"acknowledged\":true}", EntityUtils.toString(putResponse.getEntity())); + } +} diff --git a/docs/java-rest/high-level/script/delete_script.asciidoc b/docs/java-rest/high-level/script/delete_script.asciidoc new file mode 100644 index 000000000000..79b3b0b32471 --- /dev/null +++ b/docs/java-rest/high-level/script/delete_script.asciidoc @@ -0,0 +1,81 @@ +[[java-rest-high-delete-stored-script]] + +=== Delete Stored Script API + +[[java-rest-high-delete-stored-script-request]] +==== Delete Stored Script Request + +A `DeleteStoredScriptRequest` requires an `id`: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/StoredScriptsDocumentationIT.java[delete-stored-script-request] +-------------------------------------------------- +<1> The id of the script + +==== Optional arguments +The following arguments can optionally be provided: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/StoredScriptsDocumentationIT.java[delete-stored-script-request-timeout] +-------------------------------------------------- +<1> Timeout to wait for the all the nodes to acknowledge the stored script is deleted as a `TimeValue` +<2> Timeout to wait for the all the nodes to acknowledge the stored script is deleted as a `String` + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/StoredScriptsDocumentationIT.java[delete-stored-script-request-masterTimeout] +-------------------------------------------------- +<1> Timeout to connect to the master node as a `TimeValue` +<2> Timeout to connect to the master node as a `String` + +[[java-rest-high-delete-stored-script-sync]] +==== Synchronous Execution +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/StoredScriptsDocumentationIT.java[delete-stored-script-execute] +-------------------------------------------------- + +[[java-rest-high-delete-stored-script-async]] +==== Asynchronous Execution + +The asynchronous execution of a delete stored script request requires both the `DeleteStoredScriptRequest` +instance and an `ActionListener` instance to be passed to the asynchronous method: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/StoredScriptsDocumentationIT.java[delete-stored-script-execute-async] +-------------------------------------------------- +<1> The `DeleteStoredScriptRequest` to execute and the `ActionListener` to use when +the execution completes + +[[java-rest-high-delete-stored-script-listener]] +===== Action Listener + +The asynchronous method does not block and returns immediately. Once it is +completed the `ActionListener` is called back using the `onResponse` method +if the execution successfully completed or using the `onFailure` method if +it failed. + +A typical listener for `DeleteStoredScriptResponse` looks like: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/StoredScriptsDocumentationIT.java[delete-stored-script-execute-listener] +-------------------------------------------------- +<1> Called when the execution is successfully completed. The response is +provided as an argument +<2> Called in case of failure. The raised exception is provided as an argument + +[[java-rest-high-delete-stored-script-response]] +==== Delete Stored Script Response + +The returned `DeleteStoredScriptResponse` allows to retrieve information about the +executed operation as follows: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/StoredScriptsDocumentationIT.java[delete-stored-script-response] +-------------------------------------------------- +<1> Indicates whether all of the nodes have acknowledged the request \ No newline at end of file diff --git a/docs/java-rest/high-level/script/get_script.asciidoc b/docs/java-rest/high-level/script/get_script.asciidoc new file mode 100644 index 000000000000..a38bdad2bd6a --- /dev/null +++ b/docs/java-rest/high-level/script/get_script.asciidoc @@ -0,0 +1,77 @@ +[[java-rest-high-get-stored-script]] + +=== Get Stored Script API + +[[java-rest-high-get-stored-script-request]] +==== Get Stored Script Request + +A `GetStoredScriptRequest` requires an `id`: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/StoredScriptsDocumentationIT.java[get-stored-script-request] +-------------------------------------------------- +<1> The id of the script + +==== Optional arguments +The following arguments can optionally be provided: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/StoredScriptsDocumentationIT.java[get-stored-script-request-masterTimeout] +-------------------------------------------------- +<1> Timeout to connect to the master node as a `TimeValue` +<2> Timeout to connect to the master node as a `String` + +[[java-rest-high-get-stored-script-sync]] +==== Synchronous Execution +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/StoredScriptsDocumentationIT.java[get-stored-script-execute] +-------------------------------------------------- + +[[java-rest-high-get-stored-script-async]] +==== Asynchronous Execution + +The asynchronous execution of a get stored script request requires both the `GetStoredScriptRequest` +instance and an `ActionListener` instance to be passed to the asynchronous method: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/StoredScriptsDocumentationIT.java[get-stored-script-execute-async] +-------------------------------------------------- +<1> The `GetStoredScriptRequest` to execute and the `ActionListener` to use when +the execution completes + +[[java-rest-high-get-stored-script-listener]] +===== Action Listener + +The asynchronous method does not block and returns immediately. Once it is +completed the `ActionListener` is called back using the `onResponse` method +if the execution successfully completed or using the `onFailure` method if +it failed. + +A typical listener for `GetStoredScriptResponse` looks like: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/StoredScriptsDocumentationIT.java[get-stored-script-execute-listener] +-------------------------------------------------- +<1> Called when the execution is successfully completed. The response is +provided as an argument +<2> Called in case of failure. The raised exception is provided as an argument + +[[java-rest-high-get-stored-script-response]] +==== Get Stored Script Response + +The returned `GetStoredScriptResponse` allows to retrieve information about the +executed operation as follows: + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests}/StoredScriptsDocumentationIT.java[get-stored-script-response] +-------------------------------------------------- +<1> The script object consists of a content and a metadata +<2> The language the script is written in, which defaults to `painless`. +<3> The content of the script +<4> Any named options that should be passed into the script. \ No newline at end of file diff --git a/docs/java-rest/high-level/supported-apis.asciidoc b/docs/java-rest/high-level/supported-apis.asciidoc index 4cd87a521d10..17acc8f13c04 100644 --- a/docs/java-rest/high-level/supported-apis.asciidoc +++ b/docs/java-rest/high-level/supported-apis.asciidoc @@ -151,3 +151,14 @@ The Java High Level REST Client supports the following Tasks APIs: include::tasks/list_tasks.asciidoc[] include::tasks/cancel_tasks.asciidoc[] + +== Script APIs + +The Java High Level REST Client supports the following Scripts APIs: + +* <> +* <> + +include::script/get_script.asciidoc[] +include::script/delete_script.asciidoc[] + diff --git a/rest-api-spec/src/main/resources/rest-api-spec/api/get_script.json b/rest-api-spec/src/main/resources/rest-api-spec/api/get_script.json index 2240f0e1a0b7..0b2d6c5a5b9c 100644 --- a/rest-api-spec/src/main/resources/rest-api-spec/api/get_script.json +++ b/rest-api-spec/src/main/resources/rest-api-spec/api/get_script.json @@ -13,6 +13,10 @@ } }, "params" : { + "master_timeout": { + "type" : "time", + "description" : "Specify timeout for connection to master" + } } }, "body": null diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/storedscripts/DeleteStoredScriptResponse.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/storedscripts/DeleteStoredScriptResponse.java index 42f08ae73e06..741c105866f4 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/storedscripts/DeleteStoredScriptResponse.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/storedscripts/DeleteStoredScriptResponse.java @@ -20,6 +20,7 @@ package org.elasticsearch.action.admin.cluster.storedscripts; import org.elasticsearch.action.support.master.AcknowledgedResponse; +import org.elasticsearch.common.xcontent.XContentParser; public class DeleteStoredScriptResponse extends AcknowledgedResponse { @@ -29,4 +30,8 @@ public class DeleteStoredScriptResponse extends AcknowledgedResponse { public DeleteStoredScriptResponse(boolean acknowledged) { super(acknowledged); } + + public static DeleteStoredScriptResponse fromXContent(XContentParser parser) { + return new DeleteStoredScriptResponse(parseAcknowledged(parser)); + } } diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/storedscripts/GetStoredScriptResponse.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/storedscripts/GetStoredScriptResponse.java index a394fe17f217..7bfbbe7ad4d7 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/storedscripts/GetStoredScriptResponse.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/storedscripts/GetStoredScriptResponse.java @@ -21,25 +21,63 @@ package org.elasticsearch.action.admin.cluster.storedscripts; import org.elasticsearch.Version; import org.elasticsearch.action.ActionResponse; +import org.elasticsearch.common.ParseField; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; -import org.elasticsearch.common.xcontent.ToXContentObject; +import org.elasticsearch.common.xcontent.ConstructingObjectParser; +import org.elasticsearch.common.xcontent.ObjectParser; +import org.elasticsearch.common.xcontent.StatusToXContentObject; import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.rest.RestStatus; import org.elasticsearch.script.StoredScriptSource; import java.io.IOException; +import java.util.Objects; -public class GetStoredScriptResponse extends ActionResponse implements ToXContentObject { +import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg; +import static org.elasticsearch.common.xcontent.ConstructingObjectParser.optionalConstructorArg; +public class GetStoredScriptResponse extends ActionResponse implements StatusToXContentObject { + + public static final ParseField _ID_PARSE_FIELD = new ParseField("_id"); + public static final ParseField FOUND_PARSE_FIELD = new ParseField("found"); + public static final ParseField SCRIPT = new ParseField("script"); + + private static final ConstructingObjectParser PARSER = + new ConstructingObjectParser<>("GetStoredScriptResponse", + true, + (a, c) -> { + String id = (String) a[0]; + boolean found = (Boolean)a[1]; + StoredScriptSource scriptSource = (StoredScriptSource)a[2]; + return found ? new GetStoredScriptResponse(id, scriptSource) : new GetStoredScriptResponse(id, null); + }); + + static { + PARSER.declareField(constructorArg(), (p, c) -> p.text(), + _ID_PARSE_FIELD, ObjectParser.ValueType.STRING); + PARSER.declareField(constructorArg(), (p, c) -> p.booleanValue(), + FOUND_PARSE_FIELD, ObjectParser.ValueType.BOOLEAN); + PARSER.declareField(optionalConstructorArg(), (p, c) -> StoredScriptSource.fromXContent(p, true), + SCRIPT, ObjectParser.ValueType.OBJECT); + } + + private String id; private StoredScriptSource source; GetStoredScriptResponse() { } - GetStoredScriptResponse(StoredScriptSource source) { + GetStoredScriptResponse(String id, StoredScriptSource source) { + this.id = id; this.source = source; } + public String getId() { + return id; + } + /** * @return if a stored script and if not found null */ @@ -48,12 +86,29 @@ public class GetStoredScriptResponse extends ActionResponse implements ToXConten } @Override - public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - source.toXContent(builder, params); + public RestStatus status() { + return source != null ? RestStatus.OK : RestStatus.NOT_FOUND; + } + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + + builder.field(_ID_PARSE_FIELD.getPreferredName(), id); + builder.field(FOUND_PARSE_FIELD.getPreferredName(), source != null); + if (source != null) { + builder.field(StoredScriptSource.SCRIPT_PARSE_FIELD.getPreferredName()); + source.toXContent(builder, params); + } + + builder.endObject(); return builder; } + public static GetStoredScriptResponse fromXContent(XContentParser parser) throws IOException { + return PARSER.parse(parser, null); + } + @Override public void readFrom(StreamInput in) throws IOException { super.readFrom(in); @@ -67,6 +122,10 @@ public class GetStoredScriptResponse extends ActionResponse implements ToXConten } else { source = null; } + + if (in.getVersion().onOrAfter(Version.V_7_0_0_alpha1)) { + id = in.readString(); + } } @Override @@ -84,5 +143,22 @@ public class GetStoredScriptResponse extends ActionResponse implements ToXConten out.writeString(source.getSource()); } } + if (out.getVersion().onOrAfter(Version.V_7_0_0_alpha1)) { + out.writeString(id); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + GetStoredScriptResponse that = (GetStoredScriptResponse) o; + return Objects.equals(id, that.id) && + Objects.equals(source, that.source); + } + + @Override + public int hashCode() { + return Objects.hash(id, source); } } diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/storedscripts/TransportGetStoredScriptAction.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/storedscripts/TransportGetStoredScriptAction.java index 63f24f31f59b..368e40b96b7b 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/storedscripts/TransportGetStoredScriptAction.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/storedscripts/TransportGetStoredScriptAction.java @@ -60,7 +60,7 @@ public class TransportGetStoredScriptAction extends TransportMasterNodeReadActio @Override protected void masterOperation(GetStoredScriptRequest request, ClusterState state, ActionListener listener) throws Exception { - listener.onResponse(new GetStoredScriptResponse(scriptService.getStoredScript(state, request))); + listener.onResponse(new GetStoredScriptResponse(request.id(), scriptService.getStoredScript(state, request))); } @Override diff --git a/server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestGetStoredScriptAction.java b/server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestGetStoredScriptAction.java index 10050dda8823..1a14d5053823 100644 --- a/server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestGetStoredScriptAction.java +++ b/server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestGetStoredScriptAction.java @@ -19,19 +19,12 @@ package org.elasticsearch.rest.action.admin.cluster; import org.elasticsearch.action.admin.cluster.storedscripts.GetStoredScriptRequest; -import org.elasticsearch.action.admin.cluster.storedscripts.GetStoredScriptResponse; import org.elasticsearch.client.node.NodeClient; -import org.elasticsearch.common.ParseField; import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.rest.BaseRestHandler; -import org.elasticsearch.rest.BytesRestResponse; import org.elasticsearch.rest.RestController; import org.elasticsearch.rest.RestRequest; -import org.elasticsearch.rest.RestResponse; -import org.elasticsearch.rest.RestStatus; -import org.elasticsearch.rest.action.RestBuilderListener; -import org.elasticsearch.script.StoredScriptSource; +import org.elasticsearch.rest.action.RestStatusToXContentListener; import java.io.IOException; @@ -39,9 +32,6 @@ import static org.elasticsearch.rest.RestRequest.Method.GET; public class RestGetStoredScriptAction extends BaseRestHandler { - public static final ParseField _ID_PARSE_FIELD = new ParseField("_id"); - public static final ParseField FOUND_PARSE_FIELD = new ParseField("found"); - public RestGetStoredScriptAction(Settings settings, RestController controller) { super(settings); @@ -57,33 +47,7 @@ public class RestGetStoredScriptAction extends BaseRestHandler { public RestChannelConsumer prepareRequest(final RestRequest request, NodeClient client) throws IOException { String id = request.param("id"); GetStoredScriptRequest getRequest = new GetStoredScriptRequest(id); - - return channel -> client.admin().cluster().getStoredScript(getRequest, new RestBuilderListener(channel) { - @Override - public RestResponse buildResponse(GetStoredScriptResponse response, XContentBuilder builder) throws Exception { - builder.startObject(); - builder.field(_ID_PARSE_FIELD.getPreferredName(), id); - - StoredScriptSource source = response.getSource(); - boolean found = source != null; - builder.field(FOUND_PARSE_FIELD.getPreferredName(), found); - - if (found) { - builder.startObject(StoredScriptSource.SCRIPT_PARSE_FIELD.getPreferredName()); - builder.field(StoredScriptSource.LANG_PARSE_FIELD.getPreferredName(), source.getLang()); - builder.field(StoredScriptSource.SOURCE_PARSE_FIELD.getPreferredName(), source.getSource()); - - if (source.getOptions().isEmpty() == false) { - builder.field(StoredScriptSource.OPTIONS_PARSE_FIELD.getPreferredName(), source.getOptions()); - } - - builder.endObject(); - } - - builder.endObject(); - - return new BytesRestResponse(found ? RestStatus.OK : RestStatus.NOT_FOUND, builder); - } - }); + getRequest.masterNodeTimeout(request.paramAsTime("master_timeout", getRequest.masterNodeTimeout())); + return channel -> client.admin().cluster().getStoredScript(getRequest, new RestStatusToXContentListener<>(channel)); } } diff --git a/server/src/main/java/org/elasticsearch/script/StoredScriptSource.java b/server/src/main/java/org/elasticsearch/script/StoredScriptSource.java index 11f8769c86b1..885d72bdec6f 100644 --- a/server/src/main/java/org/elasticsearch/script/StoredScriptSource.java +++ b/server/src/main/java/org/elasticsearch/script/StoredScriptSource.java @@ -185,7 +185,7 @@ public class StoredScriptSource extends AbstractDiffable imp } } - private static final ObjectParser PARSER = new ObjectParser<>("stored script source", Builder::new); + private static final ObjectParser PARSER = new ObjectParser<>("stored script source", true, Builder::new); static { // Defines the fields necessary to parse a Script as XContent using an ObjectParser. @@ -481,7 +481,9 @@ public class StoredScriptSource extends AbstractDiffable imp builder.startObject(); builder.field(LANG_PARSE_FIELD.getPreferredName(), lang); builder.field(SOURCE_PARSE_FIELD.getPreferredName(), source); - builder.field(OPTIONS_PARSE_FIELD.getPreferredName(), options); + if (options.isEmpty() == false) { + builder.field(OPTIONS_PARSE_FIELD.getPreferredName(), options); + } builder.endObject(); return builder; diff --git a/server/src/test/java/org/elasticsearch/action/admin/cluster/storedscripts/DeleteStoredScriptResponseTests.java b/server/src/test/java/org/elasticsearch/action/admin/cluster/storedscripts/DeleteStoredScriptResponseTests.java new file mode 100644 index 000000000000..375a67226306 --- /dev/null +++ b/server/src/test/java/org/elasticsearch/action/admin/cluster/storedscripts/DeleteStoredScriptResponseTests.java @@ -0,0 +1,46 @@ +package org.elasticsearch.action.admin.cluster.storedscripts;/* + * 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. + */ + +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.test.AbstractStreamableXContentTestCase; + +import java.io.IOException; + +public class DeleteStoredScriptResponseTests extends AbstractStreamableXContentTestCase { + + @Override + protected DeleteStoredScriptResponse doParseInstance(XContentParser parser) throws IOException { + return DeleteStoredScriptResponse.fromXContent(parser); + } + + @Override + protected DeleteStoredScriptResponse createBlankInstance() { + return new DeleteStoredScriptResponse(); + } + + @Override + protected DeleteStoredScriptResponse createTestInstance() { + return new DeleteStoredScriptResponse(randomBoolean()); + } + + @Override + protected DeleteStoredScriptResponse mutateInstance(DeleteStoredScriptResponse instance) throws IOException { + return new DeleteStoredScriptResponse(instance.isAcknowledged() == false); + } +} diff --git a/server/src/test/java/org/elasticsearch/action/admin/cluster/storedscripts/GetStoredScriptResponseTests.java b/server/src/test/java/org/elasticsearch/action/admin/cluster/storedscripts/GetStoredScriptResponseTests.java new file mode 100644 index 000000000000..1c92c0c8c2bf --- /dev/null +++ b/server/src/test/java/org/elasticsearch/action/admin/cluster/storedscripts/GetStoredScriptResponseTests.java @@ -0,0 +1,61 @@ +package org.elasticsearch.action.admin.cluster.storedscripts;/* + * 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. + */ + +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.common.xcontent.XContentType; +import org.elasticsearch.script.Script; +import org.elasticsearch.script.StoredScriptSource; +import org.elasticsearch.test.AbstractStreamableXContentTestCase; + +import java.io.IOException; +import java.util.Collections; +import java.util.Map; +import java.util.function.Predicate; + +public class GetStoredScriptResponseTests extends AbstractStreamableXContentTestCase { + + @Override + protected GetStoredScriptResponse doParseInstance(XContentParser parser) throws IOException { + return GetStoredScriptResponse.fromXContent(parser); + } + + @Override + protected GetStoredScriptResponse createBlankInstance() { + return new GetStoredScriptResponse(); + } + + @Override + protected GetStoredScriptResponse createTestInstance() { + return new GetStoredScriptResponse(randomAlphaOfLengthBetween(1, 10), randomScriptSource()); + } + + @Override + protected Predicate getRandomFieldsExcludeFilter() { + return s -> "script.options".equals(s); + } + + private static StoredScriptSource randomScriptSource() { + final String lang = randomFrom("lang", "painless", "mustache"); + final String source = randomAlphaOfLengthBetween(1, 10); + final Map options = randomBoolean() + ? Collections.singletonMap(Script.CONTENT_TYPE_OPTION, XContentType.JSON.mediaType()) + : Collections.emptyMap(); + return new StoredScriptSource(lang, source, options); + } +}