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 7fe3e08f3afb0..89f81512bc9d2 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
@@ -32,6 +32,7 @@
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 @@ static Request getTemplates(GetIndexTemplatesRequest getIndexTemplatesRequest) t
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();
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 17f8f65943012..687290abe8866 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
@@ -28,6 +28,7 @@
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.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 void deleteScriptAsync(DeleteStoredScriptRequest request, RequestOptions
AcknowledgedResponse::fromXContent, listener, emptySet());
}
+ /**
+ * Puts an stored script using the Scripting API.
+ * See Scripting API
+ * on elastic.co
+ * @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 Scripting API
+ * on elastic.co
+ * @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 listener) {
+ performRequestAsyncAndParseEntity(putStoredScriptRequest, RequestConverters::putScript, options,
+ AcknowledgedResponse::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 840df49b47811..6f48d305a7799 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
@@ -31,6 +31,7 @@
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 void testGetTemplateRequest() throws Exception {
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 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")
diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/RestHighLevelClientTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/RestHighLevelClientTests.java
index b6562cd44cd55..3bd47306e5e10 100644
--- a/client/rest-high-level/src/test/java/org/elasticsearch/client/RestHighLevelClientTests.java
+++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/RestHighLevelClientTests.java
@@ -658,7 +658,6 @@ public void testApiNamingConventions() throws Exception {
"indices.get_upgrade",
"indices.put_alias",
"mtermvectors",
- "put_script",
"reindex_rethrottle",
"render_search_template",
"scripts_painless_execute",
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
index 1d693eee8396e..b15467d24ba2b 100644
--- 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
@@ -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 @@
* 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 void testGetStoredScript() throws Exception {
"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 void testDeleteStoredScript() throws Exception {
"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 void testDeleteStoredScript() throws Exception {
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 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"));
+ }
}
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
index fc38090ef5b5b..c5d53abd978e1 100644
--- 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
@@ -17,21 +17,21 @@
* 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.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 void onFailure(Exception e) {
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 listener =
+ new ActionListener() {
+ @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 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));
}
}
diff --git a/distribution/archives/integ-test-zip/src/test/java/org/elasticsearch/test/rest/WaitForRefreshAndCloseIT.java b/distribution/archives/integ-test-zip/src/test/java/org/elasticsearch/test/rest/WaitForRefreshAndCloseIT.java
index 09d6b9e51a5fc..ffd3a1f6c0c3c 100644
--- a/distribution/archives/integ-test-zip/src/test/java/org/elasticsearch/test/rest/WaitForRefreshAndCloseIT.java
+++ b/distribution/archives/integ-test-zip/src/test/java/org/elasticsearch/test/rest/WaitForRefreshAndCloseIT.java
@@ -38,6 +38,7 @@
/**
* Tests that wait for refresh is fired if the index is closed.
*/
+@AwaitsFix(bugUrl = "https://github.com/elastic/elasticsearch/issues/33533")
public class WaitForRefreshAndCloseIT extends ESRestTestCase {
@Before
public void setupIndex() throws IOException {
@@ -76,7 +77,6 @@ public void testUpdateAndThenClose() throws Exception {
closeWhileListenerEngaged(start("POST", "/_update", "{\"doc\":{\"name\":\"test\"}}"));
}
- @AwaitsFix(bugUrl = "https://github.com/elastic/elasticsearch/issues/33533")
public void testDeleteAndThenClose() throws Exception {
Request request = new Request("PUT", docPath());
request.setJsonEntity("{\"test\":\"test\"}");
diff --git a/docs/java-rest/high-level/script/put_script.asciidoc b/docs/java-rest/high-level/script/put_script.asciidoc
new file mode 100644
index 0000000000000..acc80e82d11e6
--- /dev/null
+++ b/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
\ 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 8d49724353e6f..8d92653ce5702 100644
--- a/docs/java-rest/high-level/supported-apis.asciidoc
+++ b/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:
* <>
+* <>
* <>
include::script/get_script.asciidoc[]
+include::script/put_script.asciidoc[]
include::script/delete_script.asciidoc[]
== Licensing APIs
diff --git a/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/Whitelist.java b/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/Whitelist.java
index 7acbff6cb0b93..31a9e595d0b6d 100644
--- a/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/Whitelist.java
+++ b/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/Whitelist.java
@@ -61,12 +61,19 @@ public final class Whitelist {
/** The {@link List} of all the whitelisted Painless classes. */
public final List whitelistClasses;
+ /** The {@link List} of all the whitelisted static Painless methods. */
+ public final List whitelistImportedMethods;
+
+ /** The {@link List} of all the whitelisted Painless bindings. */
public final List whitelistBindings;
- /** Standard constructor. All values must be not {@code null}. */
- public Whitelist(ClassLoader classLoader, List whitelistClasses, List whitelistBindings) {
+ /** Standard constructor. All values must be not {@code null}. */
+ public Whitelist(ClassLoader classLoader, List whitelistClasses,
+ List whitelistImportedMethods, List whitelistBindings) {
+
this.classLoader = Objects.requireNonNull(classLoader);
this.whitelistClasses = Collections.unmodifiableList(Objects.requireNonNull(whitelistClasses));
+ this.whitelistImportedMethods = Collections.unmodifiableList(Objects.requireNonNull(whitelistImportedMethods));
this.whitelistBindings = Collections.unmodifiableList(Objects.requireNonNull(whitelistBindings));
}
}
diff --git a/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/WhitelistLoader.java b/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/WhitelistLoader.java
index 0279c82f1b67b..2f5dec769fc2f 100644
--- a/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/WhitelistLoader.java
+++ b/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/WhitelistLoader.java
@@ -133,6 +133,7 @@ public final class WhitelistLoader {
*/
public static Whitelist loadFromResourceFiles(Class> resource, String... filepaths) {
List whitelistClasses = new ArrayList<>();
+ List whitelistStatics = new ArrayList<>();
List whitelistBindings = new ArrayList<>();
// Execute a single pass through the whitelist text files. This will gather all the
@@ -192,18 +193,18 @@ public static Whitelist loadFromResourceFiles(Class> resource, String... filep
whitelistConstructors = new ArrayList<>();
whitelistMethods = new ArrayList<>();
whitelistFields = new ArrayList<>();
- } else if (line.startsWith("static ")) {
+ } else if (line.startsWith("static_import ")) {
// Ensure the final token of the line is '{'.
if (line.endsWith("{") == false) {
throw new IllegalArgumentException(
- "invalid static definition: failed to parse static opening bracket [" + line + "]");
+ "invalid static import definition: failed to parse static import opening bracket [" + line + "]");
}
if (parseType != null) {
- throw new IllegalArgumentException("invalid definition: cannot embed static definition [" + line + "]");
+ throw new IllegalArgumentException("invalid definition: cannot embed static import definition [" + line + "]");
}
- parseType = "static";
+ parseType = "static_import";
// Handle the end of a definition and reset all previously gathered values.
// Expects the following format: '}' '\n'
@@ -229,9 +230,9 @@ public static Whitelist loadFromResourceFiles(Class> resource, String... filep
// Reset the parseType.
parseType = null;
- // Handle static definition types.
- // Expects the following format: ID ID '(' ( ID ( ',' ID )* )? ')' 'bound_to' ID '\n'
- } else if ("static".equals(parseType)) {
+ // Handle static import definition types.
+ // Expects the following format: ID ID '(' ( ID ( ',' ID )* )? ')' ( 'from_class' | 'bound_to' ) ID '\n'
+ } else if ("static_import".equals(parseType)) {
// Mark the origin of this parsable object.
String origin = "[" + filepath + "]:[" + number + "]";
@@ -240,7 +241,7 @@ public static Whitelist loadFromResourceFiles(Class> resource, String... filep
if (parameterStartIndex == -1) {
throw new IllegalArgumentException(
- "illegal static definition: start of method parameters not found [" + line + "]");
+ "illegal static import definition: start of method parameters not found [" + line + "]");
}
String[] tokens = line.substring(0, parameterStartIndex).trim().split("\\s+");
@@ -261,7 +262,7 @@ public static Whitelist loadFromResourceFiles(Class> resource, String... filep
if (parameterEndIndex == -1) {
throw new IllegalArgumentException(
- "illegal static definition: end of method parameters not found [" + line + "]");
+ "illegal static import definition: end of method parameters not found [" + line + "]");
}
String[] canonicalTypeNameParameters =
@@ -272,39 +273,37 @@ public static Whitelist loadFromResourceFiles(Class> resource, String... filep
canonicalTypeNameParameters = new String[0];
}
- // Parse the static type and class.
+ // Parse the static import type and class.
tokens = line.substring(parameterEndIndex + 1).trim().split("\\s+");
- String staticType;
+ String staticImportType;
String targetJavaClassName;
// Based on the number of tokens, look up the type and class.
if (tokens.length == 2) {
- staticType = tokens[0];
+ staticImportType = tokens[0];
targetJavaClassName = tokens[1];
} else {
- throw new IllegalArgumentException("invalid static definition: unexpected format [" + line + "]");
+ throw new IllegalArgumentException("invalid static import definition: unexpected format [" + line + "]");
}
- // Check the static type is valid.
- if ("bound_to".equals(staticType) == false) {
- throw new IllegalArgumentException(
- "invalid static definition: unexpected static type [" + staticType + "] [" + line + "]");
+ // Add a static import method or binding depending on the static import type.
+ if ("from_class".equals(staticImportType)) {
+ whitelistStatics.add(new WhitelistMethod(origin, targetJavaClassName,
+ methodName, returnCanonicalTypeName, Arrays.asList(canonicalTypeNameParameters)));
+ } else if ("bound_to".equals(staticImportType)) {
+ whitelistBindings.add(new WhitelistBinding(origin, targetJavaClassName,
+ methodName, returnCanonicalTypeName, Arrays.asList(canonicalTypeNameParameters)));
+ } else {
+ throw new IllegalArgumentException("invalid static import definition: " +
+ "unexpected static import type [" + staticImportType + "] [" + line + "]");
}
- whitelistBindings.add(new WhitelistBinding(origin, targetJavaClassName,
- methodName, returnCanonicalTypeName, Arrays.asList(canonicalTypeNameParameters)));
-
// Handle class definition types.
} else if ("class".equals(parseType)) {
// Mark the origin of this parsable object.
String origin = "[" + filepath + "]:[" + number + "]";
- // Ensure we have a defined class before adding any constructors, methods, augmented methods, or fields.
- if (parseType == null) {
- throw new IllegalArgumentException("invalid definition: expected one of ['class', 'static'] [" + line + "]");
- }
-
// Handle the case for a constructor definition.
// Expects the following format: '(' ( ID ( ',' ID )* )? ')' '\n'
if (line.startsWith("(")) {
@@ -393,7 +392,7 @@ public static Whitelist loadFromResourceFiles(Class> resource, String... filep
ClassLoader loader = AccessController.doPrivileged((PrivilegedAction)resource::getClassLoader);
- return new Whitelist(loader, whitelistClasses, whitelistBindings);
+ return new Whitelist(loader, whitelistClasses, whitelistStatics, whitelistBindings);
}
private WhitelistLoader() {}
diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/FeatureTest.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/FeatureTest.java
index 1e94c19f6d90e..28cbb4aee19a6 100644
--- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/FeatureTest.java
+++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/FeatureTest.java
@@ -24,6 +24,21 @@
/** Currently just a dummy class for testing a few features not yet exposed by whitelist! */
public class FeatureTest {
+ /** static method that returns true */
+ public static boolean overloadedStatic() {
+ return true;
+ }
+
+ /** static method that returns what you ask it */
+ public static boolean overloadedStatic(boolean whatToReturn) {
+ return whatToReturn;
+ }
+
+ /** static method only whitelisted as a static */
+ public static float staticAddFloatsTest(float x, float y) {
+ return x + y;
+ }
+
private int x;
private int y;
public int z;
@@ -58,21 +73,12 @@ public void setY(int y) {
this.y = y;
}
- /** static method that returns true */
- public static boolean overloadedStatic() {
- return true;
- }
-
- /** static method that returns what you ask it */
- public static boolean overloadedStatic(boolean whatToReturn) {
- return whatToReturn;
- }
-
/** method taking two functions! */
public Object twoFunctionsOfX(Function