Kaynağa Gözat

HLRC: Add put stored script support to high-level rest client (#31323)

Relates to #27205
S.Y. Wang 7 yıl önce
ebeveyn
işleme
9073dbefd6

+ 14 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/RequestConverters.java

@@ -32,6 +32,7 @@ import org.elasticsearch.action.DocWriteRequest;
 import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest;
 import org.elasticsearch.action.admin.cluster.storedscripts.DeleteStoredScriptRequest;
 import org.elasticsearch.action.admin.cluster.storedscripts.GetStoredScriptRequest;
+import org.elasticsearch.action.admin.cluster.storedscripts.PutStoredScriptRequest;
 import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest;
 import org.elasticsearch.action.admin.indices.alias.get.GetAliasesRequest;
 import org.elasticsearch.action.admin.indices.analyze.AnalyzeRequest;
@@ -887,6 +888,19 @@ final class RequestConverters {
         return request;
     }
 
+    static Request putScript(PutStoredScriptRequest putStoredScriptRequest) throws IOException {
+        String endpoint = new EndpointBuilder().addPathPartAsIs("_scripts").addPathPart(putStoredScriptRequest.id()).build();
+        Request request = new Request(HttpPost.METHOD_NAME, endpoint);
+        Params params = new Params(request);
+        params.withTimeout(putStoredScriptRequest.timeout());
+        params.withMasterTimeout(putStoredScriptRequest.masterNodeTimeout());
+        if (Strings.hasText(putStoredScriptRequest.context())) {
+            params.putParam("context", putStoredScriptRequest.context());
+        }
+        request.setEntity(createEntity(putStoredScriptRequest, REQUEST_BODY_CONTENT_TYPE));
+        return request;
+    }
+
     static Request analyze(AnalyzeRequest request) throws IOException {
         EndpointBuilder builder = new EndpointBuilder();
         String index = request.index();

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

@@ -28,6 +28,7 @@ import org.elasticsearch.action.ActionRequestValidationException;
 import org.elasticsearch.action.admin.cluster.storedscripts.DeleteStoredScriptRequest;
 import org.elasticsearch.action.admin.cluster.storedscripts.GetStoredScriptRequest;
 import org.elasticsearch.action.admin.cluster.storedscripts.GetStoredScriptResponse;
+import org.elasticsearch.action.admin.cluster.storedscripts.PutStoredScriptRequest;
 import org.elasticsearch.action.bulk.BulkRequest;
 import org.elasticsearch.action.bulk.BulkResponse;
 import org.elasticsearch.action.delete.DeleteRequest;
@@ -121,36 +122,36 @@ import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
 import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
 import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
 import org.elasticsearch.search.aggregations.metrics.AvgAggregationBuilder;
-import org.elasticsearch.search.aggregations.metrics.ParsedAvg;
 import org.elasticsearch.search.aggregations.metrics.CardinalityAggregationBuilder;
-import org.elasticsearch.search.aggregations.metrics.ParsedCardinality;
+import org.elasticsearch.search.aggregations.metrics.ExtendedStatsAggregationBuilder;
 import org.elasticsearch.search.aggregations.metrics.GeoBoundsAggregationBuilder;
-import org.elasticsearch.search.aggregations.metrics.ParsedGeoBounds;
 import org.elasticsearch.search.aggregations.metrics.GeoCentroidAggregationBuilder;
-import org.elasticsearch.search.aggregations.metrics.ParsedGeoCentroid;
-import org.elasticsearch.search.aggregations.metrics.MaxAggregationBuilder;
-import org.elasticsearch.search.aggregations.metrics.ParsedMax;
-import org.elasticsearch.search.aggregations.metrics.MinAggregationBuilder;
-import org.elasticsearch.search.aggregations.metrics.ParsedMin;
 import org.elasticsearch.search.aggregations.metrics.InternalHDRPercentileRanks;
 import org.elasticsearch.search.aggregations.metrics.InternalHDRPercentiles;
-import org.elasticsearch.search.aggregations.metrics.ParsedHDRPercentileRanks;
-import org.elasticsearch.search.aggregations.metrics.ParsedHDRPercentiles;
 import org.elasticsearch.search.aggregations.metrics.InternalTDigestPercentileRanks;
 import org.elasticsearch.search.aggregations.metrics.InternalTDigestPercentiles;
+import org.elasticsearch.search.aggregations.metrics.MaxAggregationBuilder;
+import org.elasticsearch.search.aggregations.metrics.MinAggregationBuilder;
+import org.elasticsearch.search.aggregations.metrics.ParsedAvg;
+import org.elasticsearch.search.aggregations.metrics.ParsedCardinality;
+import org.elasticsearch.search.aggregations.metrics.ParsedExtendedStats;
+import org.elasticsearch.search.aggregations.metrics.ParsedGeoBounds;
+import org.elasticsearch.search.aggregations.metrics.ParsedGeoCentroid;
+import org.elasticsearch.search.aggregations.metrics.ParsedHDRPercentileRanks;
+import org.elasticsearch.search.aggregations.metrics.ParsedHDRPercentiles;
+import org.elasticsearch.search.aggregations.metrics.ParsedMax;
+import org.elasticsearch.search.aggregations.metrics.ParsedMin;
+import org.elasticsearch.search.aggregations.metrics.ParsedScriptedMetric;
+import org.elasticsearch.search.aggregations.metrics.ParsedStats;
+import org.elasticsearch.search.aggregations.metrics.ParsedSum;
 import org.elasticsearch.search.aggregations.metrics.ParsedTDigestPercentileRanks;
 import org.elasticsearch.search.aggregations.metrics.ParsedTDigestPercentiles;
-import org.elasticsearch.search.aggregations.metrics.ParsedScriptedMetric;
+import org.elasticsearch.search.aggregations.metrics.ParsedTopHits;
+import org.elasticsearch.search.aggregations.metrics.ParsedValueCount;
 import org.elasticsearch.search.aggregations.metrics.ScriptedMetricAggregationBuilder;
-import org.elasticsearch.search.aggregations.metrics.ParsedStats;
 import org.elasticsearch.search.aggregations.metrics.StatsAggregationBuilder;
-import org.elasticsearch.search.aggregations.metrics.ExtendedStatsAggregationBuilder;
-import org.elasticsearch.search.aggregations.metrics.ParsedExtendedStats;
-import org.elasticsearch.search.aggregations.metrics.ParsedSum;
 import org.elasticsearch.search.aggregations.metrics.SumAggregationBuilder;
-import org.elasticsearch.search.aggregations.metrics.ParsedTopHits;
 import org.elasticsearch.search.aggregations.metrics.TopHitsAggregationBuilder;
-import org.elasticsearch.search.aggregations.metrics.ParsedValueCount;
 import org.elasticsearch.search.aggregations.metrics.ValueCountAggregationBuilder;
 import org.elasticsearch.search.aggregations.pipeline.InternalSimpleValue;
 import org.elasticsearch.search.aggregations.pipeline.ParsedSimpleValue;
@@ -1050,6 +1051,35 @@ public class RestHighLevelClient implements Closeable {
             AcknowledgedResponse::fromXContent, listener, emptySet());
     }
 
+    /**
+     * Puts an stored script using the Scripting API.
+     * See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-scripting-using.html"> Scripting API
+     * on elastic.co</a>
+     * @param putStoredScriptRequest 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 AcknowledgedResponse putScript(PutStoredScriptRequest putStoredScriptRequest,
+                                             RequestOptions options) throws IOException {
+        return performRequestAndParseEntity(putStoredScriptRequest, RequestConverters::putScript, options,
+            AcknowledgedResponse::fromXContent, emptySet());
+    }
+
+    /**
+     * Asynchronously puts an stored script using the Scripting API.
+     * See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-scripting-using.html"> Scripting API
+     * on elastic.co</a>
+     * @param putStoredScriptRequest 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 putScriptAsync(PutStoredScriptRequest putStoredScriptRequest, RequestOptions options,
+                               ActionListener<AcknowledgedResponse> listener) {
+        performRequestAsyncAndParseEntity(putStoredScriptRequest, RequestConverters::putScript, options,
+            AcknowledgedResponse::fromXContent, listener, emptySet());
+    }
+
     /**
      * Asynchronously executes a request using the Field Capabilities API.
      * See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-field-caps.html">Field Capabilities API

+ 37 - 0
client/rest-high-level/src/test/java/org/elasticsearch/client/RequestConvertersTests.java

@@ -31,6 +31,7 @@ import org.elasticsearch.action.ActionRequestValidationException;
 import org.elasticsearch.action.DocWriteRequest;
 import org.elasticsearch.action.admin.cluster.storedscripts.DeleteStoredScriptRequest;
 import org.elasticsearch.action.admin.cluster.storedscripts.GetStoredScriptRequest;
+import org.elasticsearch.action.admin.cluster.storedscripts.PutStoredScriptRequest;
 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;
@@ -1991,6 +1992,42 @@ public class RequestConvertersTests extends ESTestCase {
         assertThat(request.getEntity(), nullValue());
     }
 
+    public void testPutScript() throws Exception {
+        PutStoredScriptRequest putStoredScriptRequest = new PutStoredScriptRequest();
+
+        String id = randomAlphaOfLengthBetween(5, 10);
+        putStoredScriptRequest.id(id);
+
+        XContentType xContentType = randomFrom(XContentType.values());
+        try (XContentBuilder builder = XContentBuilder.builder(xContentType.xContent())) {
+            builder.startObject();
+            builder.startObject("script")
+                .field("lang", "painless")
+                .field("source", "Math.log(_score * 2) + params.multiplier")
+                .endObject();
+            builder.endObject();
+
+            putStoredScriptRequest.content(BytesReference.bytes(builder), xContentType);
+        }
+
+        Map<String, String> expectedParams = new HashMap<>();
+        setRandomMasterTimeout(putStoredScriptRequest, expectedParams);
+        setRandomTimeout(putStoredScriptRequest::timeout, AcknowledgedRequest.DEFAULT_ACK_TIMEOUT, expectedParams);
+
+        if (randomBoolean()) {
+            String context = randomAlphaOfLengthBetween(5, 10);
+            putStoredScriptRequest.context(context);
+            expectedParams.put("context", context);
+        }
+
+        Request request = RequestConverters.putScript(putStoredScriptRequest);
+
+        assertThat(request.getEndpoint(), equalTo("/_scripts/" + id));
+        assertThat(request.getParameters(), equalTo(expectedParams));
+        assertNotNull(request.getEntity());
+        assertToXContentBody(putStoredScriptRequest, request.getEntity());
+    }
+
     public void testAnalyzeRequest() throws Exception {
         AnalyzeRequest indexAnalyzeRequest = new AnalyzeRequest()
             .text("Here is some text")

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

@@ -658,7 +658,6 @@ public class RestHighLevelClientTests extends ESTestCase {
             "indices.get_upgrade",
             "indices.put_alias",
             "mtermvectors",
-            "put_script",
             "reindex_rethrottle",
             "render_search_template",
             "scripts_painless_execute",

+ 32 - 26
client/rest-high-level/src/test/java/org/elasticsearch/client/StoredScriptsIT.java

@@ -1,4 +1,5 @@
-package org.elasticsearch.client;/*
+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
@@ -17,27 +18,27 @@ package org.elasticsearch.client;/*
  * under the License.
  */
 
-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.GetStoredScriptRequest;
 import org.elasticsearch.action.admin.cluster.storedscripts.GetStoredScriptResponse;
-import org.elasticsearch.action.support.master.AcknowledgedResponse;
-import org.elasticsearch.common.Strings;
-import org.elasticsearch.common.xcontent.ToXContent;
+import org.elasticsearch.action.admin.cluster.storedscripts.PutStoredScriptRequest;
+import org.elasticsearch.common.bytes.BytesArray;
 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 java.util.Map;
 
-import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
+import static org.elasticsearch.common.xcontent.support.XContentMapValues.extractValue;
+import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
 import static org.hamcrest.Matchers.equalTo;
 
 public class StoredScriptsIT extends ESRestHighLevelClientTestCase {
 
-    final String id = "calculate-score";
+    private static final String id = "calculate-score";
 
     public void testGetStoredScript() throws Exception {
         final StoredScriptSource scriptSource =
@@ -45,13 +46,9 @@ public class StoredScriptsIT extends ESRestHighLevelClientTestCase {
                 "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
-        Request putRequest = new Request("PUT", "/_scripts/calculate-score");
-        putRequest.setJsonEntity("{\"script\":" + script + "}");
-        Response putResponse = adminClient().performRequest(putRequest);
-        assertEquals("{\"acknowledged\":true}", EntityUtils.toString(putResponse.getEntity()));
+        PutStoredScriptRequest request =
+            new PutStoredScriptRequest(id, "search", new BytesArray("{}"), XContentType.JSON, scriptSource);
+        assertAcked(execute(request, highLevelClient()::putScript, highLevelClient()::putScriptAsync));
 
         GetStoredScriptRequest getRequest = new GetStoredScriptRequest("calculate-score");
         getRequest.masterNodeTimeout("50s");
@@ -68,22 +65,14 @@ public class StoredScriptsIT extends ESRestHighLevelClientTestCase {
                 "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
-        Request putRequest = new Request("PUT", "/_scripts/" + id);
-        putRequest.setJsonEntity("{\"script\":" + script + "}");
-        Response putResponse = adminClient().performRequest(putRequest);
-        assertEquals("{\"acknowledged\":true}", EntityUtils.toString(putResponse.getEntity()));
+        PutStoredScriptRequest request =
+            new PutStoredScriptRequest(id, "search", new BytesArray("{}"), XContentType.JSON, scriptSource);
+        assertAcked(execute(request, highLevelClient()::putScript, highLevelClient()::putScriptAsync));
 
         DeleteStoredScriptRequest deleteRequest = new DeleteStoredScriptRequest(id);
         deleteRequest.masterNodeTimeout("50s");
         deleteRequest.timeout("50s");
-
-        AcknowledgedResponse deleteResponse = execute(deleteRequest, highLevelClient()::deleteScript,
-            highLevelClient()::deleteScriptAsync);
-
-        assertThat(deleteResponse.isAcknowledged(), equalTo(true));
+        assertAcked(execute(deleteRequest, highLevelClient()::deleteScript, highLevelClient()::deleteScriptAsync));
 
         GetStoredScriptRequest getRequest = new GetStoredScriptRequest(id);
 
@@ -92,4 +81,21 @@ public class StoredScriptsIT extends ESRestHighLevelClientTestCase {
                 highLevelClient()::getScriptAsync));
         assertThat(statusException.status(), equalTo(RestStatus.NOT_FOUND));
     }
+
+    public void testPutScript() throws Exception {
+        final StoredScriptSource scriptSource =
+            new StoredScriptSource("painless",
+                "Math.log(_score * 2) + params.my_modifier",
+                Collections.singletonMap(Script.CONTENT_TYPE_OPTION, XContentType.JSON.mediaType()));
+
+        PutStoredScriptRequest request =
+            new PutStoredScriptRequest(id, "search", new BytesArray("{}"), XContentType.JSON, scriptSource);
+        assertAcked(execute(request, highLevelClient()::putScript, highLevelClient()::putScriptAsync));
+
+        Map<String, Object> script = getAsMap("/_scripts/" + id);
+        assertThat(extractValue("_id", script), equalTo(id));
+        assertThat(extractValue("found", script), equalTo(true));
+        assertThat(extractValue("script.lang", script), equalTo("painless"));
+        assertThat(extractValue("script.source", script), equalTo("Math.log(_score * 2) + params.my_modifier"));
+    }
 }

+ 125 - 14
client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/StoredScriptsDocumentationIT.java

@@ -17,21 +17,21 @@ package org.elasticsearch.client.documentation;/*
  * under the License.
  */
 
-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.GetStoredScriptRequest;
 import org.elasticsearch.action.admin.cluster.storedscripts.GetStoredScriptResponse;
+import org.elasticsearch.action.admin.cluster.storedscripts.PutStoredScriptRequest;
 import org.elasticsearch.action.support.master.AcknowledgedResponse;
 import org.elasticsearch.client.ESRestHighLevelClientTestCase;
-import org.elasticsearch.client.Request;
 import org.elasticsearch.client.RequestOptions;
-import org.elasticsearch.client.Response;
 import org.elasticsearch.client.RestHighLevelClient;
-import org.elasticsearch.common.Strings;
+import org.elasticsearch.common.bytes.BytesArray;
+import org.elasticsearch.common.bytes.BytesReference;
 import org.elasticsearch.common.unit.TimeValue;
-import org.elasticsearch.common.xcontent.ToXContent;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentFactory;
 import org.elasticsearch.common.xcontent.XContentType;
 import org.elasticsearch.script.Script;
 import org.elasticsearch.script.StoredScriptSource;
@@ -42,7 +42,8 @@ import java.util.Map;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
-import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
+import static org.elasticsearch.common.xcontent.support.XContentMapValues.extractValue;
+import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
 import static org.hamcrest.Matchers.equalTo;
 
 /**
@@ -187,14 +188,124 @@ public class StoredScriptsDocumentationIT extends ESRestHighLevelClientTestCase
         assertTrue(latch.await(30L, TimeUnit.SECONDS));
     }
 
+    public void testPutScript() throws Exception {
+        RestHighLevelClient client = highLevelClient();
+
+        {
+            // tag::put-stored-script-request
+            PutStoredScriptRequest request = new PutStoredScriptRequest();
+            request.id("id"); // <1>
+            request.content(new BytesArray(
+                "{\n" +
+                    "\"script\": {\n" +
+                    "\"lang\": \"painless\",\n" +
+                    "\"source\": \"Math.log(_score * 2) + params.multiplier\"" +
+                    "}\n" +
+                    "}\n"
+            ), XContentType.JSON); // <2>
+            // end::put-stored-script-request
+
+            // tag::put-stored-script-context
+            request.context("context"); // <1>
+            // end::put-stored-script-context
+
+            // tag::put-stored-script-timeout
+            request.timeout(TimeValue.timeValueMinutes(2)); // <1>
+            request.timeout("2m"); // <2>
+            // end::put-stored-script-timeout
+
+            // tag::put-stored-script-masterTimeout
+            request.masterNodeTimeout(TimeValue.timeValueMinutes(1)); // <1>
+            request.masterNodeTimeout("1m"); // <2>
+            // end::put-stored-script-masterTimeout
+        }
+
+        {
+            PutStoredScriptRequest request = new PutStoredScriptRequest();
+            request.id("id");
+
+            // tag::put-stored-script-content-painless
+            XContentBuilder builder = XContentFactory.jsonBuilder();
+            builder.startObject();
+            {
+                builder.startObject("script");
+                {
+                    builder.field("lang", "painless");
+                    builder.field("source", "Math.log(_score * 2) + params.multiplier");
+                }
+                builder.endObject();
+            }
+            builder.endObject();
+            request.content(BytesReference.bytes(builder), XContentType.JSON); // <1>
+            // end::put-stored-script-content-painless
+
+
+            // tag::put-stored-script-execute
+            AcknowledgedResponse putStoredScriptResponse = client.putScript(request, RequestOptions.DEFAULT);
+            // end::put-stored-script-execute
+
+            // tag::put-stored-script-response
+            boolean acknowledged = putStoredScriptResponse.isAcknowledged(); // <1>
+            // end::put-stored-script-response
+
+            assertTrue(acknowledged);
+
+            // tag::put-stored-script-execute-listener
+            ActionListener<AcknowledgedResponse> listener =
+                new ActionListener<AcknowledgedResponse>() {
+                    @Override
+                    public void onResponse(AcknowledgedResponse response) {
+                        // <1>
+                    }
+
+                    @Override
+                    public void onFailure(Exception e) {
+                        // <2>
+                    }
+                };
+            // end::put-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::put-stored-script-execute-async
+            client.putScriptAsync(request, RequestOptions.DEFAULT, listener); // <1>
+            // end::put-stored-script-execute-async
+
+            assertTrue(latch.await(30L, TimeUnit.SECONDS));
+        }
+
+        {
+            PutStoredScriptRequest request = new PutStoredScriptRequest();
+            request.id("id");
+
+            // tag::put-stored-script-content-mustache
+            XContentBuilder builder = XContentFactory.jsonBuilder();
+            builder.startObject();
+            {
+                builder.startObject("script");
+                {
+                    builder.field("lang", "mustache");
+                    builder.field("source", "{\"query\":{\"match\":{\"title\":\"{{query_string}}\"}}}");
+                }
+                builder.endObject();
+            }
+            builder.endObject();
+            request.content(BytesReference.bytes(builder), XContentType.JSON); // <1>
+            // end::put-stored-script-content-mustache
+
+            client.putScript(request, RequestOptions.DEFAULT);
+
+            Map<String, Object> script = getAsMap("/_scripts/id");
+            assertThat(extractValue("script.lang", script), equalTo("mustache"));
+            assertThat(extractValue("script.source", script), equalTo("{\"query\":{\"match\":{\"title\":\"{{query_string}}\"}}}"));
+        }
+    }
+
     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
-        Request request = new Request("PUT", "/_scripts/" + id);
-        request.setJsonEntity("{\"script\":" + script + "}");
-        Response putResponse = adminClient().performRequest(request);
-        assertEquals(putResponse.getStatusLine().getReasonPhrase(), 200, putResponse.getStatusLine().getStatusCode());
-        assertEquals("{\"acknowledged\":true}", EntityUtils.toString(putResponse.getEntity()));
+        PutStoredScriptRequest request =
+            new PutStoredScriptRequest(id, "search", new BytesArray("{}"), XContentType.JSON, scriptSource);
+        assertAcked(execute(request, highLevelClient()::putScript, highLevelClient()::putScriptAsync));
     }
 }

+ 106 - 0
docs/java-rest/high-level/script/put_script.asciidoc

@@ -0,0 +1,106 @@
+[[java-rest-high-put-stored-script]]
+=== Put Stored Script API
+
+[[java-rest-high-put-stored-script-request]]
+==== Put Stored Script Request
+
+A `PutStoredScriptRequest` requires an `id` and `content`:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/StoredScriptsDocumentationIT.java[put-stored-script-request]
+--------------------------------------------------
+<1> The id of the script
+<2> The content of the script
+
+[[java-rest-high-put-stored-script-content]]
+==== Content
+The content of a script can be written in different languages and provided in
+different ways:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/StoredScriptsDocumentationIT.java[put-stored-script-content-painless]
+--------------------------------------------------
+<1> Specify a painless script and provided as `XContentBuilder` object.
+Note that the builder needs to be passed as a `BytesReference` object
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/StoredScriptsDocumentationIT.java[put-stored-script-content-mustache]
+--------------------------------------------------
+<1> Specify a mustache script and provided as `XContentBuilder` object.
+Note that value of source can be directly provided as a JSON string
+
+==== Optional arguments
+The following arguments can optionally be provided:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/StoredScriptsDocumentationIT.java[put-stored-script-context]
+--------------------------------------------------
+<1> The context the script should be executed in.
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/StoredScriptsDocumentationIT.java[put-stored-script-timeout]
+--------------------------------------------------
+<1> Timeout to wait for the all the nodes to acknowledge the script creation as a `TimeValue`
+<2> Timeout to wait for the all the nodes to acknowledge the script creation as a `String`
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/StoredScriptsDocumentationIT.java[put-stored-script-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-put-stored-script-sync]]
+==== Synchronous Execution
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/StoredScriptsDocumentationIT.java[put-stored-script-execute]
+--------------------------------------------------
+
+[[java-rest-high-put-stored-script-async]]
+==== Asynchronous Execution
+
+The asynchronous execution of a put stored script request requires both the `PutStoredScriptRequest`
+instance and an `ActionListener` instance to be passed to the asynchronous method:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/StoredScriptsDocumentationIT.java[put-stored-script-execute-async]
+--------------------------------------------------
+<1> The `PutStoredScriptRequest` to execute and the `ActionListener` to use when
+the execution completes
+
+[[java-rest-high-put-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 `AcknowledgedResponse` looks like:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/StoredScriptsDocumentationIT.java[put-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-put-stored-script-response]]
+==== Put Stored Script Response
+
+The returned `AcknowledgedResponse` allows to retrieve information about the
+executed operation as follows:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/StoredScriptsDocumentationIT.java[put-stored-script-response]
+--------------------------------------------------
+<1> Indicates whether all of the nodes have acknowledged the request

+ 2 - 0
docs/java-rest/high-level/supported-apis.asciidoc

@@ -189,9 +189,11 @@ include::tasks/cancel_tasks.asciidoc[]
 The Java High Level REST Client supports the following Scripts APIs:
 
 * <<java-rest-high-get-stored-script>>
+* <<java-rest-high-put-stored-script>>
 * <<java-rest-high-delete-stored-script>>
 
 include::script/get_script.asciidoc[]
+include::script/put_script.asciidoc[]
 include::script/delete_script.asciidoc[]
 
 == Licensing APIs

+ 11 - 1
server/src/main/java/org/elasticsearch/action/admin/cluster/storedscripts/PutStoredScriptRequest.java

@@ -25,6 +25,8 @@ import org.elasticsearch.action.support.master.AcknowledgedRequest;
 import org.elasticsearch.common.bytes.BytesReference;
 import org.elasticsearch.common.io.stream.StreamInput;
 import org.elasticsearch.common.io.stream.StreamOutput;
+import org.elasticsearch.common.xcontent.ToXContent;
+import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.common.xcontent.XContentHelper;
 import org.elasticsearch.common.xcontent.XContentType;
 import org.elasticsearch.script.StoredScriptSource;
@@ -34,7 +36,7 @@ import java.util.Objects;
 
 import static org.elasticsearch.action.ValidateActions.addValidationError;
 
-public class PutStoredScriptRequest extends AcknowledgedRequest<PutStoredScriptRequest> {
+public class PutStoredScriptRequest extends AcknowledgedRequest<PutStoredScriptRequest> implements ToXContent {
 
     private String id;
     private String context;
@@ -160,4 +162,12 @@ public class PutStoredScriptRequest extends AcknowledgedRequest<PutStoredScriptR
             (context != null ? ", context [" + context + "]" : "") +
             ", content [" + source + "]}";
     }
+
+    @Override
+    public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+        builder.field("script");
+        source.toXContent(builder, params);
+
+        return builder;
+    }
 }

+ 29 - 0
server/src/test/java/org/elasticsearch/action/admin/cluster/storedscripts/PutStoredScriptRequestTests.java

@@ -20,8 +20,11 @@
 package org.elasticsearch.action.admin.cluster.storedscripts;
 
 import org.elasticsearch.common.bytes.BytesArray;
+import org.elasticsearch.common.bytes.BytesReference;
 import org.elasticsearch.common.io.stream.BytesStreamOutput;
 import org.elasticsearch.common.io.stream.StreamInput;
+import org.elasticsearch.common.xcontent.ToXContent;
+import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.common.xcontent.XContentType;
 import org.elasticsearch.script.StoredScriptSource;
 import org.elasticsearch.test.ESTestCase;
@@ -48,4 +51,30 @@ public class PutStoredScriptRequestTests extends ESTestCase {
             }
         }
     }
+
+    public void testToXContent() throws IOException {
+        XContentType xContentType = randomFrom(XContentType.values());
+        XContentBuilder builder = XContentBuilder.builder(xContentType.xContent());
+        builder.startObject();
+        builder.startObject("script")
+            .field("lang", "painless")
+            .field("source", "Math.log(_score * 2) + params.multiplier")
+            .endObject();
+        builder.endObject();
+
+        BytesReference expectedRequestBody = BytesReference.bytes(builder);
+
+        PutStoredScriptRequest request = new PutStoredScriptRequest();
+        request.id("test1");
+        request.content(expectedRequestBody, xContentType);
+
+        XContentBuilder requestBuilder = XContentBuilder.builder(xContentType.xContent());
+        requestBuilder.startObject();
+        request.toXContent(requestBuilder, ToXContent.EMPTY_PARAMS);
+        requestBuilder.endObject();
+
+        BytesReference actualRequestBody = BytesReference.bytes(requestBuilder);
+
+        assertEquals(expectedRequestBody, actualRequestBody);
+    }
 }